three.js 图纸3D标注

1,549 阅读12分钟

实现 dwg 图纸的 3d 标注功能;云线标注,矩形标注,圆形标注,剪头标注,文字标注。

代码架构

基类封装

分析需求后可知,绘制交互、绘制结果,基本类似可复用,所以设计一个标记操作基类、标记结果基类,有差别的交互细节可在子类中单独实现

// OpAnnotationDwg.ts

import {
  Plane,
  Raycaster,
  Vector3,
  Vector2,
  Mesh,
  Scene,
  Object3D,
  Font,
  LineBasicMaterial,
  Material,
  CircleBufferGeometry,
} from "../../WebViewer/THREE/Three";
// @ts-ignore
import { AutoTransform } from "../../WebViewer/Core/AutoTransform.js";

let currentLeft: number;
let currentTop: number;
let currentPageX: number;
let currentPageY: number;
let isMobileMove: boolean;
let isDownMove: boolean;

let _viewZ = new Vector3();
let _viewPlane = new Plane();
let _rayCaster = new Raycaster();
// @ts-ignore
_rayCaster.params.Line.threshold = 10;

class OpAnnotationDwg {
  app: any;

  private _onPointerDown: (event: any) => void;
  private _onPointerMove: (event: any) => void;
  private _onPointerUp: (event: any) => void;
  private _onMouseZoom: (event: any) => void;
  private _onKeyDown: (event: any) => void;

  tempAnnotation: AnnotationMesh | null;
  selectedAnnotation: AnnotationMesh | null;
  editDragIndex: number;

  annotationMeshArr: Array<AnnotationMesh>;

  lineSize: number;
  textSize: number;
  color: string;

  mouseSimulator: HTMLElement | null;

  constructor(app: any) {
    this.app = app;

    this._onPointerDown = onPointerDown.bind(this);
    this._onPointerMove = onPointerMove.bind(this);
    this._onPointerUp = onPointerUp.bind(this);
    this._onMouseZoom = onMouseZoom.bind(this);
    this._onKeyDown = onKeyDown.bind(this);

    this.tempAnnotation = null;
    this.selectedAnnotation = null;
    this.editDragIndex = -1;

    this.annotationMeshArr = [];

    this.lineSize = 2;
    this.textSize = 500;
    this.color = "#ff0000";

    this.mouseSimulator = null;
  }

  initEvent() {
    if (isMobile()) {
      this.mouseSimulator = document.createElement("div");
      this.mouseSimulator.setAttribute("class", "ccbim__mouseImg");
      const html = `<div class="ccbim__mouseImg__imshar"></div><img src='' alt='模拟鼠标'/>`;
      this.mouseSimulator.innerHTML = html;
      this.app.parentElement.appendChild(this.mouseSimulator);
      this.mouseSimulator.lastChild.src = "../../assets/images/mouse1.png";

      this.mouseSimulator.addEventListener("touchstart", this._onPointerDown);
      this.mouseSimulator.addEventListener("touchmove", this._onPointerMove);
      this.mouseSimulator.addEventListener("touchend", this._onPointerUp);
    } else {
      this.app.bimViewer.dom.addEventListener(
        "pointerdown",
        this._onPointerDown
      );
      this.app.bimViewer.dom.addEventListener(
        "pointermove",
        this._onPointerMove
      );
      this.app.bimViewer.dom.addEventListener("pointerup", this._onPointerUp);
      this.app.bimViewer.dom.addEventListener("wheel", this._onMouseZoom);
      document.addEventListener("keydown", this._onKeyDown);
    }
  }

  disposeEvent() {
    if (isMobile()) {
      if (this.mouseSimulator) {
        this.mouseSimulator.removeEventListener(
          "touchstart",
          this._onPointerDown
        );
        this.mouseSimulator.removeEventListener(
          "touchmove",
          this._onPointerMove
        );
        this.mouseSimulator.removeEventListener("touchend", this._onPointerUp);

        this.app.parentElement.removeChild(this.mouseSimulator);
        this.mouseSimulator = null;
      }
    } else {
      this.app.bimViewer.dom.removeEventListener(
        "pointerdown",
        this._onPointerDown
      );
      this.app.bimViewer.dom.removeEventListener(
        "pointermove",
        this._onPointerMove
      );
      this.app.bimViewer.dom.removeEventListener(
        "pointerup",
        this._onPointerUp
      );
    }

    this.stopAnnotation();
    this.setSelectAnnotation(null);
  }

  private pointerDown(e: any) {
    if (e.button === 0) e.preventDefault();

    isMobileMove = false;

    if (isMobile()) {
      e.preventDefault();

      if (this.mouseSimulator) {
        currentLeft = this.mouseSimulator.offsetLeft;
        currentTop = this.mouseSimulator.offsetTop;

        this.mouseSimulator.lastChild.src = "../../assets/images/mouse2.png";
      }

      currentPageX = e.touches[0].pageX;
      currentPageY = e.touches[0].pageY;
    } else {
      this.handleClick(e);
    }
  }

  private pointerMove(e: any): void {
    this.app.getViewer().cameraChanged();

    let event: any = e;

    if (event.buttons & 2 || event.buttons & 4) {
      isDownMove = true;
    }

    if (isMobile()) {
      e.preventDefault();

      let eMovePageX = e.touches[0].pageX;
      let eMovePageY = e.touches[0].pageY;

      if (
        Math.abs(eMovePageX - currentPageX) > 20 ||
        Math.abs(eMovePageY - currentPageY) > 20
      ) {
        isMobileMove = true;
      }

      let left = eMovePageX - currentPageX + currentLeft;
      let top = eMovePageY - currentPageY + currentTop;

      left < 0 && (left = 0);
      top < 0 && (top = 0);

      if (this.mouseSimulator && this.mouseSimulator.parentElement) {
        let maxLeft =
          this.mouseSimulator.parentElement.clientWidth -
          this.mouseSimulator.clientWidth;
        let maxTop =
          this.mouseSimulator.parentElement.clientHeight -
          this.mouseSimulator.clientHeight;

        left > maxLeft && (left = maxLeft);
        top > maxTop && (top = maxTop);

        this.mouseSimulator.style.left = left + "px";
        this.mouseSimulator.style.top = top + "px";

        event = {
          offsetX: left,
          offsetY: top,
        };
      }
    }

    let point = this.getMouseWorldPos(event);

    if (this.tempAnnotation) {
      const index = this.tempAnnotation.ptsArr.length - 1;
      const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
      this.tempAnnotation.editPoint({ point, index, pixelScale2d });

      return;
    }

    if (this.editDragIndex > -1) {
      const index = this.editDragIndex;
      const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
      this.selectedAnnotation?.editPoint({ point, index, pixelScale2d });

      return;
    }

    this.trySelectMeasure(event);
    this.trySelectDrag(event);
  }

