MXCAD McDbCustomEntity Custom Entity
In MXCAD, developers can create custom entities that meet specific business needs by inheriting the McDbCustomEntity class. Custom entities allow you to define unique data structures, rendering logic, and interaction behaviors. The following section details the purposes, invocation timing, and general implementation specifications of core methods in custom entities.
Click McDbCustomEntity to view detailed property and method descriptions.
Data Persistence Methods
Custom entities need to correctly serialize and deserialize their internal data during file saving, reading, or copying.
1. dwgInFields(filter: IMcDbDwgFiler): boolean
Purpose: Reads custom entity data from a file or stream to restore the entity state.
Invocation Timing: Scenarios requiring data loading from storage media, such as opening drawings, inserting blocks, or copying entities.
Implementation Points:
- Use the
filterobject to read data by field name (e.g.,readPoint,readString,readInt). - Assign the read data to the entity's private attributes.
- For complex structures (e.g., matrices), manually parse and reconstruct them (e.g., parse a JSON string into a 2D array and fill the matrix).
- Return
trueon success,falseon failure.
public dwgInFields(filter: IMcDbDwgFiler): boolean {
this.pt1 = filter.readPoint("pt1").val;
this.pt2 = filter.readPoint("pt2").val;
const matrixStr = filter.readString("matrix").val;
const matrixData = JSON.parse(matrixStr) as number[][];
// Reconstruct matrix data
for (let i = 0; i < matrixData.length; i++) {
for (let j = 0; j < matrixData[i].length; j++) {
this.matrix.setData(i, j, matrixData[i][j]);
}
}
return true;
}2. dwgOutFields(filter: IMcDbDwgFiler): boolean
Purpose: Writes the current state of the custom entity to a file or stream for persistent storage.
Invocation Timing: Scenarios requiring memory data to be written to storage, such as saving drawings, copying entities, or exporting blocks.
Implementation Points:
- Use the
filterobject to write data by field name (e.g.,writePoint,writeString,writeInt). - Complex data types (e.g., matrices) must be converted into a serializable format (e.g., JSON strings).
- Return
trueon success,falseon failure.
public dwgOutFields(filter: IMcDbDwgFiler): boolean {
filter.writePoint("pt1", this.pt1);
filter.writePoint("pt2", this.pt2);
const matrix: number[][] = [];
for (let i = 0; i < 4; i++) {
matrix[i] = [];
for (let j = 0; j < 4; j++) {
matrix[i][j] = this.matrix.getData(i, j);
}
}
filter.writeString("matrix", JSON.stringify(matrix));
return true;
}Interaction and Editing Methods
Custom entities must support intuitive editing by users via Grip Points.
1. getGripPoints(): McGePoint3dArray
Purpose: Returns the collection of edit grip point positions displayed for the entity in the view.
Invocation Timing: Called by the system to retrieve operable grips when the entity is selected.
Implementation Points:
- Return a
McGePoint3dArrayobject containing all key control points. - The grip order corresponds to the index
iIndexinmoveGripPointsAt(e.g., index 0 corresponds to the start point, index 1 to the end point). - Typically returns key geometric positions such as start point, end point, and center point.
// Example using a custom line entity
public getGripPoints(): McGePoint3dArray {
const points = new McGePoint3dArray();
points.append(this.pt1); // Index 0
points.append(this.pt2); // Index 1
return points;
}2. moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number): void
Purpose: Handles coordinate offsets after a user drags a grip, updating the entity's geometric data.
Invocation Timing: Triggered when the user drags a grip and releases the mouse.
Implementation Points:
- Determine which grip is moved based on
iIndex. - Apply offsets (
dXOffset,dYOffset,dZOffset) to the corresponding attributes. - Must call
this.assertWrite()to ensure the entity is in a writable state. - No return value; directly modifies the internal state.
// Example using a custom line entity
public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) {
this.assertWrite(); // Mark as writable
if (iIndex === 0) {
this.pt1.x += dXOffset;
this.pt1.y += dYOffset;
this.pt1.z += dZOffset;
} else if (iIndex === 1) {
this.pt2.x += dXOffset;
this.pt2.y += dYOffset;
this.pt2.z += dZOffset;
}
}Rendering and Display Methods
Custom entities must define their visual representation in the view.
1. worldDraw(draw: MxCADWorldDraw): void
Purpose: Draws the graphical representation of the entity in the view.
Invocation Timing: Automatically triggered after view refresh, entity creation, or transformation operations.
Implementation Points:
- Use the
drawobject to draw basic primitives (e.g., lines, circles, polylines) combined into custom graphics. - Transformation matrices, colors, line types, and other attributes can be applied.
- Do not modify entity data in this method; it is responsible for visualization only.
// Example using a custom line entity
public worldDraw(draw: MxCADWorldDraw): void {
// Create a temporary base entity for drawing
const line = new McDbLine(this.pt1, this.pt2);
// Apply custom transformation
line.transformBy(this.matrix);
// Delegate to system for drawing
draw.drawEntity(line);
}TIP
Note: Since instances are rebuilt on every redraw, ensure all state is saved in entity attributes, not in local variables or external references.
Geometric Calculation and Transformation Methods
Custom entities must support spatial transformations and boundary calculations to be compatible with general CAD system operations.
1. getBoundingBox(): { minPt: McGePoint3d; maxPt: McGePoint3d; ret: boolean }
Purpose: Calculates the minimum bounding box of the entity.
Invocation Timing: Scenarios such as zooming view to entity, selection set judgment, collision detection, etc.
Implementation Points:
- Return an object containing
minPt(minimum corner point),maxPt(maximum corner point), andret(success status). - Consider the spatial range of all geometric parts of the entity (including transformed coordinates).
- If the entity has no valid geometry,
retshould be set tofalse.
public getBoundingBox(): { minPt: McGePoint3d; maxPt: McGePoint3d; ret: boolean } {
const minX = Math.min(this.pt1.x, this.pt2.x);
const minY = Math.min(this.pt1.y, this.pt2.y);
const maxX = Math.max(this.pt1.x, this.pt2.x);
const maxY = Math.max(this.pt1.y, this.pt2.y);
return {
minPt: new McGePoint3d(minX, minY, 0),
maxPt: new McGePoint3d(maxX, maxY, 0),
ret: true
};
}2. transformBy(mat: McGeMatrix3d): boolean
Purpose: Applies an affine transformation (translation, rotation, scaling, mirroring, etc.) to the entity.
Invocation Timing: When executing editing commands such as MOVE, ROTATE, SCALE, MIRROR, etc.
Implementation Points:
- Multiply the incoming matrix
matwith the entity's current transformation matrix (usually usingpreMultByfor pre-multiplication orpostMultByfor post-multiplication, depending on coordinate system requirements). - Call
this.assertWrite()to mark the entity as writable. - Return
trueon success,falseon failure.
// Example using a custom line entity
public transformBy(mat: McGeMatrix3d): boolean {
// Apply external transformation matrix to the internally stored matrix
this.matrix = this.matrix.preMultBy(mat);
this.assertWrite();
return true;
}Auxiliary Methods and Best Practices
1. Constructor and create Method
- The constructor is used to initialize the instance.
create(imp: any)is a factory method used to create new instances during deserialization or cloning and must be overridden.
// Example using a custom line entity
constructor(imp?: any) {
super(imp);
}
public create(imp: any) {
return new McDbTestLineCustomEntity(imp);
}2. Type Identification
Override getTypeName(): string to return a unique type name for system identification and debugging.
// Custom entity type name
public getTypeName(): string {
return "McDbTestLineCustomEntity";
}3. Data Accessors
Provide getXXX() and setXXX() methods for safe external access to private attributes, and call assertWrite() within the setter.
// Example using a custom line entity
public setPoint1(pt1: McGePoint3d) {
this.assertWrite();
this.pt1 = pt1.clone();
}
public getPoint1() {
return this.pt1;
}TIP
Other method access names can also be set, as long as they ensure access to public methods.
Custom Entity Registration
After creating the custom entity according to the steps above, you need to register the custom entity in the mxcad object. A custom entity only needs to be registered once in the mxcad object, and the class name of the custom entity must be unique. If the same custom entity class name is registered multiple times, the console will throw an error: "MxCADError:already has this class name McDbTestLineCustomEntity". Therefore, we can perform registration during mxcad initialization.
import { MxCpp } from "mxcad";
// McDbTestLineCustomEntity is the custom entity class name you created
const mxcad = MxCpp.getCurrentMxCAD();
mxcad.on("init", () => {
new McDbTestLineCustomEntity().rxInit();
});Functional Practice
The following example demonstrates the complete creation of a custom entity by creating a custom line entity.
Inherit McDbCustomEntity to Define the Entity
import {
IMcDbDwgFiler,
McDbCustomEntity,
McDbLine,
McGePoint3d,
McGePoint3dArray,
MxCADUiPrPoint,
MxCADWorldDraw,
MxCpp,
McGeMatrix3d,
} from "mxcad";
class McDbTestLineCustomEntity extends McDbCustomEntity {
private pt1: McGePoint3d = new McGePoint3d();
private pt2: McGePoint3d = new McGePoint3d();
private matrix: McGeMatrix3d = new McGeMatrix3d();
constructor(imp?: any) {
super(imp);
}
public create(imp: any) {
return new McDbTestLineCustomEntity(imp);
}
public getTypeName(): string {
return "McDbTestLineCustomEntity";
}
public dwgInFields(filter: IMcDbDwgFiler): boolean {
this.pt1 = filter.readPoint("pt1").val;
this.pt2 = filter.readPoint("pt2").val;
const matrix = filter.readString("matrix").val;
const matrixData = JSON.parse(matrix) as number[][];
for (let index = 0; index < matrixData.length; index++) {
const element = matrixData[index];
for (let j = 0; j < element.length; j++) {
const data = element[j];
this.matrix.setData(index, j, data);
}
}
return true;
}
public dwgOutFields(filter: IMcDbDwgFiler): boolean {
filter.writePoint("pt1", this.pt1);
filter.writePoint("pt2", this.pt2);
const matrix: number[][] = [];
for (let i = 0; i < 4; i++) {
matrix[i] = [];
for (let j = 0; j < 4; j++) {
const data = this.matrix.getData(i, j);
matrix[i][j] = data;
}
}
filter.writeString("matrix", JSON.stringify(matrix));
return true;
}
public moveGripPointsAt(
iIndex: number,
dXOffset: number,
dYOffset: number,
dZOffset: number,
) {
this.assertWrite();
if (iIndex == 0) {
this.pt1.x += dXOffset;
this.pt1.y += dYOffset;
this.pt1.z += dZOffset;
} else if (iIndex == 1) {
this.pt2.x += dXOffset;
this.pt2.y += dYOffset;
this.pt2.z += dZOffset;
}
}
public getGripPoints(): McGePoint3dArray {
let ret = new McGePoint3dArray();
ret.append(this.pt1);
ret.append(this.pt2);
return ret;
}
public worldDraw(draw: MxCADWorldDraw): void {
let tmpline = new McDbLine(this.pt1, this.pt2);
tmpline.transformBy(this.matrix);
draw.drawEntity(tmpline);
}
public setPoint1(pt1: McGePoint3d) {
this.assertWrite();
this.pt1 = pt1.clone();
}
public setPoint2(pt2: McGePoint3d) {
this.assertWrite();
this.pt2 = pt2.clone();
}
public getPoint1() {
return this.pt1;
}
public getPoint2() {
return this.pt2;
}
// Edit transformation
public transformBy(_mat: McGeMatrix3d): boolean {
this.matrix = this.matrix.preMultBy(_mat);
this.assertWrite();
return true;
}
public getBoundingBox(): {
minPt: McGePoint3d;
maxPt: McGePoint3d;
ret: boolean;
} {
return {
minPt: new McGePoint3d(
Math.min(this.pt1.x, this.pt2.x),
Math.min(this.pt1.y, this.pt2.y),
),
maxPt: new McGePoint3d(
Math.max(this.pt1.x, this.pt2.x),
Math.max(this.pt1.y, this.pt2.y),
),
ret: true,
};
}
}Register the Entity
import { MxCpp } from "mxcad";
const mxcad = MxCpp.getCurrentMxCAD();
mxcad.on("init", () => {
new McDbTestLineCustomEntity().rxInit();
});Setup Method to Draw Target Entity
import { MxCADUiPrPoint, MxCpp } from "mxcad";
async function MxTest_DrawCustomEntity() {
const getPoint = new MxCADUiPrPoint();
getPoint.setMessage("\nSpecify first point:");
let pt1 = await getPoint.go();
if (!pt1) return;
getPoint.setBasePt(pt1);
getPoint.setUseBasePt(true);
getPoint.setMessage("\nSpecify second point:");
let pt2 = await getPoint.go();
if (!pt2) return;
let myline = new McDbTestLineCustomEntity();
myline.setPoint1(pt1);
myline.setPoint2(pt2);
const mxcad = MxCpp.getCurrentMxCAD();
mxcad.drawEntity(myline);
}