UNPKG

@itwin/core-common

Version:

iTwin.js components common to frontend and backend

508 lines • 28.9 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * 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 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.GeometryStreamIterator = exports.GeometryStreamBuilder = exports.GeometryStreamFlags = exports.BRepEntity = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const core_geometry_1 = require("@itwin/core-geometry"); const ColorDef_1 = require("../ColorDef"); const ElementProps_1 = require("../ElementProps"); const GeometryParams_1 = require("../GeometryParams"); const Gradient_1 = require("../Gradient"); const IModelError_1 = require("../IModelError"); const AreaPattern_1 = require("./AreaPattern"); const ImageGraphic_1 = require("./ImageGraphic"); const LineStyle_1 = require("./LineStyle"); const TextString_1 = require("./TextString"); const Placement_1 = require("./Placement"); /** JSON representation of a brep GeometryStream entry. * @public */ var BRepEntity; (function (BRepEntity) { /** Enum for type of solid kernel entity this represents */ let Type; (function (Type) { /** Body consisting of at least one solid region */ Type[Type["Solid"] = 0] = "Solid"; /** Body consisting of connected sets of faces having edges that are shared by a maximum of two faces */ Type[Type["Sheet"] = 1] = "Sheet"; /** Body consisting of connected sets of edges having vertices that are shared by a maximum of two edges */ Type[Type["Wire"] = 2] = "Wire"; })(Type = BRepEntity.Type || (BRepEntity.Type = {})); })(BRepEntity || (exports.BRepEntity = BRepEntity = {})); /** Flags applied to the entire contents of a [[GeometryStreamProps]]. * @see GeometryStreamHeaderProps * @public * @extensions */ var GeometryStreamFlags; (function (GeometryStreamFlags) { /** No flags. */ GeometryStreamFlags[GeometryStreamFlags["None"] = 0] = "None"; /** When the geometry is displayed, it is always oriented to face the viewer. The placement origin of the element associated with the geometry is used as the rotation point. * If the placement origin is outside of the view, the geometry will not necessarily be displayed, even if rotating it to face the viewer would cause its range to intersect the viewed volume. */ GeometryStreamFlags[GeometryStreamFlags["ViewIndependent"] = 1] = "ViewIndependent"; })(GeometryStreamFlags || (exports.GeometryStreamFlags = GeometryStreamFlags = {})); /** GeometryStreamBuilder is a helper class for populating the [[GeometryStreamProps]] array needed to create a [[GeometricElement]] or [[GeometryPart]]. * @public */ class GeometryStreamBuilder { /** Current inverse placement transform, used for converting world coordinate input to be placement relative */ _worldToLocal; /** GeometryStream entries */ geometryStream = []; /** 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._worldToLocal = (undefined === localToWorld || localToWorld.isIdentity ? undefined : localToWorld.inverse()); } /** Supply local to world transform from a Point3d and optional YawPitchRollAngles. * @see [[Placement3d]] */ setLocalToWorld3d(origin, angles = core_geometry_1.YawPitchRollAngles.createDegrees(0.0, 0.0, 0.0)) { this.setLocalToWorld(core_geometry_1.Transform.createOriginAndMatrix(origin, angles.toMatrix3d())); } /** Supply local to world transform from a Point2d and optional Angle. * @see [[Placement2d]] */ setLocalToWorld2d(origin, angle = core_geometry_1.Angle.createDegrees(0.0)) { this.setLocalToWorld(core_geometry_1.Transform.createOriginAndMatrix(core_geometry_1.Point3d.createFrom(origin), core_geometry_1.Matrix3d.createRotationAroundVector(core_geometry_1.Vector3d.unitZ(), angle))); } /** Supply local to world transform from a PlacementProps2d or PlacementProps3d. * @see [[PlacementProps]] */ setLocalToWorldFromPlacement(props) { const placement = (0, ElementProps_1.isPlacement2dProps)(props) ? Placement_1.Placement2d.fromJSON(props) : Placement_1.Placement3d.fromJSON(props); this.setLocalToWorld(placement.transform); } /** Store local ranges in GeometryStream 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() { this.geometryStream.push({ subRange: core_geometry_1.Range3d.createNull() }); } /** Change [[SubCategory]] or reset to [[SubCategoryAppearance]] for subsequent geometry. * An invalid sub-category id can be supplied to force a reset to the current [[SubCategoryAppearance]]. * It is not valid to change the sub-category when defining a [[GeometryPart]]. A [[GeometryPart]] inherit the symbology of their instance for anything not explicitly overridden. */ appendSubCategoryChange(subCategoryId) { this.geometryStream.push({ appearance: { subCategory: subCategoryId } }); 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) { const appearance = { subCategory: geomParams.subCategoryId, color: geomParams.lineColor?.toJSON(), weight: geomParams.weight, style: geomParams.styleInfo?.styleId, transparency: geomParams.elmTransparency, displayPriority: geomParams.elmPriority, geometryClass: geomParams.geometryClass, }; this.geometryStream.push({ appearance }); if (undefined !== geomParams.materialId) this.geometryStream.push({ material: { materialId: geomParams.materialId } }); if (undefined !== geomParams.fillDisplay && GeometryParams_1.FillDisplay.Never !== geomParams.fillDisplay) { const fill = { display: geomParams.fillDisplay, transparency: geomParams.fillTransparency, }; if (undefined !== geomParams.gradient && Gradient_1.Gradient.Mode.None !== geomParams.gradient.mode) fill.gradient = geomParams.gradient?.toJSON(); else if (undefined !== geomParams.backgroundFill && GeometryParams_1.BackgroundFill.None !== geomParams.backgroundFill) fill.backgroundFill = geomParams.backgroundFill; else if (undefined !== geomParams.fillColor) fill.color = geomParams.fillColor.toJSON(); this.geometryStream.push({ fill }); } if (undefined !== geomParams.pattern) { const localPattern = this._worldToLocal ? geomParams.pattern.clone() : geomParams.pattern; if (undefined !== this._worldToLocal && !localPattern.applyTransform(this._worldToLocal)) return false; this.geometryStream.push({ pattern: localPattern.toJSON() }); } if (undefined !== geomParams.styleInfo && undefined !== geomParams.styleInfo.styleMod) { const localStyleMod = new LineStyle_1.LineStyle.Modifier(geomParams.styleInfo.styleMod); if (undefined !== this._worldToLocal && !localStyleMod.applyTransform(this._worldToLocal)) return false; this.geometryStream.push({ styleMod: localStyleMod }); } return true; } /** Append a [[GeometryPart]] instance with relative position, orientation, and scale to a [[GeometryStreamProps]] array for creating a [[GeometricElement3d]]. * Not valid when defining a [[GeometryPart]] as nesting of parts is not supported. */ appendGeometryPart3d(partId, instanceOrigin, instanceRotation, instanceScale) { if (undefined === this._worldToLocal) { this.geometryStream.push({ geomPart: { part: partId, origin: instanceOrigin, rotation: instanceRotation, scale: instanceScale } }); return true; } const partTrans = core_geometry_1.Transform.createOriginAndMatrix(instanceOrigin, instanceRotation ? instanceRotation.toMatrix3d() : core_geometry_1.Matrix3d.createIdentity()); if (undefined !== instanceScale) partTrans.matrix.scaleColumnsInPlace(instanceScale, instanceScale, instanceScale); const resultTrans = this._worldToLocal.multiplyTransformTransform(partTrans); const scales = new core_geometry_1.Vector3d(); if (!resultTrans.matrix.normalizeColumnsInPlace(scales)) return false; const newRotation = core_geometry_1.YawPitchRollAngles.createFromMatrix3d(resultTrans.matrix); if (undefined === newRotation) return false; this.geometryStream.push({ geomPart: { part: partId, origin: resultTrans.getOrigin(), rotation: newRotation, scale: scales.x } }); return true; } /** Append a [[GeometryPart]] instance with relative position, orientation, and scale to a [[GeometryStreamProps]] 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 ? core_geometry_1.Point3d.createFrom(instanceOrigin) : undefined, instanceRotation ? new core_geometry_1.YawPitchRollAngles(instanceRotation) : undefined, instanceScale); } /** Append a [[TextString]] supplied in either local or world coordinates to the [[GeometryStreamProps]] array */ appendTextString(textString) { if (this._worldToLocal) { textString = new TextString_1.TextString(textString); if (!textString.transformInPlace(this._worldToLocal)) { return false; } } this.geometryStream.push({ textString: textString.toJSON() }); return true; } /** Append a series of entries representing a [[TextBlock]] to the [[GeometryStreamProps]] array. * @beta */ appendTextBlock(block) { for (const entry of block.entries) { let result; if (undefined !== entry.text) { result = this.appendTextString(new TextString_1.TextString(entry.text)); } else if (undefined !== entry.color) { if (entry.color === "subcategory") { result = this.appendSubCategoryChange(core_bentley_1.Id64.invalid); } else { this.geometryStream.push({ appearance: { color: entry.color } }); result = true; } } else { result = this.appendGeometry(core_geometry_1.LineSegment3d.fromJSON(entry.separator)); } if (!result) { return false; } } return true; } /** Append an [[ImageGraphic]] supplied in either local or world coordinates. */ appendImage(image) { if (undefined !== this._worldToLocal) image = image.cloneTransformed(this._worldToLocal); this.geometryStream.push({ image: image.toJSON() }); return true; } /** Append a [[GeometryQuery]] supplied in either local or world coordinates to the [[GeometryStreamProps]] array */ appendGeometry(geometry) { if (undefined === this._worldToLocal) { const geomData = core_geometry_1.IModelJson.Writer.toIModelJson(geometry); if (undefined === geomData) return false; this.geometryStream.push(geomData); return true; } const localGeometry = geometry.cloneTransformed(this._worldToLocal); if (undefined === localGeometry) return false; const localGeomData = core_geometry_1.IModelJson.Writer.toIModelJson(localGeometry); if (undefined === localGeomData) return false; this.geometryStream.push(localGeomData); return true; } /** Append [[BRepEntity.DataProps]] supplied in either local or world coordinates to the [[GeometryStreamProps]] array * @beta */ appendBRepData(brep) { if (undefined === this._worldToLocal) { this.geometryStream.push({ brep }); return true; } const entityTrans = core_geometry_1.Transform.fromJSON(brep.transform); const localTrans = this._worldToLocal.multiplyTransformTransform(entityTrans); const localBrep = { data: brep.data, type: brep.type, transform: localTrans.isIdentity ? undefined : localTrans.toJSON(), faceSymbology: brep.faceSymbology, }; this.geometryStream.push({ brep: localBrep }); return true; } /** @internal */ getHeader() { return 0 < this.geometryStream.length ? this.geometryStream[0].header : undefined; } /** @internal */ obtainHeader() { const hdr = this.getHeader(); if (undefined !== hdr) return hdr; const entry = { header: { flags: GeometryStreamFlags.None } }; this.geometryStream.unshift(entry); return entry.header; } /** Controls whether or not the geometry in the stream should be displayed as view-independent. * When view-independent geometry is displayed, it is always oriented to face the viewer, using the placement origin of the element as the rotation point. * If the placement origin is outside of the view, the geometry will not necessarily be displayed, even if rotating it to face the viewer would cause its range to intersect the viewed volume * @public */ get isViewIndependent() { const hdr = this.getHeader(); return undefined !== hdr && GeometryStreamFlags.None !== (hdr.flags & GeometryStreamFlags.ViewIndependent); } set isViewIndependent(viewIndependent) { if (viewIndependent === this.isViewIndependent) return; const hdr = this.obtainHeader(); if (viewIndependent) hdr.flags |= GeometryStreamFlags.ViewIndependent; else hdr.flags &= ~GeometryStreamFlags.ViewIndependent; } } exports.GeometryStreamBuilder = GeometryStreamBuilder; class IteratorEntry { _primitive; geomParams; localToWorld; localRange; constructor(appearance, localToWorld) { this.geomParams = typeof appearance === "string" ? new GeometryParams_1.GeometryParams(appearance) : appearance; this.localToWorld = localToWorld; } get primitive() { return (0, core_bentley_1.expectDefined)(this._primitive); } set primitive(primitive) { this._primitive = primitive; } setGeometryQuery(geometry) { this._primitive = { type: "geometryQuery", geometry }; } setTextString(textString) { this._primitive = { type: "textString", textString }; } setBRep(brep) { this._primitive = { type: "brep", brep }; } setImage(image) { this._primitive = { type: "image", image }; } setPartReference(id, toLocal) { this._primitive = { type: "partReference", part: { id, toLocal }, }; } } /** GeometryStreamIterator is a helper class for iterating a [[GeometryStreamProps]]. * A [[GeometricElement]]'s GeometryStream must be specifically requested using [[ElementLoadProps.wantGeometry]]. * Each [[GeometryStreamIteratorEntry]] returned by the iterator represents exactly one geometric primitive in the stream. * @public */ class GeometryStreamIterator { /** GeometryStream entries */ geometryStream; /** Flags applied to the entire geometry stream. */ flags; /** Current entry position */ _index = 0; /** Allocated on first call to next() and reused thereafter. */ _entry; /** Used to initialize this._entry. */ _appearance; _localToWorld; /** Construct a new GeometryStreamIterator given a [[GeometryStreamProps]] from either a [[GeometricElement3d]], [[GeometricElement2d]], or [[GeometryPart]]. * Supply the [[GeometricElement]]'s category to initialize the appearance information for each geometric entry. */ constructor(geometryStream, categoryOrGeometryParams, localToWorld) { this.geometryStream = geometryStream; this._appearance = categoryOrGeometryParams ?? core_bentley_1.Id64.invalid; this._localToWorld = localToWorld; if (0 < geometryStream.length && undefined !== geometryStream[0].header) { this.flags = geometryStream[0].header.flags; ++this._index; } else { this.flags = GeometryStreamFlags.None; } } // eslint-disable-next-line @typescript-eslint/naming-convention get entry() { if (undefined === this._entry) this._entry = new IteratorEntry(this._appearance, this._localToWorld); return this._entry; } /** Create a new GeometryStream iterator for a [[GeometricElement3d]]. * If [[GeometricElement3dProps.placement]] is not undefined, placement relative entries will be returned transformed to world coordinates. * @throws [[IModelError]] if element.geom is undefined. */ static fromGeometricElement3d(element) { if (element.geom === undefined) throw new IModelError_1.IModelError(core_bentley_1.IModelStatus.NoGeometry, "GeometricElement has no geometry or geometry wasn't requested"); let transform; if (element.placement !== undefined) transform = core_geometry_1.Transform.createOriginAndMatrix(core_geometry_1.Point3d.fromJSON(element.placement.origin), core_geometry_1.YawPitchRollAngles.fromJSON(element.placement.angles).toMatrix3d()); return new GeometryStreamIterator(element.geom, element.category, transform); } /** Create a new GeometryStream iterator for a [[GeometricElement2d]]. * If [[GeometricElement2dProps.placement]] is not undefined, placement relative entries will be returned transformed to world coordinates. * @throws [[IModelError]] if element.geom is undefined. */ static fromGeometricElement2d(element) { if (element.geom === undefined) throw new IModelError_1.IModelError(core_bentley_1.IModelStatus.NoGeometry, "GeometricElement has no geometry or geometry wasn't requested"); let transform; if (element.placement !== undefined) { const origin = core_geometry_1.Point3d.createFrom(core_geometry_1.Point2d.fromJSON(element.placement.origin)); const matrix = (0, core_bentley_1.expectDefined)(core_geometry_1.Matrix3d.createRotationAroundVector(core_geometry_1.Vector3d.unitZ(), core_geometry_1.Angle.fromJSON(element.placement.angle))); transform = core_geometry_1.Transform.createOriginAndMatrix(origin, matrix); } return new GeometryStreamIterator(element.geom, element.category, transform); } /** Create a new GeometryStream iterator for a [[GeometryPart]]. * To iterate a part's GeometryStream in the context of a part instance found for a [[GeometricElement]], provide the optional [[GeometryParams]] and Transform from the [[GeometricElement]]'s [[GeometryStreamIterator]]. * Supply the [[GeometryParams]] to return appearance information as inherited from the [[GeometricElement]]. * Supply the partToWorld transform to return the part geometry in world coordinates. * Supply the partToLocal transform to return the part geometry relative to the [[GeometricElement]]'s placement. * @throws [[IModelError]] if geomPart.geom is undefined. */ static fromGeometryPart(geomPart, geomParams, partTransform) { if (geomPart.geom === undefined) throw new IModelError_1.IModelError(core_bentley_1.IModelStatus.NoGeometry, "GeometryPart has no geometry or geometry wasn't requested"); return new GeometryStreamIterator(geomPart.geom, geomParams?.clone(), partTransform); } /** Get the transform that if applied to a [[GeometryPart]]'s GeometryStream entries would return them in world coordinates. */ partToWorld() { if (undefined === this._entry) return this._localToWorld; const partToLocal = "partReference" === this._entry.primitive.type ? this._entry.primitive.part.toLocal : undefined; if (this._entry.localToWorld === undefined || partToLocal === undefined) return this._entry.localToWorld; return this._entry.localToWorld.multiplyTransformTransform(partToLocal); } /** Advance to next displayable geometric entry while updating the current [[GeometryParams]] from appearance related entries. * Geometric entries are [[TextString]], [[GeometryQuery]], [[GeometryPart]], [[ImageGraphic]], and [[BRepEntity.DataProps]]. */ next() { // NOTE: localRange remains valid until we encounter either a new subRange entry or a geometry part reference. while (this._index < this.geometryStream.length) { const entry = this.geometryStream[this._index++]; if (entry.appearance) { this.entry.geomParams.resetAppearance(); if (entry.appearance.subCategory) this.entry.geomParams.subCategoryId = core_bentley_1.Id64.fromJSON(entry.appearance.subCategory); if (undefined !== entry.appearance.color) this.entry.geomParams.lineColor = ColorDef_1.ColorDef.fromJSON(entry.appearance.color); if (undefined !== entry.appearance.weight) this.entry.geomParams.weight = entry.appearance.weight; if (undefined !== entry.appearance.style) this.entry.geomParams.styleInfo = new LineStyle_1.LineStyle.Info(core_bentley_1.Id64.fromJSON(entry.appearance.style)); if (undefined !== entry.appearance.transparency) this.entry.geomParams.elmTransparency = entry.appearance.transparency; if (undefined !== entry.appearance.displayPriority) this.entry.geomParams.elmPriority = entry.appearance.displayPriority; if (undefined !== entry.appearance.geometryClass) this.entry.geomParams.geometryClass = entry.appearance.geometryClass; } else if (entry.styleMod) { if (this.entry.geomParams.styleInfo === undefined) continue; const styleMod = new LineStyle_1.LineStyle.Modifier(entry.styleMod); if (this.entry.localToWorld !== undefined) styleMod.applyTransform(this.entry.localToWorld); this.entry.geomParams.styleInfo = new LineStyle_1.LineStyle.Info(this.entry.geomParams.styleInfo.styleId, styleMod); } else if (entry.fill) { if (entry.fill.display) this.entry.geomParams.fillDisplay = entry.fill.display; if (entry.fill.transparency) this.entry.geomParams.fillTransparency = entry.fill.transparency; if (entry.fill.gradient) this.entry.geomParams.gradient = Gradient_1.Gradient.Symb.fromJSON(entry.fill.gradient); else if (entry.fill.backgroundFill) this.entry.geomParams.backgroundFill = entry.fill.backgroundFill; else if (entry.fill.color) this.entry.geomParams.fillColor = ColorDef_1.ColorDef.fromJSON(entry.fill.color); } else if (entry.pattern) { const params = AreaPattern_1.AreaPattern.Params.fromJSON(entry.pattern); if (this.entry.localToWorld !== undefined) params.applyTransform(this.entry.localToWorld); this.entry.geomParams.pattern = params; } else if (entry.material) { if (entry.material.materialId) this.entry.geomParams.materialId = core_bentley_1.Id64.fromJSON(entry.material.materialId); } else if (entry.subRange) { this.entry.localRange = core_geometry_1.Range3d.fromJSON(entry.subRange); } else if (entry.geomPart) { let transform; if (entry.geomPart.origin !== undefined || entry.geomPart.rotation !== undefined || entry.geomPart.scale !== undefined) { const origin = entry.geomPart.origin ? core_geometry_1.Point3d.fromJSON(entry.geomPart.origin) : core_geometry_1.Point3d.createZero(); const rotation = entry.geomPart.rotation ? core_geometry_1.YawPitchRollAngles.fromJSON(entry.geomPart.rotation).toMatrix3d() : core_geometry_1.Matrix3d.createIdentity(); transform = core_geometry_1.Transform.createRefs(origin, rotation); if (entry.geomPart.scale) transform.multiplyTransformTransform(core_geometry_1.Transform.createRefs(core_geometry_1.Point3d.createZero(), core_geometry_1.Matrix3d.createUniformScale(entry.geomPart.scale)), transform); } // Subgraphic range doesn't apply to parts. A sane geometry stream (i.e., any that has been through the native layers or GeometryStreamBuilder) // will have a new subgraphic range for any geometric primitive following the part. this.entry.localRange = undefined; this.entry.setPartReference(core_bentley_1.Id64.fromJSON(entry.geomPart.part), transform); return { value: this.entry, done: false }; } else if (entry.textString) { const textString = new TextString_1.TextString(entry.textString); if (this.entry.localToWorld !== undefined) textString.transformInPlace(this.entry.localToWorld); this.entry.setTextString(textString); return { value: this.entry, done: false }; } else if (entry.image) { const image = ImageGraphic_1.ImageGraphic.fromJSON(entry.image); if (undefined !== this.entry.localToWorld) image.transformInPlace(this.entry.localToWorld); this.entry.setImage(image); return { value: this.entry, done: false }; } else if (entry.brep) { if (this.entry.localToWorld !== undefined) { const entityTrans = core_geometry_1.Transform.fromJSON(entry.brep.transform); entry.brep.transform = this.entry.localToWorld.multiplyTransformTransform(entityTrans).toJSON(); } this.entry.setBRep(entry.brep); return { value: this.entry, done: false }; } else { const geometryQuery = core_geometry_1.IModelJson.Reader.parse(entry); if (!(geometryQuery instanceof core_geometry_1.GeometryQuery)) continue; if (this.entry.localToWorld !== undefined) geometryQuery.tryTransformInPlace(this.entry.localToWorld); this.entry.setGeometryQuery(geometryQuery); return { value: this.entry, done: false }; } } return { value: this.entry, done: true }; } [Symbol.iterator]() { return this; } /** @internal */ get isViewIndependent() { return GeometryStreamFlags.None !== (this.flags & GeometryStreamFlags.ViewIndependent); } } exports.GeometryStreamIterator = GeometryStreamIterator; //# sourceMappingURL=GeometryStream.js.map