UNPKG

@itwin/measure-tools-react

Version:
292 lines 12.8 kB
/*--------------------------------------------------------------------------------------------- * 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, QuantityType } 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; if (props) this.readFromJSON(props); this.createTextMarker().catch(); // eslint-disable-line @typescript-eslint/no-floating-promises } 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; } 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); 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(); } /** * 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 = await IModelApp.quantityFormatter.getFormatterSpecByQuantityType(QuantityType.Angle); 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 = await IModelApp.quantityFormatter.getFormatterSpecByQuantityType(QuantityType.Angle); 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) { const measurement = new AngleMeasurement({ startPoint, center, endPoint, }); if (viewType) measurement.viewTarget.include(viewType); return measurement; } } AngleMeasurement.serializer = Measurement.registerSerializer(new AngleMeasurementSerializer()); //# sourceMappingURL=AngleMeasurement.js.map