@itwin/core-markup
Version:
iTwin.js markup package
497 lines • 24 kB
JavaScript
"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