一、前言

Mxcad是使用TypeScript、C++语言开发的一个网页CAD底层平台,它为用户提供了丰富的开发接口,此框架功能丰富、使用简易高效,可帮助大家在网页二开与自己专业相关的网页CAD应用。我们以家装行业为例,介绍mxcad如何快速实现墙体、单开门、标准窗等实体,并实现这些实体之间的联动。

在线查看效果:(位置:工具=>家装设置=>示例户型图,命令:Mx_PlanView)

打开网易新闻 查看精彩图片

二、基础实体实现

在本文介绍的所有实体,都将通过mxcad的自定义实体实现,如果不清楚mxcad的自定义实体实现原理和使用方法,请参考[mxcad自定义实体介绍]、[McDbCustomEntity],下面我们将以实现标准窗为例,详细介绍如何通过自定义实体实现标准窗实体。

[在线DEMO]绘制示例:(位置:工具=》家装设计=》标准窗;命令:BZC)

打开网易新闻 查看精彩图片

三、实体分析

3.1实体形状

标准窗形状为一个矩形框与两条平分矩形内部的直线组成,可以使用 mxcad 中的[多段线实体]、[直线实体]绘制标准窗。

3.2实体交互方式

绘制标准窗实体时,应先确定窗体宽度,若用户未输入窗体宽度值则设置一个默认值;再确定窗体长度,用户可再图纸中绘制线段指定长度,又可直接输入窗体长度;最后再在图纸中选择放置实体的位置。因此,我们可以使用[取点对象MxCADUiPrPoint()]和[获取距离对象MxCADUiPrDist()]实现交互。

3.3实体夹点

标准窗有三个夹点,分别位于标准窗实体开始端、中心、结束端。移动首尾两端的夹点,可以修改标准窗的长度和方向,中心夹点也应该在首尾夹点移动后重新计算;移动中心夹点,整个标准窗保持方向不变,位置随中心夹点移动,因此首尾两夹点位置跟随中心夹点移动。

3.4实体关联(句柄关联)

标准窗在实现后应与图纸中的墙体联动,当标准窗靠近目标墙体时应该自动旋转角度与墙体适配,并自动识别墙体宽度,调整标准窗宽度与墙体宽度一致。

若关联位置的墙体有交叉、拐点、墙体长度不够等情况则只绘制实体,不与墙体关联。若与墙体关联后又离开墙体,则需要取消与墙体的关联。因此,我们可以监听图纸中的夹点编辑事件,当标准窗夹点移动后进行相关操作。

四、实体实现

4.1标准窗

实现 McDbTestStandardWindow 自定义实体窗

