DEV Community

MxCAD
MxCAD

Posted on

How online CAD works with three.js to draw line segments with line widths

preamble

  1. Online CAD products are often integrated into many users of the web system , front-end developers as long as the Java Script, you can integrate and secondary development of online CAD, today's article we talk about the dream of CAD controls cloud map (H5 way) how to work with three.js draw line segments with line width .
  2. Until then, if you haven't installed Dream CAD Controls yet, you can check out the Quick Start at the following link: http://help.mxdraw.com/?pid=32

function configuration

First of all mxdraw's graphic has a line width attribute, but there may be some problems in continuous line segments, or you wish to implement some customized graphic with three.js, then we can use the MxDbEntity provided by mxdraw to implement such a line segment with line width, let's write out the most basic functions that need to be rewritten first:.

import { McGiWorldDraw, MxDbEntity } from "mxdraw"
class MxDbLine extends MxDbEntity {
    getTypeName(): string {
        return "MxDbLine"
    }
    worldDraw(pWorldDraw: McGiWorldDraw): void {

    }
    getGripPoints(): THREE.Vector3[] {
        return []
    }
    moveGripPointsAt(index: number, offset: THREE.Vector3): boolean {
       return true
    }
    dwgIn(obj: any): boolean {
       this.onDwgIn(obj)
       return true
    }
    dwgOut(obj: any): object {
        this.onDwgOut(obj)
        return obj
    }
}

Enter fullscreen mode Exit fullscreen mode

Defining line segment data

Now we have a MxDbLine class, used to indicate that it is a line, but it does not have any data related to the line, we have to define some line data, the code is as follows:

class MxDbLine extends MxDbEntity {
    // ...
    points: THREE.Vector3[] = []
    dwgIn(obj: any): boolean {
       this.onDwgIn(obj)
       this.dwgInHelp(obj, ["points"])
       return true
    }
    dwgOut(obj: any): object {
        this.onDwgOut(obj)
        this.dwgOutHelp(obj, ["points"])
        return obj
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we have points data These points can form a segment of the line segment, but it is not yet in the canvas rendering, this time also need to use three.js to achieve a line with the line width of the combination of this how to achieve it?
First of all, you can find a related class like Line2 in the three.js example, which enables line segments with line widths, let's install.

npm i three@0.113.2
Enter fullscreen mode Exit fullscreen mode

Now only need Line2, LineGeometry, LineMaterial these three classes, 你可以不用安装three@113.2的依赖, just need to find it corresponding to the example of the file, introduced to the project can be, the following is the specific implementation of the code:

import { Line2 } from 'three/examples/jsm/lines/Line2'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
export class MxDbLine extends MxDbEntity { 
      // ...
     // Temporary storage of material
     material: LineMaterial
     worldDraw(pWorldDraw: McGiWorldDraw): void {
        const material = new LineMaterial()
        this.material = material
        const geometry = new LineGeometry()
        // Get the three.js used in mxdraw.
        const THREE = MxFun.getMxFunTHREE()
        // Setting the color
        material.color = new THREE.Color(this.color)

        // line2 must be set to the resolution
        const canvas = MxFun.getCurrentDraw().getCanvas()
        material.resolution.set( canvas.width, canvas.height)
        // Set the transparency, because that's how you cover the underlying cad drawing.
        material.transparent = true

        // Set the point vector position, line2 needs to be set like this
        const positions: number[] = []
        for(let i = 0; i < this.points.length; i++) {
            positions.push(this.points[i].x, this.points[i].y, (this.points[i] as any)? .z || 0)
        }
        geometry.setPositions(positions)

        const line2 = new Line2(geometry, material)
        // Finally draw the line segment generated by this three.js
        pWorldDraw.drawEntity(line2)
    }
}
Enter fullscreen mode Exit fullscreen mode

Configuration of width

Now that we're basically drawing lines with the Line2 class provided in the three.js example, MxDbLine can also display a line segment in its entirety, but it doesn't have a width yet.
In MxDbEntity provides dLinewidth attribute for line width, with lineWidthByPixels attribute to indicate whether the line width always follows the screen width, that is, canvas scaling, the width of the line is always the same, when the lineWidthByPixels is false it is another kind of coordinate system in three.js When lineWidthByPixels is false, it's another coordinate system in three.js. This width is fixed, and won't change as the canvas scales.
To realize these two widths also need to understand the MxDbEntity override method onViewChange when the canvas is scaled onViewChange will be executed, also need to understand is to convert the current screen coordinate length to three.js coordinate system length, in mxdraw provides a MxFun. screenCoordLong2World to convert.
Default dLinewidth are following the width of the screen, we need to record the current drawing of this line segment, 1 screen pixels into the three.js coordinate system length of the value of how much, and then need to later according to lineWidthByPixels attribute to determine whether to follow the screen pixels of the width or the same three.js coordinate system as a fixed width.
If lineWidthByPixels = false, then we can use the value of the length of the three.js coordinate system recorded at the time of drawing to compare to the length of the three.js coordinate system at this point in time at 1 screen pixel, which gives us a linewidth ratio, which we can then multiply by the dLinewidth width we've set to achieve the desired width. This will give you a linewidth ratio.
If lineWidthByPixels = true you don't need to bother, dLinewidth is the width we need, the specific code is as follows:

export class MxDbLine extends MxDbEntity {
    // Record the length of three.js in 1 screen pixel Scale
     _lineWidthRatio: number
    // Update the actual line width
    updateLineWidth() {
        if(!this._lineWidthRatio) {
            this._lineWidthRatio = MxFun.screenCoordLong2World(1)
        }
        this.material.linewidth = this.lineWidthByPixels ? this.dLineWidth : this.dLineWidth * this._lineWidthRatio / MxFun.screenCoordLong2World(1)
    }
     worldDraw(pWorldDraw: McGiWorldDraw): void {
        // ...
        this.updateLineWidth()
        // ...
     }
    // Trigger update rewrite rendering as soon as canvas view zoom changes
     onViewChange() {
        // Only update to adjust the width immediately if it's in units of three.js length at the time
        if(!this.lineWidthByPixels) {
            this.setNeedUpdateDisplay()
            MxFun.updateDisplay()
        }
        return true
    }
    // By the way, we've written in a pinch move so that we can move each vector point to change the line segment.
    getGripPoints(): THREE.Vector3[] {
        return this.points
    }
    moveGripPointsAt(index: number, offset: THREE.Vector3): boolean {
        this.points[index] = this.points[index].clone().add(offset)
       return true
    }
    // The _lineWidthRatio property must always be present for the line width to be correct.
    dwgIn(obj: any): boolean {
       this.onDwgIn(obj)
       this.dwgInHelp(obj, ["points", "_lineWidthRatio"])
       return true
    }
    dwgOut(obj: any): object {
        this.onDwgOut(obj)
        this.dwgOutHelp(obj, ["points", "_lineWidthRatio"])
        return obj
    }
}
Enter fullscreen mode Exit fullscreen mode

The last thing is to use the MxDbLine class we wrote with the following code:

import { MxFun, MrxDbgUiPrPoint } from "mxdraw"
const drawLine = ()=> {
    const line = new MxDbLine()
    line.dLineWidth = 10
    const getPoint = new MrxDbgUiPrPoint()
    // This is the dynamic draw function set during the drawing process.
    getPoint.setUserDraw((currentPoint, pWorldDraw) => {
        if(line.points.length === 0) return
        if(line.points.length >= 2) {
            pWorldDraw.drawCustomEntity(line)
        }
        pWorldDraw.drawLine(currentPoint, line.points[line.points.length - 1])
    })
    getPoint.goWhile(()=> {
        // Left mouse click
        line.points.push(getPoint.value())
    }, ()=> {
        // End drawing with the right mouse button
        MxFun.getCurrentDraw().addMxEntity(line)
    })
}
Enter fullscreen mode Exit fullscreen mode

The process of drawing a line segment with width is shown below:

Image description
This is what it looks like when the drawing is complete:

Image description
Set line.lineWidthByPixels to false When scaling the canvas, the line segments won't always be the width of the screen, but the actual width of three.js at the time of drawing.
Line segments with widths do not grow larger with the screen when the canvas is scaled, as shown below:
Image description

Here is the complete code for MxDbLine:.

import { McGiWorldDraw, McGiWorldDrawType, MxDbEntity, MxFun } from "mxdraw"
import { Line2 } from 'three/examples/jsm/lines/Line2'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
export class MxDbLine extends MxDbEntity {
    points: THREE.Vector3[] = []
    getTypeName(): string {
        return "MxDbLine"
    }
    material: LineMaterial
    _lineWidthRatio: number
    worldDraw(pWorldDraw: McGiWorldDraw): void {
        const material = new LineMaterial()
        this.material = material
        const geometry = new LineGeometry()
        // Get the three.js used in mxdraw.
        const THREE = MxFun.getMxFunTHREE()
        // Setting the color
        material.color = new THREE.Color(this.color)
        // line2 must be set to the resolution
        const canvas = MxFun.getCurrentDraw().getCanvas()
        material.resolution.set( canvas.width, canvas.height)
        // Set the transparency, because that's how you cover the underlying cad drawing.
        material.transparent = true
        // Update the line width
        this.updateLineWidth()
        // Set the point vector position, line2 needs to be set like this
        const positions: number[] = []
        for(let i = 0; i < this.points.length; i++) {
            positions.push(this.points[i].x, this.points[i].y, (this.points[i] as any)? .z || 0)
        }
        geometry.setPositions(positions)
       const line2 = new Line2(geometry, material)
        pWorldDraw.drawEntity(line2)
    }
    updateLineWidth() {
        if(!this._lineWidthRatio) {
            this._lineWidthRatio = MxFun.screenCoordLong2World(1)
        }
        this.material.linewidth = this.lineWidthByPixels ? this.dLineWidth : this.dLineWidth * this._lineWidthRatio / MxFun.screenCoordLong2World(1)
    }
    onViewChange() {
        this.setNeedUpdateDisplay()
        MxFun.updateDisplay()
      
        return true
    }
    getGripPoints(): THREE.Vector3[] {
        return this.points
    }
    moveGripPointsAt(index: number, offset: THREE.Vector3): boolean {
        this.points[index] = this.points[index].clone().add(offset)
       return true
    }
    dwgIn(obj: any): boolean {
       this.onDwgIn(obj)
       this.dwgInHelp(obj, ["points", "_lineWidthRatio"])
       return true
    }
    dwgOut(obj: any): object {
        this.onDwgOut(obj)
        this.dwgOutHelp(obj, ["points", "_lineWidthRatio"])
        return obj
    }
}
Enter fullscreen mode Exit fullscreen mode

Demo source code link:

https://github.com/mxcad/mxdraw-article/tree/master/mxdraw%E5%A6%82%E4%BD%95%E9%85%8D%E5%90%88three.js%E5%AE%9E%E7%8E%B0%E5%B8%A6% E7%BA%BF%E5%AE%BD%E7%9A%84%E7%BA%BF%E6%AE%B5/demo
Above, online CAD how to cooperate with three.js draw line segment with line width function is completed, there are unclear please move to the official website of the dream CAD control.

Top comments (0)