明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 718|回复: 2

在线CAD如何配合three.js绘制带线宽的线段

[复制链接]
发表于 2023-7-21 10:16:12 | 显示全部楼层 |阅读模式
本帖最后由 MxDraw 于 2023-7-21 10:23 编辑

前言
1.在线CAD的产品经常会被集成到很多用户的网页系统内,前端开发人员只要会Java Script,就可以对在线CAD进行集成和二次开发,今天这篇文章我们讲一下梦想CAD控件云图(H5方式)如何配合three.js绘制带线宽的线段。
2.在这之前,如果还没有安装梦想CAD控件的朋友,可以查看快速入门,链接如下:http://help.mxdraw.com/?pid=32
函数配置
首先mxdraw的图形是有线宽属性的,但是在连续线段可能会存在一些问题,或者你希望用three.js来实现一些自定义的图形那么我们就可以使用mxdraw提供的MxDbEntity来实现这样一个带线宽的线段我们先把最基本需要重写的函数写出来:
  1. import { McGiWorldDraw, MxDbEntity } from "mxdraw"
  2. class MxDbLine extends MxDbEntity {
  3.     getTypeName(): string {
  4.         return "MxDbLine"
  5.     }
  6.     worldDraw(pWorldDraw: McGiWorldDraw): void {

  7.     }
  8.     getGripPoints(): THREE.Vector3[] {
  9.         return []
  10.     }
  11.     moveGripPointsAt(index: number, offset: THREE.Vector3): boolean {
  12.        return true
  13.     }
  14.     dwgIn(obj: any): boolean {
  15.        this.onDwgIn(obj)
  16.        return true
  17.     }
  18.     dwgOut(obj: any): object {
  19.         this.onDwgOut(obj)
  20.         return obj
  21.     }
  22. }
复制代码


定义线段数据
现在我们就有了一个MxDbLine类,用来表示它是一条线段,但是它没有任何与线段有关的数据,我们要先定义一些线段数据,代码如下:
  1. class MxDbLine extends MxDbEntity {
  2.     // ...
  3.     points: THREE.Vector3[] = []
  4.     dwgIn(obj: any): boolean {
  5.        this.onDwgIn(obj)
  6.        this.dwgInHelp(obj, ["points"])
  7.        return true
  8.     }
  9.     dwgOut(obj: any): object {
  10.         this.onDwgOut(obj)
  11.         this.dwgOutHelp(obj, ["points"])
  12.         return obj
  13.     }
  14. }
复制代码

现在我们有了points数据了 这些点可以构成一段段的线段但是它现在还不能在画布中渲染,这时还需要用three.js来实现一个带线宽的线段结合体,这个如何实现呢?
首先,可以在three.js示例中找到Line2 这样相关的类,它就可以实现带线宽的线段我们先安装:

现在只需要Line2、LineGeometry、LineMaterial这三个类,你可以不用安装three@113.2的依赖,只需要找到它对应示例的文件,引入到项目中就可以了,以下是具体的实现代码:
  1. import { Line2 } from 'three/examples/jsm/lines/Line2'
  2. import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
  3. import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
  4. export class MxDbLine extends MxDbEntity {
  5.       // ...
  6.      // 临时存放的material
  7.      material: LineMaterial
  8.      worldDraw(pWorldDraw: McGiWorldDraw): void {
  9.         const material = new LineMaterial()
  10.         this.material = material
  11.         const geometry = new LineGeometry()
  12.         // 得到mxdraw中使用的three.js
  13.         const THREE = MxFun.getMxFunTHREE()
  14.         // 设置颜色
  15.         material.color = new THREE.Color(this.color)

  16.         // line2必须设置的分辨率
  17.         const canvas = MxFun.getCurrentDraw().getCanvas()
  18.         material.resolution.set( canvas.width, canvas.height)
  19.         // 设置透明 因为这样才能覆盖底层的cad图纸
  20.         material.transparent = true

  21.         // 设置点向量位置,line2需要这样设置
  22.         const positions: number[] = []
  23.         for(let i = 0; i < this.points.length; i++) {
  24.             positions.push(this.points[i].x, this.points[i].y, (this.points[i] as any)?.z || 0)
  25.         }
  26.         geometry.setPositions(positions)

  27.         const line2 = new Line2(geometry, material)
  28.         // 最后把这个three.js生成的线段绘制出来
  29.         pWorldDraw.drawEntity(line2)
  30.     }
  31. }
