UNPKG

@itwin/core-markup

Version:
497 lines • 24 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module MarkupTools */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SymbolTool = exports.SketchTool = exports.DistanceTool = exports.ArrowTool = exports.EllipseTool = exports.CircleTool = exports.CloudTool = exports.PolygonTool = exports.RectangleTool = exports.LineTool = exports.RedlineTool = void 0; // cspell:ignore rtmp stmp const core_geometry_1 = require("@itwin/core-geometry"); const core_frontend_1 = require("@itwin/core-frontend"); const Markup_1 = require("./Markup"); const MarkupTool_1 = require("./MarkupTool"); /** Base class for tools that place new Markup elements * @public */ class RedlineTool extends MarkupTool_1.MarkupTool { _minPoints = 1; _nRequiredPoints = 2; _points = []; onAdded(el) { const markup = this.markup; const undo = markup.undo; undo.performOperation(this.keyin, () => undo.onAdded(el)); markup.selected.restart(el); } isComplete(_ev) { return this._points.length >= this._nRequiredPoints; } setupAndPromptForNextAction() { super.setupAndPromptForNextAction(); this.markup.disablePick(); core_frontend_1.IModelApp.toolAdmin.setCursor(0 === this._points.length ? core_frontend_1.IModelApp.viewManager.crossHairCursor : core_frontend_1.IModelApp.viewManager.dynamicsCursor); } createMarkup(_svgMarkup, _ev, _isDynamics) { } clearDynamicsMarkup(_isDynamics) { this.markup.svgDynamics.clear(); } async onRestartTool() { return this.exitTool(); } // Default to single shot and return control to select tool... async onCleanup() { this.clearDynamicsMarkup(false); } async onReinitialize() { this.clearDynamicsMarkup(false); return super.onReinitialize(); } async onUndoPreviousStep() { return (0 === this._points.length) ? false : (this.onReinitialize(), true); } async onMouseMotion(ev) { if (undefined === ev.viewport || this._points.length < this._minPoints) return; this.clearDynamicsMarkup(true); this.createMarkup(this.markup.svgDynamics, ev, true); } async onDataButtonDown(ev) { if (undefined === ev.viewport) return core_frontend_1.EventHandled.No; this._points.push(Markup_1.MarkupApp.convertVpToVb(ev.viewPoint)); if (!this.isComplete(ev)) { this.setupAndPromptForNextAction(); return core_frontend_1.EventHandled.No; } this.createMarkup(this.markup.svgMarkup, ev, false); await this.onReinitialize(); return core_frontend_1.EventHandled.No; } async onResetButtonUp(_ev) { await this.onReinitialize(); return core_frontend_1.EventHandled.No; } provideToolAssistance(mainInstrKey, singlePoint = false) { const mainInstruction = core_frontend_1.ToolAssistance.createInstruction(this.iconSpec, core_frontend_1.IModelApp.localization.getLocalizedString(mainInstrKey)); const mouseInstructions = []; const touchInstructions = []; const acceptMsg = core_frontend_1.CoreTools.translate("ElementSet.Inputs.AcceptPoint"); const rejectMsg = core_frontend_1.CoreTools.translate("ElementSet.Inputs.Exit"); if (!core_frontend_1.ToolAssistance.createTouchCursorInstructions(touchInstructions)) touchInstructions.push(core_frontend_1.ToolAssistance.createInstruction(singlePoint ? core_frontend_1.ToolAssistanceImage.OneTouchTap : core_frontend_1.ToolAssistanceImage.OneTouchDrag, acceptMsg, false, core_frontend_1.ToolAssistanceInputMethod.Touch)); mouseInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.LeftClick, acceptMsg, false, core_frontend_1.ToolAssistanceInputMethod.Mouse)); touchInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.TwoTouchTap, rejectMsg, false, core_frontend_1.ToolAssistanceInputMethod.Touch)); mouseInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.RightClick, rejectMsg, false, core_frontend_1.ToolAssistanceInputMethod.Mouse)); const sections = []; sections.push(core_frontend_1.ToolAssistance.createSection(mouseInstructions, core_frontend_1.ToolAssistance.inputsLabel)); sections.push(core_frontend_1.ToolAssistance.createSection(touchInstructions, core_frontend_1.ToolAssistance.inputsLabel)); const instructions = core_frontend_1.ToolAssistance.createInstructions(mainInstruction, sections); core_frontend_1.IModelApp.notifications.setToolAssistance(instructions); } } exports.RedlineTool = RedlineTool; /** Tool for placing Markup Lines * @public */ class LineTool extends RedlineTool { static toolId = "Markup.Line"; static iconSpec = "icon-line"; showPrompt() { this.provideToolAssistance(core_frontend_1.CoreTools.tools + (0 === this._points.length ? "ElementSet.Prompts.StartPoint" : "ElementSet.Prompts.EndPoint")); } createMarkup(svgMarkup, ev, isDynamics) { if (this._points.length < (isDynamics ? this._nRequiredPoints - 1 : this._nRequiredPoints)) return; const start = this._points[0]; const end = isDynamics ? Markup_1.MarkupApp.convertVpToVb(ev.viewPoint) : this._points[1]; const element = svgMarkup.line(start.x, start.y, end.x, end.y); this.setCurrentStyle(element, false); if (!isDynamics) this.onAdded(element); } } exports.LineTool = LineTool; /** Tool for placing Markup Rectangles * @public */ class RectangleTool extends RedlineTool { _cornerRadius; static toolId = "Markup.Rectangle"; static iconSpec = "icon-rectangle"; constructor(_cornerRadius) { super(); this._cornerRadius = _cornerRadius; } // Specify radius to create a rectangle with rounded corners. showPrompt() { this.provideToolAssistance(core_frontend_1.CoreTools.tools + (0 === this._points.length ? "ElementSet.Prompts.StartCorner" : "ElementSet.Prompts.OppositeCorner")); } createMarkup(svgMarkup, ev, isDynamics) { if (this._points.length < (isDynamics ? this._nRequiredPoints - 1 : this._nRequiredPoints)) return; const start = this._points[0]; const end = isDynamics ? Markup_1.MarkupApp.convertVpToVb(ev.viewPoint) : this._points[1]; const vec = start.vectorTo(end); const width = Math.abs(vec.x); const height = Math.abs(vec.y); if (width < 1 || height < 1) return; const offset = core_geometry_1.Point3d.create(vec.x < 0 ? end.x : start.x, vec.y < 0 ? end.y : start.y); // define location by corner points... const element = svgMarkup.rect(width, height).move(offset.x, offset.y); this.setCurrentStyle(element, true); if (undefined !== this._cornerRadius) element.radius(this._cornerRadius); if (!isDynamics) this.onAdded(element); } } exports.RectangleTool = RectangleTool; /** Tool for placing Markup Polygons * @public */ class PolygonTool extends RedlineTool { _numSides; static toolId = "Markup.Polygon"; static iconSpec = "icon-polygon"; constructor(_numSides) { super(); this._numSides = _numSides; } // Specify number of polygon sides. Default if undefined is 5. showPrompt() { this.provideToolAssistance(MarkupTool_1.MarkupTool.toolKey + (0 === this._points.length ? "Polygon.Prompts.FirstPoint" : "Polygon.Prompts.NextPoint")); } getPoints(points, center, edge, numSides, inscribe) { if (numSides < 3 || numSides > 100) return false; let radius = center.distanceXY(edge); if (radius < 1) return false; const delta = (Math.PI * 2.0) / numSides; const vec = center.vectorTo(edge); let angle = core_geometry_1.Vector3d.unitX().planarRadiansTo(vec, core_geometry_1.Vector3d.unitZ()); if (!inscribe) { const theta = delta * 0.5; angle -= theta; radius /= Math.cos(theta); } const rtmp = core_geometry_1.Point3d.create(); const stmp = core_geometry_1.Point3d.create(); for (let i = 0; i < numSides; i++, angle += delta) { rtmp.x = radius * Math.cos(angle); rtmp.y = radius * Math.sin(angle); rtmp.z = 0.0; center.plus(rtmp, stmp); points.push(stmp.x); points.push(stmp.y); } return true; } createMarkup(svgMarkup, ev, isDynamics) { if (this._points.length < (isDynamics ? this._nRequiredPoints - 1 : this._nRequiredPoints)) return; const center = this._points[0]; const edge = isDynamics ? Markup_1.MarkupApp.convertVpToVb(ev.viewPoint) : this._points[1]; const pts = []; if (!this.getPoints(pts, center, edge, undefined !== this._numSides ? this._numSides : 5, true)) return; const element = svgMarkup.polygon(pts); this.setCurrentStyle(element, true); if (!isDynamics) this.onAdded(element); } } exports.PolygonTool = PolygonTool; /** Tool for placing Markup Clouds * @public */ class CloudTool extends RedlineTool { static toolId = "Markup.Cloud"; static iconSpec = "icon-cloud"; _cloud; showPrompt() { this.provideToolAssistance(core_frontend_1.CoreTools.tools + (0 === this._points.length ? "ElementSet.Prompts.StartCorner" : "ElementSet.Prompts.OppositeCorner")); } createMarkup(svgMarkup, ev, isDynamics) { if (this._points.length < (isDynamics ? this._nRequiredPoints - 1 : this._nRequiredPoints)) return; const start = this._points[0]; const end = isDynamics ? Markup_1.MarkupApp.convertVpToVb(ev.viewPoint) : this._points[1]; const vec = start.vectorTo(end); const width = Math.abs(vec.x); const height = Math.abs(vec.y); if (width < 10 || height < 10) return; if (undefined === this._cloud) { this._cloud = svgMarkup.path(Markup_1.MarkupApp.props.active.cloud.path); } else if (!isDynamics) { svgMarkup.add(this._cloud); } const offset = core_geometry_1.Point3d.create(vec.x < 0 ? end.x : start.x, vec.y < 0 ? end.y : start.y); // define location by corner points... this._cloud.move(offset.x, offset.y); this._cloud.width(width); this._cloud.height(height); this.setCurrentStyle(this._cloud, true); if (!isDynamics) this.onAdded(this._cloud); } clearDynamicsMarkup(isDynamics) { if (!isDynamics) super.clearDynamicsMarkup(isDynamics); // For dynamics we don't create a new cloud each frame, we just set the width/height... } } exports.CloudTool = CloudTool; /** Tool for placing Markup Circles * @public */ class CircleTool extends RedlineTool { static toolId = "Markup.Circle"; static iconSpec = "icon-circle"; showPrompt() { this.provideToolAssistance(MarkupTool_1.MarkupTool.toolKey + (0 === this._points.length ? "Circle.Prompts.FirstPoint" : "Circle.Prompts.NextPoint")); } createMarkup(svgMarkup, ev, isDynamics) { if (this._points.length < (isDynamics ? this._nRequiredPoints - 1 : this._nRequiredPoints)) return; const start = this._points[0]; const end = isDynamics ? Markup_1.MarkupApp.convertVpToVb(ev.viewPoint) : this._points[1]; const radius = start.distanceXY(end); if (radius < 1) return; const element = svgMarkup.circle(radius * 2.0).center(start.x, start.y); this.setCurrentStyle(element, true); if (!isDynamics) this.onAdded(element); } } exports.CircleTool = CircleTool; /** Tool for placing Markup Ellipses * @public */ class EllipseTool extends RedlineTool { static toolId = "Markup.Ellipse"; static iconSpec = "icon-ellipse"; showPrompt() { this.provideToolAssistance(core_frontend_1.CoreTools.tools + (0 === this._points.length ? "ElementSet.Prompts.StartCorner" : "ElementSet.Prompts.OppositeCorner")); } createMarkup(svgMarkup, ev, isDynamics) { if (this._points.length < (isDynamics ? this._nRequiredPoints - 1 : this._nRequiredPoints)) return; const start = this._points[0]; const end = isDynamics ? Markup_1.MarkupApp.convertVpToVb(ev.viewPoint) : this._points[1]; const vec = start.vectorTo(end); const width = Math.abs(vec.x); const height = Math.abs(vec.y); if (width < 1 || height < 1) return; const offset = core_geometry_1.Point3d.create(vec.x < 0 ? end.x : start.x, vec.y < 0 ? end.y : start.y); // define location by corner points... const element = svgMarkup.ellipse(width, height).move(offset.x, offset.y); this.setCurrentStyle(element, true); if (!isDynamics) this.onAdded(element); } } exports.EllipseTool = EllipseTool; /** Tool for placing Markup Arrows * @public */ class ArrowTool extends RedlineTool { _arrowPos; static toolId = "Markup.Arrow"; static iconSpec = "icon-callout"; /** ctor for ArrowTool * @param _arrowPos "start", "end", or "both". If undefined = "end". */ constructor(_arrowPos) { super(); this._arrowPos = _arrowPos; } showPrompt() { this.provideToolAssistance(core_frontend_1.CoreTools.tools + (0 === this._points.length ? "ElementSet.Prompts.StartPoint" : "ElementSet.Prompts.EndPoint")); } getOrCreateArrowMarker(color) { const arrowProps = Markup_1.MarkupApp.props.active.arrow; return this.markup.createArrowMarker(color, arrowProps.length, arrowProps.width); } createMarkup(svgMarkup, ev, isDynamics) { if (this._points.length < (isDynamics ? this._nRequiredPoints - 1 : this._nRequiredPoints)) return; const start = this._points[0]; const end = isDynamics ? Markup_1.MarkupApp.convertVpToVb(ev.viewPoint) : this._points[1]; const vec = start.vectorTo(end); if (!vec.normalizeInPlace()) return; const element = svgMarkup.line(start.x, start.y, end.x, end.y); this.setCurrentStyle(element, false); const marker = this.getOrCreateArrowMarker(element.css("stroke")); const addToStart = ("start" === this._arrowPos || "both" === this._arrowPos); const addToEnd = ("end" === this._arrowPos || "both" === this._arrowPos); if (addToStart) element.marker("start", marker); if (addToEnd || !addToStart) element.marker("end", marker); if (!isDynamics) this.onAdded(element); } } exports.ArrowTool = ArrowTool; /** Tool for measuring distances and placing Markups of them * @public */ class DistanceTool extends ArrowTool { static toolId = "Markup.Distance"; static iconSpec = "icon-distance"; _startPointWorld = new core_geometry_1.Point3d(); showPrompt() { this.provideToolAssistance(core_frontend_1.CoreTools.tools + (0 === this._points.length ? "ElementSet.Prompts.StartPoint" : "ElementSet.Prompts.EndPoint")); } setupAndPromptForNextAction() { core_frontend_1.IModelApp.accuSnap.enableSnap(true); core_frontend_1.IModelApp.toolAdmin.toolState.coordLockOvr = core_frontend_1.CoordinateLockOverrides.None; super.setupAndPromptForNextAction(); } getFormattedDistance(distance) { const formatterSpec = core_frontend_1.IModelApp.quantityFormatter.findFormatterSpecByQuantityType(core_frontend_1.QuantityType.Length); if (undefined === formatterSpec) return undefined; return core_frontend_1.IModelApp.quantityFormatter.formatQuantity(distance, formatterSpec); } createMarkup(svgMarkup, ev, isDynamics) { if (this._points.length < (isDynamics ? this._nRequiredPoints - 1 : this._nRequiredPoints)) return; const start = this._points[0]; const end = isDynamics ? Markup_1.MarkupApp.convertVpToVb(ev.viewPoint) : this._points[1]; const vec = start.vectorTo(end); if (!vec.normalizeInPlace()) return; const formatterSpec = core_frontend_1.IModelApp.quantityFormatter.findFormatterSpecByQuantityType(core_frontend_1.QuantityType.Length); if (undefined === formatterSpec) return; const distanceLine = svgMarkup.line(start.x, start.y, end.x, end.y); this.setCurrentStyle(distanceLine, false); const marker = this.getOrCreateArrowMarker(distanceLine.css("stroke")); distanceLine.marker("start", marker); distanceLine.marker("end", marker); const loc = start.interpolate(0.5, end); const distance = core_frontend_1.IModelApp.quantityFormatter.formatQuantity(this._startPointWorld.distance(ev.point), formatterSpec); const text = svgMarkup.plain(distance).addClass(Markup_1.MarkupApp.textClass).attr("text-anchor", "middle").translate(loc.x, loc.y); this.setCurrentTextStyle(text); const textWithBg = this.createBoxedText(svgMarkup, text); if (!isDynamics) { const markup = this.markup; const undo = markup.undo; undo.performOperation(this.keyin, () => { undo.onAdded(distanceLine); undo.onAdded(textWithBg); }); markup.selected.restart(textWithBg); // Select text+box so that user can freely position relative to distance line... } } async onDataButtonDown(ev) { if (undefined === await core_frontend_1.IModelApp.quantityFormatter.getFormatterSpecByQuantityType(core_frontend_1.QuantityType.Length)) { await this.onReinitialize(); return core_frontend_1.EventHandled.No; } if (0 === this._points.length) this._startPointWorld.setFrom(ev.point); return super.onDataButtonDown(ev); } } exports.DistanceTool = DistanceTool; /** Tool for placing Markup freehand sketches * @public */ class SketchTool extends RedlineTool { static toolId = "Markup.Sketch"; static iconSpec = "icon-draw"; _minDistSquared = 100; showPrompt() { this.provideToolAssistance(core_frontend_1.CoreTools.tools + (0 === this._points.length ? "ElementSet.Prompts.StartPoint" : "ElementSet.Prompts.EndPoint")); } createMarkup(svgMarkup, ev, isDynamics) { if (this._points.length < (isDynamics ? this._nRequiredPoints - 1 : this._nRequiredPoints)) return; const pts = []; const evPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint); this._points.forEach((pt) => { pts.push(pt.x); pts.push(pt.y); }); if (isDynamics && !evPt.isAlmostEqualXY(this._points[this._points.length - 1])) { pts.push(evPt.x); pts.push(evPt.y); } const isClosed = (this._points.length > 2 && (this._points[0].distanceSquaredXY(isDynamics ? evPt : this._points[this._points.length - 1]) < this._minDistSquared * 2)); const element = isClosed ? svgMarkup.polygon(pts) : svgMarkup.polyline(pts); this.setCurrentStyle(element, isClosed); if (!isDynamics) this.onAdded(element); } async onMouseMotion(ev) { const evPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint); if (undefined !== ev.viewport && this._points.length > 0 && evPt.distanceSquaredXY(this._points[this._points.length - 1]) > this._minDistSquared) this._points.push(evPt); return super.onMouseMotion(ev); } } exports.SketchTool = SketchTool; /** Tool for placing SVG symbols on a Markup * @public */ class SymbolTool extends RedlineTool { _symbolData; _applyCurrentStyle; static toolId = "Markup.Symbol"; static iconSpec = "icon-symbol"; _symbol; constructor(_symbolData, _applyCurrentStyle) { super(); this._symbolData = _symbolData; this._applyCurrentStyle = _applyCurrentStyle; } async onInstall() { if (undefined === this._symbolData) return false; return super.onInstall(); } showPrompt() { this.provideToolAssistance(0 === this._points.length ? (`${MarkupTool_1.MarkupTool.toolKey}Symbol.Prompts.FirstPoint`) : `${core_frontend_1.CoreTools.tools}ElementSet.Prompts.OppositeCorner`, true); } createMarkup(svgMarkup, ev, isDynamics) { if (undefined === this._symbolData) return; if (this._points.length < this._minPoints) return; const start = this._points[0]; const end = isDynamics ? Markup_1.MarkupApp.convertVpToVb(ev.viewPoint) : this._points[this._points.length - 1]; const vec = start.vectorTo(end); const width = Math.abs(vec.x); const height = Math.abs(vec.y); if ((width < 10 || height < 10) && (isDynamics || this._points.length !== this._minPoints)) return; if (undefined === this._symbol) { const symbol = svgMarkup.group().svg(this._symbolData); // creating group instead of using symbol because of inability to flash/hilite multi-color symbol instance... if (0 === symbol.children().length) { symbol.remove(); this._symbolData = undefined; } try { symbol.flatten(symbol); } catch { } this._symbol = symbol; } else if (!isDynamics) { svgMarkup.add(this._symbol); } const offset = core_geometry_1.Point3d.create(vec.x < 0 ? end.x : start.x, vec.y < 0 ? end.y : start.y); // define location by corner points... if (!isDynamics && this._points.length === this._minPoints) this._symbol.size(ev.viewport.viewRect.width * 0.1).center(offset.x, offset.y); else if (!ev.isShiftKey) this._symbol.size(width).move(offset.x, offset.y); else this._symbol.size(width, height).move(offset.x, offset.y); if (this._applyCurrentStyle) { const active = Markup_1.MarkupApp.props.active; // Apply color and transparency only; using active stroke-width, etc. for pre-defined symbols is likely undesirable... this._symbol.forElementsOfGroup((child) => { const css = window.getComputedStyle(child.node); const toValue = (val, newVal) => (!val || val === "none") ? "none" : newVal; child.css({ "fill": toValue(css.fill, active.element.fill), "stroke": toValue(css.stroke, active.element.stroke), "fill-opacity": active.element["fill-opacity"], "stroke-opacity": active.element["stroke-opacity"] }); }); } if (!isDynamics) this.onAdded(this._symbol); } async onDataButtonUp(ev) { if (undefined === ev.viewport || this._points.length !== this._minPoints) return core_frontend_1.EventHandled.No; this.createMarkup(this.markup.svgMarkup, ev, false); await this.onReinitialize(); return core_frontend_1.EventHandled.No; } async onResetButtonUp(_ev) { await this.onReinitialize(); return core_frontend_1.EventHandled.No; } clearDynamicsMarkup(isDynamics) { if (!isDynamics) super.clearDynamicsMarkup(isDynamics); // For dynamics we don't create a new symbol each frame, we just set the width/height... } } exports.SymbolTool = SymbolTool; //# sourceMappingURL=RedlineTool.js.map