UNPKG

@itwin/core-frontend

Version:
589 lines • 26.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HitList = exports.IntersectDetail = exports.SnapDetail = exports.HitDetail = exports.HitDetailType = exports.HitPriority = exports.HitParentGeomType = exports.HitGeomType = exports.HitSource = exports.SnapHeat = exports.SnapMode = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module LocatingElements */ const core_bentley_1 = require("@itwin/core-bentley"); const core_geometry_1 = require("@itwin/core-geometry"); const core_common_1 = require("@itwin/core-common"); const IModelApp_1 = require("./IModelApp"); const Sprites_1 = require("./Sprites"); const GraphicType_1 = require("./common/render/GraphicType"); /** * @public * @extensions */ var SnapMode; (function (SnapMode) { SnapMode[SnapMode["Nearest"] = 1] = "Nearest"; SnapMode[SnapMode["NearestKeypoint"] = 2] = "NearestKeypoint"; SnapMode[SnapMode["MidPoint"] = 4] = "MidPoint"; SnapMode[SnapMode["Center"] = 8] = "Center"; SnapMode[SnapMode["Origin"] = 16] = "Origin"; SnapMode[SnapMode["Bisector"] = 32] = "Bisector"; SnapMode[SnapMode["Intersection"] = 64] = "Intersection"; SnapMode[SnapMode["PerpendicularPoint"] = 128] = "PerpendicularPoint"; SnapMode[SnapMode["TangentPoint"] = 256] = "TangentPoint"; })(SnapMode || (exports.SnapMode = SnapMode = {})); /** * @public * @extensions */ var SnapHeat; (function (SnapHeat) { SnapHeat[SnapHeat["None"] = 0] = "None"; SnapHeat[SnapHeat["NotInRange"] = 1] = "NotInRange"; SnapHeat[SnapHeat["InRange"] = 2] = "InRange"; })(SnapHeat || (exports.SnapHeat = SnapHeat = {})); /** The procedure that generated this Hit. * @public * @extensions */ var HitSource; (function (HitSource) { HitSource[HitSource["None"] = 0] = "None"; HitSource[HitSource["FromUser"] = 1] = "FromUser"; HitSource[HitSource["MotionLocate"] = 2] = "MotionLocate"; HitSource[HitSource["AccuSnap"] = 3] = "AccuSnap"; HitSource[HitSource["TentativeSnap"] = 4] = "TentativeSnap"; HitSource[HitSource["DataPoint"] = 5] = "DataPoint"; HitSource[HitSource["Application"] = 6] = "Application"; HitSource[HitSource["EditAction"] = 7] = "EditAction"; HitSource[HitSource["EditActionSS"] = 8] = "EditActionSS"; })(HitSource || (exports.HitSource = HitSource = {})); /** What was being tested to generate this hit. This is not the element or * GeometricPrimitive that generated the Hit, it is an indication of whether it is an edge or interior hit. * @public * @extensions */ var HitGeomType; (function (HitGeomType) { HitGeomType[HitGeomType["None"] = 0] = "None"; HitGeomType[HitGeomType["Point"] = 1] = "Point"; HitGeomType[HitGeomType["Segment"] = 2] = "Segment"; HitGeomType[HitGeomType["Curve"] = 3] = "Curve"; HitGeomType[HitGeomType["Arc"] = 4] = "Arc"; HitGeomType[HitGeomType["Surface"] = 5] = "Surface"; })(HitGeomType || (exports.HitGeomType = HitGeomType = {})); /** Classification of GeometricPrimitive that generated the Hit. * @public * @extensions */ var HitParentGeomType; (function (HitParentGeomType) { HitParentGeomType[HitParentGeomType["None"] = 0] = "None"; HitParentGeomType[HitParentGeomType["Wire"] = 1] = "Wire"; HitParentGeomType[HitParentGeomType["Sheet"] = 2] = "Sheet"; HitParentGeomType[HitParentGeomType["Solid"] = 3] = "Solid"; HitParentGeomType[HitParentGeomType["Mesh"] = 4] = "Mesh"; HitParentGeomType[HitParentGeomType["Text"] = 5] = "Text"; })(HitParentGeomType || (exports.HitParentGeomType = HitParentGeomType = {})); /** * @public * @extensions */ var HitPriority; (function (HitPriority) { HitPriority[HitPriority["WireEdge"] = 0] = "WireEdge"; HitPriority[HitPriority["PlanarEdge"] = 1] = "PlanarEdge"; HitPriority[HitPriority["NonPlanarEdge"] = 2] = "NonPlanarEdge"; HitPriority[HitPriority["SilhouetteEdge"] = 3] = "SilhouetteEdge"; HitPriority[HitPriority["PlanarSurface"] = 4] = "PlanarSurface"; HitPriority[HitPriority["NonPlanarSurface"] = 5] = "NonPlanarSurface"; HitPriority[HitPriority["Unknown"] = 6] = "Unknown"; })(HitPriority || (exports.HitPriority = HitPriority = {})); /** * @public * @extensions */ var HitDetailType; (function (HitDetailType) { HitDetailType[HitDetailType["Hit"] = 1] = "Hit"; HitDetailType[HitDetailType["Snap"] = 2] = "Snap"; HitDetailType[HitDetailType["Intersection"] = 3] = "Intersection"; })(HitDetailType || (exports.HitDetailType = HitDetailType = {})); ; /** A HitDetail stores the result when locating geometry displayed in a view. * It holds an approximate location on an element (or decoration) from a *pick*. * @public * @extensions */ class HitDetail { _props; /** The point in world coordinates that was used as the initial locate point. */ get testPoint() { return this._props.testPoint; } /** The viewport in which the locate operation was performed. */ get viewport() { return this._props.viewport; } /** The procedure that requested the locate operation. */ get hitSource() { return this._props.hitSource; } /** The approximate location in world coordinates on the geometry identified by this HitDetail. */ get hitPoint() { return this._props.hitPoint; } /** The source of the geometry. This may be a persistent element Id, or a transient Id used for, e.g., pickable decorations. */ get sourceId() { return this._props.sourceId; } /** The hit geometry priority/classification. */ get priority() { return this._props.priority; } /** The xy distance to the hit in view coordinates. */ get distXY() { return this._props.distXY; } /** The distance in view coordinates between the hit and the near plane. */ get distFraction() { return this._props.distFraction; } /** The [SubCategory]($backend) to which the hit geometry belongs. */ get subCategoryId() { return this._props.subCategoryId; } /** The class of the hit geometry. */ get geometryClass() { return this._props.geometryClass; } /** The Id of the [[ModelState]] from which the hit originated. */ get modelId() { return this._props.modelId; } /** The IModelConnection from which the hit originated. * This should almost always be left undefined, unless the hit is known to have originated from an iModel * other than the one associated with the viewport. * @internal */ get sourceIModel() { return this._props.sourceIModel; } /** @internal */ get transformFromSourceIModel() { return this._props.transformFromSourceIModel; } /** @internal chiefly for debugging */ get tileId() { return this._props.tileId; } /** True if the hit originated from a reality model classifier. * @alpha */ get isClassifier() { return this._props.isClassifier; } /** Information about the [ViewAttachment]($backend) within which the hit geometry resides, if any. * @note Only [[SheetViewState]]s can have view attachments. * @beta */ get viewAttachment() { return this._props.path?.viewAttachment; } /** Describes the path by which the hit was located through a series of attached views. * @beta */ get path() { return this._props.path; } /** Information about the [contour line]($docs/learning/display/ContourDisplay.md), if any, from which this hit originated. * @beta */ get contour() { return this._props.contour; } /** @internal */ constructor(arg0, viewport, hitSource, hitPoint, sourceId, priority, distXY, distFraction, subCategoryId, geometryClass, modelId, sourceIModel, tileId, isClassifier) { if (arg0 instanceof core_geometry_1.Point3d) { (0, core_bentley_1.assert)(undefined !== viewport && undefined !== hitSource && undefined !== hitPoint && undefined !== sourceId); (0, core_bentley_1.assert)(undefined !== priority && undefined !== distXY && undefined !== distFraction); this._props = { testPoint: arg0, viewport, hitSource, hitPoint, sourceId, priority, distXY, distFraction, subCategoryId, geometryClass, modelId, sourceIModel, tileId, isClassifier, }; } else { // Ignore an empty path. const path = arg0.path?.sectionDrawingAttachment || arg0.path?.viewAttachment ? arg0.path : undefined; // Tempting to use { ...arg0 } but spread operator omits getters so, e.g., if input is a HitDetail we would lose all the properties. this._props = { testPoint: arg0.testPoint, viewport: arg0.viewport, hitSource: arg0.hitSource, hitPoint: arg0.hitPoint, sourceId: arg0.sourceId, priority: arg0.priority, distXY: arg0.distXY, distFraction: arg0.distFraction, subCategoryId: arg0.subCategoryId, geometryClass: arg0.geometryClass, modelId: arg0.modelId, sourceIModel: arg0.sourceIModel, transformFromSourceIModel: arg0.transformFromSourceIModel, tileId: arg0.tileId, isClassifier: arg0.isClassifier, path, contour: arg0.contour, }; } } /** Get the type of HitDetail. * @returns HitDetailType.Hit if this is a HitDetail, HitDetailType.Snap if it is a SnapDetail */ getHitType() { return HitDetailType.Hit; } /** Get the *hit point* for this HitDetail. Returns the approximate point on the element that caused the hit when not a SnapDetail or IntersectDetail. * For a snap that is *hot*, the *exact* point on the Element for the snap mode is returned, otherwise the close point on the hit geometry is returned. */ getPoint() { return this.hitPoint; } /** Determine if this HitPoint is from the same source as another HitDetail. */ isSameHit(otherHit) { return (undefined !== otherHit && this.sourceId === otherHit.sourceId && this.iModel === otherHit.iModel); } /** Return whether sourceId is for a persistent element and not a pickable decoration. */ get isElementHit() { return !core_bentley_1.Id64.isInvalid(this.sourceId) && !core_bentley_1.Id64.isTransient(this.sourceId); } // return whether the sourceId is for a model (reality models etc.) get isModelHit() { return this.modelId === this.sourceId; } // return whether the hit point is from map. get isMapHit() { return 0 !== this.viewport.mapLayerFromHit(this).length; } /** Create a deep copy of this HitDetail */ clone() { return new HitDetail(this); } /** Draw this HitDetail as a Decoration. Causes the picked element to *flash* */ draw(_context) { this.viewport.flashedId = this.sourceId; } /** Get the tooltip content for this HitDetail. */ async getToolTip() { let toolTipPromise = this.isElementHit ? IModelApp_1.IModelApp.viewManager.overrideElementToolTip(this) : IModelApp_1.IModelApp.viewManager.getDecorationToolTip(this); for (const toolTipProvider of IModelApp_1.IModelApp.viewManager.toolTipProviders) toolTipPromise = toolTipProvider.augmentToolTip(this, toolTipPromise); return toolTipPromise; } /** The IModelConnection from which the hit originated. In some cases this may not be the same as the iModel associated with the Viewport - * for example, if a [[TiledGraphicsProvider]] is used to display graphics from a different iModel in the viewport. * This HitDetail's element, subcategory, and model Ids are defined in the context of this IModelConnection. */ get iModel() { return this.sourceIModel ?? this.viewport.iModel; } /** Returns true if this hit originated from an [[IModelConnection]] other than the one associated with the [[Viewport]]. * @see [[iModel]]. */ get isExternalIModelHit() { return this.iModel !== this.viewport.iModel; } } exports.HitDetail = HitDetail; /** A SnapDetail is generated from the result of a snap request. In addition to the HitDetail about the reason the element was *picked*, * it holds the *exact* point on the element from the snapping logic, plus additional information that varies with the type of element and snap mode. * @public * @extensions */ class SnapDetail extends HitDetail { snapMode; heat; /** A sprite to show the user the type of snap performed */ sprite; /** HitPoint adjusted by snap */ snapPoint; /** AccuSnap/AccuDraw can adjust the point after the snap. */ adjustedPoint; /** Curve primitive for snap. */ primitive; /** Surface normal at snapPoint */ normal; /** The HitGeomType of this SnapDetail */ geomType; /** The HitGeomType of this SnapDetail */ parentGeomType; /** Constructor for SnapDetail. * @param from The HitDetail that created this snap * @param snapMode The SnapMode used to create this SnapDetail * @param heat The SnapHeat of this SnapDetail * @param snapPoint The snapped point in the element */ constructor(from, snapMode = SnapMode.Nearest, heat = SnapHeat.None, snapPoint) { super(from); this.snapMode = snapMode; this.heat = heat; this.snapPoint = core_geometry_1.Point3d.fromJSON(snapPoint ? snapPoint : from.hitPoint); this.adjustedPoint = this.snapPoint.clone(); this.sprite = Sprites_1.IconSprites.getSpriteFromUrl(SnapDetail.getSnapSpriteUrl(snapMode)); } /** Returns `HitDetailType.Snap` */ getHitType() { return HitDetailType.Snap; } /** Get the snap point if this SnapDetail is *hot*, the pick point otherwise. */ getPoint() { return this.isHot ? this.snapPoint : super.getPoint(); } /** Return true if the pick point was closer than the snap aperture from the generated snap point. */ get isHot() { return this.heat !== SnapHeat.None; } /** Determine whether the [[adjustedPoint]] is different than the [[snapPoint]]. This happens, for example, when points are adjusted for grids, acs plane snap, and AccuDraw. */ get isPointAdjusted() { return !this.adjustedPoint.isExactEqual(this.snapPoint); } /** Change the snap point. */ setSnapPoint(point, heat) { this.snapPoint.setFrom(point); this.adjustedPoint.setFrom(point); this.heat = heat; } /** Change the snap mode. */ setSnapMode(snapMode) { this.snapMode = snapMode; this.sprite = Sprites_1.IconSprites.getSpriteFromUrl(SnapDetail.getSnapSpriteUrl(snapMode)); } /** Set curve primitive and HitGeometryType for this SnapDetail. */ setCurvePrimitive(primitive, localToWorld, geomType) { this.primitive = primitive; this.geomType = undefined; // Only HitGeomType.Point and HitGeomType.Surface are valid without a curve primitive. if (undefined === this.primitive) { if (HitGeomType.Point === geomType || HitGeomType.Surface === geomType) this.geomType = geomType; return; } if (undefined !== localToWorld) this.primitive.tryTransformInPlace(localToWorld); if (this.primitive instanceof core_geometry_1.Arc3d) this.geomType = HitGeomType.Arc; else if (this.primitive instanceof core_geometry_1.LineSegment3d) this.geomType = HitGeomType.Segment; else if (this.primitive instanceof core_geometry_1.LineString3d) this.geomType = HitGeomType.Segment; else this.geomType = HitGeomType.Curve; // Set curve primitive geometry type override... // - HitGeomType.Point with arc/ellipse denotes center. // - HitGeomType.Surface with any curve primitive denotes an interior hit. if (undefined !== geomType && HitGeomType.None !== geomType) this.geomType = geomType; } /** Make a copy of this SnapDetail. */ clone() { const val = new SnapDetail(this, this.snapMode, this.heat, this.snapPoint); val.sprite = this.sprite; val.geomType = this.geomType; val.parentGeomType = this.parentGeomType; val.adjustedPoint.setFrom(this.adjustedPoint); if (undefined !== this.primitive) val.primitive = this.primitive.clone(); if (undefined !== this.normal) val.normal = this.normal.clone(); return val; } getCurvePrimitive(singleSegment = true) { if (!singleSegment || undefined === this.primitive) return this.primitive; if (this.primitive instanceof core_geometry_1.LineString3d) { const ls = this.primitive; if (ls.points.length > 2) { const loc = ls.closestPoint(this.snapPoint, false); const nSegments = ls.points.length - 1; const uSegRange = (1.0 / nSegments); let segmentNo = Math.floor(loc.fraction / uSegRange); if (segmentNo >= nSegments) segmentNo = nSegments - 1; return core_geometry_1.LineSegment3d.create(ls.points[segmentNo], ls.points[segmentNo + 1]); } } return this.primitive; } draw(context) { if (undefined !== this.primitive) { let singleSegment = false; switch (this.snapMode) { case SnapMode.Center: case SnapMode.Origin: case SnapMode.Bisector: break; // Snap point for these is computed using entire linestring, not just the hit segment... default: { singleSegment = true; break; } } const builder = context.createGraphicBuilder(GraphicType_1.GraphicType.WorldOverlay); const outline = context.viewport.hilite.color.adjustedForContrast(context.viewport.view.backgroundColor, 50); const centerLine = context.viewport.hilite.color.adjustedForContrast(outline, 175); const path = core_geometry_1.Path.create(this.getCurvePrimitive(singleSegment)); builder.setSymbology(outline, outline, 6); builder.addPath(path); builder.setSymbology(centerLine, centerLine, 2); builder.addPath(path); context.addDecorationFromBuilder(builder); return; } super.draw(context); } static getSnapSpriteUrl(snapType) { switch (snapType) { case SnapMode.Nearest: return `${IModelApp_1.IModelApp.publicPath}sprites/SnapPointOn.png`; case SnapMode.NearestKeypoint: return `${IModelApp_1.IModelApp.publicPath}sprites/SnapKeypoint.png`; case SnapMode.MidPoint: return `${IModelApp_1.IModelApp.publicPath}sprites/SnapMidpoint.png`; case SnapMode.Center: return `${IModelApp_1.IModelApp.publicPath}sprites/SnapCenter.png`; case SnapMode.Origin: return `${IModelApp_1.IModelApp.publicPath}sprites/SnapOrigin.png`; case SnapMode.Bisector: return `${IModelApp_1.IModelApp.publicPath}sprites/SnapBisector.png`; case SnapMode.Intersection: return `${IModelApp_1.IModelApp.publicPath}sprites/SnapIntersection.png`; case SnapMode.PerpendicularPoint: return `${IModelApp_1.IModelApp.publicPath}sprites/SnapPerpendicularPoint.png`; case SnapMode.TangentPoint: return `${IModelApp_1.IModelApp.publicPath}sprites/SnapTangentPoint.png`; } return ""; } } exports.SnapDetail = SnapDetail; /** * @public * @extensions */ class IntersectDetail extends SnapDetail { otherPrimitive; otherId; constructor(from, heat = SnapHeat.None, snapPoint, otherPrimitive, otherId) { super(from, SnapMode.Intersection, heat, snapPoint); this.otherPrimitive = otherPrimitive; this.otherId = otherId; this.primitive = from.primitive; this.normal = from.normal; // Preserve normal from primary snap location for AccuDraw smart rotation... } draw(context) { if (undefined !== this.primitive && undefined !== this.otherPrimitive) { const builder = context.createGraphicBuilder(GraphicType_1.GraphicType.WorldOverlay); const outline = context.viewport.hilite.color.adjustedForContrast(context.viewport.view.backgroundColor, 50); const centerLine = context.viewport.hilite.color.adjustedForContrast(outline, 175); const path1 = core_geometry_1.Path.create(this.primitive); const path2 = core_geometry_1.Path.create(this.otherPrimitive); builder.setSymbology(outline, outline, 6); builder.addPath(path1); builder.addPath(path2); builder.setSymbology(centerLine, centerLine, 2); builder.addPath(path1); builder.setSymbology(centerLine, centerLine, 2, core_common_1.LinePixels.Code2); builder.addPath(path2); context.addDecorationFromBuilder(builder); return; } super.draw(context); } } exports.IntersectDetail = IntersectDetail; /** The result of a "locate" is a sorted list of objects that satisfied the search criteria (a HitList). Earlier hits in the list * are somehow *better* than those later on. * @public * @extensions */ class HitList { hits = []; currHit = -1; get length() { return this.hits.length; } empty() { this.hits.length = 0; this.currHit = -1; } resetCurrentHit() { this.currHit = -1; } /** Get a hit from a particular index into a HitList * return the requested hit from the HitList or undefined */ getHit(hitNum) { if (hitNum < 0) hitNum = this.length - 1; return (hitNum >= this.length) ? undefined : this.hits[hitNum]; } /** When setting one or more indices to undefined you must call dropNulls afterwards */ setHit(i, p) { if (i < 0 || i >= this.length) return; this.hits[i] = p; } dropNulls() { const hits = this.hits; this.hits = []; for (const hit of hits) this.hits.push(hit); } getNextHit() { this.currHit++; return this.getCurrentHit(); } getCurrentHit() { return -1 === this.currHit ? undefined : this.getHit(this.currHit); } setCurrentHit(hit) { this.resetCurrentHit(); for (let thisHit; undefined !== (thisHit = this.getNextHit());) { if (thisHit === hit) return; } } /** remove the current hit from the list. */ removeCurrentHit() { this.removeHit(this.currHit); } /** remove a hit in the list. */ removeHit(hitNum) { if (hitNum < 0) // Support -1 == END hitNum = this.length - 1; if (hitNum <= this.currHit) this.currHit = -1; if (hitNum >= this.length) // Locate calls GetNextHit, which increments currHit, until it goes beyond the end of size of the array. return; // Then Reset call RemoteCurrentHit, which passes in currHit. When it is out of range, we do nothing. this.hits.splice(hitNum, 1); } /** search through list and remove any hits that contain a specified element id. */ removeHitsFrom(sourceId) { let removedOne = false; // walk backwards through list so we don't have to worry about what happens on remove for (let i = this.length - 1; i >= 0; i--) { const thisHit = this.hits[i]; if (thisHit && sourceId === thisHit.sourceId) { removedOne = true; this.removeHit(i); } } return removedOne; } getPriorityZOverride(priority) { switch (priority) { case HitPriority.WireEdge: case HitPriority.PlanarEdge: case HitPriority.NonPlanarEdge: return 0; case HitPriority.SilhouetteEdge: return 1; case HitPriority.PlanarSurface: case HitPriority.NonPlanarSurface: return 2; default: return 3; } } /** compare two hits for insertion into list. */ compare(hit1, hit2) { if (!hit1 || !hit2) return 0; const zOverride1 = this.getPriorityZOverride(hit1.priority); const zOverride2 = this.getPriorityZOverride(hit2.priority); // Prefer edges over surfaces, this is more important than z because we know the edge isn't obscured... if (zOverride1 < zOverride2) return -1; if (zOverride1 > zOverride2) return 1; // Compare xy distance from pick point, prefer hits closer to center... if (hit1.distXY < hit2.distXY) return -1; if (hit1.distXY > hit2.distXY) return 1; // Compare distance fraction, prefer hits closer to eye... if (hit1.distFraction > hit2.distFraction) return -1; if (hit1.distFraction < hit2.distFraction) return 1; // Compare geometry class, prefer path/region hits over surface hits when all else is equal... if (hit1.priority < hit2.priority) return -1; if (hit1.priority > hit2.priority) return 1; return 0; } /** Add a new hit to the list. Hits are sorted according to their priority and distance. */ addHit(newHit) { if (0 === this.hits.length) { this.hits.push(newHit); return 0; } let index = 0; for (; index < this.hits.length; ++index) { const oldHit = this.hits[index]; const comparison = this.compare(newHit, oldHit); if (comparison < 0) break; } this.hits.splice(index, 0, newHit); return index; } /** Insert a new hit into the list at the supplied index. */ insertHit(i, hit) { if (i < 0 || i >= this.length) this.hits.push(hit); else this.hits.splice(i, 0, hit); } } exports.HitList = HitList; //# sourceMappingURL=HitDetail.js.map