@itwin/core-common
Version:
iTwin.js components common to frontend and backend
912 lines • 99.3 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Geometry
*/
import { flatbuffers } from "flatbuffers";
import { expectDefined, expectNotNull, Id64 } from "@itwin/core-bentley";
import { Angle, AngleSweep, Arc3d, BentleyGeometryFlatBuffer, FrameBuilder, LineSegment3d, LineString3d, Loop, Matrix3d, Plane3dByOriginAndUnitNormal, Point2d, Point3d, Point3dArray, PointString3d, PolyfaceQuery, Range2d, Range3d, Transform, Vector3d, YawPitchRollAngles } from "@itwin/core-geometry";
import { EGFBAccessors } from "./ElementGeometryFB";
import { Base64EncodedString } from "../Base64EncodedString";
import { TextString } from "./TextString";
import { ColorDef } from "../ColorDef";
import { BackgroundFill, FillDisplay, GeometryClass, GeometryParams } from "../GeometryParams";
import { Gradient } from "../Gradient";
import { ThematicGradientSettings } from "../ThematicDisplay";
import { AreaPattern } from "./AreaPattern";
import { BRepEntity } from "./GeometryStream";
import { ImageGraphic, ImageGraphicCorners } from "./ImageGraphic";
import { LineStyle } from "./LineStyle";
import { Placement2d, Placement3d } from "./Placement";
import { isPlacement2dProps } from "../ElementProps";
/** Specifies the type of an entry in a geometry stream.
* @see [[ElementGeometryDataEntry.opcode]].
* @public
* @extensions
*/
export var ElementGeometryOpcode;
(function (ElementGeometryOpcode) {
/** Local range of the next geometric primitive in the geometry stream. */
ElementGeometryOpcode[ElementGeometryOpcode["SubGraphicRange"] = 2] = "SubGraphicRange";
/** A reference to a [GeometryPart]($backend). */
ElementGeometryOpcode[ElementGeometryOpcode["PartReference"] = 3] = "PartReference";
/** Sets symbology for subsequent geometry to override [SubCategory]($backend) appearance */
ElementGeometryOpcode[ElementGeometryOpcode["BasicSymbology"] = 4] = "BasicSymbology";
/** A line, line string, shape, or point string (automatic simplification of a [CurvePrimitive]($core-geometry) or [CurveCollection]($core-geometry)) */
ElementGeometryOpcode[ElementGeometryOpcode["PointPrimitive"] = 5] = "PointPrimitive";
/** A 2d line, line string, shape, or point string (automatic simplification of a [CurvePrimitive]($core-geometry) or [CurveCollection]($core-geometry)) */
ElementGeometryOpcode[ElementGeometryOpcode["PointPrimitive2d"] = 6] = "PointPrimitive2d";
/** Arc or ellipse (automatic simplification of a [CurvePrimitive]($core-geometry) or [CurveCollection]($core-geometry)) */
ElementGeometryOpcode[ElementGeometryOpcode["ArcPrimitive"] = 7] = "ArcPrimitive";
/** [CurveCollection]($core-geometry) */
// eslint-disable-next-line @typescript-eslint/no-shadow
ElementGeometryOpcode[ElementGeometryOpcode["CurveCollection"] = 8] = "CurveCollection";
/** [Polyface]($core-geometry) */
// eslint-disable-next-line @typescript-eslint/no-shadow
ElementGeometryOpcode[ElementGeometryOpcode["Polyface"] = 9] = "Polyface";
/** [CurvePrimitive]($core-geometry) */
ElementGeometryOpcode[ElementGeometryOpcode["CurvePrimitive"] = 10] = "CurvePrimitive";
/** [SolidPrimitive]($core-geometry) */
// eslint-disable-next-line @typescript-eslint/no-shadow
ElementGeometryOpcode[ElementGeometryOpcode["SolidPrimitive"] = 11] = "SolidPrimitive";
/** [BSplineSurface3d]($core-geometry) */
ElementGeometryOpcode[ElementGeometryOpcode["BsplineSurface"] = 12] = "BsplineSurface";
/** Opaque and [[Gradient]] fills. */
ElementGeometryOpcode[ElementGeometryOpcode["Fill"] = 19] = "Fill";
/** Hatch, cross-hatch, or [[AreaPattern]]. */
ElementGeometryOpcode[ElementGeometryOpcode["Pattern"] = 20] = "Pattern";
/** [[RenderMaterial]] */
ElementGeometryOpcode[ElementGeometryOpcode["Material"] = 21] = "Material";
/** [[TextString]] */
// eslint-disable-next-line @typescript-eslint/no-shadow
ElementGeometryOpcode[ElementGeometryOpcode["TextString"] = 22] = "TextString";
/** Specifies line style overrides as a [[LineStyle.Modifier]] */
ElementGeometryOpcode[ElementGeometryOpcode["LineStyleModifiers"] = 23] = "LineStyleModifiers";
/** Boundary representation solid, sheet, or wire body as a [[BRepEntity.DataProps]] */
ElementGeometryOpcode[ElementGeometryOpcode["BRep"] = 25] = "BRep";
/** Small single-tile raster image as an [[ImageGraphic]] */
ElementGeometryOpcode[ElementGeometryOpcode["Image"] = 28] = "Image";
})(ElementGeometryOpcode || (ElementGeometryOpcode = {}));
/** Values for [[BRepGeometryCreate.operation]]
* @alpha
*/
export var BRepGeometryOperation;
(function (BRepGeometryOperation) {
/** Unite target (first entry) with one or more tool entities. */
BRepGeometryOperation[BRepGeometryOperation["Unite"] = 0] = "Unite";
/** Subtract one or more tool entities from target entity (first entry) */
BRepGeometryOperation[BRepGeometryOperation["Subtract"] = 1] = "Subtract";
/** Intersect target (first entry) with one or more tool entities */
BRepGeometryOperation[BRepGeometryOperation["Intersect"] = 2] = "Intersect";
/** Sew the given set of surfaces together by joining those that share edges in common */
BRepGeometryOperation[BRepGeometryOperation["Sew"] = 3] = "Sew";
/** Create a cut in the target (first entry) using a planar region (second entry) and optional depth */
BRepGeometryOperation[BRepGeometryOperation["Cut"] = 4] = "Cut";
/** Create a pad or pocket in the target (first entry) using a planar region (second entry) */
BRepGeometryOperation[BRepGeometryOperation["Emboss"] = 5] = "Emboss";
/** Create a solid from a surface by offsetting using the specified forward and backward distances */
BRepGeometryOperation[BRepGeometryOperation["Thicken"] = 6] = "Thicken";
/** Create a shelled solid by offsetting all faces by the supplied distance */
BRepGeometryOperation[BRepGeometryOperation["Hollow"] = 7] = "Hollow";
/** Create a solid or surface by sweeping a planar profile (first entry) along a path (second entry) */
BRepGeometryOperation[BRepGeometryOperation["Sweep"] = 8] = "Sweep";
/** Create a solid or surface by lofting through a set of paths or regions */
BRepGeometryOperation[BRepGeometryOperation["Loft"] = 9] = "Loft";
/** Create a solid or sheet with all non-smooth/non-laminar edges rounded */
BRepGeometryOperation[BRepGeometryOperation["Round"] = 10] = "Round";
/** Offset all faces of a solid or sheet target by the supplied distance. */
BRepGeometryOperation[BRepGeometryOperation["Offset"] = 11] = "Offset";
})(BRepGeometryOperation || (BRepGeometryOperation = {}));
/** Provides utility functions for working with [[ElementGeometryDataEntry]].
* @beta
*/
export var ElementGeometry;
(function (ElementGeometry) {
/** [[ElementGeometry.Builder]] is a helper class for populating a [[ElementGeometryDataEntry]] array needed to create a [[GeometricElement]] or [[GeometryPart]]. */
class Builder {
_localToWorld;
_worldToLocal;
/** GeometryStream entries */
entries = [];
/** Current placement transform, converts local coordinate (placement relative) input to world */
get localToWorld() { return this._localToWorld; }
/** Current inverse placement transform, converts world coordinate input to local (placement relative) */
get worldToLocal() { return this._worldToLocal; }
/** Supply optional local to world transform. Used to transform world coordinate input relative to element placement.
* For a [[GeometricElement]]'s placement to be meaningful, world coordinate geometry should never be appended to an element with an identity placement.
* Can be called with undefined or identity transform to start appending geometry supplied in local coordinates again.
*/
setLocalToWorld(localToWorld) {
this._localToWorld = (undefined === localToWorld || localToWorld.isIdentity ? undefined : localToWorld.clone());
this._worldToLocal = (undefined === this._localToWorld ? undefined : this._localToWorld.inverse());
}
/** Supply local to world transform from a Point3d and optional YawPitchRollAngles.
* @see [[Placement3d]]
*/
setLocalToWorld3d(origin, angles = YawPitchRollAngles.createDegrees(0.0, 0.0, 0.0)) {
this.setLocalToWorld(Transform.createOriginAndMatrix(origin, angles.toMatrix3d()));
}
/** Supply local to world transform from a Point2d and optional Angle.
* @see [[Placement2d]]
*/
setLocalToWorld2d(origin, angle = Angle.createDegrees(0.0)) {
this.setLocalToWorld(Transform.createOriginAndMatrix(Point3d.createFrom(origin), Matrix3d.createRotationAroundVector(Vector3d.unitZ(), angle)));
}
/** Supply local to world transform from a PlacementProps2d or PlacementProps3d.
* @see [[PlacementProps]]
*/
setLocalToWorldFromPlacement(props) {
const placement = isPlacement2dProps(props) ? Placement2d.fromJSON(props) : Placement3d.fromJSON(props);
this.setLocalToWorld(placement.transform);
}
/** Compute angles suitable for passing to [[setLocalToWorld3d]] from an array of 3d points. */
static placementAnglesFromPoints(pts, defaultUp, result) {
const angles = result ? result : new YawPitchRollAngles();
const zVec = defaultUp ? defaultUp.clone() : Vector3d.unitZ();
const matrix = Matrix3d.createRigidHeadsUp(zVec);
YawPitchRollAngles.createFromMatrix3d(matrix, angles);
if (pts.length < 2 || pts[0].isAlmostEqual(pts[1]))
return angles;
// Check if points have a well defined normal to use instead of defaultUp...
const frameTransform = FrameBuilder.createFrameToDistantPoints(pts);
if (undefined !== frameTransform) {
const plane = Plane3dByOriginAndUnitNormal.create(pts[0], frameTransform.matrix.getColumn(2));
if (undefined !== plane && Point3dArray.isCloseToPlane(pts, plane))
zVec.setFrom(plane.getNormalRef());
}
const xVec = Vector3d.createStartEnd(pts[0], pts[1]);
if (xVec.isParallelTo(zVec, true))
return angles;
const yVec = xVec.unitCrossProduct(zVec);
if (undefined === yVec)
return angles;
Matrix3d.createColumns(xVec, yVec, zVec, matrix);
if (undefined === Matrix3d.createRigidFromMatrix3d(matrix, undefined, matrix))
return angles;
YawPitchRollAngles.createFromMatrix3d(matrix, angles);
return angles;
}
/** Compute angle suitable for passing to [[setLocalToWorld2d]] from an array of xy plane points. */
static placementAngleFromPoints(pts, result) {
const angles = ElementGeometry.Builder.placementAnglesFromPoints(pts);
if (undefined === result)
return angles.yaw;
result.setFrom(angles.yaw);
return result;
}
/** Store local ranges for all subsequent geometry appended. Can improve performance of range testing for elements with a GeometryStream
* containing more than one [[GeometryQuery]] differentiable by range. Not useful for a single [[GeometryQuery]] as its range and that of the [[GeometricElement]] are the same.
* Ignored when defining a [[GeometryPart]] and not needed when only appending [[GeometryPart]] instances to a [[GeometricElement]] as these store their own range.
*/
appendGeometryRanges() {
const entry = fromSubGraphicRange(Range3d.create()); // Computed on backend, just need opcode...
if (undefined === entry)
return false;
this.entries.push(entry);
return true;
}
/** Change [[GeometryParams]] for subsequent geometry.
* It is not valid to change the sub-category when defining a [[GeometryPart]]. A [[GeometryPart]] inherits the symbology of their instance for anything not explicitly overridden.
*/
appendGeometryParamsChange(geomParams) {
return appendGeometryParams(geomParams, this.entries, this._worldToLocal);
}
/** Append a [[GeometryQuery]] supplied in either local or world coordinates to the [[ElementGeometryDataEntry]] array */
appendGeometryQuery(geometry) {
const entry = ElementGeometry.fromGeometryQuery(geometry, this._worldToLocal);
if (undefined === entry)
return false;
this.entries.push(entry);
return true;
}
/** Append a [[TextString]] supplied in either local or world coordinates to the [[ElementGeometryDataEntry]] array */
appendTextString(text) {
const entry = ElementGeometry.fromTextString(text.toJSON(), this._worldToLocal);
if (undefined === entry)
return false;
this.entries.push(entry);
return true;
}
/** Append a series of entries representing a [[TextBlock]] to the [[ElementGeometryDataEntry]] array.
* @beta
*/
appendTextBlock(block, geomParams) {
for (const entry of block.entries) {
let result;
if (entry.text) {
result = this.appendTextString(new TextString(entry.text));
}
else if (entry.color) {
const params = geomParams?.clone() ?? new GeometryParams(Id64.invalid);
if (entry.color !== "subcategory") {
params.lineColor = ColorDef.fromJSON(entry.color);
}
result = this.appendGeometryParamsChange(params);
}
else {
result = this.appendGeometryQuery(LineSegment3d.fromJSON(entry.separator));
}
if (!result) {
return false;
}
}
return true;
}
/** Append a [[ImageGraphic]] supplied in either local or world coordinates to the [[ElementGeometryDataEntry]] array */
appendImageGraphic(image) {
const entry = ElementGeometry.fromImageGraphic(image.toJSON(), this._worldToLocal);
if (undefined === entry)
return false;
this.entries.push(entry);
return true;
}
/** Append a [[BRepEntity.DataProps]] supplied in either local or world coordinates to the [[ElementGeometryDataEntry]] array.
* Provided for compatibility with GeometryStreamBuilder only.
* Backend code should use IModelDb.createBRepGeometry to create a brep [[ElementGeometryDataEntry]] directly.
*/
appendBRepData(brep) {
const entry = ElementGeometry.fromBRep(brep, this._worldToLocal);
if (undefined === entry)
return false;
this.entries.push(entry);
return true;
}
/** Append a [[GeometryPart]] instance with relative transform to the [[ElementGeometryDataEntry]] array for creating a [[GeometricElement]].
* Not valid when defining a [[GeometryPart]] as nesting of parts is not supported.
*/
appendGeometryPart(partId, partTransform) {
const entry = ElementGeometry.fromGeometryPart(partId, partTransform, this._worldToLocal);
if (undefined === entry)
return false;
this.entries.push(entry);
return true;
}
/** Append a [[GeometryPart]] instance with relative position, orientation, and scale to the [[ElementGeometryDataEntry]] array for creating a [[GeometricElement3d]].
* Not valid when defining a [[GeometryPart]] as nesting of parts is not supported.
*/
appendGeometryPart3d(partId, instanceOrigin, instanceRotation, instanceScale) {
const partTransform = Transform.createOriginAndMatrix(instanceOrigin, instanceRotation ? instanceRotation.toMatrix3d() : Matrix3d.createIdentity());
if (undefined !== instanceScale)
partTransform.matrix.scaleColumnsInPlace(instanceScale, instanceScale, instanceScale);
return this.appendGeometryPart(partId, partTransform);
}
/** Append a [[GeometryPart]] instance with relative position, orientation, and scale to the [[ElementGeometryDataEntry]] array for creating a [[GeometricElement2d]].
* Not valid when defining a [[GeometryPart]] as nesting of parts is not supported.
*/
appendGeometryPart2d(partId, instanceOrigin, instanceRotation, instanceScale) {
return this.appendGeometryPart3d(partId, instanceOrigin ? Point3d.createFrom(instanceOrigin) : undefined, instanceRotation ? new YawPitchRollAngles(instanceRotation) : undefined, instanceScale);
}
}
ElementGeometry.Builder = Builder;
class IteratorEntry {
geomParams;
localToWorld;
localRange;
_value;
_applyLocalToWorld;
constructor(geomParams, localToWorld, applyLocalToWorld) {
this.geomParams = geomParams;
this.localToWorld = localToWorld;
this._applyLocalToWorld = applyLocalToWorld ? !localToWorld.isIdentity : false;
}
get value() { return expectDefined(this._value); }
set value(value) { this._value = value; }
get outputTransform() {
return this._applyLocalToWorld ? this.localToWorld : undefined;
}
/** Return the [[GeometryQuery]] representation for the current entry */
toGeometryQuery() {
return toGeometryQuery(this.value, this.outputTransform);
}
/** Return the [[BRepEntity.DataProps]] representation for the current entry for checking brep type and face attachments. */
toBRepData(wantBRepData = false) {
return toBRep(this.value, wantBRepData, this.outputTransform);
}
/** Return the [[TextString]] representation for the current entry */
toTextString() {
const props = toTextString(this.value, this.outputTransform);
return (undefined !== props ? new TextString(props) : undefined);
}
/** Return the [[ImageGraphic]] representation for the current entry */
toImageGraphic() {
const props = toImageGraphic(this.value, this.outputTransform);
return (undefined !== props ? ImageGraphic.fromJSON(props) : undefined);
}
/** Return the GeometryPart information for the current entry */
toGeometryPart(partToLocal, partToWorld) {
if (undefined === partToLocal && undefined !== partToWorld)
partToLocal = Transform.createIdentity();
const partId = toGeometryPart(this.value, partToLocal);
if (undefined === partId || undefined === partToLocal || undefined === partToWorld)
return partId;
if (undefined !== this.localToWorld)
this.localToWorld.multiplyTransformTransform(partToLocal, partToWorld);
return partId;
}
}
ElementGeometry.IteratorEntry = IteratorEntry;
/** [[ElementGeometry.Iterator]] is a helper class for iterating a [[ElementGeometryDataEntry]] array.
* Each [[ElementGeometryDataEntry]] returned by the iterator represents exactly one displayable entry.
*/
class Iterator {
/** GeometryStream entries */
entryArray;
/** The geometric element's placement or geometry part's local range (placement.bbox) */
placement;
/** If true, geometry displays oriented to face the camera */
viewIndependent;
/** If true, geometry stream contained breps that were omitted or replaced as requested */
brepsPresent;
/** Current entry position */
_index = 0;
/** Allocated on first call to next() and reused thereafter */
_entry;
/** Used to initialize this._entry */
_appearance;
_localToWorld;
/** Whether deserialized entry data is returned in world or local coordinates */
_applyLocalToWorld = false;
/** Construct a new Iterator given a [[ElementGeometryInfo]] from either a [[GeometricElement3d]], [[GeometricElement2d]], or [[GeometryPart]].
* Supply the optional [[GeometryParams]] and localToWorld transform to iterate a [[GeometryPart]] in the context of a [[GeometricElement]] reference.
*/
constructor(info, categoryOrGeometryParams, localToWorld) {
this.entryArray = info.entryArray;
this.viewIndependent = info.viewIndependent;
this.brepsPresent = info.brepsPresent;
if (undefined !== info.categoryId)
categoryOrGeometryParams = info.categoryId;
if (undefined !== categoryOrGeometryParams)
this._appearance = typeof categoryOrGeometryParams === "string" ? new GeometryParams(categoryOrGeometryParams) : categoryOrGeometryParams;
else
this._appearance = new GeometryParams(Id64.invalid);
if (undefined !== info.sourceToWorld)
localToWorld = ElementGeometry.toTransform(info.sourceToWorld);
if (undefined !== localToWorld)
this._localToWorld = localToWorld;
else
this._localToWorld = Transform.createIdentity();
const orgAng = YawPitchRollAngles.tryFromTransform(this._localToWorld);
if (undefined === orgAng.angles)
orgAng.angles = YawPitchRollAngles.createDegrees(0, 0, 0);
let bbox = (undefined !== info.bbox ? ElementGeometry.toElementAlignedBox3d(info.bbox) : undefined);
if (undefined === bbox)
bbox = Range3d.createNull();
this.placement = new Placement3d(orgAng.origin, orgAng.angles, bbox);
}
/** Call to return deserialized entry data in world coordinates */
requestWorldCoordinates() {
this._applyLocalToWorld = !this._localToWorld.isIdentity;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
get entry() {
if (undefined === this._entry)
this._entry = new IteratorEntry(this._appearance, this._localToWorld, this._applyLocalToWorld);
return this._entry;
}
/** Advance to next displayable opcode (geometric entry or geometry part) while updating the current [[GeometryParams]] from appearance related opcodes. */
next() {
while (this._index < this.entryArray.length) {
const value = this.entryArray[this._index++];
if (ElementGeometry.isAppearanceEntry(value)) {
const localToWorld = (this._applyLocalToWorld ? this._localToWorld : undefined);
ElementGeometry.updateGeometryParams(value, this.entry.geomParams, localToWorld);
}
else if (ElementGeometryOpcode.SubGraphicRange === value.opcode) {
// NOTE: localRange remains valid until the next sub-range entry is encountered...
this.entry.localRange = ElementGeometry.toSubGraphicRange(value);
}
else if (ElementGeometryOpcode.PartReference === value.opcode) {
this.entry.value = value;
return { value: this.entry, done: false };
}
else if (ElementGeometry.isGeometricEntry(value)) {
this.entry.value = value;
return { value: this.entry, done: false };
}
}
return { value: this.entry, done: true };
}
[Symbol.iterator]() {
return this;
}
}
ElementGeometry.Iterator = Iterator;
/** Return whether the supplied entry can be represented as a [[GeometryQuery]] */
function isGeometryQueryEntry(entry) {
switch (entry.opcode) {
case ElementGeometryOpcode.PointPrimitive:
case ElementGeometryOpcode.PointPrimitive2d:
case ElementGeometryOpcode.ArcPrimitive:
case ElementGeometryOpcode.CurveCollection:
case ElementGeometryOpcode.Polyface:
case ElementGeometryOpcode.CurvePrimitive:
case ElementGeometryOpcode.SolidPrimitive:
case ElementGeometryOpcode.BsplineSurface:
return true;
default:
return false;
}
}
ElementGeometry.isGeometryQueryEntry = isGeometryQueryEntry;
/** Return whether the supplied entry is displayable geometry [[GeometryQuery]], [[BRepEntity.DataProps]], [[TextString]], or [[ImageGraphic]] */
function isGeometricEntry(entry) {
switch (entry.opcode) {
case ElementGeometryOpcode.BRep:
case ElementGeometryOpcode.TextString:
case ElementGeometryOpcode.Image:
return true;
default:
return isGeometryQueryEntry(entry);
}
}
ElementGeometry.isGeometricEntry = isGeometricEntry;
/** Return whether the supplied entry is geometric or a part reference */
function isDisplayableEntry(entry) {
switch (entry.opcode) {
case ElementGeometryOpcode.PartReference:
return true;
default:
return isGeometricEntry(entry);
}
}
ElementGeometry.isDisplayableEntry = isDisplayableEntry;
/** Return whether the supplied entry represents appearance information */
function isAppearanceEntry(entry) {
switch (entry.opcode) {
case ElementGeometryOpcode.BasicSymbology:
case ElementGeometryOpcode.Fill:
case ElementGeometryOpcode.Pattern:
case ElementGeometryOpcode.Material:
case ElementGeometryOpcode.LineStyleModifiers:
return true;
default:
return false;
}
}
ElementGeometry.isAppearanceEntry = isAppearanceEntry;
/** Return whether the supplied entry represents a single open curve or path */
function isCurve(entry) {
switch (entry.opcode) {
case ElementGeometryOpcode.PointPrimitive: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.PointPrimitive.getRootAsPointPrimitive(buffer);
return (EGFBAccessors.BoundaryType.Open === ppfb.boundary());
}
case ElementGeometryOpcode.PointPrimitive2d: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.PointPrimitive2d.getRootAsPointPrimitive2d(buffer);
return (EGFBAccessors.BoundaryType.Open === ppfb.boundary());
}
case ElementGeometryOpcode.ArcPrimitive: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.ArcPrimitive.getRootAsArcPrimitive(buffer);
return (EGFBAccessors.BoundaryType.Open === ppfb.boundary());
}
case ElementGeometryOpcode.CurvePrimitive: {
// should never be a point string or closed bcurve...
return true;
}
case ElementGeometryOpcode.CurveCollection: {
const geom = BentleyGeometryFlatBuffer.bytesToGeometry(entry.data, true);
if (undefined === geom || Array.isArray(geom))
return false;
return ("curveCollection" === geom.geometryCategory && !geom.isAnyRegionType);
}
default:
return false;
}
}
ElementGeometry.isCurve = isCurve;
/** Return whether the supplied entry represents a loop, planar region, open polyface, or sheet body */
function isSurface(entry) {
switch (entry.opcode) {
case ElementGeometryOpcode.PointPrimitive: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.PointPrimitive.getRootAsPointPrimitive(buffer);
return (EGFBAccessors.BoundaryType.Closed === ppfb.boundary());
}
case ElementGeometryOpcode.PointPrimitive2d: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.PointPrimitive2d.getRootAsPointPrimitive2d(buffer);
return (EGFBAccessors.BoundaryType.Closed === ppfb.boundary());
}
case ElementGeometryOpcode.ArcPrimitive: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.ArcPrimitive.getRootAsArcPrimitive(buffer);
return (EGFBAccessors.BoundaryType.Closed === ppfb.boundary());
}
case ElementGeometryOpcode.CurvePrimitive: {
// should never be a closed bcurve...
return false;
}
case ElementGeometryOpcode.CurveCollection: {
const geom = BentleyGeometryFlatBuffer.bytesToGeometry(entry.data, true);
if (undefined === geom || Array.isArray(geom))
return false;
return ("curveCollection" === geom.geometryCategory && geom.isAnyRegionType);
}
case ElementGeometryOpcode.SolidPrimitive: {
const geom = BentleyGeometryFlatBuffer.bytesToGeometry(entry.data, true);
if (undefined === geom || Array.isArray(geom))
return false;
return ("solid" === geom.geometryCategory && !geom.isClosedVolume);
}
case ElementGeometryOpcode.Polyface: {
const geom = BentleyGeometryFlatBuffer.bytesToGeometry(entry.data, true);
if (undefined === geom || Array.isArray(geom))
return false;
if ("polyface" !== geom.geometryCategory)
return false;
const polyface = geom;
switch (polyface.expectedClosure) {
case 0:
return !PolyfaceQuery.isPolyfaceClosedByEdgePairing(polyface);
case 1:
return true;
case 2:
default:
return false;
}
}
case ElementGeometryOpcode.BsplineSurface: {
// never treated as a solid even if closed/periodic in u/v...
return true;
}
case ElementGeometryOpcode.BRep: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.BRepData.getRootAsBRepData(buffer);
return (EGFBAccessors.BRepType.Sheet === ppfb.brepType());
}
default:
return false;
}
}
ElementGeometry.isSurface = isSurface;
/** Return whether the supplied entry represents a capped solid, closed polyface, or solid body */
function isSolid(entry) {
switch (entry.opcode) {
case ElementGeometryOpcode.SolidPrimitive: {
const geom = BentleyGeometryFlatBuffer.bytesToGeometry(entry.data, true);
if (undefined === geom || Array.isArray(geom))
return false;
return ("solid" === geom.geometryCategory && geom.isClosedVolume);
}
case ElementGeometryOpcode.Polyface: {
const geom = BentleyGeometryFlatBuffer.bytesToGeometry(entry.data, true);
if (undefined === geom || Array.isArray(geom))
return false;
if ("polyface" !== geom.geometryCategory)
return false;
const polyface = geom;
switch (polyface.expectedClosure) {
case 0:
return PolyfaceQuery.isPolyfaceClosedByEdgePairing(polyface);
case 2:
return true;
case 1:
default:
return false;
}
}
case ElementGeometryOpcode.BRep: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.BRepData.getRootAsBRepData(buffer);
return (EGFBAccessors.BRepType.Solid === ppfb.brepType());
}
default:
return false;
}
}
ElementGeometry.isSolid = isSolid;
/** Return the body type that would be used to represent the supplied entry */
function getBRepEntityType(entry) {
switch (entry.opcode) {
case ElementGeometryOpcode.PointPrimitive: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.PointPrimitive.getRootAsPointPrimitive(buffer);
if (EGFBAccessors.BoundaryType.None === ppfb.boundary())
return undefined;
return (EGFBAccessors.BoundaryType.Closed === ppfb.boundary() ? BRepEntity.Type.Sheet : BRepEntity.Type.Wire);
}
case ElementGeometryOpcode.PointPrimitive2d: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.PointPrimitive2d.getRootAsPointPrimitive2d(buffer);
if (EGFBAccessors.BoundaryType.None === ppfb.boundary())
return undefined;
return (EGFBAccessors.BoundaryType.Closed === ppfb.boundary() ? BRepEntity.Type.Sheet : BRepEntity.Type.Wire);
}
case ElementGeometryOpcode.ArcPrimitive: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.ArcPrimitive.getRootAsArcPrimitive(buffer);
return (EGFBAccessors.BoundaryType.Closed === ppfb.boundary() ? BRepEntity.Type.Sheet : BRepEntity.Type.Wire);
}
case ElementGeometryOpcode.CurvePrimitive: {
// should never be a point string or closed bcurve...
return BRepEntity.Type.Wire;
}
case ElementGeometryOpcode.CurveCollection: {
const geom = BentleyGeometryFlatBuffer.bytesToGeometry(entry.data, true);
if (undefined === geom || Array.isArray(geom))
return undefined;
if ("curveCollection" !== geom.geometryCategory)
return undefined;
const curves = geom;
return (curves.isAnyRegionType ? BRepEntity.Type.Sheet : BRepEntity.Type.Wire);
}
case ElementGeometryOpcode.SolidPrimitive: {
const geom = BentleyGeometryFlatBuffer.bytesToGeometry(entry.data, true);
if (undefined === geom || Array.isArray(geom))
return undefined;
if ("solid" !== geom.geometryCategory)
return undefined;
const solid = geom;
return (solid.isClosedVolume ? BRepEntity.Type.Solid : BRepEntity.Type.Sheet);
}
case ElementGeometryOpcode.BsplineSurface: {
// always a surface...
return BRepEntity.Type.Sheet;
}
case ElementGeometryOpcode.Polyface: {
const geom = BentleyGeometryFlatBuffer.bytesToGeometry(entry.data, true);
if (undefined === geom || Array.isArray(geom))
return undefined;
if ("polyface" !== geom.geometryCategory)
return undefined;
const polyface = geom;
switch (polyface.expectedClosure) {
case 0:
return PolyfaceQuery.isPolyfaceClosedByEdgePairing(polyface) ? BRepEntity.Type.Solid : BRepEntity.Type.Sheet;
case 1:
return BRepEntity.Type.Sheet;
case 2:
return BRepEntity.Type.Solid;
default:
return undefined;
}
}
case ElementGeometryOpcode.BRep: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.BRepData.getRootAsBRepData(buffer);
switch (ppfb.brepType()) {
case EGFBAccessors.BRepType.Wire:
return BRepEntity.Type.Wire; // always be persisted as a curve type...
case EGFBAccessors.BRepType.Sheet:
return BRepEntity.Type.Sheet;
case EGFBAccessors.BRepType.Solid:
return BRepEntity.Type.Solid;
default:
return undefined;
}
}
default:
return undefined;
}
}
ElementGeometry.getBRepEntityType = getBRepEntityType;
/** Return entry as a [[GeometryQuery]] */
function toGeometryQuery(entry, localToWorld) {
if (!isGeometryQueryEntry(entry))
return undefined;
switch (entry.opcode) {
case ElementGeometryOpcode.PointPrimitive: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.PointPrimitive.getRootAsPointPrimitive(buffer);
const pts = [];
for (let i = 0; i < ppfb.coordsLength(); i++) {
const coord = expectNotNull(ppfb.coords(i));
pts.push(Point3d.create(coord.x(), coord.y(), coord.z()));
}
if (0 === pts.length)
return undefined;
if (undefined !== localToWorld)
localToWorld.multiplyPoint3dArrayInPlace(pts);
switch (ppfb.boundary()) {
case EGFBAccessors.BoundaryType.Open:
return LineString3d.createPoints(pts);
case EGFBAccessors.BoundaryType.Closed:
return Loop.createPolygon(pts);
default:
return PointString3d.createPoints(pts);
}
}
case ElementGeometryOpcode.PointPrimitive2d: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.PointPrimitive2d.getRootAsPointPrimitive2d(buffer);
const pts = [];
for (let i = 0; i < ppfb.coordsLength(); i++) {
const coord = expectNotNull(ppfb.coords(i));
pts.push(Point3d.create(coord.x(), coord.y()));
}
if (0 === pts.length)
return undefined;
if (undefined !== localToWorld)
localToWorld.multiplyPoint3dArrayInPlace(pts);
switch (ppfb.boundary()) {
case EGFBAccessors.BoundaryType.Open:
return LineString3d.createPoints(pts);
case EGFBAccessors.BoundaryType.Closed:
return Loop.createPolygon(pts);
default:
return PointString3d.createPoints(pts);
}
}
case ElementGeometryOpcode.ArcPrimitive: {
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.ArcPrimitive.getRootAsArcPrimitive(buffer);
const ppfbCenter = expectNotNull(ppfb.center());
const center = Point3d.create(ppfbCenter.x(), ppfbCenter.y(), ppfbCenter.z());
const ppfbVector0 = expectNotNull(ppfb.vector0());
const vector0 = Vector3d.create(ppfbVector0.x(), ppfbVector0.y(), ppfbVector0.z());
const ppfbVector90 = expectNotNull(ppfb.vector90());
const vector90 = Vector3d.create(ppfbVector90.x(), ppfbVector90.y(), ppfbVector90.z());
const arc = Arc3d.create(center, vector0, vector90, AngleSweep.createStartSweepRadians(ppfb.start(), ppfb.sweep()));
if (undefined !== localToWorld && !arc.tryTransformInPlace(localToWorld))
return undefined;
return (EGFBAccessors.BoundaryType.Closed === ppfb.boundary() ? Loop.create(arc) : arc);
}
case ElementGeometryOpcode.CurvePrimitive:
case ElementGeometryOpcode.CurveCollection:
case ElementGeometryOpcode.SolidPrimitive:
case ElementGeometryOpcode.BsplineSurface:
case ElementGeometryOpcode.Polyface:
const geom = BentleyGeometryFlatBuffer.bytesToGeometry(entry.data, true);
if (undefined === geom || Array.isArray(geom))
return undefined; // Should always be a single entry not an array...
if (undefined !== localToWorld && !geom.tryTransformInPlace(localToWorld))
return undefined;
return geom;
default:
return undefined; // Not a GeometryQuery, need to be handled explicitly...
}
}
ElementGeometry.toGeometryQuery = toGeometryQuery;
/** Create entry from a [[GeometryQuery]] */
function fromGeometryQuery(geom, worldToLocal) {
let opcode;
switch (geom.geometryCategory) {
case "bsurf":
opcode = ElementGeometryOpcode.BsplineSurface;
break;
case "curveCollection":
opcode = ElementGeometryOpcode.CurveCollection;
break;
case "curvePrimitive":
case "pointCollection":
opcode = ElementGeometryOpcode.CurvePrimitive;
break;
case "polyface":
opcode = ElementGeometryOpcode.Polyface;
break;
case "solid":
opcode = ElementGeometryOpcode.SolidPrimitive;
break;
default:
return undefined;
}
if (undefined !== worldToLocal) {
const localGeom = geom.cloneTransformed(worldToLocal);
if (undefined === localGeom)
return undefined;
geom = localGeom;
}
const data = BentleyGeometryFlatBuffer.geometryToBytes(geom, true);
if (undefined === data)
return undefined;
return { opcode, data };
}
ElementGeometry.fromGeometryQuery = fromGeometryQuery;
/** Return entry as a [[TextString]] */
function toTextString(entry, localToWorld) {
if (ElementGeometryOpcode.TextString !== entry.opcode)
return undefined;
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.TextString.getRootAsTextString(buffer);
const style = ppfb.style();
if (null === style)
return undefined;
const text = ppfb.text();
const props = { text: (null !== text ? text : ""), font: style.fontId(), height: style.height() };
props.widthFactor = style.widthFactor();
props.bold = style.isBold();
props.italic = style.isItalic();
props.underline = style.isUnderlined();
const transform = ppfb.transform();
if (null !== transform) {
props.origin = Point3d.create(transform.form3d03(), transform.form3d13(), transform.form3d23());
props.rotation = YawPitchRollAngles.createFromMatrix3d(Matrix3d.createRowValues(transform.form3d00(), transform.form3d01(), transform.form3d02(), transform.form3d10(), transform.form3d11(), transform.form3d12(), transform.form3d20(), transform.form3d21(), transform.form3d22()));
}
if (undefined === localToWorld)
return props;
const textString = new TextString(props);
if (!textString.transformInPlace(localToWorld))
return undefined;
return textString.toJSON();
}
ElementGeometry.toTextString = toTextString;
/** Returns only the [[TextStringGlyphData]] embedded in the [[TextString]] flatbuffer. This data is only internal to the native display libaries. */
function toTextStringGlyphData(entry) {
if (ElementGeometryOpcode.TextString !== entry.opcode)
return undefined;
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.TextString.getRootAsTextString(buffer);
const glyphOriginToPoint2d = (origin) => {
if (!origin)
throw new Error("Value cannot be null.");
return new Point2d(origin.x(), origin.y());
};
const textStringRangeToRange2d = (r) => r ? new Range2d(r.lowx(), r.lowy(), r.highx(), r.highy()) : null;
const range = textStringRangeToRange2d(ppfb.range());
const glyphIds = Array.from({ length: ppfb.glyphIdsLength() }, (_, i) => ppfb.glyphIds(i) ?? 0);
const glyphOrigins = Array.from({ length: ppfb.glyphOriginsLength() }, (_, i) => glyphOriginToPoint2d(ppfb.glyphOrigins(i)));
if (!range || range.isNull || glyphIds.length === 0 || glyphOrigins.length === 0)
return undefined;
return { glyphIds, glyphOrigins, range };
}
ElementGeometry.toTextStringGlyphData = toTextStringGlyphData;
/** Create entry from a [[TextString]] */
function fromTextString(text, worldToLocal, glyphs) {
if (undefined !== worldToLocal) {
const localText = new TextString(text);
if (!localText.transformInPlace(worldToLocal))
return undefined;
text = localText.toJSON();
}
const fbb = new flatbuffers.Builder();
const builder = EGFBAccessors.TextString;
const textOffset = fbb.createString(text.text);
const styleOffset = EGFBAccessors.TextStringStyle.createTextStringStyle(fbb, 1, 0, text.font, undefined === text.bold ? false : text.bold, undefined === text.italic ? false : text.italic, undefined === text.underline ? false : text.underline, text.height, undefined === text.widthFactor ? 1.0 : text.widthFactor);
const fbbGlyphs = (() => {
if (!glyphs || glyphs.range.isNull)
return undefined;
const glyphIdsOffset = builder.createGlyphIdsVector(fbb, glyphs.glyphIds);
builder.startGlyphOriginsVector(fbb, glyphs.glyphOrigins.length);
for (const origin of [...glyphs.glyphOrigins].reverse()) {
EGFBAccessors.TextStringGlyphOrigin.createTextStringGlyphOrigin(fbb, origin.x, origin.y);
}
const glyphOriginsOffset = fbb.endVector();
return { glyphIdsOffset, glyphOriginsOffset, range: glyphs.range };
})();
builder.startTextString(fbb);
builder.addMajorVersion(fbb, 1);
builder.addMinorVersion(fbb, 0);
builder.addText(fbb, textOffset);
builder.addStyle(fbb, styleOffset);
if (undefined !== text.origin || undefined !== text.rotation) {
const origin = Point3d.fromJSON(text.origin);
const angles = YawPitchRollAngles.fromJSON(text.rotation);
const matrix = angles.toMatrix3d();
const coffs = matrix.coffs;
const transformOffset = EGFBAccessors.TextStringTransform.createTextStringTransform(fbb, coffs[0], coffs[1], coffs[2], origin.x, coffs[3], coffs[4], coffs[5], origin.y, coffs[6], coffs[7], coffs[8], origin.z);
builder.addTransform(fbb, transformOffset);
}
if (fbbGlyphs) {
builder.addRange(fbb, EGFBAccessors.TextStringRange.createTextStringRange(fbb, fbbGlyphs.range.low.x, fbbGlyphs.range.low.y, fbbGlyphs.range.high.x, fbbGlyphs.range.high.y));
builder.addGlyphIds(fbb, fbbGlyphs.glyphIdsOffset);
builder.addGlyphOrigins(fbb, fbbGlyphs.glyphOriginsOffset);
}
const mLoc = builder.endTextString(fbb);
fbb.finish(mLoc);
const data = fbb.asUint8Array();
return { opcode: ElementGeometryOpcode.TextString, data };
}
ElementGeometry.fromTextString = fromTextString;
/** Return entry as a [[ImageGraphic]] */
function toImageGraphic(entry, localToWorld) {
if (ElementGeometryOpcode.Image !== entry.opcode)
return undefined;
const buffer = new flatbuffers.ByteBuffer(entry.data);
const ppfb = EGFBAccessors.Image.getRootAsImage(buffer);
const textureLong = ppfb.textureId();
const textureId = Id64.fromUint32Pair(textureLong.low, textureLong.high);
const hasBorder = (1 === ppfb.drawBorder());
const corners = new ImageGraphicCorners(Point3d.createZero(), Point3d.createZero(), Point3d.createZero(), Point3d.createZero());
const corner0 = ppfb.tileCorner0();
const corner1 = ppfb.tileCorner1();
const corner2 = ppfb.tileCorner2();
const corner3 = ppfb.tileCorner3();
if (null !== corner0)
corners[0].setFrom(Point3d.create(corner0.x(), corner0.y(), corner0.z()));
if (null !== corner1)
corners[1].setFrom(Point3d.create(corner1.x(