UNPKG

@itwin/core-common

Version:

iTwin.js components common to frontend and backend

433 lines • 20.2 kB
/*--------------------------------------------------------------------------------------------- * 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