UNPKG

@itwin/core-frontend

Version:
922 lines • 78.8 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 Measure */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MeasureVolumeTool = exports.MeasureAreaTool = exports.MeasureLengthTool = exports.MeasureElementTool = exports.MeasureAreaByPointsTool = exports.MeasureLocationTool = exports.MeasureDistanceTool = void 0; 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 AccuDraw_1 = require("../AccuDraw"); const ElementLocateManager_1 = require("../ElementLocateManager"); const HitDetail_1 = require("../HitDetail"); const IModelApp_1 = require("../IModelApp"); const Marker_1 = require("../Marker"); const NotificationManager_1 = require("../NotificationManager"); const PrimitiveTool_1 = require("./PrimitiveTool"); const Tool_1 = require("./Tool"); const ToolAssistance_1 = require("./ToolAssistance"); const GraphicType_1 = require("../common/render/GraphicType"); function translateBold(key) { return `<b>${Tool_1.CoreTools.translate(`Measure.Labels.${key}`)}:</b> `; } async function getFormatterSpecByKoQAndPersistenceUnit(koq, persistenceUnitName) { const formatProps = await IModelApp_1.IModelApp.formatsProvider.getFormat(koq); if (undefined === formatProps) return undefined; return IModelApp_1.IModelApp.quantityFormatter.createFormatterSpec({ persistenceUnitName, formatProps, formatName: koq }); } /** @internal */ class MeasureLabel { worldLocation = new core_geometry_1.Point3d(); position = new core_geometry_1.Point3d(); label; constructor(worldLocation, label) { this.worldLocation.setFrom(worldLocation); this.label = label; } drawDecoration(ctx) { ctx.font = "16px sans-serif"; const labelHeight = ctx.measureText("M").width; // Close enough for border padding... const labelWidth = ctx.measureText(this.label).width + labelHeight; ctx.lineWidth = 1; ctx.strokeStyle = "white"; ctx.fillStyle = "rgba(0,0,0,.4)"; ctx.shadowColor = "black"; ctx.shadowBlur = 10; ctx.fillRect(-(labelWidth / 2), -labelHeight, labelWidth, labelHeight * 2); ctx.strokeRect(-(labelWidth / 2), -labelHeight, labelWidth, labelHeight * 2); ctx.fillStyle = "white"; ctx.shadowBlur = 0; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(this.label, 0, 0); } setPosition(vp) { vp.worldToView(this.worldLocation, this.position); this.position.y -= Math.floor(vp.pixelsFromInches(0.44)) + 0.5; // Offset from snap location... return vp.viewRect.containsPoint(this.position); } addDecoration(context) { if (this.setPosition(context.viewport)) context.addCanvasDecoration(this); } } /** @internal */ class MeasureMarker extends Marker_1.Marker { isSelected = false; constructor(label, title, worldLocation, size) { super(worldLocation, size); const markerDrawFunc = (ctx) => { ctx.beginPath(); ctx.arc(0, 0, this.size.x * 0.5, 0, 2 * Math.PI); ctx.lineWidth = 2; ctx.strokeStyle = "black"; const hilite = this.isSelected && this._hiliteColor ? this._hiliteColor.colors : undefined; ctx.fillStyle = undefined !== hilite ? `rgba(${hilite.r | 0},${hilite.g | 0},${hilite.b | 0}, 0.5)` : "rgba(255,255,255,.5)"; ctx.fill(); ctx.stroke(); }; this.drawFunc = markerDrawFunc; this.title = title; this.label = label; this.labelFont = "16px sans-serif"; this.labelColor = "black"; this.labelMaxWidth = this.size.x * 0.75; this.labelOffset = { x: 0, y: -1 }; } onMouseButton(_ev) { return true; } // Never forward event to active tool... onMouseEnter(ev) { super.onMouseEnter(ev); if (this.title && Tool_1.InputSource.Touch === ev.inputSource && ev.viewport) ev.viewport.openToolTip(this.title, ev.viewPoint, this.tooltipOptions); } onMouseLeave() { super.onMouseLeave(); if (this.title) IModelApp_1.IModelApp.notifications.clearToolTip(); // Clear tool tip from tap since we won't get a motion event... } } /** @internal */ function adjustPoint(ev, segments, locations) { // If the point was from a hit we must transform it by the display transform of what got hit. const hit = IModelApp_1.IModelApp.accuSnap.currHit; if (!hit || !ev.viewport || !hit.modelId) return ev.point; if ("0" !== hit.modelId) { const transform = ev.viewport.view.computeDisplayTransform({ modelId: hit.modelId, elementId: hit.sourceId }); return transform?.multiplyInversePoint3d(ev.point) ?? ev.point; } // Must have snapped to a decoration, so look through previous any segments & locations for a match to get an adjusted point. if (segments) { for (const seg of segments) { if (seg.start.isExactEqual(ev.point)) return seg.adjustedStart.clone(); if (seg.end.isExactEqual(ev.point)) return seg.adjustedEnd.clone(); } } if (locations) for (const loc of locations) if (loc.point.isExactEqual(ev.point)) return loc.adjustedPoint.clone(); return ev.point; } /** Report distance between 2 points using current quantity formatter for length. * @public */ class MeasureDistanceTool extends PrimitiveTool_1.PrimitiveTool { static toolId = "Measure.Distance"; static iconSpec = "icon-measure-distance"; /** @internal */ _locationData = new Array(); /** @internal */ _acceptedSegments = new Array(); /** @internal */ _totalDistance = 0.0; /** @internal */ _totalDistanceMarker; /** @internal */ _snapGeomId; /** @internal */ _lastMotionPt; /** @internal */ _lastMotionAdjustedPt; /** @internal */ _lengthFormatterSpec; /** @internal */ _angleFormatterSpec; /** @internal */ _removeFormatterListener; /** @internal */ allowView(vp) { return vp.view.isSpatialView() || vp.view.isDrawingView(); } /** @internal */ isCompatibleViewport(vp, isSelectedViewChange) { return (super.isCompatibleViewport(vp, isSelectedViewChange) && undefined !== vp && this.allowView(vp)); } /** @internal */ isValidLocation(_ev, _isButtonEvent) { return true; } /** @internal */ requireWriteableTarget() { return false; } /** @internal */ async onPostInstall() { await super.onPostInstall(); this._lengthFormatterSpec = await getFormatterSpecByKoQAndPersistenceUnit("AecUnits.LENGTH", "Units.M"); this._angleFormatterSpec = await getFormatterSpecByKoQAndPersistenceUnit("AecUnits.ANGLE", "Units.RAD"); this._removeFormatterListener = IModelApp_1.IModelApp.formatsProvider.onFormatsChanged.addListener(async (args) => { if (args.formatsChanged === "all" || args.formatsChanged.includes("AecUnits.LENGTH")) this._lengthFormatterSpec = await getFormatterSpecByKoQAndPersistenceUnit("AecUnits.LENGTH", "Units.M"); if (args.formatsChanged === "all" || args.formatsChanged.includes("AecUnits.ANGLE")) this._angleFormatterSpec = await getFormatterSpecByKoQAndPersistenceUnit("AecUnits.ANGLE", "Units.RAD"); }); this.setupAndPromptForNextAction(); } /** @internal */ async onCleanup() { if (this._removeFormatterListener) { this._removeFormatterListener(); this._removeFormatterListener = undefined; } await super.onCleanup(); } /** @internal */ async onUnsuspend() { this.showPrompt(); } /** @internal */ showPrompt() { const mainInstruction = ToolAssistance_1.ToolAssistance.createInstruction(this.iconSpec, Tool_1.CoreTools.translate(0 === this._locationData.length ? "Measure.Distance.Prompts.FirstPoint" : "Measure.Distance.Prompts.NextPoint")); const mouseInstructions = []; const touchInstructions = []; if (!ToolAssistance_1.ToolAssistance.createTouchCursorInstructions(touchInstructions)) touchInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.OneTouchTap, Tool_1.CoreTools.translate("ElementSet.Inputs.AcceptPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Touch)); mouseInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.LeftClick, Tool_1.CoreTools.translate("ElementSet.Inputs.AcceptPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); if (0 === this._locationData.length) { if (this._acceptedSegments.length > 0) { touchInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.TwoTouchTap, Tool_1.CoreTools.translate("ElementSet.Inputs.Restart"), false, ToolAssistance_1.ToolAssistanceInputMethod.Touch)); mouseInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.RightClick, Tool_1.CoreTools.translate("ElementSet.Inputs.Restart"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); } } else { touchInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.TwoTouchTap, Tool_1.CoreTools.translate("ElementSet.Inputs.Cancel"), false, ToolAssistance_1.ToolAssistanceInputMethod.Touch)); mouseInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.RightClick, Tool_1.CoreTools.translate("ElementSet.Inputs.Cancel"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); mouseInstructions.push(ToolAssistance_1.ToolAssistance.createModifierKeyInstruction(ToolAssistance_1.ToolAssistance.ctrlKey, ToolAssistance_1.ToolAssistanceImage.LeftClick, Tool_1.CoreTools.translate("ElementSet.Inputs.AdditionalPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); mouseInstructions.push(ToolAssistance_1.ToolAssistance.createKeyboardInstruction(ToolAssistance_1.ToolAssistance.createKeyboardInfo([ToolAssistance_1.ToolAssistance.ctrlKey, "Z"]), Tool_1.CoreTools.translate("ElementSet.Inputs.UndoLastPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); } const sections = []; sections.push(ToolAssistance_1.ToolAssistance.createSection(mouseInstructions, ToolAssistance_1.ToolAssistance.inputsLabel)); sections.push(ToolAssistance_1.ToolAssistance.createSection(touchInstructions, ToolAssistance_1.ToolAssistance.inputsLabel)); const instructions = ToolAssistance_1.ToolAssistance.createInstructions(mainInstruction, sections); IModelApp_1.IModelApp.notifications.setToolAssistance(instructions); } /** @internal */ setupAndPromptForNextAction() { IModelApp_1.IModelApp.accuSnap.enableSnap(true); const hints = new AccuDraw_1.AccuDrawHintBuilder(); hints.enableSmartRotation = true; hints.setModeRectangular(); hints.sendHints(false); IModelApp_1.IModelApp.toolAdmin.setCursor(0 === this._locationData.length ? IModelApp_1.IModelApp.viewManager.crossHairCursor : IModelApp_1.IModelApp.viewManager.dynamicsCursor); this.showPrompt(); } /** @internal */ testDecorationHit(id) { return id === this._snapGeomId; } /** @internal */ getSnapPoints() { if (this._acceptedSegments.length < 1 && this._locationData.length < 2) return undefined; const snapPoints = []; for (const seg of this._acceptedSegments) { if (0 === snapPoints.length || !seg.start.isAlmostEqual(snapPoints[snapPoints.length - 1])) snapPoints.push(seg.start); if (!seg.end.isAlmostEqual(snapPoints[0])) snapPoints.push(seg.end); } if (this._locationData.length > 1) for (const loc of this._locationData) snapPoints.push(loc.point); return snapPoints; } /** @internal */ getDecorationGeometry(_hit) { const snapPoints = this.getSnapPoints(); if (undefined === snapPoints) return undefined; const geomData = core_geometry_1.IModelJson.Writer.toIModelJson(core_geometry_1.PointString3d.create(snapPoints)); return (undefined === geomData ? undefined : [geomData]); } /** @internal */ displayDynamicDistance(context, points, adjustedPoints) { let totalDistance = 0.0; for (let i = 0; i < adjustedPoints.length - 1; i++) totalDistance += adjustedPoints[i].distance(adjustedPoints[i + 1]); if (0.0 === totalDistance) return; const formatterSpec = this._lengthFormatterSpec; if (formatterSpec === undefined) return; const formattedTotalDistance = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(totalDistance, formatterSpec); const distDyn = new MeasureLabel(points[points.length - 1], formattedTotalDistance); distDyn.addDecoration(context); } /** @internal */ displayDelta(context, seg) { const xVec = new core_geometry_1.Vector3d(seg.delta.x, 0.0, 0.0); const yVec = new core_geometry_1.Vector3d(0.0, seg.delta.y, 0.0); const zVec = new core_geometry_1.Vector3d(0.0, 0.0, seg.delta.z); seg.refAxes.multiplyVectorInPlace(xVec); seg.refAxes.multiplyVectorInPlace(yVec); seg.refAxes.multiplyVectorInPlace(zVec); const builderAxes = context.createGraphicBuilder(GraphicType_1.GraphicType.WorldOverlay); let basePt = seg.start.clone(); if (xVec.magnitude() > 1.0e-5) { const segPoints = []; segPoints.push(basePt); basePt = basePt.plus(xVec); segPoints.push(basePt); const colorX = core_common_1.ColorDef.red.adjustedForContrast(context.viewport.view.backgroundColor); builderAxes.setSymbology(colorX, core_common_1.ColorDef.black, 5); builderAxes.addLineString(segPoints); } if (yVec.magnitude() > 1.0e-5) { const segPoints = []; segPoints.push(basePt); basePt = basePt.plus(yVec); segPoints.push(basePt); const colorY = core_common_1.ColorDef.green.adjustedForContrast(context.viewport.view.backgroundColor); builderAxes.setSymbology(colorY, core_common_1.ColorDef.black, 5); builderAxes.addLineString(segPoints); } if (zVec.magnitude() > 1.0e-5) { const segPoints = []; segPoints.push(basePt); basePt = basePt.plus(zVec); segPoints.push(basePt); const colorZ = core_common_1.ColorDef.blue.adjustedForContrast(context.viewport.view.backgroundColor); builderAxes.setSymbology(colorZ, core_common_1.ColorDef.black, 5); builderAxes.addLineString(segPoints); } const segGlow = context.viewport.hilite.color.withAlpha(50); builderAxes.setSymbology(segGlow, core_common_1.ColorDef.black, 8); builderAxes.addLineString([seg.start, seg.end]); context.addDecorationFromBuilder(builderAxes); } /** @internal */ createDecorations(context, isSuspended) { if (!this.isCompatibleViewport(context.viewport, false)) return; if (!isSuspended && this._locationData.length > 0 && undefined !== this._lastMotionPt && undefined !== this._lastMotionAdjustedPt) { const tmpPoints = []; const tmpAdjustedPoints = []; for (const loc of this._locationData) { tmpPoints.push(loc.point); // Deep copy not necessary... tmpAdjustedPoints.push(loc.adjustedPoint); } tmpPoints.push(this._lastMotionPt); tmpAdjustedPoints.push(this._lastMotionAdjustedPt); const builderDynVis = context.createGraphicBuilder(GraphicType_1.GraphicType.WorldDecoration); const colorDynVis = context.viewport.hilite.color; builderDynVis.setSymbology(colorDynVis, core_common_1.ColorDef.black, 3); builderDynVis.addLineString(tmpPoints); context.addDecorationFromBuilder(builderDynVis); const builderDynHid = context.createGraphicBuilder(GraphicType_1.GraphicType.WorldOverlay); const colorDynHid = colorDynVis.withAlpha(100); builderDynHid.setSymbology(colorDynHid, core_common_1.ColorDef.black, 1, core_common_1.LinePixels.Code2); builderDynHid.addLineString(tmpPoints); context.addDecorationFromBuilder(builderDynHid); this.displayDynamicDistance(context, tmpPoints, tmpAdjustedPoints); } if (this._acceptedSegments.length > 0) { const builderAccVis = context.createGraphicBuilder(GraphicType_1.GraphicType.WorldDecoration); const builderAccHid = context.createGraphicBuilder(GraphicType_1.GraphicType.WorldOverlay); const colorAccVis = core_common_1.ColorDef.white.adjustedForContrast(context.viewport.view.backgroundColor); const colorAccHid = colorAccVis.withAlpha(100); builderAccVis.setSymbology(colorAccVis, core_common_1.ColorDef.black, 3); builderAccHid.setSymbology(colorAccHid, core_common_1.ColorDef.black, 1, core_common_1.LinePixels.Code2); for (const seg of this._acceptedSegments) { builderAccVis.addLineString([seg.start, seg.end]); builderAccHid.addLineString([seg.start, seg.end]); seg.marker.addDecoration(context); if (seg.marker.isSelected) this.displayDelta(context, seg); } context.addDecorationFromBuilder(builderAccVis); context.addDecorationFromBuilder(builderAccHid); } if (undefined !== this._totalDistanceMarker) this._totalDistanceMarker.addDecoration(context); const snapPoints = this.getSnapPoints(); if (undefined === snapPoints) return; if (undefined === this._snapGeomId) this._snapGeomId = this.iModel.transientIds.getNext(); const builderSnapPts = context.createGraphicBuilder(GraphicType_1.GraphicType.WorldOverlay, undefined, this._snapGeomId); const colorAccPts = core_common_1.ColorDef.white.adjustedForContrast(context.viewport.view.backgroundColor); builderSnapPts.setSymbology(colorAccPts, core_common_1.ColorDef.black, 7); builderSnapPts.addPointString(snapPoints); context.addDecorationFromBuilder(builderSnapPts); } /** @internal */ decorate(context) { this.createDecorations(context, false); } /** @internal */ decorateSuspended(context) { this.createDecorations(context, true); } /** @internal */ async onMouseMotion(ev) { if (this._locationData.length > 0 && undefined !== ev.viewport) { const point = ev.point; const adjustedPoint = adjustPoint(ev, this._acceptedSegments, this._locationData); if (undefined !== this._lastMotionPt) { this._lastMotionPt.setFrom(point); this._lastMotionAdjustedPt?.setFrom(adjustedPoint); } else { this._lastMotionPt = point.clone(); this._lastMotionAdjustedPt = adjustedPoint; } ev.viewport.invalidateDecorations(); } } reportMeasurements() { if (undefined === this._totalDistanceMarker) return; const briefMsg = `${Tool_1.CoreTools.translate(this._acceptedSegments.length > 1 ? "Measure.Labels.CumulativeDistance" : "Measure.Labels.Distance")}: ${this._totalDistanceMarker.label}`; const msgDetail = new NotificationManager_1.NotifyMessageDetails(NotificationManager_1.OutputMessagePriority.Info, briefMsg, undefined, NotificationManager_1.OutputMessageType.Sticky); IModelApp_1.IModelApp.notifications.outputMessage(msgDetail); } async updateTotals() { this._totalDistance = 0.0; this._totalDistanceMarker = undefined; for (const seg of this._acceptedSegments) this._totalDistance += seg.distance; if (0.0 === this._totalDistance) return; const formatterSpec = this._lengthFormatterSpec; if (undefined === formatterSpec) return; const formattedTotalDistance = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(this._totalDistance, formatterSpec); this._totalDistanceMarker = new MeasureLabel(this._acceptedSegments[this._acceptedSegments.length - 1].end, formattedTotalDistance); this.reportMeasurements(); } async getMarkerToolTip(distance, slope, start, end, delta) { const is3d = (undefined === this.targetView || this.targetView.view.is3d()); const isSpatial = (undefined !== this.targetView && this.targetView.view.isSpatialView()); const toolTip = document.createElement("div"); const distanceFormatterSpec = this._lengthFormatterSpec; if (undefined === distanceFormatterSpec) return toolTip; let toolTipHtml = ""; const formattedDistance = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(distance, distanceFormatterSpec); toolTipHtml += `${translateBold("Distance") + formattedDistance}<br>`; if (is3d) { const angleFormatterSpec = this._angleFormatterSpec; if (undefined !== angleFormatterSpec) { const formattedSlope = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(slope, angleFormatterSpec); toolTipHtml += `${translateBold("Slope") + formattedSlope}<br>`; } } const coordFormatterSpec = this._lengthFormatterSpec; if (undefined !== coordFormatterSpec) { let startAdjusted = start; let endAdjusted = end; if (isSpatial) { const globalOrigin = this.iModel.globalOrigin; startAdjusted = startAdjusted.minus(globalOrigin); endAdjusted = endAdjusted.minus(globalOrigin); } { const formattedStartX = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(startAdjusted.x, coordFormatterSpec); const formattedStartY = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(startAdjusted.y, coordFormatterSpec); const formattedStartZ = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(startAdjusted.z, coordFormatterSpec); toolTipHtml += `${translateBold("StartCoord") + formattedStartX}, ${formattedStartY}`; if (is3d) toolTipHtml += `, ${formattedStartZ}`; toolTipHtml += "<br>"; } const formattedEndX = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(endAdjusted.x, coordFormatterSpec); const formattedEndY = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(endAdjusted.y, coordFormatterSpec); const formattedEndZ = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(endAdjusted.z, coordFormatterSpec); toolTipHtml += `${translateBold("EndCoord") + formattedEndX}, ${formattedEndY}`; if (is3d) toolTipHtml += `, ${formattedEndZ}`; toolTipHtml += "<br>"; } if (undefined !== delta) { const formattedDeltaX = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(Math.abs(delta.x), distanceFormatterSpec); const formattedDeltaY = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(Math.abs(delta.y), distanceFormatterSpec); const formattedDeltaZ = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(Math.abs(delta.z), distanceFormatterSpec); toolTipHtml += `${translateBold("Delta") + formattedDeltaX}, ${formattedDeltaY}`; if (is3d) toolTipHtml += `, ${formattedDeltaZ}`; toolTipHtml += "<br>"; } toolTip.innerHTML = toolTipHtml; return toolTip; } /** @internal */ async updateSelectedMarkerToolTip(seg, ev, reopenToolTip) { seg.marker.title = await this.getMarkerToolTip(seg.distance, seg.slope, seg.adjustedStart, seg.adjustedEnd, seg.marker.isSelected ? seg.adjustedDelta : undefined); if (!reopenToolTip || undefined === ev.viewport || !IModelApp_1.IModelApp.notifications.isToolTipOpen) return; IModelApp_1.IModelApp.notifications.clearToolTip(); ev.viewport.openToolTip(seg.marker.title, ev.viewPoint); } /** @internal */ async acceptNewSegments() { if (this._locationData.length > 1) { for (let i = 0; i <= this._locationData.length - 2; i++) { const adjustedStart = this._locationData[i].adjustedPoint; const adjustedEnd = this._locationData[i + 1].adjustedPoint; const distance = adjustedStart.distance(adjustedEnd); const xyDist = adjustedStart.distanceXY(adjustedEnd); const zDist = adjustedEnd.z - adjustedStart.z; const slope = (0.0 === xyDist ? Math.PI : Math.atan(zDist / xyDist)); const adjustedDelta = core_geometry_1.Vector3d.createStartEnd(adjustedStart, adjustedEnd); const refAxes = this._locationData[i].refAxes; refAxes.multiplyTransposeVectorInPlace(adjustedDelta); const start = this._locationData[i].point; const end = this._locationData[i + 1].point; const delta = core_geometry_1.Vector3d.createStartEnd(start, end); refAxes.multiplyTransposeVectorInPlace(delta); const toolTip = await this.getMarkerToolTip(distance, slope, adjustedStart, adjustedEnd); const marker = new MeasureMarker((this._acceptedSegments.length + 1).toString(), toolTip, start.interpolate(0.5, end), core_geometry_1.Point2d.create(25, 25)); const segMarkerButtonFunc = (ev) => { if (ev.isDown) return true; let selectedMarker; let pickedMarker; for (const seg of this._acceptedSegments) { if (!seg.marker.pick(ev.viewPoint)) continue; selectedMarker = (seg.marker.isSelected ? undefined : seg.marker); pickedMarker = seg.marker; break; } for (const seg of this._acceptedSegments) { const wasSelected = seg.marker.isSelected; seg.marker.isSelected = (seg.marker === selectedMarker); if (wasSelected !== seg.marker.isSelected) this.updateSelectedMarkerToolTip(seg, ev, (seg.marker === pickedMarker)); // eslint-disable-line @typescript-eslint/no-floating-promises } if (undefined !== ev.viewport) ev.viewport.invalidateDecorations(); return true; }; marker.onMouseButton = segMarkerButtonFunc; this._acceptedSegments.push({ distance, slope, start, end, delta, adjustedStart, adjustedEnd, adjustedDelta, refAxes, marker }); } } this._locationData.length = 0; await this.updateTotals(); } /** @internal */ getReferenceAxes(vp) { const refAxes = core_geometry_1.Matrix3d.createIdentity(); if (undefined !== vp && vp.isContextRotationRequired) vp.getAuxCoordRotation(refAxes); return refAxes; } /** @internal */ async onDataButtonDown(ev) { const point = ev.point.clone(); const adjustedPoint = adjustPoint(ev, this._acceptedSegments, this._locationData); const refAxes = this.getReferenceAxes(ev.viewport); const zDir = refAxes.columnZ(); const normal = refAxes.columnZ(); const tangent = refAxes.columnX(); const snap = IModelApp_1.IModelApp.accuSnap.getCurrSnapDetail(); // Report xyz delta relative to world up. The surface normal and edge tangent help determine the rotation about z... if (undefined !== snap) { if (undefined !== snap.primitive) { const locDetail = snap.primitive.closestPoint(point, false); const snapPlane = core_geometry_1.Plane3dByOriginAndUnitNormal.create(point, undefined !== snap.normal ? snap.normal : normal); if (undefined !== locDetail && (HitDetail_1.HitGeomType.Segment === snap.geomType || (snapPlane && snap.primitive.isInPlane(snapPlane)))) { const locRay = snap.primitive.fractionToPointAndUnitTangent(locDetail.fraction); tangent.setFrom(locRay.direction); if (undefined !== snap.normal) normal.setFrom(snap.normal); } } else if (undefined !== snap.normal) { normal.setFrom(snap.normal); } } if (!normal.isParallelTo(zDir, true)) { const yDir = zDir.unitCrossProduct(normal); if (undefined !== yDir) { yDir.unitCrossProduct(zDir, normal); core_geometry_1.Matrix3d.createColumnsInAxisOrder(core_geometry_1.AxisOrder.ZXY, normal, yDir, zDir, refAxes); } } else if (!tangent.isParallelTo(zDir, true)) { const yDir = zDir.unitCrossProduct(tangent); if (undefined !== yDir) { yDir.unitCrossProduct(zDir, tangent); core_geometry_1.Matrix3d.createColumnsInAxisOrder(core_geometry_1.AxisOrder.XYZ, tangent, yDir, zDir, refAxes); } } this._locationData.push({ point, adjustedPoint, refAxes }); if (this._locationData.length > 1 && !ev.isControlKey) await this.acceptNewSegments(); this.setupAndPromptForNextAction(); if (undefined !== ev.viewport) ev.viewport.invalidateDecorations(); return Tool_1.EventHandled.No; } /** @internal */ async onResetButtonUp(ev) { if (0 === this._locationData.length) { await this.onReinitialize(); return Tool_1.EventHandled.No; } await this.acceptNewSegments(); this.setupAndPromptForNextAction(); if (undefined !== ev.viewport) ev.viewport.invalidateDecorations(); return Tool_1.EventHandled.No; } /** @internal */ async onUndoPreviousStep() { if (0 === this._locationData.length && 0 === this._acceptedSegments.length) return false; if (0 !== this._locationData.length) { this._locationData.pop(); } else if (0 !== this._acceptedSegments.length) { this._acceptedSegments.pop(); } if (0 === this._locationData.length && 0 === this._acceptedSegments.length) { await this.onReinitialize(); } else { await this.updateTotals(); this.setupAndPromptForNextAction(); } return true; } /** @internal */ async onRestartTool() { const tool = new MeasureDistanceTool(); if (!await tool.run()) return this.exitTool(); } } exports.MeasureDistanceTool = MeasureDistanceTool; /** Report spatial coordinate at a point as well as cartographic location for geolocated models using current quantity formatters. * @public */ class MeasureLocationTool extends PrimitiveTool_1.PrimitiveTool { static toolId = "Measure.Location"; static iconSpec = "icon-measure-location"; /** @internal */ _acceptedLocations = []; /** @internal */ allowView(vp) { return vp.view.isSpatialView() || vp.view.isDrawingView(); } /** @internal */ isCompatibleViewport(vp, isSelectedViewChange) { return (super.isCompatibleViewport(vp, isSelectedViewChange) && undefined !== vp && this.allowView(vp)); } /** @internal */ isValidLocation(_ev, _isButtonEvent) { return true; } /** @internal */ requireWriteableTarget() { return false; } /** @internal */ async onPostInstall() { await super.onPostInstall(); this.setupAndPromptForNextAction(); } /** @internal */ async onUnsuspend() { this.showPrompt(); } /** @internal */ showPrompt() { const mainInstruction = ToolAssistance_1.ToolAssistance.createInstruction(this.iconSpec, Tool_1.CoreTools.translate("Measure.Location.Prompts.EnterPoint")); const mouseInstructions = []; const touchInstructions = []; if (!ToolAssistance_1.ToolAssistance.createTouchCursorInstructions(touchInstructions)) touchInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.OneTouchTap, Tool_1.CoreTools.translate("ElementSet.Inputs.AcceptPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Touch)); mouseInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.LeftClick, Tool_1.CoreTools.translate("ElementSet.Inputs.AcceptPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); if (0 !== this._acceptedLocations.length) { touchInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.TwoTouchTap, Tool_1.CoreTools.translate("ElementSet.Inputs.Restart"), false, ToolAssistance_1.ToolAssistanceInputMethod.Touch)); mouseInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.RightClick, Tool_1.CoreTools.translate("ElementSet.Inputs.Restart"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); } const sections = []; sections.push(ToolAssistance_1.ToolAssistance.createSection(mouseInstructions, ToolAssistance_1.ToolAssistance.inputsLabel)); sections.push(ToolAssistance_1.ToolAssistance.createSection(touchInstructions, ToolAssistance_1.ToolAssistance.inputsLabel)); const instructions = ToolAssistance_1.ToolAssistance.createInstructions(mainInstruction, sections); IModelApp_1.IModelApp.notifications.setToolAssistance(instructions); } /** @internal */ setupAndPromptForNextAction() { IModelApp_1.IModelApp.accuSnap.enableSnap(true); this.showPrompt(); } async getMarkerToolTip(point) { const is3d = (undefined === this.targetView || this.targetView.view.is3d()); const isSpatial = (undefined !== this.targetView && this.targetView.view.isSpatialView()); const toolTip = document.createElement("div"); let toolTipHtml = ""; const coordFormatterSpec = await getFormatterSpecByKoQAndPersistenceUnit("AecUnits.LENGTH", "Units.M"); if (undefined !== coordFormatterSpec) { let pointAdjusted = point; if (isSpatial) { const globalOrigin = this.iModel.globalOrigin; pointAdjusted = pointAdjusted.minus(globalOrigin); } const formattedPointX = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(pointAdjusted.x, coordFormatterSpec); const formattedPointY = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(pointAdjusted.y, coordFormatterSpec); const formattedPointZ = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(pointAdjusted.z, coordFormatterSpec); toolTipHtml += `${translateBold("Coordinate") + formattedPointX}, ${formattedPointY}`; if (is3d) toolTipHtml += `, ${formattedPointZ}`; toolTipHtml += "<br>"; } if (isSpatial) { const latLongFormatterSpec = await getFormatterSpecByKoQAndPersistenceUnit("AecUnits.ANGLE", "Units.RAD"); if (undefined !== latLongFormatterSpec && undefined !== coordFormatterSpec) { try { const cartographic = await this.iModel.spatialToCartographic(point); const formattedLat = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(Math.abs(cartographic.latitude), latLongFormatterSpec); const formattedLong = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(Math.abs(cartographic.longitude), latLongFormatterSpec); const formattedHeight = IModelApp_1.IModelApp.quantityFormatter.formatQuantity(cartographic.height, coordFormatterSpec); const latDir = Tool_1.CoreTools.translate(cartographic.latitude < 0 ? "Measure.Labels.S" : "Measure.Labels.N"); const longDir = Tool_1.CoreTools.translate(cartographic.longitude < 0 ? "Measure.Labels.W" : "Measure.Labels.E"); toolTipHtml += `${translateBold("LatLong") + formattedLat + latDir}, ${formattedLong}${longDir}<br>`; toolTipHtml += `${translateBold("Altitude") + formattedHeight}<br>`; } catch { } } } toolTip.innerHTML = toolTipHtml; return toolTip; } /** @internal */ decorate(context) { if (!this.isCompatibleViewport(context.viewport, false)) return; this._acceptedLocations.forEach((marker) => marker.addDecoration(context)); } /** @internal */ decorateSuspended(context) { this.decorate(context); } reportMeasurements() { if (0 === this._acceptedLocations.length) return; const briefMsg = this._acceptedLocations[this._acceptedLocations.length - 1].title; if (undefined === briefMsg) return; const msgDetail = new NotificationManager_1.NotifyMessageDetails(NotificationManager_1.OutputMessagePriority.Info, briefMsg, undefined, NotificationManager_1.OutputMessageType.Sticky); IModelApp_1.IModelApp.notifications.outputMessage(msgDetail); } /** @internal */ async onDataButtonDown(ev) { const point = ev.point.clone(); const adjustedPoint = adjustPoint(ev); const toolTip = await this.getMarkerToolTip(adjustedPoint); const marker = new MeasureMarker((this._acceptedLocations.length + 1).toString(), toolTip, point, core_geometry_1.Point2d.create(25, 25)); this._acceptedLocations.push(marker); this.reportMeasurements(); this.setupAndPromptForNextAction(); if (undefined !== ev.viewport) ev.viewport.invalidateDecorations(); return Tool_1.EventHandled.No; } /** @internal */ async onResetButtonUp(_ev) { await this.onReinitialize(); return Tool_1.EventHandled.No; } /** @internal */ async onUndoPreviousStep() { if (0 === this._acceptedLocations.length) return false; this._acceptedLocations.pop(); if (0 === this._acceptedLocations.length) { await this.onReinitialize(); } else { this.reportMeasurements(); this.setupAndPromptForNextAction(); } return true; } /** @internal */ async onRestartTool() { const tool = new MeasureLocationTool(); if (!await tool.run()) return this.exitTool(); } } exports.MeasureLocationTool = MeasureLocationTool; /** Report area defined by points using current quantity formatter for area. * @public */ class MeasureAreaByPointsTool extends PrimitiveTool_1.PrimitiveTool { static toolId = "Measure.AreaByPoints"; static iconSpec = "icon-measure-2d"; /** @internal */ _orientationValue = { value: AccuDraw_1.ContextRotationId.Top }; /** @internal */ _points = []; /** @internal */ _matrix; /** @internal */ _isComplete = false; /** @internal */ _area = 0.0; /** @internal */ _perimeter = 0.0; /** @internal */ _centroid = core_geometry_1.Point3d.createZero(); /** @internal */ _marker; /** @internal */ _acceptedMeasurement; /** @internal */ _lastMotionPt; /** @internal */ get orientation() { return this._orientationValue.value; } set orientation(option) { this._orientationValue.value = option; } /** @internal */ static _orientationName = "enumAsOrientation"; /** @internal */ static enumAsOrientationMessage(str) { return Tool_1.CoreTools.translate(`Settings.Orientation.${str}`); } /** @internal */ static _getEnumAsOrientationDescription = () => { return { name: MeasureAreaByPointsTool._orientationName, displayLabel: Tool_1.CoreTools.translate("Settings.Orientation.Label"), typename: "enum", enum: { choices: [ { label: MeasureAreaByPointsTool.enumAsOrientationMessage("Top"), value: AccuDraw_1.ContextRotationId.Top }, { label: MeasureAreaByPointsTool.enumAsOrientationMessage("Front"), value: AccuDraw_1.ContextRotationId.Front }, { label: MeasureAreaByPointsTool.enumAsOrientationMessage("Left"), value: AccuDraw_1.ContextRotationId.Left }, { label: MeasureAreaByPointsTool.enumAsOrientationMessage("Bottom"), value: AccuDraw_1.ContextRotationId.Bottom }, { label: MeasureAreaByPointsTool.enumAsOrientationMessage("Back"), value: AccuDraw_1.ContextRotationId.Back }, { label: MeasureAreaByPointsTool.enumAsOrientationMessage("Right"), value: AccuDraw_1.ContextRotationId.Right }, { label: MeasureAreaByPointsTool.enumAsOrientationMessage("View"), value: AccuDraw_1.ContextRotationId.View }, { label: MeasureAreaByPointsTool.enumAsOrientationMessage("Face"), value: AccuDraw_1.ContextRotationId.Face }, ], }, }; }; /** @internal */ supplyToolSettingsProperties() { const initialValue = IModelApp_1.IModelApp.toolAdmin.toolSettingsState.getInitialToolSettingValue(this.toolId, MeasureAreaByPointsTool._orientationName); initialValue && (this._orientationValue = initialValue); const toolSettings = new Array(); toolSettings.push({ value: this._orientationValue, property: MeasureAreaByPointsTool._getEnumAsOrientationDescription(), editorPosition: { rowPriority: 0, columnIndex: 2 } }); return toolSettings; } /** @internal */ async applyToolSettingPropertyChange(updatedValue) { if (updatedValue.propertyName === MeasureAreaByPointsTool._orientationName) { this._orientationValue = updatedValue.value; if (!this._orientationValue) return false; IModelApp_1.IModelApp.toolAdmin.toolSettingsState.saveToolSettingProperty(this.toolId, { propertyName: MeasureAreaByPointsTool._orientationName, value: this._orientationValue }); await this.onReinitialize(); return true; } return false; } /** @internal */ allowView(vp) { return vp.view.isSpatialView() || vp.view.isDrawingView(); } /** @internal */ isCompatibleViewport(vp, isSelectedViewChange) { return (super.isCompatibleViewport(vp, isSelectedViewChange) && undefined !== vp && this.allowView(vp)); } /** @internal */ isValidLocation(_ev, _isButtonEvent) { return true; } /** @internal */ requireWriteableTarget() { return false; } /** @internal */ async onPostInstall() { await super.onPostInstall(); this.setupAndPromptForNextAction(); } /** @internal */ async onUnsuspend() { this.showPrompt(); } /** @internal */ showPrompt() { let mainMsg = "Measure.AreaByPoints.Prompts."; switch (this._points.length) { case 0: mainMsg += "FirstPoint"; break; case 1: mainMsg += "SecondPoint"; break; case 2: mainMsg += "ThirdPoint"; break; default: mainMsg += this._isComplete ? "FirstPoint" : "NextPoint"; break; } const mainInstruction = ToolAssistance_1.ToolAssistance.createInstruction(this.iconSpec, Tool_1.CoreTools.translate(mainMsg)); const mouseInstructions = []; const touchInstructions = []; const acceptMsg = Tool_1.CoreTools.translate(this._isComplete ? "ElementSet.Inputs.Restart" : "ElementSet.Inputs.AcceptPoint"); if (!ToolAssistance_1.ToolAssistance.createTouchCursorInstructions(touchInstructions)) touchInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.OneTouchTap, acceptMsg, false, ToolAssistance_1.ToolAssistanceInputMethod.Touch)); mouseInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.LeftClick, acceptMsg, false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); const resetMsg = Tool_1.CoreTools.translate("ElementSet.Inputs.Restart"); touchInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.TwoTouchTap, resetMsg, false, ToolAssistance_1.ToolAssistanceInputMethod.Touch)); mouseInstructions.push(ToolAssistance_1.ToolAssistance.createInstruction(ToolAssistance_1.ToolAssistanceImage.RightClick, resetMsg, false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); if (this._points.length > 1) mouseInstructions.push(ToolAssistance_1.ToolAssistance.createModifierKeyInstruction(ToolAssistance_1.ToolAssistance.ctrlKey, ToolAssistance_1.ToolAssistanceImage.LeftClick, Tool_1.CoreTools.translate("ElementSet.Inputs.AdditionalPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); if (0 !== this._points.length) mouseInstructions.push(ToolAssistance_1.ToolAssistance.createKeyboardInstruction(ToolAssistance_1.ToolAssistance.createKeyboardInfo([ToolAssistance_1.ToolAssistance.ctrlKey, "Z"]), Tool_1.CoreTools.translate("ElementSet.Inputs.UndoLastPoint"), false, ToolAssistance_1.ToolAssistanceInputMethod.Mouse)); const sections = []; sections.push(ToolAssistance_1.ToolAssistance.createSection(mouseInstructions, ToolAssistance_1.ToolAssistance.inputsLabel)); sections.push(ToolAssistance_1.ToolAssistance.createSection(touchInstructions, ToolAssistance_1.ToolAssistance.inputsLabel)); const instructions = ToolAssistance_1.ToolAssistance.createInstructions(mainInstruction, sections); IModelApp_1.IModelApp.notifications.setToolAssistance(instructions); } /** @internal */ setupAndPromptForNextAction() { IModelApp_1.IModelApp.accuSnap.enableSnap(true); this.showPrompt(); if (this._isComplete) { AccuDraw_1.AccuDrawHintBuilder.deactivate(); return; } if (0 === this._points.length) return; const hints = new AccuDraw_1.AccuDrawHintBuilder(); hints.setOrigin(this._points[this._points.length - 1]); if (this._matrix) { if (1 === this._points.length) { hints.setMatrix(this._matrix); hints.setModeRectangular(); } else if (this._points.length > 1 && !(this._points[this._points.length - 1].isAlmostEqual(this._points[this._points.length - 2]))) { const xVec = core_geometry_1.Vector3d.createStartEnd(this._points[this._points.length - 2], this._points[this._points.length - 1]); const zVec = this._matrix.getColumn(2); const matrix = core_geometry_1.Matrix3d.createRigidFromColumns(xVec, zVec, core_geometry_1.AxisOrder.XZY); if (undefined !== matrix) hints.setMatrix(matrix); // Rotate AccuDraw x axis to last segment preserving current up vector... } } hints.setLockZ = true; hints.sendHints(); } /** @internal */ getShapePoints(cursorPt) { const points = []; if (undefined === this.targetView || this._points.length < 1) return points; for (const pt of this._points) points.push(pt.clone()); if (this._isComplete || !this._matrix) return points; const normal = this._matrix.getColumn(2); let currentPt = AccuDraw_1.AccuDrawHintBuilder.projectPointToPlaneInView(cursorPt, points[0], normal, this.targetView, true); if (undefined === currentPt) currentPt = cursorPt.clone(); if (2 === points.length && 0 === (IModelApp_1.IModelApp.toolAdmin.currentInputState.qualifiers & Tool_1.BeModifierKeys.Control)) { const xDir = core_geometry_1.Vector3d.createStartEnd(points[0], points[1]); const xLen = xDir.magnitude(); xDir.normalizeInPlace(); const yDir = xDir.crossProduct(normal); yDir.normalizeInPlace(); const cornerPt = AccuDraw_1.AccuDrawHintBuilder.projectPointToLineInView(currentPt, points[1], yDi