  private pointerUp(e: any): void {
    if (isMobile()) {
      e.preventDefault();

      this.mouseSimulator.lastChild.src = "../../assets/images/mouse1.png";

      this.handleClick(e);
    }
  }

  private handleClick(e: PointerEvent) {
    let event: any = e;

    if (isMobile()) {
      if (isMobileMove) return;

      if (this.mouseSimulator) {
        const left = this.mouseSimulator.offsetLeft;
        const top = this.mouseSimulator.offsetTop;
        event = {
          offsetX: left,
          offsetY: top,
        };
      }
    }

    if (isDownMove) {
      isDownMove = false;
      return;
    }

    let point = this.getMouseWorldPos(event);

    if (this.tempAnnotation) {
      this.tempAnnotationPointerUp({ event, point });
      return;
    }

    const selectedAnnotation = this.trySelectMeasure(event);
    if (selectedAnnotation) {
      this.setSelectAnnotation(selectedAnnotation);
      return;
    }

    const selectDrag = this.trySelectDrag(event);
    if (selectDrag) {
      if (this.editDragIndex === -1) {
        this.editDragIndex = selectDrag.index;
      } else {
        this.editDragIndex = -1;
      }
      return;
    }

    if (event.button === 1 || event.button === 2) return;

    const measure = this.createNewAnnotation(point);
    if (measure) {
      this.tempAnnotation = measure;
      this.annotationMeshArr.push(measure);
    }

    this.setSelectAnnotation(null);
  }

  private mouseZoom(e: Event) {
    const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
    this.annotationMeshArr.forEach((item) => {
      item.updateLineTextShape(pixelScale2d);
    });
    this.app.getViewer().cameraChanged();
  }

  private keyDown(e: KeyboardEvent) {
    if (e.keyCode === 27) {
      if (this.tempAnnotation) {
        this.tempAnnotation.dispose();
        this.tempAnnotation = null;
        this.app.getViewer().cameraChanged();
      }
    }
  }

  tempAnnotationPointerUp(param: { event: PointerEvent; point: Vector3 }) {}

  createNewAnnotation(point: Vector3): any {}

  stopAnnotation() {
    this.setSelectAnnotation(this.tempAnnotation);

    this.tempAnnotation = null;
  }

  trySelectMeasure(e: PointerEvent) {
    this.getMouseRaycaster(e, _rayCaster);

    let measure = null;
    this.annotationMeshArr.forEach((item) => {
      if (item !== this.selectedAnnotation) {
        const interse = item.intersectObjects(_rayCaster);
        if (interse) {
          measure = item;
          item.changeMeshMaterial(item.hoverMaterial);
        } else {
          item.changeMeshMaterial(item.material);
        }
      } else {
        item.changeMeshMaterial(item.material);
      }
    });

    return measure;
  }

  trySelectDrag(e: PointerEvent): any {
    this.getMouseRaycaster(e, _rayCaster);

    let drag = null;
    this.annotationMeshArr.forEach((item) => {
      if (item === this.selectedAnnotation) {
        const interse = item.intersectDrag(_rayCaster);
        drag = interse;
      }
    });

    return drag;
  }

  setSelectAnnotation(mesh: AnnotationMesh | null) {
    this.annotationMeshArr.forEach((item) => {
      item.setDragHandleVisible(false);
    });

    if (mesh) {
      this.selectedAnnotation = mesh;
      this.selectedAnnotation.setDragHandleVisible(true);
    } else {
      this.selectedAnnotation = null;
    }
    this.app.getViewer().cameraChanged();
  }

  deleteSelectAnnotation() {
    if (!this.selectedAnnotation) return;

    for (let i = 0, il = this.annotationMeshArr.length; i < il; ++i) {
      if (this.annotationMeshArr[i] === this.selectedAnnotation) {
        this.annotationMeshArr.splice(i, 1);
        break;
      }
    }

    this.selectedAnnotation.dispose();
    this.selectedAnnotation = null;
  }

  setAnnotationMeshArrVisible(value: boolean) {
    this.annotationMeshArr.forEach((item) => {
      item.setMeshVisible(value);
    });
  }

  deleteAllAnnotation() {
    this.stopAnnotation();

    this.annotationMeshArr.forEach((item) => {
      item.dispose();
    });
    this.annotationMeshArr = [];
  }

  setNextLineSize(val: number) {
    this.lineSize = val;
  }

  setNextTextSize(val: number) {
    this.textSize = val;
  }

  setNextColor(color: string) {
    this.color = color;
  }

  getAnnotationData() {
    let data: any = [];
    this.annotationMeshArr.forEach((item) => {
      const d = item.getData();
      data.push(d);
    });

    return data;
  }

  setAnnotationData(data: Array<any>) {
    data.forEach((item) => {
      const measure = this.createAnnotationFromData(item);
      this.annotationMeshArr.push(measure);
    });
  }

  createAnnotationFromData(data: any): any {}

  dispose() {
    this.disposeEvent();
    this.deleteAllAnnotation();
  }

  getMouseWorldPos(e: PointerEvent) {
    const camera = this.app.bimViewer.getMainCamera();

    _viewZ.subVectors(camera.position, camera.target).normalize();
    _viewPlane.setFromNormalAndCoplanarPoint(_viewZ, camera.target);

    this.getMouseRaycaster(e, _rayCaster);

    let target = new Vector3();
    _rayCaster.ray.intersectPlane(_viewPlane, target);

    return target;
  }

  getMouseRaycaster(e: PointerEvent, rayster: Raycaster) {
    const _windowPos = this.getMouseWindowPos(e);

    const camera = this.app.bimViewer.getMainCamera();

    rayster.far = Infinity;
    rayster.near = camera.near;
    rayster.setFromCamera(_windowPos, camera);
  }

  getMouseWindowPos(e: PointerEvent) {
    const renderDomRect = this.app.bimViewer
      .getRenderDom()
      .getBoundingClientRect();

    let windowCoordinate = new Vector2();
    windowCoordinate.set(
      (e.offsetX / renderDomRect.width) * 2 - 1,
      (e.offsetY / renderDomRect.height) * -2 + 1
    );

    return windowCoordinate;
  }
}

function onPointerDown(this: any, e: PointerEvent) {
  this.pointerDown(e);
}

function onPointerMove(this: any, e: PointerEvent) {
  this.pointerMove(e);
}