// 自定义标准窗实体类
class McDbTestStandardWindow extends McDbCustomEntity {
/** 标准窗开始点 */
private startPt: McGePoint3d = new McGePoint3d();
/** 标准窗结束点 */
private endPt: McGePoint3d = new McGePoint3d();
/** 标准窗中心点 */
private windowPosition: McGePoint3d = new McGePoint3d();
/** 窗宽 */
private _windowWidth: number = 30;
/** 窗旋转角度 */
private _windowAngle: number = 0;
/** 窗长 */
private _windowLength: number = 0;
/** 关联墙体句柄 */
private _wallHandle: string = "";
/**自身句柄 */
private _entityHandle: string = "";
/** 之前关联的墙体句柄 */
private _oldWallHandle: string = "";
constructor(imp?: any) {
super(imp);
}
public create(imp: any) {
return new McDbTestStandardWindow(imp)
}
/** 获取类名 */
public getTypeName(): string {
return "McDbTestStandardWindow";
}
//设置或获取窗宽
public set windowWidth(val: number) {
this._windowWidth = val;
}
public get windowWidth(): number {
return this._windowWidth;
}
/** 获取或设置窗旋转角度 */
public set windowAngle(val: number) {
this._windowAngle = val;
this.resetPoint();
}
public get windowrAngle(): number {
return this._windowAngle;
}
/** 获取或设置窗长 */
public set width(val: number) {
this._windowLength = val;
}
public get width(): number {
return this._windowLength;
}
/** 获取关联墙体句柄 */
public get wallHandle(): string {
return this._wallHandle;
}
/** 设置关联墙体句柄 */
public set wallHandle(val: string) {
this._wallHandle = val;
}
/** 获取自身句柄 */
public get entityHandle(): string {
return this._entityHandle;
}
/** 设置自身句柄 */
public set entityHandle(val: string) {
this._entityHandle = val;
}
/** 获取之前关联的墙体句柄 */
public get oldWallHandle(): string {
return this._oldWallHandle;
}
/** 设置之前关联的墙体句柄 */
public set oldWallHandle(val: string) {
this._oldWallHandle = val;
}
/** 读取数据 */
public dwgInFields(filter: IMcDbDwgFiler): boolean {
this.startPt = filter.readPoint('satrtPt').val;
this.endPt = filter.readPoint('endPt').val;
this.windowPosition = filter.readPoint('windowPosition').val;
this._windowWidth = filter.readDouble('windowWidth').val;
this._windowAngle = filter.readDouble('windowAngle').val;
this._windowLength = filter.readDouble('windowLength').val;
this._wallHandle = filter.readString('wallHandle').val;
this._entityHandle = filter.readString('entityHandle').val;
this._oldWallHandle = filter.readString('oldWallHandle').val;
return true;
}
/** 写入数据 */
public dwgOutFields(filter: IMcDbDwgFiler): boolean {
filter.writePoint("satrtPt", this.startPt);
filter.writePoint("endPt", this.endPt);
filter.writePoint("windowPosition", this.windowPosition);
filter.writeDouble("windowWidth", this._windowWidth);
filter.writeDouble("windowAngle", this._windowAngle);
filter.writeDouble("windowLength", this._windowLength);
filter.writeString("wallHandle", this._wallHandle);
filter.writeString("entityHandle", this._entityHandle);
filter.writeString("oldWallHandle", this._oldWallHandle);
return true;
}
/** 移动夹点 */
public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) {
this.assertWrite();
if (iIndex === 0) {
this.startPt.x += dXOffset;
this.startPt.y += dYOffset;
this.startPt.z += dZOffset;
} else if (iIndex === 1) {
this.endPt.x += dXOffset;
this.endPt.y += dYOffset;
this.endPt.z += dZOffset;
} else if (iIndex === 2) {
this.startPt.x += dXOffset;
this.startPt.y += dYOffset;
this.startPt.z += dZOffset;
this.endPt.x += dXOffset;
this.endPt.y += dYOffset;
this.endPt.z += dZOffset;
}
this._windowLength = this.startPt.distanceTo(this.endPt);
this.windowPosition = this.startPt.clone().addvec(this.endPt.sub(this.startPt).mult(1 / 2));
// 移动标准窗
const { wallArr, angle, position } = getWallPts(this.windowPosition, this._windowLength);
this._oldWallHandle = this._wallHandle;
if (wallArr.length > 0) {
// 有墙体与门窗相关联
this._windowAngle = angle;
this.windowPosition = position;
this.resetPoint();
const id = wallArr[0];
const newWall = id.getMcDbEntity() as McDbTestWall;
this._windowWidth = newWall.wallWidth;
this._wallHandle = newWall.getHandle();
} else {
// 没有墙体与门窗关联
this._wallHandle = "";
}
};
private resetPoint() {
const v = McGeVector3d.kXAxis.clone().rotateBy(this._windowAngle);
this.startPt = this.windowPosition.clone().addvec(v.clone().mult(this._windowLength / 2));
this.endPt = this.windowPosition.clone().addvec(v.clone().negate().mult(this._windowLength / 2));
}
/** 获取夹点 */
public getGripPoints(): McGePoint3dArray {
let ret = new McGePoint3dArray();
ret.append(this.startPt);
ret.append(this.endPt);
ret.append(this.windowPosition)
return ret;
};
/** 动态绘制 */
public worldDraw(draw: MxCADWorldDraw): void {
const v = this.endPt.sub(this.startPt).normalize().perpVector();
const pt1 = this.startPt.clone().addvec(v.clone().mult(this._windowWidth / 2));
const pt2 = this.endPt.clone().addvec(v.clone().mult(this._windowWidth / 2));
const pt3 = this.endPt.clone().addvec(v.clone().negate().mult(this._windowWidth / 2));
const pt4 = this.startPt.clone().addvec(v.clone().negate().mult(this._windowWidth / 2));
const pl = new McDbPolyline();
pl.isClosed = true;
pl.addVertexAt(pt1);
pl.addVertexAt(pt2);
pl.addVertexAt(pt3);
pl.addVertexAt(pt4);
draw.drawEntity(pl);
const line = new McDbLine(this.startPt, this.endPt);
const line1 = line.clone() as McDbLine;
line1.move(this.windowPosition, this.windowPosition.clone().addvec(v.clone().mult(this._windowWidth / 6)));
const line2 = line.clone() as McDbLine;
line2.move(this.windowPosition, this.windowPosition.clone().addvec(v.clone().negate().mult(this._windowWidth / 6)));
draw.drawEntity(line1);
draw.drawEntity(line2);
}
/** 设置窗所在位置 */
public setPosition(pt: McGePoint3d) {
this.windowPosition = pt.clone();
this.resetPoint();
}
/** 获取窗所在位置 */
public getPosition(): McGePoint3d {
return this.windowPosition;
}
public transformBy(_mat: McGeMatrix3d): boolean {
this.startPt.transformBy(_mat);
this.endPt.transformBy(_mat);
this.windowPosition.transformBy(_mat);
return true;
}
}

