@itwin/measure-tools-react
Version:
Frontend framework and tools for measurements
332 lines • 14.6 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 { Angle, AngleSweep, Arc3d, AxisOrder, IModelJson, Matrix3d, Point3d, PointString3d, Vector3d, } from "@itwin/core-geometry";
import { GraphicType, IModelApp } from "@itwin/core-frontend";
import { StyleSet, WellKnownGraphicStyleType, WellKnownTextStyleType, } from "../api/GraphicStyle.js";
import { Measurement, MeasurementPickContext, MeasurementSerializer, } from "../api/Measurement.js";
import { MeasurementPropertyHelper } from "../api/MeasurementPropertyHelper.js";
import { MeasurementSelectionSet } from "../api/MeasurementSelectionSet.js";
import { TextMarker } from "../api/TextMarker.js";
import { MeasureTools } from "../MeasureTools.js";
export class AngleMeasurementSerializer extends MeasurementSerializer {
get measurementName() {
return AngleMeasurementSerializer.angleMeasurementName;
}
isValidType(measurement) {
return measurement instanceof AngleMeasurement;
}
parseSingle(data) {
if (!this.isValidJSON(data))
return undefined;
const props = data;
return AngleMeasurement.fromJSON(props);
}
}
AngleMeasurementSerializer.angleMeasurementName = "angleMeasurement";
export class AngleMeasurement extends Measurement {
get isDynamic() {
return this._isDynamic;
}
set isDynamic(v) {
this._isDynamic = v;
}
constructor(props) {
super();
this._isDynamic = false;
this._angleKoQ = "AecUnits.ANGLE";
this._anglePersistenceUnitName = "Units.RAD";
if (props)
this.readFromJSON(props);
this.populateFormattingSpecsRegistry().then(() => this.createTextMarker().catch())
.catch();
}
get startPointRef() {
return this._startPoint;
}
get centerRef() {
return this._center;
}
get endPointRef() {
return this._endPoint;
}
get angle() {
if (this._startPoint === undefined ||
this._center === undefined ||
this._endPoint === undefined)
return undefined;
const v1 = Vector3d.createStartEnd(this._center, this._startPoint);
const v2 = Vector3d.createStartEnd(this._center, this._endPoint);
const dot = v1.dotProduct(v2);
const mags = v1.magnitude() * v2.magnitude();
const angle = mags !== 0 ? Math.acos(dot / mags) : 0;
return angle;
}
get angleKoQ() {
return this._angleKoQ;
}
set angleKoQ(koqName) {
this._angleKoQ = koqName;
this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises
}
get anglePersistenceUnitName() {
return this._anglePersistenceUnitName;
}
set anglePersistenceUnitName(unitName) {
this._anglePersistenceUnitName = unitName;
this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises
}
readFromJSON(props) {
super.readFromJSON(props);
if (props.startPoint)
this._startPoint = Point3d.fromJSON(props.startPoint);
if (props.center)
this._center = Point3d.fromJSON(props.center);
if (props.endPoint)
this._endPoint = Point3d.fromJSON(props.endPoint);
if (props.formatting?.angle?.koqName)
this._angleKoQ = props.formatting.angle.koqName;
if (props.formatting?.angle?.persistenceUnitName)
this._anglePersistenceUnitName = props.formatting.angle.persistenceUnitName;
this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises
}
/**
* Serializes properties to a JSON object.
* @param json JSON object to append data to.
*/
writeToJSON(json) {
super.writeToJSON(json);
const jsonDist = json;
if (this._startPoint)
jsonDist.startPoint = this._startPoint.toJSON();
if (this._endPoint)
jsonDist.endPoint = this._endPoint.toJSON();
if (this._center)
jsonDist.center = this._center.toJSON();
jsonDist.formatting = {
angle: {
koqName: this._angleKoQ,
persistenceUnitName: this._anglePersistenceUnitName,
}
};
}
onDisplayUnitsChanged() {
this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises
}
async populateFormattingSpecsRegistry(_force) {
const angleEntry = IModelApp.quantityFormatter.getSpecsByName(this._angleKoQ);
if (_force || !angleEntry || angleEntry.formatterSpec.persistenceUnit?.name !== this._anglePersistenceUnitName) {
const angleFormatProps = await IModelApp.formatsProvider.getFormat(this._angleKoQ);
if (angleFormatProps) {
await IModelApp.quantityFormatter.addFormattingSpecsToRegistry(this._angleKoQ, this._anglePersistenceUnitName, angleFormatProps);
}
}
}
/**
* Tests equality with another measurement.
* @param other Measurement to test equality for.
* @param opts Options for equality testing.
* @returns true if the other measurement is equal, false if some property is not the same or if the measurement is not of the same type.
*/
equals(other, opts) {
if (!super.equals(other, opts))
return false;
const equalsPointOrUndef = (a, b, tolerance) => {
if (a === undefined && b === undefined)
return true;
if (a === undefined && b !== undefined)
return false;
if (a !== undefined && b === undefined)
return false;
if (a !== undefined && b !== undefined)
return a?.isAlmostEqual(b, tolerance);
return false;
};
// Compare data (ignore isDynamic)
const tol = opts ? opts.tolerance : undefined;
const otherDist = other;
if (otherDist === undefined ||
!equalsPointOrUndef(this._startPoint, otherDist._startPoint, tol) ||
!equalsPointOrUndef(this._center, otherDist._center, tol) ||
!equalsPointOrUndef(this._endPoint, otherDist._endPoint, tol))
return false;
return true;
}
/**
* Copies data from the other measurement into this instance.
* @param other Measurement to copy property values from.
*/
copyFrom(other) {
super.copyFrom(other);
if (other instanceof AngleMeasurement) {
this._isDynamic = other._isDynamic;
this._startPoint = other._startPoint
? other._startPoint.clone()
: undefined;
this._center = other._center ? other._center.clone() : undefined;
this._endPoint = other._endPoint ? other._endPoint.clone() : undefined;
this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises
}
}
setStartPoint(point) {
this._startPoint = point;
this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises
}
setCenter(point) {
this._center = point;
this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises
}
setEndPoint(point) {
this._endPoint = point;
this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises
}
testDecorationHit(pickContext) {
if (this.transientId && this.transientId === pickContext.geomId)
return true;
if (pickContext.buttonEvent &&
this._textMarker &&
this.displayLabels &&
this._textMarker.pick(pickContext.buttonEvent.viewPoint))
return true;
return false;
}
async getDecorationToolTip(_pickContext) {
return MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureAngle.measurement");
}
getDecorationGeometry(_pickContext) {
// No need to snap to the geometry during dynamics
if (this._isDynamic)
return undefined;
const geometry = [
IModelJson.Writer.toIModelJson(PointString3d.create(this._startPoint, this._center, this._endPoint)),
];
return geometry;
}
_getSnapId() {
if (!this.transientId)
this.transientId = MeasurementSelectionSet.nextTransientId;
if (this.isDynamic)
return undefined;
return this.transientId;
}
onTransientIdChanged(_prevId) {
if (this._textMarker)
this._textMarker.transientHiliteId = this.transientId;
}
_createDecorationArc(p0, center, p1) {
if (center.isAlmostEqual(p0) ||
center.isAlmostEqual(p1) ||
p0.isAlmostEqual(p1))
return undefined;
const v1 = Vector3d.createStartEnd(center, p0);
v1.normalizeInPlace();
const v2 = Vector3d.createStartEnd(center, p1);
v2.normalizeInPlace();
const length = Math.min(v1.magnitude(), v2.magnitude()) / 2.0;
const matrix = Matrix3d.createRigidFromColumns(v1, v2, AxisOrder.XYZ);
const angle = this.angle;
if (matrix === undefined || angle === undefined)
return undefined;
const startAngle = Angle.createRadians(0);
const sweepAngle = Angle.createRadians(angle);
return Arc3d.createScaledXYColumns(center, matrix, length, length, AngleSweep.createStartSweep(startAngle, sweepAngle));
}
decorate(context) {
super.decorate(context);
const styleTheme = StyleSet.getOrDefault(this.activeStyle);
const style = styleTheme.getGraphicStyle(WellKnownGraphicStyleType.DistanceMeasurement);
const builder = context.createGraphicBuilder(GraphicType.WorldOverlay, undefined, this._getSnapId());
if (this._startPoint !== undefined &&
this._center === undefined &&
this._endPoint === undefined) {
style.addStyledPointString(builder, [this._startPoint], true);
}
else if (this._startPoint !== undefined &&
this._center !== undefined &&
this._endPoint === undefined) {
style.addStyledLineString(builder, [this._startPoint, this._center], true);
}
else if (this._startPoint !== undefined &&
this._center !== undefined &&
this._endPoint !== undefined) {
style.addStyledLineString(builder, [this._startPoint, this._center], true);
style.addStyledLineString(builder, [this._center, this._endPoint], true);
const arc = this._createDecorationArc(this._startPoint, this._center, this._endPoint);
if (arc !== undefined)
style.addStyledArc(builder, arc, true);
if (this._textMarker && this.displayLabels) {
const textLocation = arc !== undefined ? arc.fractionToPoint(0.5) : this._center;
this._textMarker.worldLocation = textLocation;
this._textMarker.addDecoration(context);
}
}
context.addDecorationFromBuilder(builder);
}
async getDataForMeasurementWidgetInternal() {
const angleSpec = IModelApp.quantityFormatter.getSpecsByName(this._angleKoQ)?.formatterSpec;
const angle = this.angle ?? 0;
const fAngle = IModelApp.quantityFormatter.formatQuantity(angle, angleSpec);
let title = MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureAngle.measurement");
title += ` [${fAngle}]`;
const data = { title, properties: [] };
MeasurementPropertyHelper.tryAddNameProperty(this, data.properties);
data.properties.push({
label: MeasureTools.localization.getLocalizedString("MeasureTools:tools.MeasureAngle.angle"),
name: "AngleMeasurement_Angle",
value: fAngle,
aggregatableValue: angleSpec !== undefined
? { value: angle, formatSpec: angleSpec }
: undefined,
});
return data;
}
async createTextMarker() {
if (this._center !== undefined && this.angle !== undefined) {
const angleSpec = IModelApp.quantityFormatter.getSpecsByName(this._angleKoQ)?.formatterSpec;
const angle = this.angle;
const fAngle = IModelApp.quantityFormatter.formatQuantity(angle, angleSpec);
const styleTheme = StyleSet.getOrDefault(this.activeStyle);
const tStyle = styleTheme.getTextStyle(WellKnownTextStyleType.DistanceMeasurement);
this._textMarker = TextMarker.createStyled([fAngle], this._center, tStyle);
this._textMarker.transientHiliteId = this.transientId;
this._textMarker.pickable = !this.isDynamic;
this._textMarker.setMouseButtonHandler(this._handleTextMarkerButtonEvent.bind(this));
}
}
updateMarkerStyle() {
if (!this._textMarker)
return;
const styleTheme = StyleSet.getOrDefault(this.activeStyle);
const tStyle = styleTheme.getTextStyle(WellKnownTextStyleType.DistanceMeasurement);
this._textMarker.applyStyle(tStyle);
}
onStyleChanged(_isLock, _prevStyle) {
this.updateMarkerStyle();
}
onLockToggled() {
this.updateMarkerStyle();
}
_handleTextMarkerButtonEvent(ev) {
if (!this.isDynamic)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.onDecorationButtonEvent(MeasurementPickContext.createFromSourceId("Invalid", ev)).catch();
return true;
}
static fromJSON(data) {
return new AngleMeasurement(data);
}
static create(startPoint, center, endPoint, viewType, formatting) {
const measurement = new AngleMeasurement({
startPoint,
center,
endPoint,
formatting
});
if (viewType)
measurement.viewTarget.include(viewType);
return measurement;
}
}
AngleMeasurement.serializer = Measurement.registerSerializer(new AngleMeasurementSerializer());
//# sourceMappingURL=AngleMeasurement.js.map