@itwin/core-backend
Version:
iTwin.js backend components
1,079 lines • 85.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 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.