function onPointerUp(this: any, e: PointerEvent) {
  this.pointerUp(e);
}

function onMouseZoom(this: any, e: Event) {
  this.mouseZoom(e);
}

function onKeyDown(this: any, e: Event) {
  this.keyDown(e);
}

class AnnotationMesh {
  ptsArr: Array<Vector3>;
  lineShape!: Mesh;
  dragHandleArr: Array<DragHadle>;

  material: LineBasicMaterial;
  hoverMaterial: LineBasicMaterial;

  scene: Scene;
  font: Font;

  lineSize: number;
  textSize: number;
  color: string;

  constructor(param: {
    start: Vector3;
    end: Vector3;
    font: Font;
    scene: Scene;
    lineSize: number;
    textSize: number;
    color: string;
  }) {
    this.material = new LineBasicMaterial({ color: param.color });
    this.hoverMaterial = new LineBasicMaterial({ color: 0xffff00 });

    this.scene = param.scene;
    this.font = param.font;

    this.ptsArr = [param.start.clone(), param.end.clone()];
    this.dragHandleArr = [];

    this.lineSize = param.lineSize;
    this.textSize = param.textSize;
    this.color = param.color;
  }

  addPoint(point: Vector3) {
    this.ptsArr.push(point);

    const drag = this.createDragHandle(point);
    this.dragHandleArr.push(drag);
    drag.install(this.scene);
  }

  editPoint(param: { point: Vector3; index: number; pixelScale2d: number }) {
    this.ptsArr[param.index].copy(param.point);

    this.updateLineTextShape(param.pixelScale2d);
    this.updateDragShape(param.index - 1);
  }

  updateLineTextShape(pixelScale2d: number) {}

  updateDragShape(current: number) {
    this.dragHandleArr[current + 1].setPosition(this.ptsArr[current + 1]);
  }

  intersectObjects(_rayCaster: Raycaster) {
    let children: any = [];
    children.push(this.lineShape);

    const intersects = _rayCaster.intersectObjects(children, true);
    if (intersects.length > 0) {
      return true;
    }

    return false;
  }

  intersectDrag(_rayCaster: Raycaster) {
    for (let i = 0; i < this.dragHandleArr.length; i++) {
      const obj = this.dragHandleArr[i].autoTransform.children[0];
      const intersect = _rayCaster.intersectObject(obj, false);
      if (intersect.length > 0) {
        obj.material = this.hoverMaterial;

        return {
          obj: this.dragHandleArr[i],
          index: i,
        };
      } else {
        obj.material = this.material;
      }
    }

    return false;
  }

  changeMeshMaterial(material: Material) {
    if (this.lineShape.material === material) {
      return;
    }

    this.lineShape.material = material;
  }

  updateText() {}

  createDragHandle(point: Vector3) {
    const dragHandle = new DragHadle();
    dragHandle.setAutoScale(true);
    dragHandle.setAlignToScreen(true);

    const circle = new CircleBufferGeometry(6);
    dragHandle.add(new Mesh(circle, this.material));

    dragHandle.setPosition(point);
    dragHandle.setVisible(false);

    return dragHandle;
  }

  setDragHandleVisible(value: boolean) {
    this.dragHandleArr.forEach((item) => {
      item.setVisible(value);
    });
  }

  setMeshVisible(value: boolean) {
    if (value === false) {
      this.dragHandleArr.forEach((item) => {
        item.setVisible(value);
      });
    }

    this.lineShape.visible = value;
  }

  getData() {
    let pointArr: any = [];
    this.ptsArr.forEach((item) => {
      const arr = item.toArray();
      pointArr.push(arr);
    });

    let obj = {
      ptsArr: pointArr,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
    };

    return obj;
  }

  setData() {}

  dispose() {
    this.dragHandleArr.forEach((item) => {
      item.unInstall(this.scene);
    });
    this.dragHandleArr = [];

    this.scene.remove(this.lineShape);
  }
}

class Handle {
  autoTransform: AutoTransform;

  constructor() {
    this.autoTransform = new AutoTransform();
  }

  install(scene: Scene) {
    scene.add(this.autoTransform);
  }

  unInstall(scene: Scene) {
    this.autoTransform.removeAll();
    scene.remove(this.autoTransform);
  }

  setAutoScale(value: boolean) {
    this.autoTransform.setAutoScale(value);
  }

  setAlignToScreen(value: boolean) {
    this.autoTransform.setAlignToScreen(value);
  }

  setPosition(value: Vector3) {
    this.autoTransform.setPosition(value);
  }

  getPosition() {
    return this.autoTransform.getPosition();
  }

  setVisible(value: boolean) {
    this.autoTransform.visible = value;
  }

  add(mesh: Object3D) {
    this.autoTransform.add(mesh);
  }

  removeAll() {
    this.autoTransform.removeAll();
  }
}

class DragHadle extends Handle {
  constructor() {
    super();
  }
}

function isMobile() {
  var userAgent = navigator.userAgent;
  return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
    userAgent
  );
}

export { OpAnnotationDwg, AnnotationMesh, DragHadle };

子类实现

不同的功能,交互细节上有点差异,绘制的几何形状也有差别,在基类中抽离公共的部分后,在子类里实现具体的功能

云线框

核心算法在于云线数据如何生成

// OpAnnotationDwgCloud.ts

import {
  Vector3,
  Font,
  Scene,
  Shape,
  ShapeGeometry,
  Mesh,
  Path,
  Vector2,
} from "../../WebViewer/THREE/Three";
import { AnnotationMesh, OpAnnotationDwg } from "./OpAnnotationDwg";

class OpAnnotationDwgCloud extends OpAnnotationDwg {
  constructor(app: any) {
    super(app);
  }

  tempAnnotationPointerUp() {
    if (!this.tempAnnotation) return;
    const length = this.tempAnnotation.ptsArr.length - 1;
    let start = this.tempAnnotation.ptsArr[length - 1].clone();
    let end = this.tempAnnotation.ptsArr[length].clone();
    let distance = start.distanceTo(end);
    if (distance === 0) return;

    super.stopAnnotation();
  }

  createNewAnnotation(point: Vector3) {
    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationCloudMesh({
      start: point,
      end: point,
      font,
      scene,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
    });
    return mesh;
  }

  createAnnotationFromData(data: {
    ptsArr: Array<number[]>;
    lineSize: number;
    textSize: number;
    color: string;
  }) {
    let pointArr: Vector3[] = [];
    data.ptsArr.forEach((item: number[]) => {
      const v3 = new Vector3().fromArray(item);
      pointArr.push(v3);
    });

    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationCloudMesh({
      start: pointArr[0],
      end: pointArr[1],
      font,
      scene,
      lineSize: data.lineSize,
      textSize: data.textSize,
      color: data.color,
    });

    const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
    mesh.updateLineTextShape(pixelScale2d);

    mesh.setDragHandleVisible(false);

    return mesh;
  }
}