关联墙体信息,代码如下:

/**
* 获取门窗关联墙体信息
* @param pt 门窗的位置
* @param width 门窗宽
* @param insertType 插入方式
* @returns { wallArr: 门窗所在墙体Id数组, angle: 门窗的旋转角度, position: 门的位置, isRed: 放置位置是否合理 }
*/
function getWallPts(pt: McGePoint3d, width: number, insertType?: string): { wallArr: McObjectId[], angle: number, position: McGePoint3d, isRed: boolean } {
let wallArr: McObjectId[] = [];
let angle: number = 0;
let isRed: boolean = false;
const dol = 0.00001;
const startPt = pt.clone().addvec(McGeVector3d.kXAxis.clone().mult(width));
const endPt = pt.clone().addvec(McGeVector3d.kXAxis.clone().negate().mult(width));
const line = new McDbLine(startPt, endPt);
line.rotate(pt, Math.PI / 4);
// 查找符合与单开门关联的墙体
const filter = new MxCADResbuf();
filter.AddString("McDbCustomEntity", 5020);
const ss = new MxCADSelectionSet();
ss.crossingSelect(line.startPoint.x, line.startPoint.y, line.endPoint.x, line.endPoint.y, filter);
if (ss.count() > 0) {
ss.forEach(id => {
const entity = id.getMcDbEntity() as McDbCustomEntity;
if (entity.getTypeName() === "McDbTestWall") {
const wall = entity.clone() as McDbTestWall;
const pt1 = wall.getStartPoint();
const pt2 = wall.getEndPoint();
const _line = new McDbLine(pt1, pt2);
const closePos = _line.getClosestPointTo(pt, false).val;
const v = pt1.sub(pt2).normalize().mult(width / 2);
const doorStart = closePos.clone().addvec(v);
const doorEnd = closePos.clone().addvec(v.negate());
const point1 = _line.getClosestPointTo(doorStart, false).val;
const point2 = _line.getClosestPointTo(doorEnd, false).val;
if (point1.distanceTo(doorStart) < dol && point2.distanceTo(doorEnd) < dol && closePos.distanceTo(pt) < wall.wallWidth / 2) {
// 判断门所在墙体位置是否还和其他墙体相交
let res = wall.insterPoints.filter(pt => {
const doorLine = new McDbLine(doorStart, doorEnd);
const point = doorLine.getClosestPointTo(pt, false).val;
return point.distanceTo(pt) < dol;
})
// 有符合条件的相交墙体
if (res.length === 0) {
const _v = startPt.sub(endPt);
const angle1 = v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis);// 墙体的角度
const angle2 = _v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis);// 门的初始角度
angle = angle1 - angle2;
wallArr.push(id);
if (insertType === 'E' || !insertType) {
pt = closePos.clone();
} else if (insertType === 'M') {
pt = _line.getPointAtDist(_line.getLength().val / 2).val;
}
} else {
isRed = true;
}
}
}
})
};
return { wallArr, angle, position: pt, isRed };
}

设置监听事件,参考代码:

