@inweb/viewer-visualize 25.3.21 → 25.3.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Viewer.visualize
2
2
 
3
- `Viewer.visualize` is JavaScript library powered by [Visualize](https://www.opendesign.com/products/visualize) for in-browser visualization of 3D CAD and BIM data in browser from `VSFX` files stored on the [Open Cloud Server](https://cloud.opendesign.com/docs/index.html#/opencloud_server), on the web, or on your local computer.
3
+ `Viewer.visualize` is JavaScript library powered by [Visualize](https://www.opendesign.com/products/visualize) for in-browser visualization of 3D CAD and BIM data files stored on the [Open Cloud Server](https://cloud.opendesign.com/docs/index.html#/opencloud_server), on the web, or on your local computer.
4
4
 
5
- This library enables you to integrate advanced visualization capabilities into your web applications, including user interaction with scenes and objects, markups, and collaboration using the [inWEB Open Cloud](https://www.opendesign.com/products/open-cloud) platform.
5
+ This library enables you to integrate advanced visualization capabilities into your web applications, including user interaction with scenes and markup creation.
6
6
 
7
- > `VSFX` is an internal [Visualize SDK](https://cloud.opendesign.com/docs/index.html#/visualizejs) file format with support for streaming and partial viewing.
7
+ > `Viewer.visualize` uses `VSFX` file format as internal geometry data format. `VSFX` is a [Visualize SDK](https://cloud.opendesign.com/docs/index.html#/visualizejs) file format with support for streaming and partial viewing.
8
8
 
9
9
  ## Installation
10
10
 
@@ -811,20 +811,6 @@
811
811
  ///////////////////////////////////////////////////////////////////////////////
812
812
  const CLICK_DELTA = 5;
813
813
  const INTERACTIVITY_FPS = 24;
814
- /**
815
- * A [Viewer]{@link Viewer} event that fires when the viewer needs to be updated.
816
- *
817
- * @property {string} type - `update`
818
- * @event update
819
- */
820
- /**
821
- * A [Viewer]{@link Viewer} event that fires when the user selects an entity with the mouse.
822
- *
823
- * @property {string} type - `select`
824
- * @property {OdTvSelectionSet} data - The set of selected entities. For more information, see
825
- * [OdTvSelectionSet](https://cloud.opendesign.com/docs/index.html#/vis/OdTvSelectionSet?id=odtvselectionset).
826
- * @event select
827
- */
828
814
  class OdBaseDragger extends OdaGeAction {
829
815
  constructor(subject) {
830
816
  super(subject.visualizeJs);
@@ -14964,6 +14950,10 @@
14964
14950
  });
14965
14951
  this._canvasImage.onload = () => {
14966
14952
  this._ref.image(this._canvasImage);
14953
+ if (this._ref.height() === 0)
14954
+ this._ref.height(this._canvasImage.height);
14955
+ if (this._ref.width() === 0)
14956
+ this._ref.width(this._canvasImage.width);
14967
14957
  this._ratio = this._ref.height() === 0 || this._ref.width() === 0 ? 1 : this._ref.height() / this._ref.width();
14968
14958
  };
14969
14959
  this._canvasImage.src = params.src;
@@ -15270,6 +15260,7 @@
15270
15260
  .concat(`oda-cursor-${draggerName.toLowerCase()}`)
15271
15261
  .join(" ");
15272
15262
  this.removeTextInput();
15263
+ this.removeImageInput();
15273
15264
  const markupMode = MarkupMode[draggerName];
15274
15265
  const konvaMode = MarkupMode2Konva.get(markupMode);
15275
15266
  if (konvaMode) {
@@ -15339,6 +15330,7 @@
15339
15330
  }
15340
15331
  clearOverlay() {
15341
15332
  this.removeTextInput();
15333
+ this.removeImageInput();
15342
15334
  this._konvaTransformer.nodes([]);
15343
15335
  Object.values(MarkupMode).forEach((mode) => this.konvaLayerFind(mode).forEach((x) => x.destroy()));
15344
15336
  }
@@ -15359,6 +15351,14 @@
15359
15351
  });
15360
15352
  this._konvaLayer.draw();
15361
15353
  }
15354
+ colorizeSelectedMarkups(r, g, b) {
15355
+ this.getSelectedObjects().forEach((obj) => {
15356
+ const colorable = obj;
15357
+ if (colorable && colorable.setColor) {
15358
+ colorable.setColor(new MarkupColor(r, g, b).HexColor);
15359
+ }
15360
+ });
15361
+ }
15362
15362
  setViewpoint(viewpoint) {
15363
15363
  const markupColor = viewpoint.custom_fields.markup_color || { r: 255, g: 0, b: 0 };
15364
15364
  this.setMarkupColor(markupColor.r, markupColor.g, markupColor.b);
@@ -15565,37 +15565,53 @@
15565
15565
  layer.add(transformer);
15566
15566
  let isPaint = false;
15567
15567
  let lastLine;
15568
+ let mouseDownPos;
15569
+ let lastObj;
15568
15570
  stage.on("mousedown touchstart", (e) => {
15569
15571
  // do nothing if we mousedown on any shape
15570
- if (!this._markupIsActive || e.target !== stage || this._markupMode === MarkupMode.Text)
15572
+ if (!this._markupIsActive ||
15573
+ e.target !== stage ||
15574
+ this._markupMode === MarkupMode.Text ||
15575
+ this._markupMode === MarkupMode.Image)
15571
15576
  return;
15572
15577
  if (e.target === stage && transformer.nodes().length > 0) {
15573
15578
  transformer.nodes([]);
15574
15579
  return;
15575
15580
  }
15576
15581
  const pos = stage.getPointerPosition();
15582
+ mouseDownPos = pos;
15583
+ isPaint = [MarkupMode.Arrow, MarkupMode.Cloud, MarkupMode.Ellipse, MarkupMode.Line, MarkupMode.Rectangle].some((m) => m === this._markupMode);
15577
15584
  if (this._markupMode === MarkupMode.Line) {
15578
15585
  // add point twice, so we have some drawings even on a simple click
15579
15586
  lastLine = this.addLine([pos.x, pos.y, pos.x, pos.y]);
15580
- isPaint = true;
15581
- }
15582
- // 25.2 - currently only for debug purposes:
15583
- else if (this._markupMode === MarkupMode.Rectangle) {
15584
- this.addRectangle({ x: pos.x, y: pos.y }, 50, 50);
15585
- }
15586
- else if (this._markupMode === MarkupMode.Ellipse) {
15587
- this.addEllipse({ x: pos.x, y: pos.y }, { x: 50, y: 50 });
15588
- }
15589
- else if (this._markupMode === MarkupMode.Arrow) {
15590
- this.addArrow({ x: pos.x, y: pos.y }, { x: pos.x + 50, y: pos.y + 50 });
15591
- }
15592
- else if (this._markupMode === MarkupMode.Cloud) {
15593
- this.addCloud({ x: pos.x, y: pos.y }, 200, 400);
15594
15587
  }
15595
15588
  });
15596
15589
  stage.on("mouseup touchend", (e) => {
15597
15590
  if (!this._markupIsActive)
15598
15591
  return;
15592
+ if (isPaint) {
15593
+ const pos = stage.getPointerPosition();
15594
+ const defParams = mouseDownPos && pos.x === mouseDownPos.x && pos.y === mouseDownPos.y;
15595
+ const startX = defParams ? mouseDownPos.x : Math.min(mouseDownPos.x, pos.x);
15596
+ const startY = defParams ? mouseDownPos.y : Math.min(mouseDownPos.y, pos.y);
15597
+ const dX = defParams ? 200 : Math.abs(mouseDownPos.x - pos.x);
15598
+ const dY = defParams ? 200 : Math.abs(mouseDownPos.y - pos.y);
15599
+ if (defParams) {
15600
+ if (this._markupMode === MarkupMode.Rectangle) {
15601
+ this.addRectangle({ x: startX, y: startY }, dX, dY);
15602
+ }
15603
+ else if (this._markupMode === MarkupMode.Ellipse) {
15604
+ this.addEllipse({ x: startX, y: startY }, { x: dX / 2, y: dY / 2 });
15605
+ }
15606
+ else if (this._markupMode === MarkupMode.Arrow) {
15607
+ this.addArrow({ x: mouseDownPos.x, y: mouseDownPos.y }, { x: defParams ? mouseDownPos.x + 200 : pos.x, y: defParams ? startY : pos.y });
15608
+ }
15609
+ else if (this._markupMode === MarkupMode.Cloud) {
15610
+ this.addCloud({ x: startX, y: startY }, Math.max(100, dX), Math.max(100, dY));
15611
+ }
15612
+ }
15613
+ }
15614
+ lastObj = undefined;
15599
15615
  isPaint = false;
15600
15616
  });
15601
15617
  stage.on("mousemove touchmove", (e) => {
@@ -15607,8 +15623,47 @@
15607
15623
  // prevent scrolling on touch devices
15608
15624
  //e.evt.preventDefault();
15609
15625
  const pos = stage.getPointerPosition();
15610
- const newPoints = lastLine.points().concat([pos.x, pos.y]);
15611
- lastLine.points(newPoints);
15626
+ const defParams = mouseDownPos && pos.x === mouseDownPos.x && pos.y === mouseDownPos.y;
15627
+ const startX = defParams ? mouseDownPos.x : Math.min(mouseDownPos.x, pos.x);
15628
+ const startY = defParams ? mouseDownPos.y : Math.min(mouseDownPos.y, pos.y);
15629
+ const dX = defParams ? 200 : Math.abs(mouseDownPos.x - pos.x);
15630
+ const dY = defParams ? 200 : Math.abs(mouseDownPos.y - pos.y);
15631
+ if (this._markupMode === MarkupMode.Line) {
15632
+ lastLine.addPoints([{ x: pos.x, y: pos.y }]);
15633
+ }
15634
+ else if (this._markupMode === MarkupMode.Arrow) {
15635
+ if (lastObj)
15636
+ lastObj.setEndPoint(pos.x, pos.y);
15637
+ else
15638
+ lastObj = this.addArrow({ x: mouseDownPos.x, y: mouseDownPos.y }, { x: pos.x, y: pos.y });
15639
+ }
15640
+ else if (this._markupMode === MarkupMode.Rectangle) {
15641
+ if (lastObj) {
15642
+ lastObj.setPosition(startX, startY);
15643
+ lastObj.setWidth(dX);
15644
+ lastObj.setHeight(dY);
15645
+ }
15646
+ else
15647
+ lastObj = this.addRectangle({ x: startX, y: startY }, dX, dY);
15648
+ }
15649
+ else if (this._markupMode === MarkupMode.Ellipse) {
15650
+ if (lastObj) {
15651
+ lastObj.setPosition(startX, startY);
15652
+ lastObj.setRadiusX(dX);
15653
+ lastObj.setRadiusY(dY);
15654
+ }
15655
+ else
15656
+ lastObj = this.addEllipse({ x: startX, y: startY }, { x: dX, y: dY });
15657
+ }
15658
+ else if (this._markupMode === MarkupMode.Cloud) {
15659
+ if (lastObj) {
15660
+ lastObj.setPosition(startX, startY);
15661
+ lastObj.setWidth(Math.max(100, dX));
15662
+ lastObj.setHeight(Math.max(100, dY));
15663
+ }
15664
+ else
15665
+ lastObj = this.addCloud({ x: startX, y: startY }, dX, dY);
15666
+ }
15612
15667
  });
15613
15668
  // clicks should select/deselect shapes
15614
15669
  stage.on("click tap", (e) => {
@@ -15617,18 +15672,26 @@
15617
15672
  // if click on empty area - remove all selections
15618
15673
  if (e.target === stage) {
15619
15674
  if (this._markupMode === MarkupMode.Text) {
15620
- if (this._textInputRef)
15675
+ if (this._textInputRef && this._textInputRef.value)
15621
15676
  this.addText(this._textInputRef.value, this._textInputPos, this._textInputAngle);
15622
15677
  else if (transformer.nodes().length === 0) {
15623
15678
  const pos = stage.getPointerPosition();
15624
15679
  this.createTextInput(pos, e.evt.pageX, e.evt.pageY, 0, null);
15625
15680
  }
15626
15681
  }
15682
+ else if (this._markupMode === MarkupMode.Image) {
15683
+ if (this._imageInputRef && this._imageInputRef.value)
15684
+ this.addImage({ x: this._imageInputPos.x, y: this._imageInputPos.y }, this._imageInputRef.value, 0, 0, this._imageInputRef.value);
15685
+ else if (transformer.nodes().length === 0) {
15686
+ const pos = stage.getPointerPosition();
15687
+ this.createImageInput(pos);
15688
+ }
15689
+ }
15627
15690
  transformer.nodes([]);
15628
15691
  return;
15629
15692
  }
15630
15693
  if (e.target.className === "Text" && transformer.nodes().length === 1 && transformer.nodes()[0] === e.target) {
15631
- if (this._textInputRef)
15694
+ if (this._textInputRef && this._textInputRef.value)
15632
15695
  this.addText(this._textInputRef.value, this._textInputPos, this._textInputAngle);
15633
15696
  else
15634
15697
  this.createTextInput({ x: e.target.attrs.x, y: e.target.attrs.y }, e.evt.pageX, e.evt.pageY, e.target.attrs.rotation, e.target.attrs.text);
@@ -15637,6 +15700,16 @@
15637
15700
  else {
15638
15701
  this.removeTextInput();
15639
15702
  }
15703
+ if (e.target.className === "Image" && transformer.nodes().length === 1 && transformer.nodes()[0] === e.target) {
15704
+ if (this._imageInputRef && this._imageInputRef.value)
15705
+ this.addImage(this._imageInputPos, this._imageInputRef.value, 0, 0);
15706
+ else
15707
+ this.createImageInput({ x: e.target.attrs.x, y: e.target.attrs.y });
15708
+ return;
15709
+ }
15710
+ else {
15711
+ this.removeImageInput();
15712
+ }
15640
15713
  if (transformer.nodes().filter((x) => x.className === "Cloud").length > 0 || e.target.className === "Cloud") {
15641
15714
  transformer.rotateEnabled(false);
15642
15715
  }
@@ -15912,7 +15985,7 @@
15912
15985
  });
15913
15986
  const obj = konvaLine.ref();
15914
15987
  this._konvaLayer.add(obj);
15915
- return obj;
15988
+ return konvaLine;
15916
15989
  }
15917
15990
  createTextInput(pos, inputX, inputY, angle, text) {
15918
15991
  if (!this._textInputRef) {
@@ -15952,6 +16025,48 @@
15952
16025
  this._textInputPos = null;
15953
16026
  this._textInputAngle = 0;
15954
16027
  }
16028
+ createImageInput(pos) {
16029
+ if (!this._imageInputRef) {
16030
+ const convertBase64 = (file) => {
16031
+ return new Promise((resolve, reject) => {
16032
+ const fileReader = new FileReader();
16033
+ fileReader.readAsDataURL(file);
16034
+ fileReader.onload = () => {
16035
+ resolve(fileReader.result);
16036
+ };
16037
+ fileReader.onerror = (error) => {
16038
+ reject(error);
16039
+ };
16040
+ });
16041
+ };
16042
+ this._imageInputPos = pos;
16043
+ this._imageInputRef = document.createElement("input");
16044
+ this._imageInputRef.style.display = "none";
16045
+ this._imageInputRef.type = "file";
16046
+ this._imageInputRef.accept = "image/png, image/jpeg";
16047
+ this._imageInputRef.onchange = async (event) => {
16048
+ const file = event.target.files[0];
16049
+ const base64 = await convertBase64(file);
16050
+ this.addImage({ x: this._imageInputPos.x, y: this._imageInputPos.y }, base64.toString(), 0, 0);
16051
+ };
16052
+ this._imageInputRef.oncancel = (event) => {
16053
+ this.removeImageInput();
16054
+ };
16055
+ document.body.appendChild(this._imageInputRef);
16056
+ setTimeout(() => {
16057
+ this._imageInputRef.click();
16058
+ }, 50);
16059
+ }
16060
+ else {
16061
+ this.removeImageInput();
16062
+ }
16063
+ }
16064
+ removeImageInput() {
16065
+ var _a;
16066
+ (_a = this._imageInputRef) === null || _a === void 0 ? void 0 : _a.remove();
16067
+ this._imageInputRef = null;
16068
+ this._imageInputPos = null;
16069
+ }
15955
16070
  addText(specifiedText, position, angle, color, textSize, fontSize, id) {
15956
16071
  if (specifiedText) {
15957
16072
  const tol = 1.0e-6;
@@ -15998,7 +16113,7 @@
15998
16113
  });
15999
16114
  const obj = konvaRectangle.ref();
16000
16115
  this._konvaLayer.add(obj);
16001
- return obj;
16116
+ return konvaRectangle;
16002
16117
  }
16003
16118
  addEllipse(position, radius, lineWidth, color, id) {
16004
16119
  if (!position)
@@ -16012,7 +16127,7 @@
16012
16127
  });
16013
16128
  const obj = konvaEllipse.ref();
16014
16129
  this._konvaLayer.add(obj);
16015
- return obj;
16130
+ return konvaEllipse;
16016
16131
  }
16017
16132
  addArrow(start, end, color, id) {
16018
16133
  if (!start || !end)
@@ -16025,7 +16140,7 @@
16025
16140
  });