class AnnotationCloudMesh extends AnnotationMesh {
  constructor(param: {
    start: Vector3;
    end: Vector3;
    font: Font;
    scene: Scene;
    lineSize: number;
    textSize: number;
    color: string;
  }) {
    super(param);

    this._createLineTextShape();
  }

  private _createLineTextShape() {
    const shape = new Shape();
    const shapeGeo = new ShapeGeometry(shape);
    this.lineShape = new Mesh(shapeGeo, this.material);
    this.scene.add(this.lineShape);

    this.ptsArr.forEach((point) => {
      const drag = this.createDragHandle(point);
      this.dragHandleArr.push(drag);
      drag.install(this.scene);
    });
  }

  updateLineTextShape(pixelScale2d: number) {
    pixelScale2d = pixelScale2d * (this.lineSize + 1);
    if (pixelScale2d < 0.00099) pixelScale2d = 0.00099;

    const startP = new Vector2(
      Math.min(this.ptsArr[0].x, this.ptsArr[1].x),
      Math.max(this.ptsArr[0].y, this.ptsArr[1].y)
    );
    const endP = new Vector2(
      Math.max(this.ptsArr[0].x, this.ptsArr[1].x),
      Math.min(this.ptsArr[0].y, this.ptsArr[1].y)
    );

    let width = Math.abs(endP.x - startP.x);
    let height = Math.abs(endP.y - startP.y);
    if (width === 0 || height === 0) return;

    const lenW = width / 10;
    const lenH = height / 10;
    const len = Math.max(lenW, lenH);
    const lenHalf = len / 2;
    const segmentW = Math.ceil(width / len);
    const segmentH = Math.ceil(height / len);
    const LT = startP;
    const RT = new Vector2(endP.x, startP.y);
    const RB = endP;
    const LB = new Vector2(startP.x, endP.y);
    const arr1 = divideLineSegment(LT, RT, segmentW);
    const arr2 = divideLineSegment(RT, RB, segmentH);
    const arr3 = divideLineSegment(RB, LB, segmentW);
    const arr4 = divideLineSegment(LB, LT, segmentH);

    const shape = new Shape();
    shape.moveTo(startP.x, startP.y);
    for (let i = 0; i < arr1.length - 1; i++) {
      shape.bezierCurveTo(
        arr1[i].x,
        arr1[i].y,
        arr1[i].x + lenHalf,
        arr1[i].y + lenHalf,
        arr1[i + 1].x,
        arr1[i + 1].y
      );
    }
    for (let i = 0; i < arr2.length - 1; i++) {
      shape.bezierCurveTo(
        arr2[i].x,
        arr2[i].y,
        arr2[i].x + lenHalf,
        arr2[i].y - lenHalf,
        arr2[i + 1].x,
        arr2[i + 1].y
      );
    }
    for (let i = 0; i < arr3.length - 1; i++) {
      shape.bezierCurveTo(
        arr3[i].x,
        arr3[i].y,
        arr3[i].x - lenHalf,
        arr3[i].y - lenHalf,
        arr3[i + 1].x,
        arr3[i + 1].y
      );
    }
    for (let i = 0; i < arr4.length - 1; i++) {
      shape.bezierCurveTo(
        arr4[i].x,
        arr4[i].y,
        arr4[i].x - lenHalf,
        arr4[i].y + lenHalf,
        arr4[i + 1].x,
        arr4[i + 1].y
      );
    }
    shape.closePath();

    const holePath = new Path();
    holePath.moveTo(startP.x, startP.y - pixelScale2d);
    for (let i = 0; i < arr1.length - 1; i++) {
      holePath.bezierCurveTo(
        arr1[i].x,
        arr1[i].y - pixelScale2d,
        arr1[i].x + lenHalf,
        arr1[i].y + lenHalf - pixelScale2d,
        arr1[i + 1].x,
        arr1[i + 1].y - pixelScale2d
      );
    }
    for (let i = 0; i < arr2.length - 1; i++) {
      holePath.bezierCurveTo(
        arr2[i].x - pixelScale2d,
        arr2[i].y,
        arr2[i].x + lenHalf - pixelScale2d,
        arr2[i].y - lenHalf,
        arr2[i + 1].x - pixelScale2d,
        arr2[i + 1].y
      );
    }
    for (let i = 0; i < arr3.length - 1; i++) {
      holePath.bezierCurveTo(
        arr3[i].x,
        arr3[i].y + pixelScale2d,
        arr3[i].x - lenHalf,
        arr3[i].y - lenHalf + pixelScale2d,
        arr3[i + 1].x,
        arr3[i + 1].y + pixelScale2d
      );
    }
    for (let i = 0; i < arr4.length - 1; i++) {
      holePath.bezierCurveTo(
        arr4[i].x + pixelScale2d,
        arr4[i].y,
        arr4[i].x - lenHalf + pixelScale2d,
        arr4[i].y + lenHalf,
        arr4[i + 1].x + pixelScale2d,
        arr4[i + 1].y
      );
    }
    holePath.closePath();

    shape.holes.push(holePath);

    const shapeGeo = new ShapeGeometry(shape);
    this.lineShape.geometry.dispose();
    this.lineShape.geometry = shapeGeo;
  }
}

function divideLineSegment(start: Vector2, end: Vector2, n: number) {
  const points = [];
  const x1 = start.x;
  const y1 = start.y;
  const x2 = end.x;
  const y2 = end.y;

  for (let i = 0; i <= n; i++) {
    const t = i / n;
    const x = x1 + t * (x2 - x1);
    const y = y1 + t * (y2 - y1);
    points.push({ x, y });
  }

  return points;
}

export { OpAnnotationDwgCloud };

矩形框

// OpAnnotationDwgRect.ts

import {
  Vector3,
  Font,
  Scene,
  Shape,
  ShapeGeometry,
  Mesh,
  Path,
  Vector2,
} from "../../WebViewer/THREE/Three";
import { AnnotationMesh, OpAnnotationDwg } from "./OpAnnotationDwg";

class OpAnnotationDwgRect extends OpAnnotationDwg {
  constructor(app: any) {
    super(app);
  }

