@itwin/core-frontend
Version:
iTwin.js frontend components
589 lines • 26.8 kB
JavaScript
"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