16026
16141
  const obj = konvaArrow.ref();
16027
16142
  this._konvaLayer.add(obj);
16028
- return obj;
16143
+ return konvaArrow;
16029
16144
  }
16030
16145
  addCloud(position, width, height, lineWidth, color, id) {
16031
16146
  if (!position || !width || !height)
@@ -16040,21 +16155,30 @@
16040
16155
  });
16041
16156
  const obj = konvaCloud.ref();
16042
16157
  this._konvaLayer.add(obj);
16043
- return obj;
16158
+ return konvaCloud;
16044
16159
  }
16045
16160
  addImage(position, src, width, height, id) {
16046
- if (!position || !width || !height)
16161
+ if (!position)
16047
16162
  return;
16048
- const konvaImage = new KonvaImage({
16049
- position,
16050
- src,
16051
- width,
16052
- height,
16053
- id,
16054
- });
16055
- const obj = konvaImage.ref();
16056
- this._konvaLayer.add(obj);
16057
- return obj;
16163
+ if (src) {
16164
+ const konvaImage = new KonvaImage({
16165
+ position,
16166
+ src,
16167
+ width,
16168
+ height,
16169
+ id,
16170
+ });
16171
+ const obj = konvaImage.ref();
16172
+ this._konvaLayer.add(obj);
16173
+ const trNodes = this._konvaTransformer.nodes();
16174
+ if (trNodes.length > 0) {
16175
+ // in case of edit - remove old Image placeholder object
16176
+ trNodes[0].destroy();
16177
+ this._konvaTransformer.nodes([]);
16178
+ }
16179
+ }
16180
+ this.removeImageInput();
16181
+ return;
16058
16182
  }