  tempAnnotationPointerUp() {
    if (!this.tempAnnotation) return;
    const length = this.tempAnnotation.ptsArr.length - 1;
    let start = this.tempAnnotation.ptsArr[length - 1].clone();
    let end = this.tempAnnotation.ptsArr[length].clone();
    let distance = start.distanceTo(end);
    if (distance === 0) return;

    super.stopAnnotation();
  }

  createNewAnnotation(point: Vector3) {
    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationRectMesh({
      start: point,
      end: point,
      font,
      scene,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
    });
    return mesh;
  }

  createAnnotationFromData(data: {
    ptsArr: Array<number[]>;
    lineSize: number;
    textSize: number;
    color: string;
  }) {
    let pointArr: Vector3[] = [];
    data.ptsArr.forEach((item: number[]) => {
      const v3 = new Vector3().fromArray(item);
      pointArr.push(v3);
    });

    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationRectMesh({
      start: pointArr[0],
      end: pointArr[1],
      font,
      scene,
      lineSize: data.lineSize,
      textSize: data.textSize,
      color: data.color,
    });

    const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
    mesh.updateLineTextShape(pixelScale2d);

    mesh.setDragHandleVisible(false);

    return mesh;
  }
}

class AnnotationRectMesh extends AnnotationMesh {
  constructor(param: {
    start: Vector3;
    end: Vector3;
    font: Font;
    scene: Scene;
    lineSize: number;
    textSize: number;
    color: string;
  }) {
    super(param);

    this._createLineTextShape();
  }

  private _createLineTextShape() {
    const shape = new Shape();
    const shapeGeo = new ShapeGeometry(shape);
    this.lineShape = new Mesh(shapeGeo, this.material);
    this.scene.add(this.lineShape);

    this.ptsArr.forEach((point) => {
      const drag = this.createDragHandle(point);
      this.dragHandleArr.push(drag);
      drag.install(this.scene);
    });
  }

  updateLineTextShape(pixelScale2d: number) {
    pixelScale2d = pixelScale2d * (this.lineSize + 1);
    if (pixelScale2d < 0.00099) pixelScale2d = 0.00099;

    const startP = new Vector2(
      Math.min(this.ptsArr[0].x, this.ptsArr[1].x),
      Math.max(this.ptsArr[0].y, this.ptsArr[1].y)
    );
    const endP = new Vector2(
      Math.max(this.ptsArr[0].x, this.ptsArr[1].x),
      Math.min(this.ptsArr[0].y, this.ptsArr[1].y)
    );

    const shape = new Shape();
    shape.moveTo(startP.x, startP.y);
    shape.lineTo(endP.x, startP.y);
    shape.lineTo(endP.x, endP.y);
    shape.lineTo(startP.x, endP.y);
    shape.closePath();

    const holePath = new Path();
    holePath.moveTo(startP.x + pixelScale2d, startP.y - pixelScale2d);
    holePath.lineTo(endP.x - pixelScale2d, startP.y - pixelScale2d);
    holePath.lineTo(endP.x - pixelScale2d, endP.y + pixelScale2d);
    holePath.lineTo(startP.x + pixelScale2d, endP.y + pixelScale2d);
    holePath.closePath();

    shape.holes.push(holePath);

    const shapeGeo = new ShapeGeometry(shape);
    this.lineShape.geometry.dispose();
    this.lineShape.geometry = shapeGeo;
  }
}

export { OpAnnotationDwgRect };

圆形框

// OpAnnotationDwgCircle

import {
  Vector3,
  Font,
  Scene,
  Shape,
  ShapeGeometry,
  Mesh,
  Path,
  Vector2,
} from "../../WebViewer/THREE/Three";
import { AnnotationMesh, OpAnnotationDwg } from "./OpAnnotationDwg";

class OpAnnotationDwgCircle extends OpAnnotationDwg {
  constructor(app: any) {
    super(app);
  }

  tempAnnotationPointerUp() {
    if (!this.tempAnnotation) return;
    const length = this.tempAnnotation.ptsArr.length - 1;
    let start = this.tempAnnotation.ptsArr[length - 1].clone();
    let end = this.tempAnnotation.ptsArr[length].clone();
    let distance = start.distanceTo(end);
    if (distance === 0) return;

    super.stopAnnotation();
  }

  createNewAnnotation(point: Vector3) {
    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationCircleMesh({
      start: point,
      end: point,
      font,
      scene,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
    });
    return mesh;
  }

  createAnnotationFromData(data: {
    ptsArr: Array<number[]>;
    lineSize: number;
    textSize: number;
    color: string;
  }) {
    let pointArr: Vector3[] = [];
    data.ptsArr.forEach((item: number[]) => {
      const v3 = new Vector3().fromArray(item);
      pointArr.push(v3);
    });

    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationCircleMesh({
      start: pointArr[0],
      end: pointArr[1],
      font,
      scene,
      lineSize: data.lineSize,
      textSize: data.textSize,
      color: data.color,
    });

    const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
    mesh.updateLineTextShape(pixelScale2d);

    mesh.setDragHandleVisible(false);

    return mesh;
  }
}

class AnnotationCircleMesh extends AnnotationMesh {
  constructor(param: {
    start: Vector3;
    end: Vector3;
    font: Font;
    scene: Scene;
    lineSize: number;
    textSize: number;
    color: string;
  }) {
    super(param);

    this._createLineTextShape();
  }

  private _createLineTextShape() {
    const shape = new Shape();
    const shapeGeo = new ShapeGeometry(shape);
    this.lineShape = new Mesh(shapeGeo, this.material);
    this.scene.add(this.lineShape);

    this.ptsArr.forEach((point) => {
      const drag = this.createDragHandle(point);
      this.dragHandleArr.push(drag);
      drag.install(this.scene);
    });
  }

  updateLineTextShape(pixelScale2d: number) {
    pixelScale2d = pixelScale2d * (this.lineSize + 1);
    if (pixelScale2d < 0.00099) pixelScale2d = 0.00099;

    const startP = new Vector2(
      Math.min(this.ptsArr[0].x, this.ptsArr[1].x),
      Math.max(this.ptsArr[0].y, this.ptsArr[1].y)
    );
    const endP = new Vector2(
      Math.max(this.ptsArr[0].x, this.ptsArr[1].x),
      Math.min(this.ptsArr[0].y, this.ptsArr[1].y)
    );

    const centerX = (startP.x + endP.x) / 2;
    const centerY = (startP.y + endP.y) / 2;
    const a = Math.abs((endP.x - startP.x) / 2);
    const b = Math.abs((endP.y - startP.y) / 2);

    const shape = new Shape();
    for (let i = 0; i <= 2 * Math.PI; i += 0.01) {
      const x = a * Math.cos(i);
      const y = b * Math.sin(i);
      if (i === 0) {
        shape.moveTo(x + centerX, y + centerY);
      } else {
        shape.lineTo(x + centerX, y + centerY);
      }
    }
    shape.closePath();

    const holePath = new Path();
    for (let i = 0; i <= 2 * Math.PI; i += 0.01) {
      const x = (a - pixelScale2d) * Math.cos(i);
      const y = (b - pixelScale2d) * Math.sin(i);
      if (i === 0) {
        holePath.moveTo(x + centerX, y + centerY);
      } else {
        holePath.lineTo(x + centerX, y + centerY);
      }
    }
    holePath.closePath();

    shape.holes.push(holePath);

    const shapeGeo = new ShapeGeometry(shape);
    this.lineShape.geometry.dispose();
    this.lineShape.geometry = shapeGeo;
  }
}

