@itwin/core-common
Version:
iTwin.js components common to frontend and backend
433 lines • 20.2 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
*/
import { assert, BeEvent } from "@itwin/core-bentley";
import { FeatureAppearance } from "./FeatureSymbology";
import { PlanarClipMaskMode, PlanarClipMaskSettings } from "./PlanarClipMask";
import { SpatialClassifiers } from "./SpatialClassification";
import { RealityModelDisplaySettings } from "./RealityModelDisplaySettings";
/** Identify the Reality Data service provider
* @beta
*/
export var RealityDataProvider;
(function (RealityDataProvider) {
/**
* This is the legacy mode where the access to the 3d tiles is hardcoded in ContextRealityModelProps.tilesetUrl property.
* It was used to support RealityMesh3DTiles, Terrain3DTiles, Cesium3DTiles
* You should use other modes when possible
* @see [[RealityDataSource.createKeyFromUrl]] that will try to detect provider from an URL
*/
RealityDataProvider["TilesetUrl"] = "TilesetUrl";
/**
* This is the legacy mode where the access to the 3d tiles is hardcoded in ContextRealityModelProps.OrbitGtBlob property.
* It was used to support OrbitPointCloud (OPC) from other server than ContextShare
* You should use other modes when possible
* @see [[RealityDataSource.createKeyFromOrbitGtBlobProps]] that will try to detect provider from an URL
*/
RealityDataProvider["OrbitGtBlob"] = "OrbitGtBlob";
/**
* Will provide access url from realityDataId and iTwinId on contextShare for 3dTile storage format or OPC storage format
* This provider supports all types of 3dTile storage format and OrbitPointCloud: RealityMesh3DTiles, Terrain3DTiles, Cesium3DTiles, OPC
* @see [[RealityDataFormat]].
*/
RealityDataProvider["ContextShare"] = "ContextShare";
/**
* Will provide Open Street Map Building (OSM) from Cesium Ion (in 3dTile format)
*/
RealityDataProvider["CesiumIonAsset"] = "CesiumIonAsset";
})(RealityDataProvider || (RealityDataProvider = {}));
/** Identify the Reality Data storage format
* @beta
*/
export var RealityDataFormat;
(function (RealityDataFormat) {
/**
* 3dTile supported formats; RealityMesh3DTiles, Terrain3DTiles, Cesium3DTiles
* */
RealityDataFormat["ThreeDTile"] = "ThreeDTile";
/**
* Orbit Point Cloud (OPC) storage format (RealityDataType.OPC)
*/
RealityDataFormat["OPC"] = "OPC";
})(RealityDataFormat || (RealityDataFormat = {}));
/** Utility function for RealityDataFormat
* @beta
*/
(function (RealityDataFormat) {
/**
* Try to extract the RealityDataFormat from the url
* @param tilesetUrl the reality data attachment url
* @returns the extracted RealityDataFormat or ThreeDTile by default if not found
*/
function fromUrl(tilesetUrl) {
let format = RealityDataFormat.ThreeDTile;
if (tilesetUrl.includes(".opc"))
format = RealityDataFormat.OPC;
return format;
}
RealityDataFormat.fromUrl = fromUrl;
})(RealityDataFormat || (RealityDataFormat = {}));
/**
* RealityDataSourceKey utility functions
* @beta */
export var RealityDataSourceKey;
(function (RealityDataSourceKey) {
/** Utility function to convert a RealityDataSourceKey into its string representation */
function convertToString(rdSourceKey) {
return `${rdSourceKey.provider}:${rdSourceKey.format}:${rdSourceKey.id}:${rdSourceKey?.iTwinId}`;
}
RealityDataSourceKey.convertToString = convertToString;
/** Utility function to compare two RealityDataSourceKey, we consider it equal even if itwinId is different */
function isEqual(key1, key2) {
if ((key1.provider === RealityDataProvider.CesiumIonAsset) && key2.provider === RealityDataProvider.CesiumIonAsset)
return true; // ignore other properties for CesiumIonAsset, id is hidden
if ((key1.provider === key2.provider) && (key1.format === key2.format) && (key1.id === key2.id)) {
// && (key1?.iTwinId === key2?.iTwinId)) -> ignore iTwinId, consider it is the same reality data
return true;
}
return false;
}
RealityDataSourceKey.isEqual = isEqual;
})(RealityDataSourceKey || (RealityDataSourceKey = {}));
/** @public */
export var ContextRealityModelProps;
(function (ContextRealityModelProps) {
/** Produce a deep copy of `input`. */
function clone(input) {
// Spread operator is shallow, and includes `undefined` properties and empty strings.
// We want to make deep copies, omit undefined properties and empty strings, and require tilesetUrl to be defined.
const output = { tilesetUrl: input.tilesetUrl ?? "" };
if (input.rdSourceKey)
output.rdSourceKey = { ...input.rdSourceKey };
if (input.name)
output.name = input.name;
if (input.realityDataId)
output.realityDataId = input.realityDataId;
if (input.description)
output.description = input.description;
if (input.orbitGtBlob)
output.orbitGtBlob = { ...input.orbitGtBlob };
if (input.appearanceOverrides) {
output.appearanceOverrides = { ...input.appearanceOverrides };
if (input.appearanceOverrides.rgb)
output.appearanceOverrides.rgb = { ...input.appearanceOverrides.rgb };
}
if (input.displaySettings) {
output.displaySettings = { ...input.displaySettings };
if (input.displaySettings.pointCloud)
output.displaySettings.pointCloud = { ...output.displaySettings.pointCloud };
}
if (input.planarClipMask)
output.planarClipMask = { ...input.planarClipMask };
if (input.classifiers)
output.classifiers = input.classifiers.map((x) => { return { ...x, flags: { ...x.flags } }; });
if (input.invisible)
output.invisible = input.invisible;
return output;
}
ContextRealityModelProps.clone = clone;
})(ContextRealityModelProps || (ContextRealityModelProps = {}));
/** A reality model not associated with a [GeometricModel]($backend) but instead defined in a [DisplayStyle]($backend) or [DisplayStyleState]($frontend).
* Such reality models are displayed to provide context to the view and can be freely attached and detached at display time.
* @see [this interactive example](https://www.itwinjs.org/sample-showcase/?group=Viewer&sample=reality-data-sample)
* @see [[DisplayStyleSettings.contextRealityModels]] to define context reality models for a display style.
* @public
*/
export class ContextRealityModel {
/** @internal */
_props;
/**
* The reality data source key identify the reality data provider and storage format.
* It takes precedence over tilesetUrl and orbitGtBlob when present and can be use to actually replace these properties.
* @beta
*/
rdSourceKey;
/** A name suitable for display in a user interface. By default, an empty string. */
name;
/** The URL that supplies the 3d tiles for displaying the reality model. */
url;
/** A description of the model suitable for display in a user interface. By default, an empty string. */
description;
/** An optional identifier that, if present, can be used to elide a request to the reality data service. */
realityDataId;
_invisible;
_classifiers;
/** @alpha */
orbitGtBlob;
_appearanceOverrides;
/** @beta */
_displaySettings;
_planarClipMask;
/** Event dispatched just before assignment to [[planarClipMaskSettings]]. */
onPlanarClipMaskChanged = new BeEvent();
/** Event dispatched just before assignment to [[appearanceOverrides]]. */
onAppearanceOverridesChanged = new BeEvent();
/** Event dispatched just before assignment to [[displaySettings]].
* @beta
*/
onDisplaySettingsChanged = new BeEvent();
/** Event dispatched just before a model become invisible
* @beta
*/
onInvisibleChanged = new BeEvent();
/** Construct a new context reality model.
* @param props JSON representation of the reality model, which will be kept in sync with changes made via the ContextRealityModel's methods.
* @param options Options to customize how the reality model is created.
*/
constructor(props, options) {
this._props = props;
this.rdSourceKey = props.rdSourceKey;
this.name = props.name ?? "";
this.url = props.tilesetUrl ?? "";
this.orbitGtBlob = props.orbitGtBlob;
this.realityDataId = props.realityDataId;
this.description = props.description ?? "";
this._invisible = props.invisible ?? false;
this._appearanceOverrides = props.appearanceOverrides ? FeatureAppearance.fromJSON(props.appearanceOverrides) : undefined;
this._displaySettings = RealityModelDisplaySettings.fromJSON(props.displaySettings);
if (props.planarClipMask && props.planarClipMask.mode !== PlanarClipMaskMode.None)
this._planarClipMask = PlanarClipMaskSettings.fromJSON(props.planarClipMask);
if (options?.createClassifiers) {
this._classifiers = options.createClassifiers(props);
}
else {
this._classifiers = new SpatialClassifiers(props);
}
}
/** A set of [[SpatialClassifier]]s, of which one at any given time can be used to classify the reality model. */
get classifiers() {
return this._classifiers;
}
/** Optionally describes how the geometry of the reality model can be masked by other models. */
get planarClipMaskSettings() {
return this._planarClipMask;
}
set planarClipMaskSettings(settings) {
this.onPlanarClipMaskChanged.raiseEvent(settings, this);
if (!settings)
delete this._props.planarClipMask;
else
this._props.planarClipMask = settings.toJSON();
this._planarClipMask = settings;
}
/** Overrides applied to the appearance of the reality model. Only the rgb, transparency, nonLocatable, and emphasized properties are applicable - the rest are ignored. */
get appearanceOverrides() {
return this._appearanceOverrides;
}
set appearanceOverrides(overrides) {
this.onAppearanceOverridesChanged.raiseEvent(overrides, this);
if (!overrides)
delete this._props.appearanceOverrides;
else
this._props.appearanceOverrides = overrides.toJSON();
this._appearanceOverrides = overrides;
}
/** Settings controlling how this reality model is displayed in a [Viewport]($frontend).
* @beta
*/
get displaySettings() {
return this._displaySettings;
}
set displaySettings(settings) {
this.onDisplaySettingsChanged.raiseEvent(settings, this);
this._props.displaySettings = settings.toJSON();
this._displaySettings = settings;
}
/** If true, reality model is not drawn.
* @beta
*/
get invisible() {
return this._invisible;
}
set invisible(invisible) {
if (invisible !== this.invisible) {
this.onInvisibleChanged.raiseEvent(invisible, this);
}
this._props.invisible = invisible;
this._invisible = invisible;
}
/** Convert this model to its JSON representation. */
toJSON() {
return ContextRealityModelProps.clone(this._props);
}
/** Returns true if [[name]] and [[url]] match the specified name and url. */
matchesNameAndUrl(name, url) {
return this.name === name && this.url === url;
}
}
/** A list of [[ContextRealityModel]]s attached to a [[DisplayStyleSettings]]. The list may be presented to the user with the name and description of each model.
* The list is automatically synchronized with the underlying JSON representation provided by the input [[ContextRealityModelsContainer]].
* @see [this interactive example](https://www.itwinjs.org/sample-showcase/?group=Viewer&sample=reality-data-sample)
* @see [[DisplayStyleSettings.contextRealityModels]].
* @public
*/
export class ContextRealityModels {
_container;
_createModel;
_models = [];
/** Event dispatched just before [[ContextRealityModel.planarClipMaskSettings]] is modified for one of the reality models. */
onPlanarClipMaskChanged = new BeEvent();
/** Event dispatched just before [[ContextRealityModel.appearanceOverrides]] is modified for one of the reality models. */
onAppearanceOverridesChanged = new BeEvent();
/** Event dispatched just before [[ContextRealityModel.displaySettings]] is modified for one of the reality models.
* @beta
*/
onDisplaySettingsChanged = new BeEvent();
/** Event dispatched just before [[ContextRealityModel.invisible]] is modified for one of the reality models.
* @beta
*/
onInvisibleChanged = new BeEvent();
/** Event dispatched when a model is [[add]]ed, [[delete]]d, [[replace]]d, or [[update]]d. */
onChanged = new BeEvent();
/** @internal */
constructor(arg0, createContextRealityModel) {
let container;
let defer = false;
if (arg0.container) {
container = arg0.container;
createContextRealityModel = arg0.createContextRealityModel;
defer = true === arg0.deferPopulating;
}
else {
container = arg0;
}
this._container = container;
this._createModel = createContextRealityModel ?? ((props) => new ContextRealityModel(props));
if (!defer)
this.populate();
}
/** @internal needs to be invoked after DisplayStyleSettings constructor by DisplayStyleState constructor.*/
/** Populate the list of [[models]] from the container that was supplied to the constructor.
* This should only be invoked once, and only if [[ContextRealityModelsArgs.deferPopulating]] was specified as `true` when calling the constructor.
* @public
*/
populate() {
assert(this._models.length === 0, "do not call ContextRealityModels.populate more than once");
const models = this._container.contextRealityModels;
if (models)
for (const model of models)
this._models.push(this.createModel(model));
}
/** The read-only list of reality models. */
get models() {
return this._models;
}
/** Append a new reality model to the list.
* @param The JSON representation of the reality model.
* @returns the newly-added reality model.
*/
add(props) {
if (!this._container.contextRealityModels)
this._container.contextRealityModels = [];
props = ContextRealityModelProps.clone(props);
const model = this.createModel(props);
this.onChanged.raiseEvent(undefined, model);
this._models.push(model);
this._container.contextRealityModels.push(props);
return model;
}
/** Remove the specified reality model from the list.
* @param model The reality model to remove.
* @returns true if the model was removed, or false if the model was not present in the list.
*/
delete(model) {
const index = this._models.indexOf(model);
if (-1 === index)
return false;
assert(undefined !== this._container.contextRealityModels);
assert(index < this._container.contextRealityModels.length);
this.dropEventListeners(model);
this.onChanged.raiseEvent(model, undefined);
this._models.splice(index, 1);
if (this.models.length === 0)
this._container.contextRealityModels = undefined;
else
this._container.contextRealityModels.splice(index, 1);
return true;
}
/** Remove all reality models from the list. */
clear() {
for (const model of this.models) {
this.dropEventListeners(model);
this.onChanged.raiseEvent(model, undefined);
}
this._container.contextRealityModels = undefined;
this._models.length = 0;
}
/** Replace a reality model in the list.
* @param toReplace The reality model to be replaced.
* @param replaceWith The JSON representation of the replacement reality model.
* @returns the newly-created reality model that replaced `toReplace`.
* @throws Error if `toReplace` is not present in the list
* @note The replacement occupies the same index in the list as `toReplace` did.
*/
replace(toReplace, replaceWith) {
const index = this._models.indexOf(toReplace);
if (-1 === index)
throw new Error("ContextRealityModel not present in list.");
assert(undefined !== this._container.contextRealityModels);
assert(index < this._container.contextRealityModels.length);
replaceWith = ContextRealityModelProps.clone(replaceWith);
const model = this.createModel(replaceWith);
this.onChanged.raiseEvent(toReplace, model);
this.dropEventListeners(toReplace);
this._models[index] = model;
this._container.contextRealityModels[index] = replaceWith;
return model;
}
/** Change selected properties of a reality model.
* @param toUpdate The reality model whose properties are to be modified.
* @param updateProps The properties to change.
* @returns The updated reality model, identical to `toUpdate` except for properties explicitly supplied by `updateProps`.
* @throws Error if `toUpdate` is not present in the list.
*/
update(toUpdate, updateProps) {
const props = {
...toUpdate.toJSON(),
...updateProps,
};
// Partial<> makes it possible to pass `undefined` for tilesetUrl...preserve previous URL in that case.
if (undefined === props.tilesetUrl)
props.tilesetUrl = toUpdate.url;
return this.replace(toUpdate, props);
}
createModel(props) {
const model = this._createModel(props);
// eslint-disable-next-line @typescript-eslint/unbound-method
model.onPlanarClipMaskChanged.addListener(this.handlePlanarClipMaskChanged, this);
// eslint-disable-next-line @typescript-eslint/unbound-method
model.onAppearanceOverridesChanged.addListener(this.handleAppearanceOverridesChanged, this);
// eslint-disable-next-line @typescript-eslint/unbound-method
model.onDisplaySettingsChanged.addListener(this.handleDisplaySettingsChanged, this);
// eslint-disable-next-line @typescript-eslint/unbound-method
model.onInvisibleChanged.addListener(this.handleInvisibleChanged, this);
return model;
}
dropEventListeners(model) {
// eslint-disable-next-line @typescript-eslint/unbound-method
model.onPlanarClipMaskChanged.removeListener(this.handlePlanarClipMaskChanged, this);
// eslint-disable-next-line @typescript-eslint/unbound-method
model.onAppearanceOverridesChanged.removeListener(this.handleAppearanceOverridesChanged, this);
// eslint-disable-next-line @typescript-eslint/unbound-method
model.onDisplaySettingsChanged.removeListener(this.handleDisplaySettingsChanged, this);
// eslint-disable-next-line @typescript-eslint/unbound-method
model.onInvisibleChanged.removeListener(this.handleInvisibleChanged, this);
}
handlePlanarClipMaskChanged(mask, model) {
this.onPlanarClipMaskChanged.raiseEvent(model, mask);
}
handleAppearanceOverridesChanged(app, model) {
this.onAppearanceOverridesChanged.raiseEvent(model, app);
}
handleDisplaySettingsChanged(settings, model) {
this.onDisplaySettingsChanged.raiseEvent(model, settings);
}
handleInvisibleChanged(invisible, model) {
this.onInvisibleChanged.raiseEvent(model, invisible);
}
}
//# sourceMappingURL=ContextRealityModel.js.map