复制代码



宽度的配置
现在我们基本上用three.js示例中提供的Line2类绘制线段,MxDbLine也可以完整的显示一条线段了,但是它还没有宽度。
MxDbEntity中提供了dLinewidth属性用于表示线宽,用lineWidthByPixels属性表示线宽是否始终跟随屏幕宽度,也就是画布缩放,线的宽度始终不变lineWidthByPixels为false时就是另一种three.js中的坐标系宽度,这种宽度是固定的了,不会随着画布缩放而变化。
要实现这两种宽度还需要了解到MxDbEntity重写方法onViewChange 当画布缩放时onViewChange 就会执行还需要了解的是要将当前的屏幕坐标长度转成three.js坐标系长度,在mxdraw中提供了MxFun.screenCoordLong2World来转换。
默认dLinewidth都是跟随屏幕的宽度,我们需要先记录当前绘制这条线段时,1屏幕像素转成three.js坐标系长度的值是多少然后后面需要根据lineWidthByPixels属性判断是用跟随屏幕像素的宽度还是three.js坐标系一样的固定宽度。
如果lineWidthByPixels = false 那么我们就可以通过当时绘制时记录的three.js坐标系长度的值去比上现在这个时候的1屏幕像素下的three.js坐标系长度这样就得到了一个线宽比,用线宽比去乘以目前设置的dLinewidth宽度,就算实现需要的宽度了
如果lineWidthByPixels = true 就不用这么麻烦了,dLinewidth就算我们需要的宽度,具体的代码如下:
  1. export class MxDbLine extends MxDbEntity {
  2.     // 记录1屏幕像素下three.js的长度 比例
  3.      _lineWidthRatio: number
  4.     //  更新实际的线宽
  5.     updateLineWidth() {
  6.         if(!this._lineWidthRatio) {
  7.             this._lineWidthRatio = MxFun.screenCoordLong2World(1)
  8.         }
  9.         this.material.linewidth =  this.lineWidthByPixels ? this.dLineWidth :  this.dLineWidth *  this._lineWidthRatio / MxFun.screenCoordLong2World(1)
  10.     }
  11.      worldDraw(pWorldDraw: McGiWorldDraw): void {
  12.         // ...
  13.         this.updateLineWidth()
  14.         // ...
  15.      }
  16.     //  在画布视图缩放变化时立即触发更新重写渲染
  17.      onViewChange() {
  18.         // 只有当时以three.js长度为单位的时候才这样立即更新去调整宽度
  19.         if(!this.lineWidthByPixels) {
  20.             this.setNeedUpdateDisplay()
  21.             MxFun.updateDisplay()
  22.         }
  23.         return true
  24.     }
  25.     // 顺便我们把夹点操作移动给写上,这样就可以移动每一个向量点来来改变线段了
  26.     getGripPoints(): THREE.Vector3[] {
  27.         return this.points
  28.     }
  29.     moveGripPointsAt(index: number, offset: THREE.Vector3): boolean {
  30.         this.points[index] = this.points[index].clone().add(offset)
  31.        return true
  32.     }
  33.     //  _lineWidthRatio属性必须一直存在 这样线宽才算正确的
  34.     dwgIn(obj: any): boolean {
  35.        this.onDwgIn(obj)
  36.        this.dwgInHelp(obj, ["points", "_lineWidthRatio"])
  37.        return true
  38.     }
  39.     dwgOut(obj: any): object {
  40.         this.onDwgOut(obj)
  41.         this.dwgOutHelp(obj, ["points", "_lineWidthRatio"])
  42.         return obj
  43.     }
  44. }
复制代码

最后就是使用我们写好的MxDbLine类,代码如下:
  1. import { MxFun, MrxDbgUiPrPoint } from "mxdraw"
  2. const drawLine = ()=> {
  3.     const line = new MxDbLine()
  4.     line.dLineWidth = 10
  5.     const getPoint = new MrxDbgUiPrPoint()
  6.     // 这是绘制过程中设置的动态绘制函数
  7.     getPoint.setUserDraw((currentPoint, pWorldDraw)=> {
  8.         if(line.points.length === 0) return
  9.         if(line.points.length >= 2) {
  10.             pWorldDraw.drawCustomEntity(line)
  11.         }
  12.         pWorldDraw.drawLine(currentPoint, line.points[line.points.length - 1])
  13.     })
  14.     getPoint.goWhile(()=> {
  15.         // 鼠标左键点击
  16.         line.points.push(getPoint.value())
  17.     }, ()=> {
  18.         // 鼠标右键结束绘制
  19.         MxFun.getCurrentDraw().addMxEntity(line)
  20.     })
  21. }