export { OpAnnotationDwgCircle };

箭头(引线文字标注)

核心算法在于箭头数据如何生成

// OpAnnotationDwgLead.ts

import {
  Vector3,
  Font,
  Scene,
  Shape,
  ShapeGeometry,
  Mesh,
  TextGeometry,
  Raycaster,
  Material,
} from "../../WebViewer/THREE/Three";
import { AnnotationMesh, OpAnnotationDwg } from "./OpAnnotationDwg";

class OpAnnotationDwgLead extends OpAnnotationDwg {
  finishcallback: Function;

  constructor(app: any) {
    super(app);

    this.finishcallback = () => {};
  }

  bindFinishCallback(callback: Function) {
    if (callback instanceof Function) {
      this.finishcallback = callback;
    }
  }

  tempAnnotationPointerUp() {
    if (!this.tempAnnotation) return;
    const length = this.tempAnnotation.ptsArr.length - 1;
    let start = this.tempAnnotation.ptsArr[length - 1].clone();
    let end = this.tempAnnotation.ptsArr[length].clone();
    let distance = start.distanceTo(end);
    if (distance === 0) return;

    super.stopAnnotation();

    this.finishcallback();
  }

  createNewAnnotation(point: Vector3) {
    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationLeadMesh({
      start: point,
      end: point,
      font,
      scene,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
    });
    return mesh;
  }

  createAnnotationFromData(data: {
    ptsArr: Array<number[]>;
    lineSize: number;
    textSize: number;
    color: string;
    text: string;
  }) {
    let pointArr: Vector3[] = [];
    data.ptsArr.forEach((item: number[]) => {
      const v3 = new Vector3().fromArray(item);
      pointArr.push(v3);
    });

    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationLeadMesh({
      start: pointArr[0],
      end: pointArr[1],
      font,
      scene,
      lineSize: data.lineSize,
      textSize: data.textSize,
      color: data.color,
    });

    mesh.createLeadText(data.text);
    const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
    mesh.updateLineTextShape(pixelScale2d);

    mesh.setDragHandleVisible(false);

    return mesh;
  }
}

class AnnotationLeadMesh extends AnnotationMesh {
  textMesh!: Mesh;
  text: string;

  constructor(param: {
    start: Vector3;
    end: Vector3;
    font: Font;
    scene: Scene;
    lineSize: number;
    textSize: number;
    color: string;
  }) {
    super(param);

    this.text = "";

    this._createLineTextShape();
  }

  private _createLineTextShape() {
    const shape = new Shape();
    const shapeGeo = new ShapeGeometry(shape);
    this.lineShape = new Mesh(shapeGeo, this.material);
    this.scene.add(this.lineShape);

    this.ptsArr.forEach((point) => {
      const drag = this.createDragHandle(point);
      this.dragHandleArr.push(drag);
      drag.install(this.scene);
    });
  }

  createLeadText(text: string) {
    this.text = text;

    const textGeo = new TextGeometry(text, {
      font: this.font,
      size: this.textSize,
      height: 0.001,
      curveSegments: 10,
    });
    this.textMesh = new Mesh(textGeo, this.material);
    this.textMesh.geometry.computeBoundingBox();

    this.scene.add(this.textMesh);

    this.textMesh.position.copy(this.ptsArr[1]);
  }

  updateLineTextShape(pixelScale2d: number) {
    pixelScale2d = pixelScale2d * 0.5 * (this.lineSize + 1);
    if (pixelScale2d < 0.00099) pixelScale2d = 0.00099;

    const direction = this.ptsArr[1]
      .clone()
      .sub(this.ptsArr[0])
      .normalize()
      .multiplyScalar(pixelScale2d);
    const normal = new Vector3(0, 0, 1)
      .cross(direction)
      .normalize()
      .multiplyScalar(pixelScale2d);

    const normal2 = normal.clone().multiplyScalar(5);
    const direction2 = direction.clone().multiplyScalar(12);

    if (
      this.ptsArr[0].distanceTo(this.ptsArr[1]) <
      this.ptsArr[0].distanceTo(
        new Vector3().copy(this.ptsArr[0]).add(direction2)
      )
    ) {
      return;
    }

    const l1 = new Vector3().copy(this.ptsArr[0]);
    const l2 = new Vector3().copy(this.ptsArr[0]).add(direction2).sub(normal2);
    const l3 = new Vector3().copy(this.ptsArr[0]).add(direction2).sub(normal);
    const l4 = new Vector3().copy(this.ptsArr[1]).sub(normal);
    const l5 = new Vector3().copy(this.ptsArr[1]).add(normal);
    const l6 = new Vector3().copy(this.ptsArr[0]).add(direction2).add(normal);
    const l7 = new Vector3().copy(this.ptsArr[0]).add(direction2).add(normal2);

    const shape = new Shape();
    shape.moveTo(l1.x, l1.y);
    shape.lineTo(l2.x, l2.y);
    shape.lineTo(l3.x, l3.y);
    shape.lineTo(l4.x, l4.y);
    shape.lineTo(l5.x, l5.y);
    shape.lineTo(l6.x, l6.y);
    shape.lineTo(l7.x, l7.y);
    shape.closePath();

    const shapeGeo = new ShapeGeometry(shape);
    this.lineShape.geometry.dispose();
    this.lineShape.geometry = shapeGeo;
  }

  updateDragShape(current: number) {
    this.dragHandleArr[current + 1].setPosition(this.ptsArr[current + 1]);

    this.textMesh?.position.copy(this.ptsArr[1]);
  }

  setMeshVisible(value: boolean) {
    if (value === false) {
      this.dragHandleArr.forEach((item) => {
        item.setVisible(value);
      });
    }

    this.lineShape.visible = value;
    if (this.textMesh) this.textMesh.visible = value;
  }

