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