复制代码

绘制带宽度的线段过程,如下图:
这时绘制完成后的效果:
line.lineWidthByPixels 设置成false 当缩放画布时,线段就不会始终是屏幕宽度了,而是当时绘制时的three.js实际宽度
带宽度的线段当画布缩放时宽度不随屏幕一起变大,如下图:
下面给MxDbLine的完整代码:
  1. import { McGiWorldDraw, McGiWorldDrawType, MxDbEntity, MxFun } from "mxdraw"
  2. import { Line2 } from 'three/examples/jsm/lines/Line2'
  3. import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
  4. import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
  5. export class MxDbLine extends MxDbEntity {
  6.     points: THREE.Vector3[] = []
  7.     getTypeName(): string {
  8.         return "MxDbLine"
  9.     }
  10.     material: LineMaterial
  11.     _lineWidthRatio: number
  12.     worldDraw(pWorldDraw: McGiWorldDraw): void {
  13.         const material = new LineMaterial()
  14.         this.material = material
  15.         const geometry = new LineGeometry()
  16.         // 得到mxdraw中使用的three.js
  17.         const THREE = MxFun.getMxFunTHREE()
  18.         // 设置颜色
  19.         material.color = new THREE.Color(this.color)
  20.         // line2必须设置的分辨率
  21.         const canvas = MxFun.getCurrentDraw().getCanvas()
  22.         material.resolution.set( canvas.width, canvas.height)
  23.         // 设置透明 因为这样才能覆盖底层的cad图纸
  24.         material.transparent = true
  25.         // 更新线宽
  26.         this.updateLineWidth()
  27.         // 设置点向量位置,line2需要这样设置
  28.         const positions: number[] = []
  29.         for(let i = 0; i < this.points.length; i++) {
  30.             positions.push(this.points[i].x, this.points[i].y, (this.points[i] as any)?.z || 0)
  31.         }
  32.         geometry.setPositions(positions)
  33.        const line2 = new Line2(geometry, material)
  34.         pWorldDraw.drawEntity(line2)
  35.     }
  36.     updateLineWidth() {
  37.         if(!this._lineWidthRatio) {
  38.             this._lineWidthRatio = MxFun.screenCoordLong2World(1)
  39.         }
  40.         this.material.linewidth =  this.lineWidthByPixels ? this.dLineWidth :  this.dLineWidth *  this._lineWidthRatio / MxFun.screenCoordLong2World(1)
  41.     }
  42.     onViewChange() {
  43.         this.setNeedUpdateDisplay()
  44.         MxFun.updateDisplay()
  45.       
  46.         return true
  47.     }
  48.     getGripPoints(): THREE.Vector3[] {
  49.         return this.points
  50.     }
  51.     moveGripPointsAt(index: number, offset: THREE.Vector3): boolean {
  52.         this.points[index] = this.points[index].clone().add(offset)
  53.        return true
  54.     }
  55.     dwgIn(obj: any): boolean {
  56.        this.onDwgIn(obj)
  57.        this.dwgInHelp(obj, ["points", "_lineWidthRatio"])
  58.        return true
  59.     }
  60.     dwgOut(obj: any): object {
  61.         this.onDwgOut(obj)
  62.         this.dwgOutHelp(obj, ["points", "_lineWidthRatio"])
  63.         return obj
  64.     }
  65. }
复制代码


Demo源码链接:
以上,在线CAD如何配合three.js绘制带线宽的线段功能就完成了,有不清楚的请移步梦想CAD控件官网。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
发表于 2023-7-21 12:49:27 | 显示全部楼层
谢谢分享谢谢分享
 楼主| 发表于 2023-7-21 18:32:27 | 显示全部楼层
hzyhzjjzh 发表于 2023-7-21 12:49
谢谢分享谢谢分享

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-11-25 04:38 , Processed in 0.157392 second(s), 24 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表