  dispose() {
    super.dispose();
    this.scene.remove(this.textMesh);
  }

  intersectObjects(_rayCaster: Raycaster) {
    let children: any = [];
    children.push(this.lineShape);
    if (this.textMesh) children.push(this.textMesh);

    const intersects = _rayCaster.intersectObjects(children, true);
    if (intersects.length > 0) {
      return true;
    }

    return false;
  }

  changeMeshMaterial(material: Material) {
    if (this.lineShape.material === material) {
      return;
    }

    this.lineShape.material = material;
    if (this.textMesh) this.textMesh.material = material;
  }

  getData() {
    let pointArr: any = [];
    this.ptsArr.forEach((item) => {
      const arr = item.toArray();
      pointArr.push(arr);
    });

    let obj = {
      ptsArr: pointArr,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
      text: this.text,
    };

    return obj;
  }
}

export { OpAnnotationDwgLead };

文字

// OpAnnotationDwgText.ts

import {
  Vector3,
  Font,
  Scene,
  Mesh,
  TextGeometry,
  Raycaster,
  Material,
} from "../../WebViewer/THREE/Three";
import { AnnotationMesh, OpAnnotationDwg } from "./OpAnnotationDwg";

class OpAnnotationDwgText extends OpAnnotationDwg {
  constructor(app: any) {
    super(app);
  }

  tempAnnotationPointerUp() {
    super.stopAnnotation();
  }

  createText(text: string) {
    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const point = new Vector3();
    const mesh = new AnnotationTextMesh({
      start: point,
      end: point,
      font,
      scene,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
      text: text,
    });

    return mesh;
  }

  createAnnotationFromData(data: {
    ptsArr: Array<number[]>;
    lineSize: number;
    textSize: number;
    color: string;
    text: string;
  }) {
    let pointArr: Vector3[] = [];
    data.ptsArr.forEach((item: number[]) => {
      const v3 = new Vector3().fromArray(item);
      pointArr.push(v3);
    });

    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationTextMesh({
      start: pointArr[0],
      end: pointArr[0],
      font,
      scene,
      lineSize: data.lineSize,
      textSize: data.textSize,
      color: data.color,
      text: data.text,
    });

    mesh.updateDragShape(0);
    mesh.setDragHandleVisible(false);

    return mesh;
  }
}

class AnnotationTextMesh extends AnnotationMesh {
  textMesh!: Mesh;
  text: string;

  constructor(param: {
    start: Vector3;
    end: Vector3;
    font: Font;
    scene: Scene;
    lineSize: number;
    textSize: number;
    color: string;
    text: string;
  }) {
    super(param);

    this.ptsArr = [param.start.clone()];

    this.text = "";

    this._createLineTextShape(param.text);
  }

  private _createLineTextShape(text: string) {
    this.text = text;

    this.ptsArr.forEach((point) => {
      const drag = this.createDragHandle(point);
      this.dragHandleArr.push(drag);
      drag.install(this.scene);
    });

    const textGeo = new TextGeometry(text, {
      font: this.font,
      size: this.textSize,
      height: 0.001,
      curveSegments: 10,
    });
    this.textMesh = new Mesh(textGeo, this.material);

    this.scene.add(this.textMesh);
  }

  updateLineTextShape() {}

  updateDragShape(current: number) {
    this.dragHandleArr[0].setPosition(this.ptsArr[0]);

    this.textMesh.position.copy(this.ptsArr[0]);
  }

  setMeshVisible(value: boolean) {
    if (value === false) {
      this.dragHandleArr.forEach((item) => {
        item.setVisible(value);
      });
    }

    this.textMesh.visible = value;
  }

  dispose() {
    super.dispose();
    this.scene.remove(this.textMesh);
  }

  intersectObjects(_rayCaster: Raycaster) {
    let children: any = [];
    children.push(this.textMesh);

    const intersects = _rayCaster.intersectObjects(children, true);
    if (intersects.length > 0) {
      return true;
    }

    return false;
  }

  changeMeshMaterial(material: Material) {
    if (this.textMesh.material === material) {
      return;
    }

    this.textMesh.material = material;
  }

  getData() {
    let pointArr: any = [];
    this.ptsArr.forEach((item) => {
      const arr = item.toArray();
      pointArr.push(arr);
    });

    let obj = {
      ptsArr: pointArr,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
      text: this.text,
    };

    return obj;
  }
}

export { OpAnnotationDwgText };

接口调用

现在对象都已经封装好了,可以进行业务调用了。封装一个标注管理器,对内进行统一的业务流程处理,外面统一接口调用

// AnnotationDwgManager.ts

import { OpAnnotationDwgCloud } from "./OpAnnotationDwgCloud";
import { OpAnnotationDwgRect } from "./OpAnnotationDwgRect";
import { OpAnnotationDwgCircle } from "./OpAnnotationDwgCircle";
import { OpAnnotationDwgLead } from "./OpAnnotationDwgLead";
import { OpAnnotationDwgText } from "./OpAnnotationDwgText";

/**
 * 标注管理器,设置不同的标注模式
 */
class AnnotationDwgManager {
  app: any;

  opAnnotationDwgCloud: OpAnnotationDwgCloud;
  opAnnotationDwgRect: OpAnnotationDwgRect;
  opAnnotationDwgCircle: OpAnnotationDwgCircle;
  opAnnotationDwgLead: OpAnnotationDwgLead;
  opAnnotationDwgText: OpAnnotationDwgText;

  annotationLineSize: number;
  annotationTextSize: number;
  annotationColor: string;

  constructor(app: any) {
    this.app = app;

    this.opAnnotationDwgCloud = new OpAnnotationDwgCloud(this.app);
    this.opAnnotationDwgRect = new OpAnnotationDwgRect(this.app);
    this.opAnnotationDwgCircle = new OpAnnotationDwgCircle(this.app);
    this.opAnnotationDwgLead = new OpAnnotationDwgLead(this.app);
    this.opAnnotationDwgText = new OpAnnotationDwgText(this.app);

    this.annotationLineSize = 2;
    this.annotationTextSize = 500;
    this.annotationColor = "#ff0000";
  }

  /**
   * 销毁标注管理器
   */
  dispose() {
    this.opAnnotationDwgCloud.dispose();
    this.opAnnotationDwgRect.dispose();
    this.opAnnotationDwgCircle.dispose();
    this.opAnnotationDwgLead.dispose();
    this.opAnnotationDwgText.dispose();

    // @ts-ignore
    this.opAnnotationDwgCloud = null;
    // @ts-ignore
    this.opAnnotationDwgRect = null;
    // @ts-ignore
    this.opAnnotationDwgCircle = null;
    // @ts-ignore
    this.opAnnotationDwgLead = null;
    // @ts-ignore
    this.opAnnotationDwgText = null;
  }

