@itwin/core-common
Version:
iTwin.js components common to frontend and backend
1,020 lines • 48.3 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module DisplayStyles
*/
// cspell:ignore greyscale ovrs
import { assert, BeEvent, CompressedId64Set, expectDefined, Id64, JsonUtils, MutableCompressedId64Set, OrderedId64Iterable, } from "@itwin/core-bentley";
import { AmbientOcclusion } from "./AmbientOcclusion";
import { AnalysisStyle } from "./AnalysisStyle";
import { BackgroundMapSettings } from "./BackgroundMapSettings";
import { ClipStyle } from "./ClipStyle";
import { ColorDef } from "./ColorDef";
import { HiddenLine } from "./HiddenLine";
import { FeatureAppearance } from "./FeatureSymbology";
import { PlanarClipMaskSettings } from "./PlanarClipMask";
import { SubCategoryOverride } from "./SubCategoryOverride";
import { LightSettings } from "./LightSettings";
import { MapImagerySettings } from "./MapImagerySettings";
import { PlanProjectionSettings } from "./PlanProjectionSettings";
import { Environment } from "./Environment";
import { SolarShadowSettings } from "./SolarShadows";
import { ThematicDisplay, ThematicDisplayMode } from "./ThematicDisplay";
import { ViewFlags } from "./ViewFlags";
import { Cartographic } from "./geometry/Cartographic";
import { IModel } from "./IModel";
import { calculateSolarDirection } from "./SolarCalculate";
import { ContextRealityModels } from "./ContextRealityModel";
import { RealityModelDisplaySettings } from "./RealityModelDisplaySettings";
import { WhiteOnWhiteReversalSettings } from "./WhiteOnWhiteReversalSettings";
import { ContourDisplay } from "./ContourDisplay";
/** Describes the style in which monochrome color is applied by a [[DisplayStyleSettings]].
* @public
* @extensions
*/
export var MonochromeMode;
(function (MonochromeMode) {
/** The color of the geometry is replaced with the monochrome color. e.g., if monochrome color is white, the geometry will be white. */
MonochromeMode[MonochromeMode["Flat"] = 0] = "Flat";
/** The color of surfaces is computed as normal, then scaled to a shade of the monochrome color based on the surface color's intensity.
* For example, if the monochrome color is white, this results in a greyscale effect.
* Geometry other than surfaces is treated the same as [[MonochromeMode.Flat]].
*/
MonochromeMode[MonochromeMode["Scaled"] = 1] = "Scaled";
})(MonochromeMode || (MonochromeMode = {}));
/** DisplayStyleSettings initially persisted its excluded elements as an array of Id64Strings in JSON, and exposed them as a Set<string>.
* This becomes problematic when these arrays become very large, in terms of the amount of data and the time required to convert them to a Set.
* The Ids are now persisted to JSON as a [[CompressedId64Set]], significantly reducing their size. However, for backwards API compatibility we must
* continue to expose [[DisplayStyleSettings.excludedElements]] as a Set<string>. The [[ExcludedElements]] class tries to minimize the impact of that requirement by
* maintaining the Ids primarily as a [[MutableCompressedId64Set]], only allocating the Set<string> if a caller actually requests it.
* The only operation Set provides more efficiently than MutableCompressedId64Set is checking for the presence of an Id (the `has()` method).
* @internal
*/
class ExcludedElements {
_json;
_ids;
_synchronizing = false;
constructor(json) {
this._json = json;
if (Array.isArray(json.excludedElements))
this._ids = new MutableCompressedId64Set(CompressedId64Set.compressIds(OrderedId64Iterable.sortArray(json.excludedElements)));
else
this._ids = new MutableCompressedId64Set(json.excludedElements);
}
reset(ids) {
this.synchronize(() => {
this._ids.reset((ids && "string" !== typeof ids) ? CompressedId64Set.compressIds(ids) : ids);
});
}
get ids() {
return this._ids.ids;
}
add(ids) {
this.synchronize(() => {
for (const id of ids)
this._ids.add(id);
});
}
delete(ids) {
this.synchronize(() => {
for (const id of ids)
this._ids.delete(id);
});
}
[Symbol.iterator]() {
return this._ids[Symbol.iterator]();
}
/** The JSON must be kept up-to-date at all times. */
synchronize(func) {
if (this._synchronizing)
return;
this._synchronizing = true;
try {
func();
}
finally {
this._synchronizing = false;
const ids = this._ids.ids;
if (0 === ids.length)
delete this._json.excludedElements;
else
this._json.excludedElements = ids;
}
}
}
/** An implementation of Map that is based on a JSON array, used for a display styles subcategory overrides, model appearance overrides,
* and planar clip masks. Ensures:
* - JSON representation kept in sync with changes to map; and
* - Events dispatched when map contents change.
*/
class OverridesMap extends Map {
_json;
_arrayKey;
_event;
_idFromProps;
_overrideToProps;
_overrideFromProps;
// This is required for mock framework used by ui libraries, which otherwise try to clone this as a standard Map.
get [Symbol.toStringTag]() { return "OverridesMap"; }
constructor(_json, _arrayKey, _event, _idFromProps, _overrideToProps, _overrideFromProps) {
super();
this._json = _json;
this._arrayKey = _arrayKey;
this._event = _event;
this._idFromProps = _idFromProps;
this._overrideToProps = _overrideToProps;
this._overrideFromProps = _overrideFromProps;
this.populate();
}
set(id, override) {
this._event.raiseEvent(id, override);
super.set(id, override);
const index = this.findOrAllocateIndex(id);
const array = this._array;
assert(undefined !== array);
array[index] = this._overrideToProps(override, id);
return this;
}
delete(id) {
this._event.raiseEvent(id, undefined);
if (!super.delete(id))
return false;
const index = this.findExistingIndex(id);
if (undefined !== index) {
assert(undefined !== this._array);
this._array.splice(index, 1);
}
return true;
}
clear() {
for (const id of this.keys())
this.delete(id);
this._json[this._arrayKey] = undefined;
}
populate() {
super.clear();
const ovrs = this._array;
if (!ovrs)
return;
for (const props of ovrs) {
const id = this._idFromProps(props);
if (undefined !== id && Id64.isValidId64(id)) {
const ovr = this._overrideFromProps(props);
if (ovr)
super.set(id, ovr);
}
}
}
get _array() {
return JsonUtils.asArray(this._json[this._arrayKey]);
}
findOrAllocateIndex(id) {
const index = this.findExistingIndex(id);
if (undefined !== index)
return index;
let ovrs = this._array;
if (!ovrs)
ovrs = this._json[this._arrayKey] = [];
return ovrs.length;
}
findExistingIndex(id) {
const ovrs = this._array;
if (!ovrs)
return undefined;
for (let i = 0; i < ovrs.length; i++)
if (this._idFromProps(ovrs[i]) === id)
return i;
return undefined;
}
}
/** Provides access to the settings defined by a [[DisplayStyle]] or [[DisplayStyleState]], and ensures that
* the style's JSON properties are kept in sync.
* @see [[DisplayStyleSettingsProps]] for the JSON representation of these settings.
* @public
*/
export class DisplayStyleSettings {
_json;
_viewFlags;
_background;
_monochrome;
_monochromeMode;
_subCategoryOverrides;
_modelAppearanceOverrides;
_realityModelDisplaySettings;
_planarClipMasks;
_excludedElements;
_backgroundMap;
_mapImagery;
_analysisStyle;
_clipStyle;
_contextRealityModels;
_whiteOnWhiteReversal;
/** Returns true if this is a [[DisplayStyle3dSettings]]. */
is3d() {
return false;
}
/** Planar clip masks to be applied to persistent reality models (@see [SpatialModelState.isRealityModel]($frontend).
* The key for each entry is the Id of the model to which the mask settings apply.
*/
get planarClipMasks() {
return this._planarClipMasks;
}
/** Reality models to be displayed in the view. */
get contextRealityModels() {
return this._contextRealityModels;
}
/** Event raised by [[applyOverrides]] just before the overrides are applied. */
onApplyOverrides = new BeEvent();
/** Event raised by [[applyOverrides]] after the overrides are applied. */
onOverridesApplied = new BeEvent();
/** Event raised just prior to assignment to the [[viewFlags]] property. */
onViewFlagsChanged = new BeEvent();
/** Event raised just prior to assignment to the [[backgroundColor]] property. */
onBackgroundColorChanged = new BeEvent();
/** Event raised just prior to assignment to the [[monochromeColor]] property. */
onMonochromeColorChanged = new BeEvent();
/** Event raised just prior to assignment to the [[monochromeMode]] property. */
onMonochromeModeChanged = new BeEvent();
/** Event raised just prior to assignment to the [[backgroundMap]] property. */
onBackgroundMapChanged = new BeEvent();
/** Event raised just prior to assignment to the [[mapImagery]] property.
* @beta
*/
onMapImageryChanged = new BeEvent();
/** Event raised just prior to assignment to the `scheduleScriptProps` property.
* @see [[onRenderTimelineChanged]] to be notified when the [[renderTimeline]] property from which a script can be obtained is changed.
*/
onScheduleScriptPropsChanged = new BeEvent();
/** Event raised just prior to assignment to the [[renderTimeline]] property. */
onRenderTimelineChanged = new BeEvent();
/** Event raised just prior to assignment to the [[timePoint]] property. */
onTimePointChanged = new BeEvent();
/** Event raised just prior to assignment to the [[analysisStyle]] property. */
onAnalysisStyleChanged = new BeEvent();
/** Event raised just prior to assignment to the [[analysisFraction]] property. */
onAnalysisFractionChanged = new BeEvent();
/** Event raised when the contents of [[excludedElementIds]] changes. */
onExcludedElementsChanged = new BeEvent();
/** Event raised just prior to assignment to the [[clipStyle]] property. */
onClipStyleChanged = new BeEvent();
/** Event raised when the [[SubCategoryOverride]]s change. */
onSubCategoryOverridesChanged = new BeEvent();
/** Event raised just before changing the appearance override for a model. */
onModelAppearanceOverrideChanged = new BeEvent();
/** Event raised just before [[setRealityModelDisplaySettings]] changes the display settings for a reality model.
* @beta
*/
onRealityModelDisplaySettingsChanged = new BeEvent();
/** Event raised just prior to assignment to the [[DisplayStyle3dSettings.thematic]] property. */
onThematicChanged = new BeEvent();
/** Event raised just prior to assignment to the [[DisplayStyle3dSettings.contours]] property. */
onContoursChanged = new BeEvent();
/** Event raised just prior to assignment to the [[DisplayStyle3dSettings.hiddenLineSettings]] property. */
onHiddenLineSettingsChanged = new BeEvent();
/** Event raised just prior to assignment to the [[DisplayStyle3dSettings.ambientOcclusionSettings]] property. */
onAmbientOcclusionSettingsChanged = new BeEvent();
/** Event raised just prior to assignment to the [[DisplayStyle3dSettings.solarShadows]] property. */
onSolarShadowsChanged = new BeEvent();
/** Event raised just prior to assignment to the [[DisplayStyle3dSettings.environment]] property. */
onEnvironmentChanged = new BeEvent();
/** Event raised just prior to assignment to the [[DisplayStyle3dSettings.lights]] property. */
onLightsChanged = new BeEvent();
/** Event raised just before changing the plan projection settings for a model. */
onPlanProjectionSettingsChanged = new BeEvent();
/** Event raised just before adding or removing an entry from [[planarClipMasks]]. */
onPlanarClipMaskChanged = new BeEvent();
/** Event raised just prior to assignment to the [[whiteOnWhiteReversal]] property. */
onWhiteOnWhiteReversalChanged = new BeEvent();
/** Construct a new DisplayStyleSettings from an [[ElementProps.jsonProperties]].
* @param jsonProperties An object with an optional `styles` property containing a display style's settings.
* @param options Options for customizing the display style settings.
* @note When the `DisplayStyleSetting`'s properties are modified by public setters, the `jsonProperties`'s `styles` object will be updated to reflect the change.
* @note If `jsonProperties` contains no `styles` member, one will be added as an empty object.
* @note Generally there is no reason to create an object of this type directly; a [[DisplayStyle]] or [[DisplayStyleState]] constructs one as part of its own construction.
*/
constructor(jsonProperties, options) {
if (undefined === jsonProperties.styles)
jsonProperties.styles = {};
this._json = jsonProperties.styles;
this._viewFlags = ViewFlags.fromJSON(this._json.viewflags);
this._background = ColorDef.fromJSON(this._json.backgroundColor);
this._monochrome = undefined !== this._json.monochromeColor ? ColorDef.fromJSON(this._json.monochromeColor) : ColorDef.white;
this._monochromeMode = MonochromeMode.Flat === this._json.monochromeMode ? MonochromeMode.Flat : MonochromeMode.Scaled;
this._backgroundMap = BackgroundMapSettings.fromPersistentJSON(this._json.backgroundMap);
this._mapImagery = MapImagerySettings.createFromJSON(this._json.mapImagery, this._json.backgroundMap);
// Ensure that if we used the deprecated imagery properties from this._backgroundMap to set up the base layer of this._mapImagery,
// we update our JSON to include that base layer.
this._json.mapImagery = this._mapImagery.toJSON();
this._excludedElements = new ExcludedElements(this._json);
if (this._json.analysisStyle)
this._analysisStyle = AnalysisStyle.fromJSON(this._json.analysisStyle);
this._whiteOnWhiteReversal = WhiteOnWhiteReversalSettings.fromJSON(this._json.whiteOnWhiteReversal);
this._clipStyle = ClipStyle.fromJSON(this._json.clipStyle);
this._subCategoryOverrides = new OverridesMap(this._json, "subCategoryOvr", this.onSubCategoryOverridesChanged, (props) => props.subCategory, (ovr, subCategory) => { return { ...ovr.toJSON(), subCategory }; }, (props) => {
const ovr = SubCategoryOverride.fromJSON(props);
return ovr.anyOverridden ? ovr : undefined;
});
this._modelAppearanceOverrides = new OverridesMap(this._json, "modelOvr", this.onModelAppearanceOverrideChanged, (props) => props.modelId, (ovr, modelId) => { return { ...ovr.toJSON(), modelId }; }, (props) => {
const app = FeatureAppearance.fromJSON(props);
return app.anyOverridden ? app : undefined;
});
this._realityModelDisplaySettings = new OverridesMap(this._json, "realityModelDisplay", this.onRealityModelDisplaySettingsChanged, (props) => props.modelId, (settings, modelId) => { return { ...settings.toJSON(), modelId }; }, (props) => RealityModelDisplaySettings.fromJSON(props));
this._planarClipMasks = new OverridesMap(this._json, "planarClipOvr", this.onPlanarClipMaskChanged, (props) => props.modelId, (ovr, modelId) => { return { ...ovr.toJSON(), modelId }; }, (props) => {
const settings = PlanarClipMaskSettings.fromJSON(props);
return settings.isValid ? settings : undefined;
});
this._contextRealityModels = new ContextRealityModels({
container: this._json,
createContextRealityModel: options?.createContextRealityModel,
deferPopulating: options?.deferContextRealityModels,
});
}
/** Flags controlling various aspects of the display style. */
get viewFlags() { return this._viewFlags; }
set viewFlags(flags) {
if (this.viewFlags.equals(flags))
return;
this.onViewFlagsChanged.raiseEvent(flags);
this._viewFlags = flags;
this._json.viewflags = flags.toJSON();
}
/** The color displayed in the view background - by default, [[ColorDef.black]]. */
get backgroundColor() { return this._background; }
set backgroundColor(color) {
if (this.backgroundColor.equals(color))
return;
this.onBackgroundColorChanged.raiseEvent(color);
this._background = color;
this._json.backgroundColor = color.toJSON();
}
/** The color used to draw geometry when [[ViewFlags.monochrome]] is enabled - by default, [[ColorDef.white]].
* The monochrome color is applied to all surfaces and linear geometry, but only applied to the **edges** of surfaces in [[RenderMode.Wireframe]].
* @see [[monochromeMode]] to control how the color is applied.
*/
get monochromeColor() { return this._monochrome; }
set monochromeColor(color) {
if (this.monochromeColor.equals(color))
return;
this.onMonochromeColorChanged.raiseEvent(color);
this._monochrome = color;
this._json.monochromeColor = color.toJSON();
}
/** The style in which [[monochromeColor]] is applied when [[ViewFlags.monochrome]] is enabled - by default, [[MonochromeMode.Scaled]]. */
get monochromeMode() { return this._monochromeMode; }
set monochromeMode(mode) {
if (this.monochromeMode === mode)
return;
this.onMonochromeModeChanged.raiseEvent(mode);
this._monochromeMode = mode;
this._json.monochromeMode = mode;
}
/** Settings controlling display of the background map within views of geolocated models. */
get backgroundMap() { return this._backgroundMap; }
set backgroundMap(map) {
if (!this.backgroundMap.equals(map)) {
this.onBackgroundMapChanged.raiseEvent(map);
this._backgroundMap = map; // it's an immutable type.
this._json.backgroundMap = map.toPersistentJSON();
}
}
/** Settings defining the map imagery layers to be displayed within the view.
* @beta
*/
get mapImagery() { return this._mapImagery; }
set mapImagery(mapImagery) {
this.onMapImageryChanged.raiseEvent(mapImagery);
this._mapImagery = mapImagery;
this._json.mapImagery = this._mapImagery.toJSON();
}
/** @internal
* Handles keeping the map imagery layers in synch after changes have been made (used internally only by front end)
*/
synchMapImagery() {
this.onMapImageryChanged.raiseEvent(this._mapImagery);
this._json.mapImagery = this._mapImagery.toJSON();
}
/** The Id of a [RenderTimeline]($backend) element containing a [[RenderSchedule.Script]] used to animate the view.
* If [[scheduleScriptProps]] is defined, it takes precedence over the script supplied by the RenderTimeline.
* @note If this [[DisplayStyleSettings]] is associated with a [DisplayStyleState]($frontend), assigning to [[renderTimeline]] will enqueue asynchronous loading of
* the script from the [RenderTimeline]($backend) element; for more readable code, prefer instead to `await` [DisplayStyleState.changeRenderTimeline]($frontend).
* @see [[onRenderTimelineChanged]] to be notified of changes to this property.
*/
get renderTimeline() {
return this._json.renderTimeline;
}
set renderTimeline(id) {
if (id !== this.renderTimeline) {
this.onRenderTimelineChanged.raiseEvent(id);
this._json.renderTimeline = id;
}
}
/** JSON representation of a [[RenderSchedule.Script]] embedded in the display style describing how to animate the contents of the view over time.
* This script, if present, takes precedence over a script supplied by [[renderTimeline]].
* @see [[onScheduleScriptPropsChanged]] to be notified when this property changes.
* @see [DisplayStyleState.scheduleScript]($frontend) to change the [[RenderSchedule.Script]] object directly rather than via JSON.
*/
get scheduleScriptProps() {
return this._json.scheduleScript;
}
set scheduleScriptProps(props) {
this.onScheduleScriptPropsChanged.raiseEvent(props);
this._json.scheduleScript = props;
}
/** The point in time currently reflected by the view, expressed in seconds in the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time).
* This identifies a point on the timeline of the style's [[RenderSchedule.Script]], if any; it may also affect display of four-dimensional reality models.
* @see [[onTimePointChanged]] to be notified of changes to this property.
*/
get timePoint() {
return this._json.timePoint;
}
set timePoint(timePoint) {
if (timePoint !== this.timePoint) {
this.onTimePointChanged.raiseEvent(timePoint);
this._json.timePoint = timePoint;
}
}
/** Settings controlling the display of analytical models.
* @see [[analysisFraction]] to control playback of the animation.
*/
get analysisStyle() { return this._analysisStyle; }
set analysisStyle(style) {
if (style === this.analysisStyle)
return;
this.onAnalysisStyleChanged.raiseEvent(style);
this._analysisStyle = style;
if (style)
this._json.analysisStyle = style.toJSON();
else
delete this._json.analysisStyle;
}
/** A floating point value in [0..1] indicating the current point in animation of the [[analysisStyle]], where 0 corresponds to the beginning of
* the animation and 1 to the end. Default: 0.0.
*/
get analysisFraction() {
const fraction = this._json.analysisFraction ?? 0;
return Math.max(0, Math.min(1, fraction));
}
set analysisFraction(fraction) {
if (this.analysisFraction === fraction)
return;
this.onAnalysisFractionChanged.raiseEvent(fraction);
this._json.analysisFraction = Math.max(0, Math.min(1, fraction));
}
/** Settings controlling how white-on-white reversal is applied when [[ViewFlags.whiteOnWhiteReversal]] is enabled. */
get whiteOnWhiteReversal() { return this._whiteOnWhiteReversal; }
set whiteOnWhiteReversal(settings) {
if (settings.equals(this.whiteOnWhiteReversal))
return;
this.onWhiteOnWhiteReversalChanged.raiseEvent(settings);
this._whiteOnWhiteReversal = settings;
const json = settings.toJSON();
if (json)
this._json.whiteOnWhiteReversal = json;
else
delete this._json.whiteOnWhiteReversal;
}
/** 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.subCategoryOverrides.set(id, ovr);
}
/** Remove any [[SubCategoryOverride]] applied to a [[SubCategoryAppearance]] by this style.
* @param id The Id of the [[SubCategory]].
* @see [[overrideSubCategory]]
*/
dropSubCategoryOverride(id) {
this.subCategoryOverrides.delete(id);
}
/** The overrides applied by this style. */
get subCategoryOverrides() {
return this._subCategoryOverrides;
}
/** Obtain the override 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.subCategoryOverrides.get(id);
}
/** Returns true if an [[SubCategoryOverride]]s are defined by this style. */
get hasSubCategoryOverride() {
return this.subCategoryOverrides.size > 0;
}
/** Customize the way a [Model]($backend) is drawn by this display style.
* @param modelId The Id of the [Model]($backend) whose appearance is to be overridden.
* @param ovr The overrides to apply to the [Model]($backend) .
* @see [[dropModelAppearanceOverride]]
*/
overrideModelAppearance(modelId, ovr) {
this.modelAppearanceOverrides.set(modelId, ovr);
}
/** Remove any appearance overrides applied to a [Model]($backend) by this style.
* @param modelId The Id of the [Model]($backend) .
* @param ovr The overrides to apply to the [Model]($backend) .
* @see [[overrideModelAppearance]]
*/
dropModelAppearanceOverride(id) {
this.modelAppearanceOverrides.delete(id);
}
/** The overrides applied by this style. */
get modelAppearanceOverrides() {
return this._modelAppearanceOverrides;
}
/** Obtain the override applied to a [Model]($backend) by this style.
* @param id The Id of the [Model]($backend).
* @returns The corresponding FeatureAppearance, or undefined if the Model's appearance is not overridden.
* @see [[overrideModelAppearance]]
*/
getModelAppearanceOverride(id) {
return this.modelAppearanceOverrides.get(id);
}
/** Returns true if model appearance overrides are defined by this style. */
get hasModelAppearanceOverride() {
return this.modelAppearanceOverrides.size > 0;
}
/** Get any settings that override how the reality model with the specified Id is displayed.
* @param modelId The Id of the [Model]($backend).
* @returns the display settings, or `undefined` if no settings have been associated with `modelId`.
* @see [[setRealityModelDisplaySettings]] to change the settings.
* @beta
*/
getRealityModelDisplaySettings(modelId) {
return this._realityModelDisplaySettings.get(modelId);
}
/** Change the settings that control how the reality model with the specified Id is displayed.
* @param modelId The Id of the [Model]($backend) to which the settings apply.
* @param settings The settings to apply to the model, or `undefined` to clear any previous settings for that model.
* @beta
*/
setRealityModelDisplaySettings(modelId, settings) {
if (settings)
this._realityModelDisplaySettings.set(modelId, settings);
else
this._realityModelDisplaySettings.delete(modelId);
}
/** The set of elements that will not be drawn by this display style.
* @returns An iterable over the elements' Ids.
*/
get excludedElementIds() {
return this._excludedElements;
}
/** @internal */
get compressedExcludedElementIds() {
return this._excludedElements.ids;
}
/** Add one or more elements to the set of elements not to be displayed.
* @param id The Ids of the element(s) to be excluded.
*/
addExcludedElements(id) {
this._excludedElements.add("string" === typeof id ? [id] : id);
this.onExcludedElementsChanged.raiseEvent();
}
/** Remove an element from the set of elements not to be displayed. */
dropExcludedElement(id) {
this._excludedElements.delete([id]);
this.onExcludedElementsChanged.raiseEvent();
}
/** Remove one or more elements from the set of elements not to be displayed.
* @param id The Ids of the element(s) to be removed from the set of excluded elements.
*/
dropExcludedElements(id) {
this._excludedElements.delete("string" === typeof id ? [id] : id);
this.onExcludedElementsChanged.raiseEvent();
}
/** Remove all elements from the set of elements not to be displayed. */
clearExcludedElements() {
this._excludedElements.reset(undefined);
this.onExcludedElementsChanged.raiseEvent();
}
/** The style applied to the view's [ClipVector]($core-geometry). */
get clipStyle() {
return this._clipStyle;
}
set clipStyle(style) {
this.onClipStyleChanged.raiseEvent(style);
this._clipStyle = style;
if (style.matchesDefaults)
delete this._json.clipStyle;
else
this._json.clipStyle = style.toJSON();
}
/** Convert these settings to their JSON representation. */
toJSON() {
return this._json;
}
/** Serialize a subset of these settings to JSON, such that they can be applied to another DisplayStyleSettings to selectively override those settings.
* @param options Specifies which settings should be serialized. By default, settings that are specific to an iModel (e.g., subcategory overrides) or iTwin (e.g., context reality models)
* are omitted, as are drawing aids (e.g., ACS triad and grid).
* @returns a JSON representation of the selected settings suitable for passing to [[applyOverrides]].
* @see [[applyOverrides]] to apply the overrides to another DisplayStyleSettings..
*/
toOverrides(options) {
if (options?.includeAll) {
return {
...this.toJSON(),
viewflags: this.viewFlags.toFullyDefinedJSON(),
};
}
const viewflags = this.viewFlags.toFullyDefinedJSON();
const props = {
viewflags,
backgroundColor: this.backgroundColor.toJSON(),
monochromeColor: this.monochromeColor.toJSON(),
monochromeMode: this.monochromeMode,
whiteOnWhiteReversal: this.whiteOnWhiteReversal.toJSON() ?? { ignoreBackgroundColor: false },
};
if (options?.includeBackgroundMap) {
props.backgroundMap = this.backgroundMap.toPersistentJSON();
props.mapImagery = this.mapImagery.toJSON();
}
else {
delete viewflags.backgroundMap;
}
if (!options?.includeDrawingAids) {
delete viewflags.acs;
delete viewflags.grid;
}
if (options?.includeITwinSpecific || options?.includeIModelSpecific) {
props.timePoint = this.timePoint;
if (this._json.contextRealityModels) {
props.contextRealityModels = this._json.contextRealityModels;
if (!options?.includeIModelSpecific)
for (const model of this._json.contextRealityModels)
delete model.classifiers;
}
}
if (options?.includeIModelSpecific) {
if (this.analysisStyle) {
props.analysisStyle = this.analysisStyle.toJSON();
props.analysisFraction = this.analysisFraction;
}
if (this.scheduleScriptProps)
props.scheduleScript = [...this.scheduleScriptProps];
if (this.renderTimeline)
props.renderTimeline = this.renderTimeline;
props.subCategoryOvr = this._json.subCategoryOvr ? [...this._json.subCategoryOvr] : [];
props.modelOvr = this._json.modelOvr ? [...this._json.modelOvr] : [];
props.excludedElements = this._excludedElements.ids;
}
return props;
}
/** Selectively override some of these settings. Any field that is explicitly defined by the input will be overridden in these settings; any fields left undefined in the input
* will retain their current values in these settings. The input's [[ViewFlags]] are applied individually - only those flags that are explicitly defined will be overridden.
* For example, the following overrides will set the render mode to "smooth", change the background color to white, turn shadows off, and leave all other settings intact:
* ```ts
* {
* viewflags: {
* renderMode: RenderMode.SmoothShade,
* shadows: false,
* },
* backgroundColor: ColorByName.white,
* }
* ```
* @see [[toOverrides]] to produce overrides from an existing DisplayStyleSettings.
*/
applyOverrides(overrides) {
this._applyOverrides(overrides);
this.onOverridesApplied.raiseEvent(overrides);
}
/** @internal */
_applyOverrides(overrides) {
this.onApplyOverrides.raiseEvent(overrides);
if (overrides.viewflags) {
this.viewFlags = ViewFlags.fromJSON({
...this.viewFlags.toJSON(),
...overrides.viewflags,
});
}
if (undefined !== overrides.backgroundColor)
this.backgroundColor = ColorDef.fromJSON(overrides.backgroundColor);
if (undefined !== overrides.monochromeColor)
this.monochromeColor = ColorDef.fromJSON(overrides.monochromeColor);
if (undefined !== overrides.monochromeMode)
this.monochromeMode = overrides.monochromeMode;
if (overrides.backgroundMap)
this.backgroundMap = BackgroundMapSettings.fromPersistentJSON(overrides.backgroundMap);
if (overrides.mapImagery)
this.mapImagery = MapImagerySettings.createFromJSON(overrides.mapImagery, this.backgroundMap.toPersistentJSON());
if (undefined !== overrides.timePoint)
this.timePoint = overrides.timePoint;
if (overrides.contextRealityModels) {
this.contextRealityModels.clear();
for (const props of overrides.contextRealityModels)
this.contextRealityModels.add(props);
}
if (overrides.analysisStyle)
this.analysisStyle = AnalysisStyle.fromJSON(overrides.analysisStyle);
if (overrides.whiteOnWhiteReversal)
this.whiteOnWhiteReversal = WhiteOnWhiteReversalSettings.fromJSON(overrides.whiteOnWhiteReversal);
if (undefined !== overrides.analysisFraction)
this.analysisFraction = overrides.analysisFraction;
if (overrides.scheduleScript)
this.scheduleScriptProps = [...overrides.scheduleScript];
if (overrides.renderTimeline)
this.renderTimeline = overrides.renderTimeline;
if (overrides.subCategoryOvr) {
this._json.subCategoryOvr = [...overrides.subCategoryOvr];
this._subCategoryOverrides.populate();
}
if (overrides.modelOvr) {
this._json.modelOvr = [...overrides.modelOvr];
this._modelAppearanceOverrides.populate();
}
if (overrides.realityModelDisplay) {
this._json.realityModelDisplay = [...overrides.realityModelDisplay];
this._realityModelDisplaySettings.populate();
}
if (overrides.excludedElements)
this._excludedElements.reset("string" === typeof overrides.excludedElements ? overrides.excludedElements : [...overrides.excludedElements]);
this.onOverridesApplied.raiseEvent(overrides);
}
}
/** Provides access to the settings defined by a [[DisplayStyle3d]] or [[DisplayStyle3dState]], and ensures that
* the style's JSON properties are kept in sync.
* @public
*/
export class DisplayStyle3dSettings extends DisplayStyleSettings {
_thematic;
_contours;
_hline;
_ao;
_solarShadows;
_lights;
_environment;
_planProjections;
get _json3d() { return this._json; }
is3d() {
return true;
}
constructor(jsonProperties, options) {
super(jsonProperties, options);
this._thematic = ThematicDisplay.fromJSON(this._json3d.thematic);
this._contours = ContourDisplay.fromJSON(this._json3d.contours);
this._hline = HiddenLine.Settings.fromJSON(this._json3d.hline);
this._ao = AmbientOcclusion.Settings.fromJSON(this._json3d.ao);
this._solarShadows = SolarShadowSettings.fromJSON(this._json3d.solarShadows);
this._environment = Environment.fromJSON(this._json3d.environment);
// Very long ago we used to stick MicroStation's light settings into json.sceneLights. Later we started adding the sunDir.
// We don't want any of MicroStation's settings. We do want to preserve the sunDir if present.
if (this._json3d.lights) {
this._lights = LightSettings.fromJSON(this._json3d.lights);
}
else {
const sunDir = this._json3d.sceneLights?.sunDir;
this._lights = LightSettings.fromJSON(sunDir ? { solar: { direction: sunDir } } : undefined);
}
this.populatePlanProjectionsFromJSON();
}
populatePlanProjectionsFromJSON() {
this._planProjections = undefined;
const projections = this._json3d.planProjections;
if (undefined !== projections) {
for (const key of Object.keys(projections)) {
const id = Id64.fromJSON(key);
if (!Id64.isValidId64(id)) {
delete projections[key];
continue;
}
const settings = PlanProjectionSettings.fromJSON(projections[key]);
if (undefined === settings) {
delete projections[key];
continue;
}
if (undefined === this._planProjections)
this._planProjections = new Map();
this._planProjections.set(id, settings);
}
}
}
/** Convert these settings to their JSON representation. */
toJSON() {
return this._json3d;
}
/** See [[DisplayStyleSettings.toOverrides]]. */
toOverrides(options) {
const props = super.toOverrides(options);
if (options?.includeAll)
return props;
assert(undefined !== props.viewflags);
props.environment = this.environment.toJSON();
props.hline = this.hiddenLineSettings.toJSON();
props.ao = this.ambientOcclusionSettings.toJSON();
props.solarShadows = this.solarShadows.toJSON();
props.lights = this.lights.toJSON();
if (options?.includeIModelSpecific) {
props.thematic = this.thematic.toJSON();
if (this._json3d.planProjections)
props.planProjections = { ...this._json3d.planProjections };
}
else if (ThematicDisplayMode.InverseDistanceWeightedSensors !== this.thematic.displayMode) {
props.thematic = {
...this.thematic.toJSON(),
sensorSettings: undefined,
};
if (ThematicDisplayMode.Height === props.thematic.displayMode) {
// DisplayStyle3dState will compute range based on project extents.
props.thematic.range = undefined;
}
}
props.contours = this.contours.toJSON();
return props;
}
/** See [[DisplayStyleSettings.applyOverrides]]. */
applyOverrides(overrides) {
super._applyOverrides(overrides);
if (overrides.environment)
this.environment = Environment.fromJSON(overrides.environment);
if (overrides.hline)
this.hiddenLineSettings = HiddenLine.Settings.fromJSON(overrides.hline);
if (overrides.ao)
this.ambientOcclusionSettings = AmbientOcclusion.Settings.fromJSON(overrides.ao);
if (overrides.solarShadows)
this.solarShadows = SolarShadowSettings.fromJSON(overrides.solarShadows);
if (overrides.lights)
this.lights = LightSettings.fromJSON(overrides.lights);
if (overrides.planProjections) {
this._json3d.planProjections = { ...overrides.planProjections };
this.populatePlanProjectionsFromJSON();
}
if (overrides.thematic)
this.thematic = ThematicDisplay.fromJSON(overrides.thematic);
if (overrides.contours)
this.contours = ContourDisplay.fromJSON(overrides.contours);
this.onOverridesApplied.raiseEvent(overrides);
}
/** The settings that control thematic display. */
get thematic() { return this._thematic; }
set thematic(thematic) {
if (thematic.equals(this.thematic))
return;
this.onThematicChanged.raiseEvent(thematic);
this._thematic = thematic;
this._json3d.thematic = thematic.toJSON();
}
/** The settings that control contour display. */
get contours() { return this._contours; }
set contours(contours) {
if (contours.equals(this.contours))
return;
this.onContoursChanged.raiseEvent(contours);
this._contours = contours;
this._json3d.contours = contours.toJSON();
}
/** The settings that control how visible and hidden edges are displayed. */
get hiddenLineSettings() { return this._hline; }
set hiddenLineSettings(hline) {
if (hline.equals(this.hiddenLineSettings))
return;
this.onHiddenLineSettingsChanged.raiseEvent(hline);
this._hline = hline;
this._json3d.hline = hline.toJSON();
}
/** The settings that control how ambient occlusion is displayed. */
get ambientOcclusionSettings() { return this._ao; }
set ambientOcclusionSettings(ao) {
this.onAmbientOcclusionSettingsChanged.raiseEvent(ao);
this._ao = ao;
this._json3d.ao = ao.toJSON();
}
/** The settings that control how solar shadows are displayed. */
get solarShadows() {
return this._solarShadows;
}
set solarShadows(solarShadows) {
if (solarShadows.equals(this.solarShadows))
return;
this.onSolarShadowsChanged.raiseEvent(solarShadows);
this._solarShadows = solarShadows;
const json = solarShadows.toJSON();
if (!json)
delete this._json3d.solarShadows;
else
this._json3d.solarShadows = json;
}
/** Controls the display of a [[SkyBox]], [[GroundPlane]], and [[Atmosphere]].
* @public
*/
get environment() {
return this._environment;
}
set environment(environment) {
if (environment !== this.environment) {
this.onEnvironmentChanged.raiseEvent(environment);
this._environment = environment;
this._json3d.environment = environment.toJSON();
}
}
/** Toggle display of the [[environment]]'s [[SkyBox]].
* @param display Whether to display the skybox, or `undefined` to toggle the current display.
*/
toggleSkyBox(display) {
display = display ?? this.environment.displaySky;
if (display !== this.environment.displaySky)
this.environment = this.environment.withDisplay({ sky: display });
}
/** Toggle display of the [[environment]]'s [[GroundPlane]].
* @param display Whether to display the ground plane, or `undefined` to toggle the current display.
*/
toggleGroundPlane(display) {
display = display ?? this.environment.displayGround;
if (display !== this.environment.displayGround)
this.environment = this.environment.withDisplay({ ground: display });
}
/** Toggle display of the [[environment]]'s [[Atmosphere]].
* @beta
* @param display Whether to display the atmosphere, or `undefined` to toggle the current display.
*/
toggleAtmosphere(display) {
display = display ?? this.environment.displayAtmosphere;
if (display !== this.environment.displayAtmosphere)
this.environment = this.environment.withDisplay({ atmosphere: display });
}
get lights() {
return this._lights;
}
set lights(lights) {
if (this.lights.equals(lights))
return;
this.onLightsChanged.raiseEvent(lights);
this._lights = lights;
this._json3d.lights = lights.toJSON();
}
/** Adjust the solar light direction based on a date and time at a geographic location.
* This replaces `this.lights` with a copy that records the time point and the computed direction.
* @param timePoint The time in UNIX milliseconds.
* @param location The geographic location; or an iModel, in which case the iModel's [[EcefLocation]] is used.
* @see [[sunTime]] to get the current sun time.
* @see [[clearSunTime]] to clear the time point.
* @note If `location` is an iModel lacking an EcefLocation, a location in Exton, Pennsylvania will be used to compute the light direction instead.
*/
setSunTime(timePoint, location) {
let cartoCenter;
if (location instanceof IModel) {
if (location.ecefLocation)
cartoCenter = Cartographic.fromEcef(location.ecefLocation.origin);
if (!cartoCenter)
cartoCenter = Cartographic.fromDegrees({ longitude: -75.17035, latitude: 39.954927, height: 0.0 });
}
else {
cartoCenter = location;
}
const direction = calculateSolarDirection(new Date(timePoint), cartoCenter);
this.lights = this.lights.clone({ solar: { direction, timePoint } });
}
/** Clear the solar time point stored in `this.lights.solarLight`.
* @note This does not affect the solar light direction.
* @see [[sunTime]] to get the current sun time.
* @see [[setSunTime]] to set the time point and the solar light direction derived from it.
*/
clearSunTime() {
if (this.lights.solar.timePoint === undefined)
return;
const solar = this.lights.solar.toJSON() ?? {};
solar.timePoint = undefined;
this.lights = this.lights.clone({ solar });
}
/** The time point from which the solar light direction was derived, in UNIX milliseconds.
* @see [[setSunTime]] to change the time point and solar direction.
* @see [[clearSunTime]] to reset the time point to `undefined`.
*/
get sunTime() {
return this.lights.solar.timePoint;
}
/** Get the plan projection settings associated with the specified model, if defined. */
getPlanProjectionSettings(modelId) {
return undefined !== this._planProjections ? this._planProjections.get(modelId) : undefined;
}
/** Set or clear the plan projection settings associated with the specified model. */
setPlanProjectionSettings(modelId, settings) {
this.onPlanProjectionSettingsChanged.raiseEvent(modelId, settings);
if (undefined === settings) {
if (undefined !== this._planProjections) {
assert(undefined !== this._json3d.planProjections);
this._planProjections.delete(modelId);
delete this._json3d.planProjections[modelId];
if (0 === this._planProjections.size) {
this._planProjections = undefined;
delete this._json3d.planProjections;
}
}
return;
}
if (undefined === this._planProjections) {
this._planProjections = new Map();
this._json3d.planProjections = {};
}
this._planProjections.set(modelId, settings);
expectDefined(this._json3d.planProjections)[modelId] = settings.toJSON();
}
/** An iterator over all of the defined plan projection settings. The iterator includes the Id of the model associated with each settings object. */
get planProjectionSettings() {
return undefined !== this._planProjections ? this._planProjections.entries() : undefined;
}
}
//# sourceMappingURL=DisplayStyleSettings.js.map