@itwin/measure-tools-react
Version:
Frontend framework and tools for measurements
211 lines • 9.22 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 { IModelApp } from "@itwin/core-frontend";
import { BeUiEvent } from "@itwin/core-bentley";
import { MeasurementPickContext } from "./Measurement.js";
import { MeasurementSelectionSet } from "./MeasurementSelectionSet.js";
import { MeasurementSyncUiEventId, WellKnownViewType } from "./MeasurementEnums.js";
import { MeasurementManager } from "./MeasurementManager.js";
import { SyncUiEventDispatcher } from "@itwin/appui-react";
class Stack {
constructor() {
this._data = [];
}
get isEmpty() { return 0 === this._data.length; }
size() { return this._data.length; }
push(m) { return this._data.push(m); }
pop() { return this._data.pop(); }
clear() { this._data = []; }
}
/** Base class for any ToolModel that creates Measurements.
* It is templated so that all functions return the correct measurement subclass.
*/
export class MeasurementToolModel {
constructor() {
/** When true, new measurements will be added/removed from the selection set */
this.synchMeasurementsWithSelectionSet = false;
/** Event when a new dynamic measurement is created. */
this.onNewMeasurement = new BeUiEvent();
/** Event when the dynamic measurement has changed. */
this.onDynamicMeasurementChanged = new BeUiEvent();
this._measurements = [];
this._redoStack = new Stack();
this._ignoreMeasurementsRemoved = false;
this._initialized = false;
}
/** Returns the dynamic measurement or undefined. */
get dynamicMeasurement() { return undefined; }
/** Resets the toolModel to its initial state, optionally clearing measurements. */
reset(clearMeasurements) {
if (clearMeasurements)
this.clearMeasurements();
else
IModelApp.viewManager.invalidateDecorationsAllViews(); // Might have decorations in multiple viewports
}
/** Returns all the measurements. */
get measurements() {
return this._measurements;
}
/** Returns a reference to the measurements array of this toolModel. */
get measurementsRef() {
return this._measurements;
}
/** Gets if the model has been initialized */
get isInitialized() {
return this._initialized;
}
/** Returns true if undo is possible. */
get canUndo() { return 0 < this._measurements.length; }
/** Returns true if redo is possible. */
get canRedo() { return !this._redoStack.isEmpty; }
/** Undo (deletes) the last measurement.
* * NOTE: ignores the current state of the ToolModel
*/
undoMeasurement() {
if (0 === this._measurements.length)
return false;
const m = this._measurements.pop();
this._redoStack.push(m);
// Drop will remove from selection set, and notify events
this.removeMeasurementsFromManager(m);
IModelApp.viewManager.invalidateDecorationsAllViews();
return true;
}
/** Redo the (previously undone) measurement.
* * NOTE: ignores the current state of the ToolModel
*/
redoMeasurement() {
if (this._redoStack.isEmpty)
return false;
const m = this._redoStack.pop();
this._measurements.push(m);
// Add measurement(s) to manager, this notifies events but doesn't automatically select
MeasurementManager.instance.addMeasurement(m);
if (this.synchMeasurementsWithSelectionSet)
MeasurementSelectionSet.global.add(m);
IModelApp.viewManager.invalidateDecorationsAllViews();
return true;
}
addMeasurementAndReset(...measurement) {
this._measurements.push(...measurement);
this.reset(false);
this._redoStack.clear();
// Add measurement(s) to manager, this notifies events but doesn't automatically select
MeasurementManager.instance.addMeasurement(measurement);
if (this.synchMeasurementsWithSelectionSet)
MeasurementSelectionSet.global.add(measurement);
}
initialize() {
if (this._initialized)
return;
this._initialized = true;
this.reset(true); // Shouldn't have any measurements anyways...
MeasurementManager.instance.onMeasurementsRemoved.addListener(this.handleMeasurementRemoved, this);
}
cleanup() {
MeasurementManager.instance.onMeasurementsRemoved.removeListener(this.handleMeasurementRemoved, this);
this.reset(true); // If measurements got persisted, shouldn't have any measurements, otherwise remove them...
this._initialized = false;
}
// Handle any of our measurements being removed externally while the tool is active (e.g. toolbar was opened or measurements cleared)
handleMeasurementRemoved(measurements) {
if (this._ignoreMeasurementsRemoved)
return;
for (const m of measurements) {
const asT = m;
if (!asT)
continue;
const index = this._measurements.indexOf(asT, 0);
if (index > -1)
this._measurements.splice(index, 1);
}
}
notifyNewMeasurement() {
if (!this.dynamicMeasurement)
return;
this.onNewMeasurement.emit(this.dynamicMeasurement);
this.notifyDynamicMeasurementChanged();
}
notifyDynamicMeasurementChanged() {
if (!this.dynamicMeasurement)
return;
this.onDynamicMeasurementChanged.emit(this.dynamicMeasurement);
SyncUiEventDispatcher.dispatchSyncUiEvent(MeasurementSyncUiEventId.DynamicMeasurementChanged);
}
/** Performs any final logic to ensure measurements are valid after a tool commits them. The current list of measurements are cleared. */
persistMeasurements() {
if (0 === this._measurements.length)
return false;
// Measurements are already in the manager, so we don't have to do anything special by default. But we do want to clear the current list so we don't hold onto them after
this._measurements = [];
this._redoStack.clear();
return true;
}
/** Clears the completed measurement this tool owns. */
clearMeasurements(viewType) {
if (0 === this._measurements.length)
return;
let removeList;
const keepList = new Array();
// If removing for a specific type of viewport, we will add it to a remove list and set the current group of measurements from a kept list
if (viewType !== undefined && viewType !== WellKnownViewType.Any) {
removeList = new Array();
for (const measurement of this._measurements) {
if (measurement.viewTarget.isOfViewType(viewType))
removeList.push(measurement);
else
keepList.push(measurement);
}
}
else {
removeList = this._measurements;
}
this._measurements = keepList;
// Drop will remove from selection set, and notify events
this.removeMeasurementsFromManager(removeList);
}
/** Returns true if the supplied `id` belong to one of the measurements. */
testDecorationHit(id) {
const pickContext = MeasurementPickContext.createFromSourceId(id);
const dynamic = this.dynamicMeasurement;
if (dynamic && dynamic.testDecorationHit(pickContext))
return true;
return false;
}
/** Return snappable geometry for the hit measurement, or undefined. */
getDecorationGeometry(hit) {
const pickContext = MeasurementPickContext.create(hit);
const dynamic = this.dynamicMeasurement;
if (dynamic && dynamic.testDecorationHit(pickContext))
return dynamic.getDecorationGeometry(pickContext);
return undefined;
}
/** Returns the tooltip of the located measurement. */
async getToolTip(hit) {
const pickContext = MeasurementPickContext.create(hit);
const dynamic = this.dynamicMeasurement;
if (dynamic && dynamic.testDecorationHit(pickContext))
return dynamic.getDecorationToolTip(pickContext);
return "";
}
/** Decorate the measurements in the provided viewport. */
decorate(context) {
const dynamic = this.dynamicMeasurement;
if (dynamic && dynamic.viewTarget.isViewportCompatible(context.viewport)) {
dynamic.decorateCached(context); // Not yet added to the manager, so this has to be called explicitly
dynamic.decorate(context);
}
}
removeMeasurementsFromManager(measurements) {
this._ignoreMeasurementsRemoved = true;
try {
MeasurementManager.instance.dropMeasurement(measurements);
}
finally {
this._ignoreMeasurementsRemoved = false;
}
}
}
//# sourceMappingURL=MeasurementToolModel.js.map