16059
16183
  }
16060
16184
 
@@ -16228,6 +16352,9 @@
16228
16352
  itr.delete();
16229
16353
  this._viewer.update();
16230
16354
  }
16355
+ colorizeSelectedMarkups(r = 255, g = 0, b = 0) {
16356
+ throw new Error("Not implemented yet");
16357
+ }
16231
16358
  setViewpoint(viewpoint) {
16232
16359
  function getLogicalPoint3dAsArray(point3d) {
16233
16360
  return [point3d.x, point3d.y, point3d.z];
@@ -16496,7 +16623,7 @@
16496
16623
  const event = { loaded, timeStamp, total, lengthComputable, type: "visualizeprogress" };
16497
16624
  onProgress === null || onProgress === void 0 ? void 0 : onProgress(event);
16498
16625
  });
16499
- this.visualizeJs = visualizeJs;
16626
+ this._visualizeJs = visualizeJs;
16500
16627
  this.visualizeJs.canvas = canvas;
16501
16628
  this.visualizeJs.Viewer.create();
16502
16629
  this.canvas = canvas;
@@ -16521,23 +16648,30 @@
16521
16648
  * release the `Viewer` instance.
16522
16649
  */
16523
16650
  dispose() {
16524
- var _a, _b, _c, _d;
16525
16651
  this.cancel();
16526
16652
  this.emitEvent({ type: "dispose" });
16527
16653
  if (this.frameId)
16528
16654
  cancelAnimationFrame(this.frameId);
16529
16655
  this.frameId = 0;
16530
16656
  this.setActiveDragger("");
16531
- (_a = this._zoomWheelDragger) === null || _a === void 0 ? void 0 : _a.dispose();
16532
- (_b = this._gestureManager) === null || _b === void 0 ? void 0 : _b.dispose();
16533
16657
  this.removeAllListeners();
16534
- (_c = this._resizeObserver) === null || _c === void 0 ? void 0 : _c.disconnect();
16658
+ if (this._gestureManager)
16659
+ this._gestureManager.dispose();
16660
+ this._gestureManager = undefined;
16661
+ if (this._zoomWheelDragger)
16662
+ this._zoomWheelDragger.dispose();
16663
+ this._zoomWheelDragger = undefined;
16664
+ if (this._resizeObserver)
16665
+ this._resizeObserver.disconnect();
16535
16666
  this._resizeObserver = undefined;
16536
16667
  this.markup.dispose();
16537
- this.canvasEvents.forEach((x) => { var _a; return (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.removeEventListener(x, this.canvaseventlistener); });
16538
- this.canvas = undefined;
16539
- (_d = this.visualizeJs) === null || _d === void 0 ? void 0 : _d.getViewer().clear();
16540
- this.visualizeJs = undefined;
16668
+ if (this.canvas) {
16669
+ this.canvasEvents.forEach((x) => this.canvas.removeEventListener(x, this.canvaseventlistener));
16670
+ this.canvas = undefined;
16671
+ }
16672
+ if (this._visualizeJs)
16673
+ this._visualizeJs.getViewer().clear();
16674
+ this._visualizeJs = undefined;
16541
16675
  return this;
16542
16676
  }
16543
16677
  /**
@@ -16640,6 +16774,13 @@
16640
16774
  this._isRunAsyncUpdate = false;
16641
16775
  }
16642
16776
  }
16777
+ /**
16778
+ * Returns `VisualizeJS`
16779
+ * {@link https://cloud.opendesign.com/docs/index.html#/visualizejs_api | module} instance.
16780
+ */
16781
+ get visualizeJs() {
16782
+ return this._visualizeJs;
16783
+ }
16643
16784
  /**
16644
16785
  * Returns `VisualizeJS`
16645
16786
  * {@link https://cloud.opendesign.com/docs/index.html#/visualizejs_api | module} instance.
@@ -16829,7 +16970,7 @@
16829
16970
  *
16830
16971
  * Fires:
16831
16972
  *
16832
- * - {@link ChangeActiveDragger | changeactivedragger}
16973
+ * - {@link ChangeActiveDraggerEvent | changeactivedragger}
16833
16974
  *
16834
16975
  * @param name - Dragger name. Can be one of the {@link Viewer#draggers | draggers} list.
16835
16976
  * @returns Returns active dragger instance or `null` if there is no dragger with the given name.
@@ -17189,6 +17330,16 @@
17189
17330
  colorizeAllMarkup(r = 255, g = 0, b = 0) {
17190
17331
  this.markup.colorizeAllMarkup(r, g, b);
17191
17332
  }
17333
+ /**
17334
+ * Colorize all selected markup entities with the specified color.
17335
+ *
17336
+ * @param r - `Red` part of color.
17337
+ * @param g - `Green` part of color.
17338
+ * @param b - `Blue` part of color.
17339
+ */
17340
+ colorizeSelectedMarkups(r = 255, g = 0, b = 0) {
17341
+ this.markup.colorizeSelectedMarkups(r, g, b);
17342
+ }
17192
17343
  /**
17193
17344
  * Add an empty markup entity to the overlay.
17194
17345
  */
@@ -17209,10 +17360,9 @@
17209
17360
  }
17210
17361
  /**
17211
17362
  * Draw a viewpoint. To get a list of available model viewpoints, use the
17212
- * {@link Model#getViewpoints | Model.getViewpoints()} or
17213
- * {@link File#getViewpoints | File.getViewpoints()}.
17363
+ * {@link Model.getViewpoints()} or {@link File.getViewpoints()}.
17214
17364
  *
17215
- * @param viewpoint - Viewpoint.
17365
+ * @param viewpoint - Viewpoint data.
17216
17366
  */
17217
17367
  drawViewpoint(viewpoint) {
17218
17368
  this.setOrthogonalCameraSettings(viewpoint.orthogonal_camera);
@@ -17221,8 +17371,7 @@
17221
17371
  }
17222
17372
  /**
17223
17373
  * Create a viewpoint. To add a viewpoint to the list of model viewpoints, use the
17224
- * {@link Model#saveViewpoint | Model.saveViewpoint()} or
17225
- * {@link File#saveViewpoint | File.saveViewpoint()}.
17374
+ * {@link Model.saveViewpoint()} or {@link File.saveViewpoint()}.
17226
17375
  */
17227
17376
  createViewpoint() {
17228
17377
  const vp = this.markup.getViewpoint();