  /**
   * 设置标注模式
   * @param type
   */
  setAnnotationType(type: AnnotationDwgType) {
    this.opAnnotationDwgCloud.disposeEvent();
    this.opAnnotationDwgRect.disposeEvent();
    this.opAnnotationDwgCircle.disposeEvent();
    this.opAnnotationDwgLead.disposeEvent();
    this.opAnnotationDwgText.disposeEvent();

    switch (type) {
      case AnnotationDwgType.CLOUD:
        this.opAnnotationDwgCloud.initEvent();
        break;
      case AnnotationDwgType.RECT:
        this.opAnnotationDwgRect.initEvent();
        break;
      case AnnotationDwgType.CIRCLE:
        this.opAnnotationDwgCircle.initEvent();
        break;
      case AnnotationDwgType.LEAD:
        this.opAnnotationDwgLead.initEvent();
        break;
      case AnnotationDwgType.TEXT:
        this.opAnnotationDwgText.initEvent();
        break;
    }
  }

  /**
   * 绑定引线文字标注引线绘制完成后的回调-执行弹窗输入文字
   * @param callback
   */
  bindLeadFinishCallback(callback: Function) {
    this.opAnnotationDwgLead.bindFinishCallback(callback);
  }

  /**
   * 引线标注的绘制文字接口
   * @param text
   */
  createLeadText(text: string) {
    const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
    //@ts-ignore
    this.opAnnotationDwgLead.selectedAnnotation?.createLeadText(
      text,
      pixelScale2d
    );
  }

  /**
   * 文字标注的绘制文字接口
   * @param text
   */
  createText(text: string) {
    const mesh = this.opAnnotationDwgText.createText(text);
    this.opAnnotationDwgText.tempAnnotation = mesh;
    this.opAnnotationDwgText.annotationMeshArr.push(mesh);
    this.opAnnotationDwgText.setSelectAnnotation(null);
  }

  /**
   * 删除选中标注结果
   */
  deleteSelectAnnotation() {
    this.opAnnotationDwgCloud.deleteSelectAnnotation();
    this.opAnnotationDwgRect.deleteSelectAnnotation();
    this.opAnnotationDwgCircle.deleteSelectAnnotation();
    this.opAnnotationDwgLead.deleteSelectAnnotation();
    this.opAnnotationDwgText.deleteSelectAnnotation();
  }

  /**
   * 删除全部标注结果
   */
  deleteAllAnnotation() {
    this.opAnnotationDwgCloud.deleteAllAnnotation();
    this.opAnnotationDwgRect.deleteAllAnnotation();
    this.opAnnotationDwgCircle.deleteAllAnnotation();
    this.opAnnotationDwgLead.deleteAllAnnotation();
    this.opAnnotationDwgText.deleteAllAnnotation();
  }

  /**
   * 设置所有标注结果显隐
   * @param value
   */
  setAnnotationMeshArrVisible(value: boolean) {
    this.opAnnotationDwgCloud.setAnnotationMeshArrVisible(value);
    this.opAnnotationDwgRect.setAnnotationMeshArrVisible(value);
    this.opAnnotationDwgCircle.setAnnotationMeshArrVisible(value);
    this.opAnnotationDwgLead.setAnnotationMeshArrVisible(value);
    this.opAnnotationDwgText.setAnnotationMeshArrVisible(value);
  }

  /**
   * 设置下一个标注线框
   * @param value
   */
  setNextAnnotationLineSize(val: number) {
    if (val < 1) val = 1;
    if (val > 10) val = 10;

    this.annotationLineSize = val;

    this.opAnnotationDwgCloud.setNextLineSize(val);
    this.opAnnotationDwgRect.setNextLineSize(val);
    this.opAnnotationDwgCircle.setNextLineSize(val);
    this.opAnnotationDwgLead.setNextLineSize(val);
    this.opAnnotationDwgText.setNextLineSize(val);
  }

  /**
   * 设置下一个文字大小
   * @param value
   */
  setNextAnnotationTextSize(val: number) {
    this.annotationTextSize = val;

    this.opAnnotationDwgCloud.setNextTextSize(val);
    this.opAnnotationDwgRect.setNextTextSize(val);
    this.opAnnotationDwgCircle.setNextTextSize(val);
    this.opAnnotationDwgLead.setNextTextSize(val);
    this.opAnnotationDwgText.setNextTextSize(val);
  }

  /**
   * 设置下一个标注颜色
   * @param value
   */
  setNextAnnotationColor(color: string) {
    this.annotationColor = color;

    this.opAnnotationDwgCloud.setNextColor(color);
    this.opAnnotationDwgRect.setNextColor(color);
    this.opAnnotationDwgCircle.setNextColor(color);
    this.opAnnotationDwgLead.setNextColor(color);
    this.opAnnotationDwgText.setNextColor(color);
  }

  /**
   * 获取所有标注数据
   */
  getAnnotationAllData() {
    const cloud = this.opAnnotationDwgCloud.getAnnotationData();
    const rect = this.opAnnotationDwgRect.getAnnotationData();
    const circle = this.opAnnotationDwgCircle.getAnnotationData();
    const lead = this.opAnnotationDwgLead.getAnnotationData();
    const text = this.opAnnotationDwgText.getAnnotationData();

    const data = { cloud, rect, circle, lead, text };
    return JSON.stringify(data);
  }

  /**
   * 设置所有标注
   * @param json
   */
  setAnnotationAllData(json: string) {
    this.deleteAllAnnotation();

    const data = JSON.parse(json);
    this.opAnnotationDwgCloud.setAnnotationData(data.cloud);
    this.opAnnotationDwgRect.setAnnotationData(data.rect);
    this.opAnnotationDwgCircle.setAnnotationData(data.circle);
    this.opAnnotationDwgLead.setAnnotationData(data.lead);
    this.opAnnotationDwgText.setAnnotationData(data.text);
  }
}

enum AnnotationDwgType {
  /**
   * 云线
   */
  CLOUD = 1,
  /**
   * 矩形
   */
  RECT = 2,
  /**
   * 圆
   */
  CIRCLE = 3,
  /**
   * 引线
   */
  LEAD = 4,
  /**
   * 文字
   */
  TEXT = 5,
}

export { AnnotationDwgManager, AnnotationDwgType };

最后只需要调用这个 Manager 的接口,就能轻松实现。业务调用方代码量极少。

效果

annotation.webp

annotationMb.webp