const mxcad: McObject = MxCpp.getCurrentMxCAD();
// 监听wall夹点变化
mxcad.mxdraw.on("objectGripEdit", (grips) => {
grips.forEach((grip) => {
const id = new McObjectId(grip.id, grip.type === "mxcad" ? McObjectIdType.kMxCAD : McObjectIdType.kMxDraw);
if (id.isErase()) return;
const ent = id.getMcDbEntity() as McDbEntity;
if (!ent) return;
if (ent.objectName === "McDbCustomEntity") {
const typeName = (ent as McDbCustomEntity).getTypeName();
if (typeName === "McDbTestStandardWindow") {
// 单开门夹点变换,更新相关墙体
const door = ent as McDbTestSingleDoor | McDbTestStandardWindow;
let newWall: McDbTestWall | null = null;
let oldWall: McDbTestWall | null = null;
const dataBase: McDbDatabase = MxCpp.getCurrentMxCAD().getDatabase();
if (door.wallHandle) {
newWall = dataBase.handleToIdIndex(door.wallHandle).getMcDbEntity() as McDbTestWall;
}
if (door.oldWallHandle) {
oldWall = dataBase.handleToIdIndex(door.oldWallHandle).getMcDbEntity() as McDbTestWall;
}
if (door.wallHandle === door.oldWallHandle) {
// 新墙体句柄与老墙体句柄相等,则更新墙体
if (newWall && oldWall) {
// 重新计算门窗在墙体中的相关信息
newWall.countDoorData();
// 更新墙体
newWall.assertObjectModification(true);
}
} else {
// 新老关联墙体不相等,新墙体添加句柄,旧墙体删除句柄并更新
if (newWall) {
newWall.doorHandles = [...newWall.doorHandles, door.entityHandle];
newWall.assertObjectModification(true);
}
if (oldWall) {
oldWall.doorHandles = oldWall.doorHandles.filter(handle => handle !== door.entityHandle);
oldWall.assertObjectModification(true);
}
}
};
mxcad.updateDisplay();
};
})
})
// 监听mxcad选择,若门窗被删除则触发更新
const oldDoorSelectIds: McObjectId[] = [];
mxcad.on("selectChange", (ids: McObjectId[]) => {
if (ids.length > 0) {
oldDoorSelectIds.length = 0;
ids.forEach(id => {
const entity = id.getMcDbEntity();
if (entity && entity.objectName === "McDbCustomEntity") {
if ((entity as McDbCustomEntity).getTypeName() === "McDbTestStandardWindow") {
oldDoorSelectIds.push(id)
}
}
})
} else {
setTimeout(() => {
if (oldDoorSelectIds.length > 0) {
oldDoorSelectIds.forEach(id => {
if (id.isErase()) {
const door = id.getMcDbEntity() as McDbTestSingleDoor | McDbTestStandardWindow;
let newWall: McDbTestWall | null = null;
const dataBase: McDbDatabase = MxCpp.getCurrentMxCAD().getDatabase();
if (door.wallHandle) {
newWall = dataBase.handleToIdIndex(door.wallHandle).getMcDbEntity() as McDbTestWall;
newWall.doorHandles = newWall.doorHandles.filter(handle => handle !== door.entityHandle);
newWall.assertObjectModification(true);
}
}
});
mxcad.updateDisplay();
};
}, 0)
}
})

实现交互绘制方法:

// 绘制标准窗
async function drawStandardWindow() {
// 设置窗的长度
const getLength = new MxCADUiPrDist();
getLength.setMessage(`${t('请设置标准窗的长度')}`)
let windowLength = await getLength.go();
if (!windowLength) windowLength = 30;
const window = new McDbTestStandardWindow();
window.width = windowLength;
// 设置窗的插入方式
const getInsertType = new MxCADUiPrKeyWord();
getInsertType.setMessage(`${t('请选择插入方式')}`);
getInsertType.setKeyWords(`[${t("任意位置")}(E)/${t("中心位置")}(M)]`)
let insertType = await getInsertType.go();
if (!insertType) insertType = 'E'
// 设置窗的位置
const getDoorPos = new MxCADUiPrPoint();
getDoorPos.setMessage(`${t('请选择窗的位置')}`);
let _wallArr: McObjectId[] = [];
getDoorPos.setUserDraw((pt, pw) => {
const { isRed, angle, position, wallArr } = getWallPts(pt, windowLength, insertType);
window.setPosition(position);
if (wallArr.length > 0) {
_wallArr = wallArr;
const wall = wallArr[0].getMcDbEntity() as McDbTestWall;
window.windowWidth = wall.wallWidth;
}
if (!isRed) {
pw.setColor(0x00FFFF);
window.windowAngle = angle;
pw.drawMcDbEntity(window)
} else {
pw.setColor(0xFF0000);
pw.drawMcDbEntity(window)
}
})
const windowPos = await getDoorPos.go();
if (!windowPos) return;
if (_wallArr.length > 0) {
// 关联门窗和墙
const id = _wallArr[0];
const wall = id.getMcDbEntity() as McDbTestWall;
const windowId = MxCpp.getCurrentMxCAD().drawEntity(window);
const _window = windowId.getMcDbEntity() as McDbTestStandardWindow;
const handle: string = _window.getHandle();
wall.doorHandles = [...wall.doorHandles, handle];
wall.assertObjectModification(true)
_window.wallHandle = wall.getHandle();
_window.entityHandle = handle;
} else {
// 绘制单开门
const id = MxCpp.getCurrentMxCAD().drawEntity(window);
const _window = id.getMcDbEntity() as McDbTestStandardWindow;
const handle: string = _window.getHandle();
_window.entityHandle = handle;
}
}

4.2实现效果

可在[在线DEMO]中查看完整绘制效果:

打开网易新闻 查看精彩图片

五、实体扩展

根据上述实现家装设计实体的思路,我们可以扩展实现更多的家装设计实体,下面我们将参照上文中的基础实体实现思路去实现家装设计中的单开门实体,单开门实体样式示例:

打开网易新闻 查看精彩图片

5.1单开门实体

A.实体形状

单卡开门实体形状为一个非闭合的矩形框与一段圆弧组成,因此,我们可以使用 mxcad 中的 [多段线实体]、[圆弧实体]绘制单开门。

B.实体夹点

单开门有四个夹点,分别位于单开门实体开始端、中心、结束端和单开门内部。移动首尾两端的夹点,可以修改单开门实体的长度和方向,中心夹点和单开门内部的夹点也应该在首尾夹点移动后重新计算;移动中心夹点,整个单开门实体保持方向不变,位置随中心夹点移动,因此首尾两夹点和单开门内部的夹点也跟随中心夹点移动;移动单开门内部夹点,改变单开门的圆弧方向,其他三个夹点位置保持不变。

C.预留与墙体关联的接口

根据上述对实体的分析,我们就可以通过[McDbCustomEntity]实现一个单开门的自定义实体 McDbTestSingleDoor ,示例代码如下:

