UNPKG

@itwin/core-backend

Version:
1,079 lines • 85.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 Elements */ import { CompressedId64Set, Id64, JsonUtils, OrderedId64Array } from "@itwin/core-bentley"; import { BisCodeSpec, Code, ConcreteEntityTypes, IModel, Placement2d, Placement3d, RelatedElement, RenderSchedule, SectionType, TypeDefinition } from "@itwin/core-common"; import { ClipVector, Range3d, Transform, YawPitchRollAngles } from "@itwin/core-geometry"; import { Entity } from "./Entity"; import { DefinitionModel, DrawingModel, PhysicalModel, SectionDrawingModel } from "./Model"; import { SubjectOwnsSubjects } from "./NavigationRelationship"; import { _cache, _elementWasCreated, _nativeDb, _verifyChannel } from "./internal/Symbols"; import { EntityClass } from "@itwin/ecschema-metadata"; /** The smallest individually identifiable building block for modeling the real world in an iModel. * Each element represents an [[Entity]] in the real world. Sets of Elements (contained in [[Model]]s) are used to model * other Elements that represent larger scale real world entities. Using this recursive modeling strategy, * Elements can represent entities at any scale. Elements can represent physical things or abstract concepts * or simply be information records. * * Every Element has a 64-bit id (inherited from Entity) that uniquely identifies it within an iModel. Every Element also * has a [[code]] that identifies its meaning in the real world. Additionally, Elements may have a [[federationGuid]] * to hold a GUID, if the element was assigned that GUID by some other federated database. The iModel database enforces * uniqueness of id, code, and federationGuid. * * The Element class provides `static` methods like [[onInsert]], [[onUpdated]], [[onCloned]], and [[onChildAdded]] that enable * it to customize persistence operations. For example, the base implementations of [[onInsert]], [[onUpdate]], and [[onDelete]] * validate that the appropriate [locks]($docs/learning/backend/ConcurrencyControl.md), [codes]($docs/learning/backend/CodeService.md), * and [channel permissions]($docs/learning/backend/Channel.md) are obtained before a change to the element is written to the iModel. * A subclass of Element that overrides any of these methods **must** call the `super` method as well. An application that supplies its * own Element subclasses should register them at startup via [[ClassRegistry.registerModule]] or [[ClassRegistry.register]]. * * See: * * [Element Fundamentals]($docs/bis/guide/fundamentals/element-fundamentals.md) * * [Working with schemas and elements in TypeScript]($docs/learning/backend/SchemasAndElementsInTypeScript.md) * * [Creating elements]($docs/learning/backend/CreateElements.md) * @public @preview */ export class Element extends Entity { static get className() { return "Element"; } /** @internal */ static get protectedOperations() { return ["onInsert", "onUpdate", "onDelete"]; } /** The ModelId of the [Model]($docs/bis/guide/fundamentals/model-fundamentals.md) containing this element */ model; /** The [Code]($docs/bis/guide/fundamentals/codes.md) for this element */ code; /** The parent element, if present, of this element. */ parent; /** A [FederationGuid]($docs/bis/guide/fundamentals/element-fundamentals.md#federationguid) assigned to this element by some other federated database */ federationGuid; /** A [user-assigned label]($docs/bis/guide/fundamentals/element-fundamentals.md#userlabel) for this element. */ userLabel; /** Optional [json properties]($docs/bis/guide/fundamentals/element-fundamentals.md#jsonproperties) of this element. */ jsonProperties; constructor(props, iModel) { super(props, iModel); this.code = Code.fromJSON(props.code); // TODO: Validate props.code - don't silently fail if it is the wrong type this.model = RelatedElement.idFromJson(props.model); this.parent = RelatedElement.fromJSON(props.parent); this.federationGuid = props.federationGuid; this.userLabel = props.userLabel; this.jsonProperties = { ...props.jsonProperties }; // make sure we have our own copy } /** * Element custom HandledProps include 'codeValue', 'codeSpec', 'codeScope', 'model', 'parent', 'federationGuid', and 'lastMod'. * @inheritdoc * @beta */ static _customHandledProps = [ { propertyName: "codeValue", source: "Class" }, { propertyName: "codeSpec", source: "Class" }, { propertyName: "codeScope", source: "Class" }, { propertyName: "model", source: "Class" }, { propertyName: "parent", source: "Class" }, { propertyName: "federationGuid", source: "Class" }, { propertyName: "lastMod", source: "Class" }, ]; /** * Element deserializes 'codeValue', 'codeSpec', 'codeScope', 'model', 'parent', and 'federationGuid'. * @inheritdoc * @beta */ static deserialize(props) { const elProps = super.deserialize(props); const instance = props.row; elProps.code = { value: instance.codeValue ?? "", spec: instance.codeSpec.id, scope: instance.codeScope.id }; elProps.model = instance.model.id; if (instance.parent) elProps.parent = instance.parent; if (instance.federationGuid) elProps.federationGuid = instance.federationGuid; return elProps; } /** * Element serialize 'codeValue', 'codeSpec', 'codeScope', 'model', 'parent', and 'federationGuid'. * @inheritdoc * @beta */ static serialize(props, iModel) { const inst = super.serialize(props, iModel); inst.codeValue = props.code.value; inst.codeSpec = { id: props.code.spec }; inst.codeScope = { id: props.code.scope }; inst.model = { id: props.model }; inst.parent = props.parent; inst.federationGuid = props.federationGuid ?? iModel[_nativeDb].newBeGuid(); return inst; } /** Called before a new Element is inserted. * @note throw an exception to disallow the insert * @note If you override this method, you must call super. * @note `this` is the class of the Element to be inserted * @beta */ static onInsert(arg) { const { iModel, props } = arg; const operation = "insert"; iModel.channels[_verifyChannel](arg.props.model); iModel.locks.checkSharedLock(props.model, "model", operation); // inserting requires shared lock on model if (props.parent) // inserting requires shared lock on parent, if present iModel.locks.checkSharedLock(props.parent.id, "parent", operation); iModel.codeService?.verifyCode(arg); } /** Called after a new Element was inserted. * @note If you override this method, you must call super. * @note `this` is the class of the Element that was inserted * @beta */ static onInserted(arg) { const locks = arg.iModel.locks; if (locks && !locks.holdsExclusiveLock(arg.model)) locks[_elementWasCreated](arg.id); arg.iModel.models[_cache].delete(arg.model); } /** Called before an Element is updated. * @note throw an exception to disallow the update * @note If you override this method, you must call super. * @note `this` is the class of the Element to be updated * @beta */ static onUpdate(arg) { const { iModel, props } = arg; iModel.channels[_verifyChannel](props.model); iModel.locks.checkExclusiveLock(props.id, "element", "update"); // eslint-disable-line @typescript-eslint/no-non-null-assertion iModel.codeService?.verifyCode(arg); } /** Called after an Element was updated. * @note If you override this method, you must call super. * @note `this` is the class of the Element that was updated * @beta */ static onUpdated(arg) { arg.iModel.elements[_cache].delete({ id: arg.id }); arg.iModel.models[_cache].delete(arg.model); } /** Called before an Element is deleted. * @note throw an exception to disallow the delete * @note If you override this method, you must call super. * @note `this` is the class of the Element to be deleted * @beta */ static onDelete(arg) { arg.iModel.channels[_verifyChannel](arg.model); arg.iModel.locks.checkExclusiveLock(arg.id, "element", "delete"); } /** Called after an Element was deleted. * @note If you override this method, you must call super. * @note `this` is the class of the Element that was deleted * @beta */ static onDeleted(arg) { arg.iModel.elements[_cache].delete(arg); arg.iModel.models[_cache].delete(arg.model); } /** Called when an element with an instance of this class as its parent is about to be deleted. * @note throw an exception if the element should not be deleted * @note implementers should not presume that the element was deleted if this method does not throw, * since the delete may fail for other reasons. Instead, rely on [[onChildDeleted]] for that purpose. * @note `this` is the class of the parent Element whose child will be deleted * @beta */ static onChildDelete(_arg) { } /** Called after an element with an instance of this class as its parent was successfully deleted. * @note `this` is the class of the parent Element whose child was deleted * @beta */ static onChildDeleted(arg) { arg.iModel.elements[_cache].delete({ id: arg.parentId }); arg.iModel.elements[_cache].delete({ id: arg.childId }); } /** Called when a *new element* with an instance of this class as its parent is about to be inserted. * @note throw an exception if the element should not be inserted * @note `this` is the class of the prospective parent Element. * @beta */ static onChildInsert(_arg) { } /** Called after a *new element* with an instance of this class as its parent was inserted. * @note `this` is the class of the parent Element. * @beta */ static onChildInserted(arg) { arg.iModel.elements[_cache].delete({ id: arg.parentId }); } /** Called when an element with an instance of this class as its parent is about to be updated. * @note throw an exception if the element should not be updated * @note `this` is the class of the parent Element. * @beta */ static onChildUpdate(_arg) { } /** Called after an element with an instance of this the class as its parent was updated. * @note `this` is the class of the parent Element. * @beta */ static onChildUpdated(arg) { arg.iModel.elements[_cache].delete({ id: arg.parentId }); } /** Called when an *existing element* is about to be updated so that an instance of this class will become its new parent. * @note throw an exception if the element should not be added * @note `this` is the class of the prospective parent Element. * @beta */ static onChildAdd(_arg) { } /** Called after an *existing element* has been updated so that an instance of this class is its new parent. * @note `this` is the class of the new parent Element. * @beta */ static onChildAdded(arg) { arg.iModel.elements[_cache].delete({ id: arg.parentId }); } /** Called when an element with an instance of this class as its parent is about to be updated change to a different parent. * @note throw an exception if the element should not be dropped * @note `this` is the class of the parent Element. * @beta */ static onChildDrop(_arg) { } /** Called after an element with an instance of this class as its previous parent was updated to have a new parent. * @note `this` is the class of the previous parent Element. * @beta */ static onChildDropped(arg) { arg.iModel.elements[_cache].delete({ id: arg.parentId }); } /** Called when an instance of this class is being *sub-modeled* by a new Model. * @note throw an exception if model should not be inserted * @note `this` is the class of Element to be sub-modeled. * @beta */ static onSubModelInsert(_arg) { } /** Called after an instance of this class was *sub-modeled* by a new Model. * @note `this` is the class of Element that is now sub-modeled. * @beta */ static onSubModelInserted(arg) { const id = arg.subModelId; arg.iModel.elements[_cache].delete({ id }); arg.iModel.models[_cache].delete(id); } /** Called when a sub-model of an instance of this class is being deleted. * @note throw an exception if model should not be deleted * @note `this` is the class of Element that is sub-modeled. * @beta */ static onSubModelDelete(_arg) { } /** Called after a sub-model of an instance of this class was deleted. * @note `this` is the class of Element that was sub-modeled. * @beta */ static onSubModelDeleted(arg) { const id = arg.subModelId; arg.iModel.elements[_cache].delete({ id }); arg.iModel.models[_cache].delete(id); } /** Called during the iModel transformation process after an Element from the source iModel was *cloned* for the target iModel. * The transformation process automatically handles remapping BisCore properties and those that are properly described in ECSchema. * This callback is only meant to be overridden if there are other Ids in non-standard locations that need to be remapped or other data that needs to be fixed up after the clone. * @param _context The context that persists any remapping between the source iModel and target iModel. * @param _sourceProps The ElementProps for the source Element that was cloned. * @param _targetProps The ElementProps that are a result of the clone. These can be further modified. * @note If you override this method, you must call super. * @beta */ static onCloned(_context, _sourceProps, _targetProps) { } /** Called when a *root* element in a subgraph is changed and before its outputs are processed. * This special callback is made when: * * the element is part of an [[ElementDrivesElement]] graph, and * * the element has no inputs, and * * none of the element's outputs have been processed. * @see [[ElementDrivesElement]] for more on element dependency graphs. * @beta */ static onBeforeOutputsHandled(_id, _iModel) { } /** Called on an element in a graph after all of its inputs have been processed and before its outputs are processed. * This callback is made when: * * the specified element is part of an [[ElementDrivesElement]] graph, and * * there was a direct change to some element upstream in the dependency graph. * * all upstream elements in the graph have been processed. * * none of the downstream elements have been processed. * This method is not called if none of the element's inputs were changed. * @see [[ElementDrivesElement]] for more on element dependency graphs. * @beta */ static onAllInputsHandled(_id, _iModel) { } toJSON() { const val = super.toJSON(); if (Code.isValid(this.code)) val.code = this.code; val.model = this.model; if (undefined !== this.userLabel) // NOTE: blank string should be included in JSON val.userLabel = this.userLabel; if (this.federationGuid) val.federationGuid = this.federationGuid; if (this.parent) val.parent = this.parent; if (Object.keys(this.jsonProperties).length > 0) val.jsonProperties = this.jsonProperties; return val; } collectReferenceIds(referenceIds) { super.collectReferenceIds(referenceIds); referenceIds.addModel(this.model); // The modeledElement is a reference if (this.code.scope && Id64.isValidId64(this.code.scope)) referenceIds.addElement(this.code.scope); // The element that scopes the code is a reference if (this.parent) referenceIds.addElement(this.parent.id); // A parent element is a reference } /** A *required reference* is an element that had to be inserted before this element could have been inserted. * This is the list of property keys on this element that store references to those elements * @note This should be overridden (with `super` called) at each level of the class hierarchy that introduces required references. * @note any property listed here must be added to the reference ids in [[collectReferenceIds]] * @beta */ static requiredReferenceKeys = ["parent", "model"]; /** A map of every [[requiredReferenceKeys]] on this class to their entity type. * @note This should be overridden (with `super` called) at each level of the class hierarchy that introduces required references. * @alpha */ static requiredReferenceKeyTypeMap = { parent: ConcreteEntityTypes.Element, model: ConcreteEntityTypes.Model, }; /** Get the class metadata for this element. * @deprecated in 5.0 - will not be removed until after 2026-06-13. Please use `getMetaData` provided by the parent class `Entity` instead. * * @example * ```typescript * // Current usage: * const metaData: EntityMetaData | undefined = element.getClassMetaData(); * * // Replacement: * const metaData: EntityClass = await element.getMetaData(); * ``` */ // eslint-disable-next-line @typescript-eslint/no-deprecated getClassMetaData() { return this.iModel.classMetaDataRegistry.find(this.classFullName); } /** Query metadata for this entity class from the iModel's schema. Returns cached metadata if available.*/ async getMetaData() { if (this._metadata && EntityClass.isEntityClass(this._metadata)) { return this._metadata; } const entity = await this.iModel.schemaContext.getSchemaItem(this.schemaItemKey, EntityClass); if (entity !== undefined) { this._metadata = entity; return this._metadata; } else { throw new Error(`Cannot get metadata for ${this.classFullName}`); } } getAllUserProperties() { if (!this.jsonProperties.UserProps) this.jsonProperties.UserProps = new Object(); return this.jsonProperties.UserProps; } /** Get a set of JSON user properties by namespace */ getUserProperties(namespace) { return this.getAllUserProperties()[namespace]; } /** Change a set of user JSON properties of this Element by namespace. */ setUserProperties(nameSpace, value) { this.getAllUserProperties()[nameSpace] = value; } /** Remove a set of JSON user properties, specified by namespace, from this Element */ removeUserProperties(nameSpace) { delete this.getAllUserProperties()[nameSpace]; } /** Get a JSON property of this element, by namespace */ getJsonProperty(nameSpace) { return this.jsonProperties[nameSpace]; } setJsonProperty(nameSpace, value) { this.jsonProperties[nameSpace] = value; } /** Get a display label for this Element. By default returns userLabel if present, otherwise code value. */ getDisplayLabel() { return this.userLabel ?? this.code.value; } /** Get a list of HTML strings that describe this Element for the tooltip. Strings will be listed on separate lines in the tooltip. * Any instances of the pattern `%{tag}` will be replaced by the localized value of tag. */ getToolTipMessage() { const addKey = (key) => `<b>%{iModelJs:Element.${key}}:</b> `; // %{iModelJs:Element.xxx} is replaced with localized value of xxx in frontend. const msg = []; const display = this.getDisplayLabel(); msg.push(display ? display : `${addKey("Id") + this.id}, ${addKey("Type")}${this.className}`); if (this instanceof GeometricElement) msg.push(addKey("Category") + this.iModel.elements.getElement(this.category).getDisplayLabel()); msg.push(addKey("Model") + this.iModel.elements.getElement(this.model).getDisplayLabel()); return msg; } /** * Insert this Element into the iModel. * @see [[IModelDb.Elements.insertElement]] * @note For convenience, the value of `this.id` is updated to reflect the resultant element's id. * However when `this.federationGuid` is not present or undefined, a new Guid will be generated and stored on the resultant element. But * the value of `this.federationGuid` is *not* updated. Generally, it is best to re-read the element after inserting (e.g. via [[IModelDb.Elements.getElement]]) * if you intend to continue working with it. That will ensure its values reflect the persistent state. */ insert() { return this.id = this.iModel.elements.insertElement(this.toJSON()); } /** Update this Element in the iModel. */ update() { this.iModel.elements.updateElement(this.toJSON()); } /** Delete this Element from the iModel. */ delete() { this.iModel.elements.deleteElement(this.id); } } /** An abstract base class to model real world entities that intrinsically have geometry. * @public @preview */ export class GeometricElement extends Element { static get className() { return "GeometricElement"; } /** The Id of the [[Category]] for this GeometricElement. */ category; /** The GeometryStream for this GeometricElement. */ geom; constructor(props, iModel) { super(props, iModel); this.category = Id64.fromJSON(props.category); this.geom = props.geom; } /** Type guard for instanceof [[GeometricElement3d]] */ is3d() { return this instanceof GeometricElement3d; } /** Type guard for instanceof [[GeometricElement2d]] */ is2d() { return this instanceof GeometricElement2d; } /** Get the [Transform]($geometry) from the Placement of this GeometricElement */ getPlacementTransform() { return this.placement.transform; } calculateRange3d() { return this.placement.calculateRange(); } toJSON() { const val = super.toJSON(); val.category = this.category; if (this.geom) val.geom = this.geom; return val; } collectReferenceIds(referenceIds) { super.collectReferenceIds(referenceIds); referenceIds.addElement(this.category); // TODO: GeometryPartIds? } /** @beta */ static requiredReferenceKeys = [...super.requiredReferenceKeys, "category"]; /** @alpha */ static requiredReferenceKeyTypeMap = { ...super.requiredReferenceKeyTypeMap, category: ConcreteEntityTypes.Element, }; /** * GeometricElement custom HandledProps includes 'inSpatialIndex'. * @inheritdoc * @beta */ static _customHandledProps = [ { propertyName: "inSpatialIndex", source: "Class" }, ]; /** * GeometricElement deserializes 'inSpatialIndex'. * @inheritdoc * @beta */ static deserialize(props) { return super.deserialize(props); } /** * GeometricElement serialize 'inSpatialIndex'. * @inheritdoc * @beta */ static serialize(props, iModel) { return super.serialize(props, iModel); } } /** An abstract base class to model real world entities that intrinsically have 3d geometry. * See [how to create a GeometricElement3d]($docs/learning/backend/CreateElements.md#GeometricElement3d). * @public @preview */ export class GeometricElement3d extends GeometricElement { static get className() { return "GeometricElement3d"; } placement; typeDefinition; constructor(props, iModel) { super(props, iModel); this.placement = Placement3d.fromJSON(props.placement); if (props.typeDefinition) this.typeDefinition = TypeDefinition.fromJSON(props.typeDefinition); } toJSON() { const val = super.toJSON(); val.placement = this.placement; if (undefined !== this.typeDefinition) val.typeDefinition = this.typeDefinition; return val; } collectReferenceIds(referenceIds) { super.collectReferenceIds(referenceIds); if (undefined !== this.typeDefinition) referenceIds.addElement(this.typeDefinition.id); } /** * GeometricElement3d custom HandledProps includes 'category', 'geometryStream', 'origin', 'yaw', 'pitch', 'roll', * 'bBoxLow', 'bBoxHigh', and 'typeDefinition'. * @inheritdoc * @beta */ static _customHandledProps = [ { propertyName: "category", source: "Class" }, { propertyName: "geometryStream", source: "Class" }, { propertyName: "origin", source: "Class" }, { propertyName: "yaw", source: "Class" }, { propertyName: "pitch", source: "Class" }, { propertyName: "roll", source: "Class" }, { propertyName: "bBoxLow", source: "Class" }, { propertyName: "bBoxHigh", source: "Class" }, { propertyName: "typeDefinition", source: "Class" } ]; /** * GeometricElement3d deserializes 'category', 'geometryStream', 'origin', 'yaw', 'pitch', 'roll', * 'bBoxLow', 'bBoxHigh', and 'typeDefinition'. * @inheritdoc * @beta */ static deserialize(props) { const elProps = super.deserialize(props); const instance = props.row; elProps.category = instance.category.id; const origin = instance.origin ? [instance.origin.x, instance.origin.y, instance.origin.z] : [0, 0, 0]; let bbox; if ("bBoxHigh" in instance && instance.bBoxHigh !== undefined && "bBoxLow" in instance && instance.bBoxLow !== undefined) { bbox = { low: [instance.bBoxLow.x, instance.bBoxLow.y, instance.bBoxLow.z], high: [instance.bBoxHigh.x, instance.bBoxHigh.y, instance.bBoxHigh.z], }; } elProps.placement = { origin, angles: YawPitchRollAngles.createDegrees(instance.yaw ?? 0, instance.pitch ?? 0, instance.roll ?? 0).toJSON(), bbox }; if (instance.geometryStream) { elProps.geom = props.iModel[_nativeDb].convertOrUpdateGeometrySource({ is2d: false, geom: instance.geometryStream, placement: elProps.placement, categoryId: elProps.category }, "GeometryStreamProps", props.options?.element ?? {}).geom; } if (instance.typeDefinition) { elProps.typeDefinition = instance.typeDefinition; } return elProps; } /** * GeometricElement3d serializes 'category', 'geometryStream', 'origin', 'yaw', 'pitch', 'roll', * 'bBoxLow', 'bBoxHigh', and 'typeDefinition'. * @inheritdoc * @beta */ static serialize(props, iModel) { const inst = super.serialize(props, iModel); inst.category = { id: props.category }; const assignPlacement = (placement) => { if (Array.isArray(placement.origin)) { inst.origin = { x: placement.origin[0], y: placement.origin[1], z: placement.origin[2] }; } else { inst.origin = placement.origin; } inst.yaw = placement.angles.yaw; inst.pitch = placement.angles.pitch; inst.roll = placement.angles.roll; if (placement.bbox) { if (Array.isArray(placement.bbox.low)) { inst.bBoxLow = { x: placement.bbox.low[0], y: placement.bbox.low[1], z: placement.bbox.low[2] }; } else { inst.bBoxLow = placement.bbox.low; } if (Array.isArray(placement.bbox.high)) { inst.bBoxHigh = { x: placement.bbox.high[0], y: placement.bbox.high[1], z: placement.bbox.high[2] }; } else { inst.bBoxHigh = placement.bbox.high; } } }; if (props.placement) { assignPlacement(props.placement); } if (props.elementGeometryBuilderParams) { const source = iModel[_nativeDb].convertOrUpdateGeometrySource({ builder: props.elementGeometryBuilderParams, is2d: true, placement: props.placement, categoryId: props.category, }, "BinaryStream", {}); inst.geometryStream = source.geom; if (source.placement) { assignPlacement(source.placement); } } if (props.geom) { const source = iModel[_nativeDb].convertOrUpdateGeometrySource({ geom: props.geom, is2d: false, placement: props.placement, categoryId: props.category, }, "BinaryStream", {}); inst.geometryStream = source.geom; if (source.placement) { assignPlacement(source.placement); } } inst.typeDefinition = props.typeDefinition; return inst; } } /** A 3d Graphical Element * @public @preview */ export class GraphicalElement3d extends GeometricElement3d { static get className() { return "GraphicalElement3d"; } constructor(props, iModel) { super(props, iModel); } } /** An abstract base class to model information entities that intrinsically have 2d geometry. * @public @preview */ export class GeometricElement2d extends GeometricElement { static get className() { return "GeometricElement2d"; } placement; typeDefinition; constructor(props, iModel) { super(props, iModel); this.placement = Placement2d.fromJSON(props.placement); if (props.typeDefinition) this.typeDefinition = TypeDefinition.fromJSON(props.typeDefinition); } toJSON() { const val = super.toJSON(); val.placement = this.placement; if (undefined !== this.typeDefinition) val.typeDefinition = this.typeDefinition; return val; } /** * GeometricElement2d custom HandledProps includes 'category', 'geometryStream', 'origin', 'rotation', * 'bBoxLow', 'bBoxHigh', and 'typeDefinition'. * @inheritdoc * @beta */ static _customHandledProps = [ { propertyName: "category", source: "Class" }, { propertyName: "geometryStream", source: "Class" }, { propertyName: "origin", source: "Class" }, { propertyName: "rotation", source: "Class" }, { propertyName: "bBoxLow", source: "Class" }, { propertyName: "bBoxHigh", source: "Class" }, { propertyName: "typeDefinition", source: "Class" } ]; /** * GeometricElement2d deserialize 'category', 'geometryStream', 'origin', 'rotation', * 'bBoxLow', 'bBoxHigh', and 'typeDefinition'. * @inheritdoc * @beta */ static deserialize(props) { const elProps = super.deserialize(props); const instance = props.row; elProps.category = instance.category.id; const origin = instance.origin ? [instance.origin.x, instance.origin.y] : [0, 0]; let bbox; if ("bBoxHigh" in instance && instance.bBoxHigh !== undefined && "bBoxLow" in instance && instance.bBoxLow !== undefined) { bbox = { low: [instance.bBoxLow.x, instance.bBoxLow.y], high: [instance.bBoxHigh.x, instance.bBoxHigh.y], }; } elProps.placement = { origin, angle: instance.rotation, bbox, }; if (instance.geometryStream) { const source = props.iModel[_nativeDb].convertOrUpdateGeometrySource({ is2d: true, geom: instance.geometryStream, placement: elProps.placement, categoryId: elProps.category }, "GeometryStreamProps", props.options?.element ?? {}); elProps.geom = source.geom; if (source.placement) { elProps.placement = source.placement; } } if (instance.typeDefinition) { elProps.typeDefinition = instance.typeDefinition; } return elProps; } /** * GeometricElement2d serializes 'category', 'geometryStream', 'origin', 'rotation', * 'bBoxLow', 'bBoxHigh', and 'typeDefinition'. * @inheritdoc * @beta */ static serialize(props, iModel) { const inst = super.serialize(props, iModel); inst.category = { id: props.category }; const assignPlacement = (placement) => { if (Array.isArray(placement.origin)) { inst.origin = { x: placement.origin[0], y: placement.origin[1] }; } else { inst.origin = placement.origin; } inst.rotation = placement.angle; if (placement.bbox) { if (Array.isArray(placement.bbox.low)) { inst.bBoxLow = { x: placement.bbox.low[0], y: placement.bbox.low[1] }; } else { inst.bBoxLow = placement.bbox.low; } if (Array.isArray(placement.bbox.high)) { inst.bBoxHigh = { x: placement.bbox.high[0], y: placement.bbox.high[1] }; } else { inst.bBoxHigh = placement.bbox.high; } } }; if (props.placement) { assignPlacement(props.placement); } if (props.elementGeometryBuilderParams) { const source = iModel[_nativeDb].convertOrUpdateGeometrySource({ builder: props.elementGeometryBuilderParams, is2d: true, placement: props.placement, categoryId: props.category, }, "BinaryStream", {}); inst.geometryStream = source.geom; if (source.placement) { assignPlacement(source.placement); } } if (props.geom) { const source = iModel[_nativeDb].convertOrUpdateGeometrySource({ geom: props.geom, is2d: true, placement: props.placement, categoryId: props.category, }, "BinaryStream", {}); inst.geometryStream = source.geom; if (source.placement) { assignPlacement(source.placement); } } inst.typeDefinition = props.typeDefinition; return inst; } collectReferenceIds(referenceIds) { super.collectReferenceIds(referenceIds); if (undefined !== this.typeDefinition) referenceIds.addElement(this.typeDefinition.id); } } /** An abstract base class for 2d Geometric Elements that are used to convey information within graphical presentations (like drawings). * @public @preview */ export class GraphicalElement2d extends GeometricElement2d { static get className() { return "GraphicalElement2d"; } constructor(props, iModel) { super(props, iModel); } } /** 2d element used to annotate drawings and sheets. * @public @preview */ export class AnnotationElement2d extends GraphicalElement2d { static get className() { return "AnnotationElement2d"; } constructor(props, iModel) { super(props, iModel); } } /** 2d element used to persist graphics for use in drawings. * @public @preview */ export class DrawingGraphic extends GraphicalElement2d { static get className() { return "DrawingGraphic"; } constructor(props, iModel) { super(props, iModel); } } /** An Element that occupies real world space. Its coordinates are in the project space of its iModel. * @public @preview */ export class SpatialElement extends GeometricElement3d { static get className() { return "SpatialElement"; } constructor(props, iModel) { super(props, iModel); } } /** An Element that is spatially located, has mass, and can be *touched*. * @public @preview */ export class PhysicalElement extends SpatialElement { static get className() { return "PhysicalElement"; } /** If defined, the [[PhysicalMaterial]] that makes up this PhysicalElement. */ physicalMaterial; constructor(props, iModel) { super(props, iModel); this.physicalMaterial = RelatedElement.fromJSON(props.physicalMaterial); } toJSON() { const val = super.toJSON(); val.physicalMaterial = this.physicalMaterial?.toJSON(); return val; } } /** Identifies a *tracked* real world location but has no mass and cannot be *touched*. * @public @preview */ export class SpatialLocationElement extends SpatialElement { static get className() { return "SpatialLocationElement"; } constructor(props, iModel) { super(props, iModel); } } /** A Volume Element is a Spatial Location Element that is restricted to defining a volume. * @public @preview */ export class VolumeElement extends SpatialLocationElement { static get className() { return "VolumeElement"; } constructor(props, iModel) { super(props, iModel); } } /** A SectionDrawingLocation element identifies the location of a [[SectionDrawing]] in the context of a [[SpatialModel]], * enabling [HyperModeling]($hypermodeling). * @note The associated ECClass was added to the BisCore schema in version 1.0.11. * @public @preview */ export class SectionDrawingLocation extends SpatialLocationElement { /** The Id of the [[ViewDefinition]] to which this location refers. */ sectionView; static get className() { return "SectionDrawingLocation"; } constructor(props, iModel) { super(props, iModel); this.sectionView = RelatedElement.fromJSON(props.sectionView) ?? RelatedElement.none; } toJSON() { return { ...super.toJSON(), sectionView: this.sectionView.toJSON(), }; } } /** Information Content Element is an abstract base class for modeling pure information entities. Only the * core framework should directly subclass from Information Content Element. Domain and application developers * should start with the most appropriate subclass of Information Content Element. * @public @preview */ export class InformationContentElement extends Element { static get className() { return "InformationContentElement"; } constructor(props, iModel) { super(props, iModel); } } /** Element used in conjunction with bis:ElementDrivesElement relationships to bundle multiple inputs before * driving the output element. * @beta */ export class DriverBundleElement extends InformationContentElement { static get className() { return "DriverBundleElement"; } constructor(props, iModel) { super(props, iModel); } } /** Information Reference is an abstract base class for modeling entities whose main purpose is to reference something else. * @public @preview */ export class InformationReferenceElement extends InformationContentElement { static get className() { return "InformationReferenceElement"; } constructor(props, iModel) { super(props, iModel); } } /** A Subject is an information element that describes what this repository (or part thereof) is about. * @public @preview */ export class Subject extends InformationReferenceElement { static get className() { return "Subject"; } description; constructor(props, iModel) { super(props, iModel); this.description = props.description; } toJSON() { return super.toJSON(); // Entity.toJSON takes care of auto-handled properties } /** Create a Code for a Subject given a name that is meant to be unique within the scope of its parent Subject. * @param iModelDb The IModelDb * @param parentSubjectId The Id of the parent Subject that provides the scope for names of its child Subjects. * @param codeValue The child Subject name */ static createCode(iModelDb, parentSubjectId, codeValue) { const codeSpec = iModelDb.codeSpecs.getByName(BisCodeSpec.subject); return new Code({ spec: codeSpec.id, scope: parentSubjectId, value: codeValue }); } /** Create a Subject * @param iModelDb The IModelDb * @param parentSubjectId The new Subject will be a child of this Subject * @param name The name (codeValue) of the Subject * @param description The optional description of the Subject * @returns The newly constructed Subject * @throws [[IModelError]] if there is a problem creating the Subject */ static create(iModelDb, parentSubjectId, name, description) { const subjectProps = { classFullName: this.classFullName, model: IModel.repositoryModelId, parent: new SubjectOwnsSubjects(parentSubjectId), code: this.createCode(iModelDb, parentSubjectId, name), description, }; return new Subject(subjectProps, iModelDb); } /** Insert a Subject * @param iModelDb Insert into this IModelDb * @param parentSubjectId The new Subject will be inserted as a child of this Subject * @param name The name (codeValue) of the Subject * @param description The optional description of the Subject * @returns The Id of the newly inserted Subject * @throws [[IModelError]] if there is a problem inserting the Subject */ static insert(iModelDb, parentSubjectId, name, description) { const subject = this.create(iModelDb, parentSubjectId, name, description); return iModelDb.elements.insertElement(subject.toJSON()); } } /** An InformationContentElement that identifies the content of a document. * The realized form of a document is called a DocumentCarrier (different class than Document). * For example, a will is a legal document. The will published into a PDF file is an ElectronicDocumentCopy. * The will printed onto paper is a PrintedDocumentCopy. * In this example, the Document only identifies, names, and tracks the content of the will. * @public @preview */ export class Document extends InformationContentElement { static get className() { return "Document"; } constructor(props, iModel) { super(props, iModel); } } /** A document that represents a drawing, that is, a two-dimensional graphical representation of engineering data. A Drawing element is usually modelled by a [[DrawingModel]]. * @public @preview */ export class Drawing extends Document { _scaleFactor; /** A factor used by tools to adjust the size of text in [GeometricElement2d]($backend)s in the associated [DrawingModel]($backend) and to compute the * size of the [ViewAttachment]($backend) created when attaching the [Drawing]($backend) to a [Sheet]($backend). * Default: 1. * @note Attempting to set this property to a value less than or equal to zero will produce an exception. * @public @preview */ get scaleFactor() { return this._scaleFactor; } set scaleFactor(factor) { if (factor <= 0) { if (this._scaleFactor === undefined) { // Entity constructor calls our setter before our constructor runs...don't throw an exception at that time, // because somebody may have persisted the value as zero. return; } throw new Error("Drawing.scaleFactor must be greater than zero"); } this._scaleFactor = factor; } static get className() { return "Drawing"; } constructor(props, iModel) { super(props, iModel); this._scaleFactor = typeof props.scaleFactor === "number" && props.scaleFactor > 0 ? props.scaleFactor : 1; } toJSON() { const drawingProps = super.toJSON(); // Entity.toJSON auto-magically sets drawingProps.scaleFactor from this.scaleFactor - unset if default value of 1. if (drawingProps.scaleFactor === 1) { delete drawingProps.scaleFactor; } return drawingProps; } /** The name of the DrawingModel class modeled by this element type. */ static get drawingModelFullClassName() { return DrawingModel.classFullName; } /** Create a Code for a Drawing given a name that is meant to be unique within the scope of the specified DocumentListModel. * @param iModel The IModelDb * @param scopeModelId The Id of the DocumentListModel that contains the Drawing and provides the scope for its name. * @param codeValue The Drawing name */ static createCode(iModel, scopeModelId, codeValue) { const codeSpec = iModel.codeSpecs.getByName(BisCodeSpec.drawing); return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); } /** Insert a Drawing element and a DrawingModel that breaks it down. * @param iModelDb Insert into this iModel * @param documentListModelId Insert the new Drawing into this DocumentListModel * @param name The name of the Drawing. * @param scaleFactor See [[scaleFactor]]. Must be greater than zero. * @returns The Id of the newly inserted Drawing element and the DrawingModel that breaks it down (same value). * @throws [[IModelError]] if unable to insert the element. * @throws Error if `scaleFactor` is less than or equal to zero. */ static insert(iModelDb, documentListModelId, name, scaleFactor) { const drawingProps = { classFullName: this.classFullName, model: documentListModelId, code: this.createCode(iModelDb, documentListModelId, name), }; if (scaleFactor !== undefined) { if (scaleFactor <= 0) { throw new Error("Drawing.scaleFactor must be greater than zero"); } drawingProps.scaleFactor = scaleFactor; } const drawingId = iModelDb.elements.insertElement(drawingProps); const model = iModelDb.models.createModel({ classFullName: this.drawingModelFullClassName, modeledElement: { id: drawingId }, }); return iModelDb.models.insertModel(model.toJSON()); } } /** A document that represents a section drawing, that is, a graphical documentation derived from a planar * section of a spatial view. A SectionDrawing element is modelled by a [[SectionDrawingModel]] or a [[GraphicalModel3d]]. * A [[SectionDrawingLocation]] can associate the drawing with a spatial location, enabling [HyperModeling]($hypermodeling). * @public @preview */ export class SectionDrawing extends Drawing { /** The type of section used to generate the drawing. */ sectionType; /** The spatial view from which the section was generated. */ spatialView; /** A transform from the section drawing model's coordinates to spatial coordinates. */ drawingToSpatialTransform; /** If the section drawing is placed onto a [[Sheet]] via a [[ViewAttachment]], a transform from the sheet's coordinates to spatial coordinates. */ sheetToSpatialTransform; /** If the section drawing is placed onto a [[Sheet]] via a [[ViewAttachment]], the clip to apply to the sheet graphics when drawn in the context * of the spatial view. * @note The ClipVector is defined in spatial coordinates. */ drawingBoundaryClip; /** If true, when displaying the section drawing as a [DrawingViewState]($frontend), the [[spatialView]] will also be displayed. */ displaySpatialView; static get className() { return "SectionDrawing"; } static get drawingModelFullClassName() { return SectionDrawingModel.classFullName; } constructor(props, iModel) { super(props, iModel); this.sectionType = JsonUtils.asInt(props.sectionType, SectionType.Section); this.spatialView = RelatedElement.fromJSON(props.spatialView) ?? RelatedElement.none; this.displaySpatialView = JsonUtils.asBool(props.jsonProperties?.displaySpatialView); const json = props.jsonProperties; if (!json) return; if (json.drawingToSpatialTransform) this.drawingToSpatialTransform = Transform.fromJSON(json.drawingToSpatialTransform); if (json.sheetToSpatialTransform) this.sheetToSpatialTransform = Transform.fromJSON(json.sheetToSpatialTransform); if (json.drawingBoundaryClip) this.drawingBoundaryClip = ClipVector.fromJSON(json.drawingBoundaryClip); } toJSON() { const props = { ...super.toJSON(), sectionType: this.sectionType, spatialView: this.spatialView.toJSON(), }; if (!props.jsonProperties) props.jsonProperties = {}; props.jsonProperties.displaySpatialView = this.displaySpatialView ? true : undefined; props.jsonProperties.drawingToSpatialTransform = this.drawingToSpatialTransform?.toJSON(); props.jsonProperties.sheetToSpatialTransform = this.sheetToSpatialTransform?.toJSON(); props.jsonProperties.drawingBoundaryClip = this.drawingBoundaryClip?.toJSON(); return props; } } /** The template for a SheetBorder * @public @preview */ export class SheetBorderTemplate extends Document { static get className() { return "SheetBorderTemplate"; } height; width; constructor(props, iModel) { super(props, iModel); this.