UNPKG

@itwin/measure-tools-react

Version:
239 lines 12.5 kB
/*--------------------------------------------------------------------------------------------- * 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