class McDbTestSingleDoor extends McDbCustomEntity {
/** 门开始点 */
private startPt: McGePoint3d = new McGePoint3d();
/** 门结束点 */
private endPt: McGePoint3d = new McGePoint3d();
/** 门中心点 */
private doorPosition: McGePoint3d = new McGePoint3d();
/** 门宽 */
private _doorWidth: number;
/** 门方向点 */
private directPt: McGePoint3d = new McGePoint3d();
/** 门所在象限 */
private quadrant: number = 3;
/** 门旋转角度 */
private _doorAngle: number = 0;
/** 门所在墙体的句柄 */
private _wallHandle: string = '';
/** 门句柄 */
private _entityHandle: string = '';
/** 移动前关联墙体句柄 */
private _oldWallHandle: string = '';
constructor(imp?: any) {
super(imp);
}
public create(imp: any) {
return new McDbTestSingleDoor(imp)
}
/** 获取类名 */
public getTypeName(): string {
return "McDbTestSingleDoor";
}
//设置或获取门宽
public set width(val: number) {
this._doorWidth = val;
}
public get width(): number {
return this._doorWidth;
}
/** 获取或设置门旋转角度 */
public set doorAngle(val: number) {
this._doorAngle = val;
}
public get doorAngle(): number {
return this._doorAngle;
}
/** 获取或设置门所在墙体的句柄 */
public set wallHandle(val: string) {
this._wallHandle = val;
}
public get wallHandle(): string {
return this._wallHandle;
}
/** 获取或设置门句柄 */
public set entityHandle(val: string) {
this._entityHandle = val;
}
public get entityHandle(): string {
return this._entityHandle;
}
/** 获取移动前关联墙体句柄 */
public set oldWallHandle(val: string) {
this._oldWallHandle = val;
}
public get oldWallHandle(): string {
return this._oldWallHandle;
}
/** 读取数据 */
public dwgInFields(filter: IMcDbDwgFiler): boolean {
this.startPt = filter.readPoint('satrtPt').val;
this.endPt = filter.readPoint('endPt').val;
this.doorPosition = filter.readPoint('doorPosition').val;
this.directPt = filter.readPoint('directPt').val;
this._doorWidth = filter.readDouble('doorWidth').val;
this.quadrant = filter.readLong('quadrant').val;
this._doorAngle = filter.readDouble('doorAngle').val;
this._wallHandle = filter.readString('wallHandle').val;
this._entityHandle = filter.readString('entityHandle').val;
this._oldWallHandle = filter.readString('oldWallHandle').val;
return true;
}
/** 写入数据 */
public dwgOutFields(filter: IMcDbDwgFiler): boolean {
filter.writePoint("satrtPt", this.startPt);
filter.writePoint("endPt", this.endPt);
filter.writePoint("doorPosition", this.doorPosition);
filter.writePoint("directPt", this.directPt);
filter.writeDouble("doorWidth", this._doorWidth);
filter.writeLong("quadrant", this.quadrant);
filter.writeDouble("doorAngle", this._doorAngle);
filter.writeString("wallHandle", this._wallHandle);
filter.writeString("entityHandle", this._entityHandle);
filter.writeString("oldWallHandle", this._oldWallHandle);
return true;
}
/** 移动夹点 */
public moveGripPointsAt(iIndex: number, dXOffset: number, dYOffset: number, dZOffset: number) {
this.assertWrite();
if (iIndex === 0) {
this.startPt.x += dXOffset;
this.startPt.y += dYOffset;
this.startPt.z += dZOffset;
this.doorPosition = this.startPt.clone().addvec(this.endPt.sub(this.startPt).mult(0.5));
this.getQuadrant();
} else if (iIndex === 1) {
this.endPt.x += dXOffset;
this.endPt.y += dYOffset;
this.endPt.z += dZOffset;
this.doorPosition = this.startPt.clone().addvec(this.endPt.sub(this.startPt).mult(0.5));
this.getQuadrant();
} else if (iIndex === 2) {
this.doorPosition.x += dXOffset;
this.doorPosition.y += dYOffset;
this.doorPosition.z += dZOffset;
this.endPt.x += dXOffset;
this.endPt.y += dYOffset;
this.endPt.z += dZOffset;
this.startPt.x += dXOffset;
this.startPt.y += dYOffset;
this.startPt.z += dZOffset;
this.directPt.x += dXOffset;
this.directPt.y += dYOffset;
this.directPt.z += dZOffset;
} else if (iIndex === 3) {
const v = this.endPt.sub(this.startPt);
const pt = this.directPt.clone();
pt.x += dXOffset;
pt.y += dYOffset;
pt.z += dZOffset;
const _v = pt.sub(this.doorPosition);
const angle = _v.angleTo2(v, McGeVector3d.kNegateZAxis);
if (0 < angle && angle < Math.PI / 2) {
this.quadrant = 1;
} else if (Math.PI / 2 <= angle && angle < Math.PI) {
this.quadrant = 2;
} else if (Math.PI <= angle && angle < Math.PI * 3 / 2) {
this.quadrant = 3;
} else {
this.quadrant = 4;
}
this.getQuadrant();
};
this._doorWidth = this.startPt.distanceTo(this.endPt);
this._oldWallHandle = this._wallHandle;
if (iIndex !== 3) {
// 移动单开门
const { wallArr, angle, position } = getWallPts(this.doorPosition, this._doorWidth);
if (wallArr.length > 0) {
// 有墙体与单开门相关联
this._doorAngle = angle;
this.doorPosition = position;
const v = McGeVector3d.kXAxis.clone().rotateBy(this._doorAngle);
this.resetPoint(v, this._doorWidth);
const id = wallArr[0];
const newWall = id.getMcDbEntity() as McDbTestWall;
this._wallHandle = newWall.getHandle();
} else {
// 没有墙体与单开门关联
this._wallHandle = "";
}
}
};
private resetPoint(v: McGeVector3d, width: number) {
this.startPt = this.doorPosition.clone().subvec(v.clone().mult(width / 2));
this.endPt = this.doorPosition.clone().addvec(v.clone().mult(width / 2));
this.directPt = this.startPt.clone().addvec(v.clone().mult(width / 5)).subvec(v.clone().perpVector().mult(width / 3));
this.quadrant = 3;
}
private getQuadrant() {
const v = this.endPt.sub(this.startPt);
const angleOrigin = v.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis);
const width = this.startPt.distanceTo(this.endPt);
let v1, v2, point;
if (this.quadrant === 1) {
point = this.endPt.clone();
v1 = McGeVector3d.kXAxis.clone().negate().mult(width / 5);
v2 = McGeVector3d.kYAxis.clone().mult(width / 3);
} else if (this.quadrant === 2) {
point = this.startPt.clone();
v1 = McGeVector3d.kXAxis.clone().mult(width / 5);
v2 = McGeVector3d.kYAxis.clone().mult(width / 3);
} else if (this.quadrant === 3) {
point = this.startPt.clone();
v1 = McGeVector3d.kXAxis.clone().mult(width / 5);
v2 = McGeVector3d.kYAxis.clone().negate().mult(width / 3);
} else if (this.quadrant === 4) {
point = this.endPt.clone();
v1 = McGeVector3d.kXAxis.clone().negate().mult(width / 5);
v2 = McGeVector3d.kYAxis.clone().negate().mult(width / 3);
}
this.directPt = point.clone().addvec(v1.rotateBy(angleOrigin)).addvec(v2.rotateBy(angleOrigin));
}
/** 获取夹点 */
public getGripPoints(): McGePoint3dArray {
let ret = new McGePoint3dArray();
ret.append(this.startPt);
ret.append(this.endPt);
ret.append(this.doorPosition);
ret.append(this.directPt);
return ret;
};
/** 动态绘制 */
public worldDraw(draw: MxCADWorldDraw): void {
const dist1 = this.directPt.distanceTo(this.startPt);
const dist2 = this.directPt.distanceTo(this.endPt);
const width = this.startPt.distanceTo(this.endPt);
let startPoint, endPoint;
if (dist1 < dist2) {
startPoint = this.startPt.clone();
endPoint = this.endPt.clone();
} else {
startPoint = this.endPt.clone();
endPoint = this.startPt.clone();
}
// 绘制圆弧
const directV = endPoint.sub(startPoint);
const arc = new McDbArc();
arc.setCenter(startPoint.x, startPoint.y, startPoint.z);
arc.radius = width;
let startAngle, endAngle, v1, v2, angle;
if (this.quadrant === 1) {
startAngle = Math.PI / 2;
endAngle = Math.PI;
v1 = McGeVector3d.kYAxis.clone().mult(width);
v2 = McGeVector3d.kXAxis.clone().negate().mult(width / 10);
angle = directV.angleTo2(McGeVector3d.kXAxis.clone().negate(), McGeVector3d.kNegateZAxis)
} else if (this.quadrant === 2) {
endAngle = Math.PI / 2;
startAngle = 0;
v1 = McGeVector3d.kYAxis.clone().mult(width);
v2 = McGeVector3d.kXAxis.clone().mult(width / 10);
angle = directV.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis)
} else if (this.quadrant === 3) {
startAngle = Math.PI * (3 / 2);
endAngle = 0;
v1 = McGeVector3d.kYAxis.clone().negate().mult(width);
v2 = McGeVector3d.kXAxis.clone().mult(width / 10);
angle = directV.angleTo2(McGeVector3d.kXAxis, McGeVector3d.kNegateZAxis)
} else if (this.quadrant === 4) {
endAngle = Math.PI * (3 / 2);
startAngle = Math.PI;
v1 = McGeVector3d.kYAxis.clone().negate().mult(width);
v2 = McGeVector3d.kXAxis.clone().negate().mult(width / 10);
angle = directV.angleTo2(McGeVector3d.kXAxis.clone().negate(), McGeVector3d.kNegateZAxis)
}
arc.startAngle = startAngle;
arc.endAngle = endAngle;
arc.rotate(startPoint, angle);
draw.drawEntity(arc);
// 绘制直线
const pl = new McDbPolyline();
pl.addVertexAt(startPoint.clone().addvec(v1))
pl.addVertexAt(startPoint)
pl.addVertexAt(startPoint.clone().addvec(v2));
pl.addVertexAt(startPoint.clone().addvec(v1).addvec(v2));
pl.rotate(startPoint, angle);
draw.drawEntity(pl);
}
/** 设置门所在位置 */
public setPosition(pt: McGePoint3d) {
this.doorPosition = pt.clone();
let v: McGeVector3d;
if (this._doorAngle === 0) {
v = McGeVector3d.kXAxis.clone();
} else {
v = McGeVector3d.kXAxis.clone().rotateBy(this._doorAngle);
}
this.resetPoint(v, this._doorWidth);
}
/** 获取门所在位置 */
public getPosition(): McGePoint3d {
return this.doorPosition;
}
/**
* 获取自定义对象矩阵坐标变换
*/
public transformBy(_mat: McGeMatrix3d): boolean {
this.startPt.transformBy(_mat);
this.endPt.transformBy(_mat);
this.doorPosition.transformBy(_mat);
this.directPt.transformBy(_mat);
return true;
};
}

