MxDbEntity Custom Graphics Object

4/22/2022

# How to Implement a Custom Graphics Class

We can implement a custom graphics class by inheriting from Mx.MxDbEntity.

Click Mx.MxDbEntity API (opens new window) to view the detailed implementation specifications.

The following example implements a custom graphics object that can draw arbitrary line segments:

import Mx from "mxdraw"
import THREE from "three"

// Inherit from custom graphics object to implement the function of drawing arbitrary line segments
class MxAnyLine extends  Mx.MxDbEntity {
    // List of vertices
    points = []

    // The center point of the line segment
    centerPt = new THREE.Vector3()

    // Dynamic drawing
    worldDraw(pWorldDraw) {
        // Create a three.js line object for any line segment
        const line = createAnyLine(this.points)
        // Calculate the bounding box of the line object
        line.geometry.computeBoundingBox()
        // Get the center point
        line.geometry.boundingBox.getCenter(this.centerPt)
        // Dynamically draw this line object
        pWorldDraw.drawEntity(line)
    }
    // Display grip points, and you can move the graphics by clicking on these points
    getGripPoints() {
        return [this.centerPt]
    }

    // Display vertex movement events. "index" indicates the point being moved, and "offset" is the offset of the movement
    moveGripPointsAt(index, offset) {
        this.points.forEach((pt)=> {
            pt.add(offset);
        })
        
        return true;
    }

    // A custom object will be recreated when it is drawn.
    create() {
        return new MxAnyLine()
    }

    // Because drawing will create new objects continuously, here the attributes of the previous object are copied to the new object
    dwgIn(obj) {
        // These are the common properties
        this.onDwgIn(obj);

        // These are the custom attributes of the object
        let ary = obj["points"];
        this.points = [];
        this.centerPt  = obj["centerPt"];
        ary.forEach((val) => {
            this.points.push(val.clone());
        });
        return true;
    }
    // The output is the same, which copies the attributes of the new and old objects to ensure that these attribute values exist when drawing
    dwgOut(obj) {
        // These are the common properties
        this.onDwgOut(obj);
        obj["points"] = [];
        obj["centerPt"] = this.centerPt
        this.points.forEach((val) => {
            obj["points"].push(val.clone());
        });
        return obj
    }
}


// Create a three.js line segment object for any line. Please refer to the three.js documentation for this section of code
function createAnyLine(points) {
    const curve = new THREE.CatmullRomCurve3(points, false,  "chordal"); 
    points = curve.getPoints( 50 )
    const geometry = new THREE.BufferGeometry()
    const divisions = Math.round( 12 * points.length );
    let point = new THREE.Vector3();
    const positions =[]
    const colors = []
    const color = new THREE.Color("#ff0000");
    for ( let i = 0, l = divisions; i < l; i ++ ) { 
        const t = i / l;
        point = curve.getPoint( t );
        positions.push( point.x, point.y, point.z );
        colors.push( color.r, color.g, color.b );
    }
    geometry.setAttribute( 'position',new THREE.Float32BufferAttribute( positions, 3 ) );
    geometry.setAttribute( 'color',new THREE.Float32BufferAttribute( colors, 3 ) );
    const material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors, linewidth: 10 } )
    const splineObject = new THREE.Line( geometry, material )
    splineObject.computeLineDistances();
    return splineObject
}

Explanation of the dynamic drawing of custom graphics objects:

  1. The dynamic drawing "worldDraw" method essentially creates a three.js object and adds it to the scene for rendering.

  2. Each time the dynamic drawing "worldDraw" is triggered, the original instance object will be deleted (and the rendered three.js object will also be deleted), and the instance will be recreated through the "create" method.

  3. Some data needs to be saved in the custom object. The "dwgIn" and "dwgOut" methods ensure that the data used when executing the "worldDraw" method will not be lost after creation.

  4. The "getGripPoints" method provides an operation point when you click on the rendered graphics, and the callback function "moveGripPointsAt" moves the point. These operations will also trigger "worldDraw".

Note:

Effect: Click to start drawing an arbitrary line

# JSON Formatting Custom Graphics Object to Implement Saving and Recovery

import Mx from "mxdraw"
// Get the current control object
let mxobj = Mx.MxFun.getCurrentDraw();

// Convert the custom graphics object in the canvas to a JSON string
const sSaveData = mxobj.saveMxEntityToJson();

// Delete all custom graphics objects in the canvas
mxobj.eraseAllMxEntity();


// Restore all deleted custom graphics objects through JSON strings 
mxobj.loadMxEntityFromJson(sSaveData)

// Finally update the display view
mxobj.updateDisplay();