@itwin/measure-tools-react
Version:
Frontend framework and tools for measurements
239 lines • 12.5 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { GeoServiceStatus } from "@itwin/core-bentley";
import { Vector3d } from "@itwin/core-geometry";
import { IModelError } from "@itwin/core-common";
import { EventHandled, IModelApp, LocateResponse, OutputMessagePriority, SnapDetail, ToolAssistance, ToolAssistanceImage, ToolAssistanceInputMethod, } from "@itwin/core-frontend";
import { FeatureTracking, MeasureToolsFeatures } from "../api/FeatureTracking.js";
import { MeasurementToolBase } from "../api/MeasurementTool.js";
import { MeasurementViewTarget } from "../api/MeasurementViewTarget.js";
import { MeasureLocationToolModel } from "../toolmodels/MeasureLocationToolModel.js";
import { MeasureTools } from "../MeasureTools.js";
import { PropertyDescriptionHelper } from "@itwin/appui-abstract";
import { SheetMeasurementsHelper } from "../api/SheetMeasurementHelper.js";
import { ViewHelper } from "../api/ViewHelper.js";
/** Tool that measure precise locations */
export class MeasureLocationTool extends MeasurementToolBase {
get allowedDrawingTypes() {
return [SheetMeasurementsHelper.DrawingType.CrossSection, SheetMeasurementsHelper.DrawingType.Plan];
}
static get flyover() {
return MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.flyover");
}
static get description() {
return MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.description");
}
static get keyin() {
return MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.keyin");
}
get feature() {
return MeasureToolsFeatures.Tools_MeasureLocation;
}
constructor(enableSheetMeasurements = false, allowedViewportCallback = (() => true)) {
super(allowedViewportCallback);
this._useDynamicMeasurement = false;
this._enableSheetMeasurements = enableSheetMeasurements;
}
async onRestartTool() {
const tool = new MeasureLocationTool(this._enableSheetMeasurements, this._allowedViewportCallback);
if (await tool.run())
return;
return this.exitTool();
}
async onPostInstall() {
await super.onPostInstall();
}
async onDataButtonDown(ev) {
if (!ev.viewport)
return EventHandled.No;
const props = await this.createLocationProps(ev, true);
this.toolModel.addLocation(props, false);
if (this._enableSheetMeasurements && ViewHelper.isSheetView(ev.viewport))
FeatureTracking.notifyFeature(MeasureToolsFeatures.Tools_MeasureDistance_createdInSheet);
this.updateToolAssistance();
return EventHandled.Yes;
}
async sheetMeasurementsDataButtonDown(ev) {
if (!ev.viewport)
return undefined;
if (this._enableSheetMeasurements) {
if (ev.viewport.view.id !== undefined) {
const drawingInfo = await SheetMeasurementsHelper.getDrawingId(this.iModel, ev.viewport.view.id, ev.point);
if (drawingInfo?.drawingId !== undefined && drawingInfo.origin !== undefined && drawingInfo.worldScale !== undefined) {
const data = { origin: drawingInfo.origin, drawingId: drawingInfo.drawingId, worldScale: drawingInfo.worldScale, extents: drawingInfo.extents, sheetToWorldTransform: drawingInfo.sheetToWorldTransform };
return data;
}
}
}
return undefined;
}
async onMouseMotion(ev) {
if (undefined === ev.viewport || !this._useDynamicMeasurement)
return;
const props = await this.createLocationProps(ev, false);
this.toolModel.addLocation(props, true);
ev.viewport.invalidateDecorations();
}
async createLocationProps(ev, requestSnap) {
const props = {
location: ev.point.clone(),
viewType: MeasurementViewTarget.classifyViewport(ev.viewport),
viewId: ev.viewport?.view.id,
drawingMetadata: (await this.sheetMeasurementsDataButtonDown(ev)),
};
await this.queryGeoLocation(props);
// Perform a snap to get more information (such as the surface normal, if any)
// Does not look for new snap point if already looking from past frame
let snap = IModelApp.accuSnap.getCurrSnapDetail();
if (!snap && requestSnap)
snap = await this.requestSnap(ev);
if (snap?.normal)
props.slope = this.getSlopeFromNormal(snap.normal);
return props;
}
createToolModel() {
return new MeasureLocationToolModel();
}
isValidLocation(ev, isButtonEvent) {
if (!super.isValidLocation(ev, isButtonEvent))
return false;
if (!this._enableSheetMeasurements || !ev.viewport?.view.isSheetView())
return true;
if (!SheetMeasurementsHelper.checkIfAllowedDrawingType(ev.viewport, ev.point, this.allowedDrawingTypes))
return false;
return true;
}
async requestSnap(ev) {
let hit = IModelApp.accuSnap.currHit;
if (undefined === hit)
hit = await IModelApp.locateManager.doLocate(new LocateResponse(), true, ev.point, ev.viewport, ev.inputSource);
if (undefined === hit)
return undefined;
// Already a snap
if (hit instanceof SnapDetail)
return hit;
const result = await IModelApp.accuSnap.doSnapRequest(hit);
if (undefined === result)
return undefined;
const snap = new SnapDetail(hit, result.snapMode, result.heat, result.snapPoint);
snap.setCurvePrimitive(result.primitive, undefined, result.geomType);
if (undefined !== result.parentGeomType)
snap.parentGeomType = result.parentGeomType;
if (undefined !== result.hitPoint)
snap.hitPoint.setFromJSON(result.hitPoint); // Update hitPoint from readPixels with exact point location corrected to surface/edge geometry...
if (undefined !== result.normal)
snap.normal = Vector3d.fromJSON(result.normal);
// Final check to make sure we've got the information from the right place
if (!ev.point.isAlmostEqual(snap.hitPoint))
return undefined;
return snap;
}
/** Update the props to add GeoLocation information when available */
async queryGeoLocation(props) {
let message = "";
let priority;
if (!this.iModel.isGeoLocated) {
if (MeasureLocationTool._isUserNotifiedOfGeolocationFailure)
return;
// Only notify user once
MeasureLocationTool._isUserNotifiedOfGeolocationFailure = true;
}
try {
props.geoLocation = await this.iModel.spatialToCartographic(props.location);
}
catch (error) {
// Probably not an error we should handle gracefully
if (!(error instanceof IModelError))
throw error;
switch (error.errorNumber) {
case GeoServiceStatus.NoGeoLocation:
message = MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.iModelNotGeoLocated");
priority = OutputMessagePriority.Info;
break;
case GeoServiceStatus.OutOfMathematicalDomain:
case GeoServiceStatus.OutOfUsefulRange:
message = MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.locationOutOfGCSRange");
priority = OutputMessagePriority.Warning;
break;
default:
message = MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.unhandledGeoLocationError");
priority = OutputMessagePriority.Error;
}
}
if (0 < message.length && undefined !== priority)
this.showMessage(priority, message);
}
getSlopeFromNormal(normal) {
if (0.0 !== normal.z)
return normal.magnitudeXY() / normal.z;
return undefined;
}
updateToolAssistance() {
const promptMainInstruction = MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.mainInstruction");
const promptClickTap = MeasureTools.localization.getLocalizedString("MeasureTools:tools.GenericPrompts.acceptPoint");
const promptRightClick = MeasureTools.localization.getLocalizedString("MeasureTools:tools.GenericPrompts.restart");
const mainInstruction = ToolAssistance.createInstruction(this.iconSpec, promptMainInstruction);
const mouseInstructions = [];
const touchInstructions = [];
if (!ToolAssistance.createTouchCursorInstructions(touchInstructions))
touchInstructions.push(ToolAssistance.createInstruction(ToolAssistanceImage.OneTouchTap, promptClickTap, false, ToolAssistanceInputMethod.Touch));
mouseInstructions.push(ToolAssistance.createInstruction(ToolAssistanceImage.LeftClick, promptClickTap, false, ToolAssistanceInputMethod.Mouse));
mouseInstructions.push(ToolAssistance.createInstruction(ToolAssistanceImage.RightClick, promptRightClick, false, ToolAssistanceInputMethod.Mouse));
if (undefined === this.toolModel.dynamicMeasurement) {
if (this.toolModel.canUndo)
mouseInstructions.push(this.createMouseUndoInstruction());
if (this.toolModel.canRedo)
mouseInstructions.push(this.createMouseRedoInstruction());
}
const sections = [
ToolAssistance.createSection(mouseInstructions, ToolAssistance.inputsLabel),
ToolAssistance.createSection(touchInstructions, ToolAssistance.inputsLabel),
];
const instructions = ToolAssistance.createInstructions(mainInstruction, sections);
IModelApp.notifications.setToolAssistance(instructions);
}
async onInstall() {
if (!await super.onInstall())
return false;
const initialValue = IModelApp.toolAdmin.toolSettingsState.getInitialToolSettingValue(this.toolId, MeasureLocationTool.useDynamicMeasurementPropertyName);
if (initialValue)
this._useDynamicMeasurement = typeof initialValue.value === "boolean" ? initialValue.value : false;
return true;
}
async onCleanup() {
const propertyName = MeasureLocationTool.useDynamicMeasurementPropertyName;
const value = { value: this._useDynamicMeasurement };
IModelApp.toolAdmin.toolSettingsState.saveToolSettingProperty(this.toolId, { propertyName, value });
return super.onCleanup();
}
supplyToolSettingsProperties() {
const toolSettings = [];
const propertyLabel = MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureLocation.useDynamicMeasurement");
toolSettings.push({
value: { value: this._useDynamicMeasurement },
property: PropertyDescriptionHelper.buildToggleDescription(MeasureLocationTool.useDynamicMeasurementPropertyName, propertyLabel),
editorPosition: { rowPriority: 0, columnIndex: 0 },
isDisabled: false,
});
return toolSettings;
}
async applyToolSettingPropertyChange(updatedValue) {
if (MeasureLocationTool.useDynamicMeasurementPropertyName === updatedValue.propertyName) {
const value = updatedValue.value.value;
if (typeof value !== "boolean")
return false;
this._useDynamicMeasurement = value;
if (!this._useDynamicMeasurement)
this.toolModel.reset(false);
return true;
}
return super.applyToolSettingPropertyChange(updatedValue);
}
}
MeasureLocationTool.toolId = "MeasureTools.MeasureLocation";
MeasureLocationTool.iconSpec = "icon-measure-location";
MeasureLocationTool.useDynamicMeasurementPropertyName = "useDynamicMeasurement";
MeasureLocationTool._isUserNotifiedOfGeolocationFailure = false;
//# sourceMappingURL=MeasureLocationTool.js.map