5.2实体关联(句柄关联)

单开门实体在实现后应与图纸中的墙体联动,当标准窗靠近目标墙体时应该自动旋转角度与墙体适配,并自动识别墙体的中心线,单开门放置位置应与墙体中心线对齐。

若关联位置的墙体有交叉、拐点、墙体长度不够等情况则只绘制实体,不与墙体关联。若与墙体关联后又离开墙体,则需要取消与墙体的关联。因此,我们可以监听图纸中的夹点编辑事件,当单开门夹点(首尾两夹点和中心点)移动后进行相关操作(同上述标准窗一样的操作,识别自定义实体类别中添加上McDbTestSingleDoor)

5.3实体交互方式

绘制单开门实体时,应先确定单开门宽度,若用户未输入单开门宽度值则设置一个默认值,然后在在图纸中指定单开门的位置。因此,我们可以使用 [取点对象 MxCADUiPrPoint()]和[获取距离对象MxCADUiPrDist()]实现交互。

根据其交互方式就可以设置出相应的绘制方法,下面为其示例代码:

// 绘制单开门
async function drawSingleDoor() {
// 设置门的宽度
const getWidth = new MxCADUiPrDist();
getWidth.setMessage(`${t('请输入门的宽度')}`)
let doorWidth = await getWidth.go();
if (!doorWidth) doorWidth = 30;
const door = new McDbTestSingleDoor();
door.width = doorWidth;
// 设置门的插入方式
const getInsertType = new MxCADUiPrKeyWord();
getInsertType.setMessage(`${t('请选择插入方式')}`);
getInsertType.setKeyWords(`[${t("任意位置")}(E)/${t("中心位置")}(M)]`)
let insertType = await getInsertType.go();
if (!insertType) insertType = 'E'
// 设置门的位置
const getDoorPos = new MxCADUiPrPoint();
getDoorPos.setMessage(`${t('请选择门的位置')}`);
let _wallArr: McObjectId[] = [];
getDoorPos.setUserDraw((pt, pw) => {
const { isRed, angle, position, wallArr } = getWallPts(pt, doorWidth, insertType);
door.setPosition(position);
_wallArr = wallArr;
if (!isRed) {
pw.setColor(0x00FFFF);
door.doorAngle = angle;
pw.drawMcDbEntity(door)
} else {
pw.setColor(0xFF0000);
pw.drawMcDbEntity(door)
}
})
const doorPos = await getDoorPos.go();
if (!doorPos) return;
if (_wallArr.length > 0) {
// 关联门窗和墙
const id = _wallArr[0];
const wall = id.getMcDbEntity() as McDbTestWall;
const doorId = MxCpp.getCurrentMxCAD().drawEntity(door);
const _door = doorId.getMcDbEntity() as McDbTestSingleDoor;
const handle: string = _door.getHandle();
wall.doorHandles = [...wall.doorHandles, handle];
wall.assertObjectModification(true)
_door.wallHandle = wall.getHandle();
_door.entityHandle = handle;
} else {
// 绘制单开门
const id = MxCpp.getCurrentMxCAD().drawEntity(door);
const _door = id.getMcDbEntity() as McDbTestSingleDoor;
const handle: string = _door.getHandle();
_door.entityHandle = handle;
}
}

5.4 运行效果

可在[在线DEMO]中查看完整绘制效果:

打开网易新闻 查看精彩图片