前言
在使用CAD工具进行绘图时,面对复杂的图形结构,如何高效地管理多个对象成为提升工作效率的关键。CAD提供的"组"功能,正是为解决这一问题而设计的实用工具。本文将全面介绍 mxcad 中"组"的概念,以及如何实现组相关的功能开发。
一、什么是"组"(Group)?
在CAD中,组(Group) 是指将多个图形对象逻辑地组合在一起,形成一个可被统一操作的集合。组不会创建新的图元实体,也不会改变对象本身的几何属性,仅是一种命名的对象集合。
组对象包含特点如下:
组内的对象保持独立,可单独编辑。
选择组中任意一个对象时,整个组可被选中(取决于系统设置)。
每个组有唯一的名称,便于识别和管理。
支持嵌套:一个组可以包含另一个组,形成层级结构。
组不作为独立实体存储在图形数据库中,仅作为对象的逻辑关联存在。
二、组的核心功能开发
1. 创建组
该功能流程是从用户执行"创建组"命令开始。首先,系统初始化相关变量(如组名、描述和对象列表),并获取当前图形数据库中的组管理字典。 随后进入主循环,提示用户"选择对象"。用户可以通过点击或框选方式选择一个或多个图形对象,所选对象的ID将被保存到临时列表中。
在选择过程中,用户可随时输入关键字进行设置:
-
输入 N(名称) :进入命名流程,系统提示"输入编组名"。此时可输入
[查询(A)]
来查看已存在的组名;若输入*
或直接回车,则列出所有组;否则查询指定组信息。输入名称后,系统检查是否重名,若无冲突则保存名称并返回选择状态。 -
输入 D(说明):进入说明设置,提示"输入编组说明",用户输入的文本将作为该组的描述信息。
当用户完成选择并按 回车或空格键 确认后,系统开始创建组:
- 首先检查所选对象中是否有成员已属于其他组。
若存在此类情况,则弹出确认提示:"包含相同对象的组已经存在。仍要创建新的组?",并提供"是(Y)/否(N)"选项。
若用户选择"否"或取消操作,命令终止。
若用户确认继续或无冲突,则调用底层API创建组,并将之前输入的描述信息赋值给新组。
最后,组创建完成,系统退出循环,命令执行结束。整个流程支持ESC中断或新命令打断,确保操作的安全性和灵活性。
根据上述流程调用 mxcad 内部API接口实现方法如下:
ts
import { McDbEntity, McDbGroup, McDbPolyline, McGePoint3d, McObjectId, MxCADSelectionSet, MxCADUiPrKeyWord, MxCADUiPrPoint, MxCADUiPrString, MxCADUtility, MxCpp } from "mxcad";
import { DetailedResult, MxFun, MrxDbgUiPrBaseReturn } from "mxdraw";
interface GroupObject {
name: string,
group: McDbGroup
}
// 根据实体查找组
const getGroupForEntity = (entity: McDbEntity): GroupObject[] => {
const database = MxCpp.getCurrentDatabase()
const groupDict = database.GetGroupDictionary()
const handle = entity.getHandle()
const groupNames = groupDict.getAllObjectName()
const length = groupNames.length();
let groupArr: GroupObject[] = [];
for (let index = 0; index < length; index++) {
const groupName = groupNames.at(index);
const groupId = groupDict.getAt(groupName)
const group = groupId.getMcDbObject() as McDbGroup
if (!group) continue;
const entityIds = group.getAllEntityId();
entityIds.forEach(entityId => {
if (entityId.getMcDbEntity()?.getHandle() === handle) groupArr.push({ name: groupName, group })
});
};
return groupArr
}
// 创建组
async function Mx_Group() {
let description = ""
let ids: McObjectId[] = [];
const database = MxCpp.getCurrentDatabase();
const groupDict = database.GetGroupDictionary();
const mxcad = MxCpp.getCurrentMxCAD();
// 设定未命名组名
const groupNames = groupDict.getAllObjectName();
let num = 0;
groupNames.forEach(item => {
if (/^\*/.test(item)) {
num += 1;
}
});
let name: string = `*A${num + 1}`;
// 创建组
const createGroup = async () => {
const isPresence = ids.some((id) => {
return database.getEntitiesInTheGroup(id).length !== 0
})
if (isPresence) {
const getKey = new MxCADUiPrKeyWord();
getKey.setMessage(`包含相同对象的组已经存在。仍要创建新的组?<N>`);
getKey.setKeyWords(`[是(Y)/否(N)]`);
const key = await getKey.go();
ids.forEach(id => {
id.getMcDbEntity().highlight(false);
})
mxcad.updateDisplay();
if (key?.toLocaleUpperCase() === "N") {
return
}
if (!key) return
}
if (database.CreateGroup(ids, name)) {
const groupId = groupDict.getAt(name)
const group = groupId.getMcDbObject() as McDbGroup;
if (description) group.description = description;
if (/^\*/.test(name)) {
MxPluginContext.useMessage().success('未命名组已创建');
} else {
MxPluginContext.useMessage().success(`组${name}已创建`);
}
ids.forEach(id => {
id.getMcDbEntity().highlight(false);
})
mxcad.updateDisplay();
};
};
while (true) {
const getEntityPt = new MxCADUiPrPoint();
getEntityPt.setMessage('选择对象');
getEntityPt.setKeyWords(`[名称(N)/说明(D)]`);
getEntityPt.setDisableOsnap(true);
getEntityPt.setDisableDynInput(true);
getEntityPt.disableAllTrace(true);
const hoverSelectEnts: McDbEntity[] = [];
getEntityPt.setUserDraw((pt, pw) => {
if (hoverSelectEnts.length) hoverSelectEnts.forEach(ent => ent.highlight(false));
hoverSelectEnts.length = 0;
const entId = MxCADUtility.findEntAtPoint(pt.x, pt.y, pt.z, -1);
if (entId.isValid() && !ids.map(item => item.id).includes(entId.id)) {
const ent = entId.getMcDbEntity();
const arr = getGroupForEntity(ent);
if (arr.length) {
const group = arr[0].group;
group.getAllEntityId().forEach(id => {
const ent = id.getMcDbEntity();
ent.highlight(true);
hoverSelectEnts.push(ent)
})
} else {
ent.highlight(true);
hoverSelectEnts.push(ent)
}
}
});
const pt = await getEntityPt.go();
hoverSelectEnts.forEach(ent => ent.highlight(false));
// 如果选择关键字,则执行相关操作
if (getEntityPt.getStatus() == MrxDbgUiPrBaseReturn.kKeyWord) {
if (getEntityPt.isKeyWordPicked("N")) {
while (true) {
const getName = new MxCADUiPrString()
getName.setMessage("输入编组名")
getName.setKeyWords(`[查询(A)]`)
const str = await getName.go()
if (getName.getDetailedResult() === DetailedResult.kCodeAbort || getName.getDetailedResult() === DetailedResult.kEcsIn || getName.getDetailedResult() === DetailedResult.kNewCommadIn) return
if (getEntityPt.getDetailedResult() === DetailedResult.kNullEnterIn || getEntityPt.getDetailedResult() === DetailedResult.kNullSpaceIn || getEntityPt.getDetailedResult() === DetailedResult.kMouseRightIn) {
return createGroup()
}
if (getName.isKeyWordPicked("A")) {
getName.setMessage("请输入要列出的编码组名"+ "<*>")
getName.setKeyWords("")
const name = await getName.go();
if (getName.getDetailedResult() === DetailedResult.kCodeAbort || getName.getDetailedResult() === DetailedResult.kEcsIn || getName.getDetailedResult() === DetailedResult.kNewCommadIn) return
if (name && name !== "*") {
const groupId = groupDict.getAt(name)
const group = groupId.getMcDbObject() as McDbGroup
MxFun.acutPrintf(`\n 定义的编组:`)
if (group) {
MxFun.acutPrintf(`\n${group.name}`)
}
}
else if (name === "*" || getName.getDetailedResult() === DetailedResult.kNullEnterIn || getName.getDetailedResult() === DetailedResult.kNullSpaceIn) {
const groupIds = groupDict.getAllObject()
MxFun.acutPrintf(`\n 定义的编组:`)
groupIds.forEach((groupId) => {
const group = groupId.getMcDbObject() as McDbGroup
group && MxFun.acutPrintf(`\n ${group.name}`)
})
}
continue;
}
if (!str) return;
if (/^\*/.test(str)) {
MxFun.acutPrintf(`*无效`);
continue;
}
const groupId = groupDict.getAt(str)
const group = groupId.getMcDbObject() as McDbGroup
if (group && groupId.isValid()) {
MxFun.acutPrintf(`编组${str} 已经存在`);
continue;
}
name = str;
if (ids.length) {
ids.forEach(id => {
const ent = id.getMcDbEntity();
ent.highlight(false);
})
return createGroup();
} else {
break;
}
}
} else if (getEntityPt.isKeyWordPicked('D')) {
const getName = new MxCADUiPrString()
getName.setMessage("输入编组说明")
const str = await getName.go();
if (!str) break;
description = str
continue;
}
} else if (getEntityPt.getStatus() === MrxDbgUiPrBaseReturn.kNone) {
if (!ids.length) {
return MxPluginContext.useMessage().success('未选择对象,未创建编组');
} else {
ids.forEach(id => {
const ent = id.getMcDbEntity();
ent.highlight(false);
})
return createGroup();
}
} else if (getEntityPt.getStatus() === MrxDbgUiPrBaseReturn.kCancel) {
ids.forEach(id => {
const ent = id.getMcDbEntity();
ent.highlight(false);
})
return
} else {
// 判断是否选中实体
if (pt && hoverSelectEnts.length) {
const selectIds = hoverSelectEnts.map(item => {
item.highlight(true);
return item.getObjectID()
})
ids.push(...selectIds);
continue;
} else if (pt && !hoverSelectEnts.length) {
getEntityPt.setUserDraw((point, pw) => {
const pts = [pt, new McGePoint3d(pt.x, point.y), point, new McGePoint3d(point.x, pt.y)]
// 设置范围框颜色即位置
let pl = new McDbPolyline();
pl.isClosed = true;
pts.forEach(pt => pl.addVertexAt(pt));
pw.setColor(0xFFFFFF);
pw.drawMcDbEntity(pl);
// 动态绘制矩形填充框
const geometry = new THREE.BufferGeometry();
geometry.setFromPoints([
new THREE.Vector3(pt.x, pt.y, pt.z),
new THREE.Vector3(pt.x, point.y, point.z),
new THREE.Vector3(point.x, point.y, point.z),
new THREE.Vector3(point.x, pt.y, pt.z)
]);
geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]), 2);
geometry.setIndex([0, 1, 2, 0, 2, 3]);
// 创建材质(半透明的颜色)
const material = new THREE.MeshBasicMaterial({
color: 0x004D00,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geometry, material);
pw.drawEntity(mesh);
});
const nextPt = await getEntityPt.go();
if (!nextPt) break;
const ss = new MxCADSelectionSet();
await ss.crossingSelect(pt.x, pt.y, nextPt.x, nextPt.y);
ss.forEach(id => {
if (!ids.map(i => i.id).includes(id.id)) {
const ent = id.getMcDbEntity();
const arr = getGroupForEntity(ent);
if (arr.length) {
const group = arr[0].group;
group.getAllEntityId().forEach(id => {
id.getMcDbEntity().highlight(true)
ids.push(id);
})
} else {
ent.highlight(true);
ids.push(id);
}
}
});
continue;
} else {
continue;
};
}
}
}
2. 解除组
解除组的功能流程如下:
命令启动后,系统提示用户"选择组",并支持通过关键字 [名称(N)]
切换为按名称分解模式。在用户操作过程中,系统启用悬停预览功能:当鼠标移动到某个对象上时,会自动查询该对象所属的组,并高亮显示该组内的所有成员对象,便于用户直观判断将要操作的范围。
接下来,根据用户的选择进入不同分支:
1.若用户输入 N(名称):
进入"按名称分解"模式,提示"输入编组名"。
支持输入关键字
[查询(A)]
:若输入
A
,可进一步输入要查询的组名;输入
*
或直接回车,则列出当前图形中所有已定义的组名;输入具体名称,则检查并显示该组是否存在。
用户输入组名后,系统查找对应组:
若存在,执行分解操作(清空组内对象并从组字典中移除),提示"组 已分解";
若不存在,提示"编组 未定义",并允许重新输入。
2.若用户点击某个对象:
系统获取该对象,并查询其所属的所有组(一个对象可能属于多个组)。
若对象仅属于一个组,则直接选中该组,准备分解。
若对象属于多个组,则进入选择流程:
提示"对象是多个组的成员<接受>",提供
[接受(A)/下一个(N)]
选项;选择
A
:接受当前高亮的组;选择
N
:切换到下一个组,并更新高亮显示;可循环切换,直到用户确认或取消。
确定目标组后,记录其名称。
最后,系统根据选定的组名执行分解操作:
从组字典中获取该组对象;
调用
clear()
清空组内成员引用;调用
remove()
从字典中删除该组;提示"组 已分解"或"对象不是组成员"(如未选中有效组)。
操作完成后,清除所有高亮显示的对象,确保界面恢复整洁,命令结束。其具体实现代码如下:
ts
import { McDbEntity, McDbGroup, McDbPolyline, McGePoint3d, McObjectId, MxCADSelectionSet, MxCADUiPrKeyWord, MxCADUiPrPoint, MxCADUiPrString, MxCADUtility, MxCpp } from "mxcad";
import { DetailedResult, MxFun, MrxDbgUiPrBaseReturn } from "mxdraw";
// 解除编组
async function Mx_Ungroup() {
const ents: McDbEntity[] = [];
let groupArr: GroupObject[] = [];
let name!: string;
const database = MxCpp.getCurrentDatabase();
const groupDict = database.GetGroupDictionary();
let index: number = 0;
const getEnt = new MxCADUiPrEntity();
getEnt.setMessage('选择组');
getEnt.setKeyWords(`[名称(N)]`);
getEnt.setUserDraw((pt, pw) => {
ents.forEach(ent => ent.highlight(false));
ents.length = 0;
const entId = MxCADUtility.findEntAtPoint(pt.x, pt.y, pt.z, -1);
if (entId.isValid()) {
const ent = entId.getMcDbEntity();
groupArr = getGroupForEntity(ent);//getGroupForEntity参考上述创建组内代码
if (groupArr.length) {
const group = groupArr[index].group;
group.getAllEntityId().forEach(id => {
const entity = id.getMcDbEntity();
entity.highlight(true);
ents.push(entity);
})
}
}
});
const entId = await getEnt.go();
if (getEnt.getStatus() === MrxDbgUiPrBaseReturn.kKeyWord) {
if (getEnt.isKeyWordPicked('N')) {
while (true) {
const getString = new MxCADUiPrString();
getString.setMessage('输入编组名');
getString.setKeyWords(`[查询(A)]`);
const str = await getString.go();
if (getString.getStatus() === MrxDbgUiPrBaseReturn.kOk) {
// 删除组
const groupId = groupDict.getAt(str);
const group = groupId.getMcDbObject() as McDbGroup;
if (groupId.isValid() && group) {
group.clear();
groupDict.remove(str);
MxPluginContext.useMessage().success('组 ' + str + ' 已分解');
if (ents.length) ents.forEach(ent => ent.highlight(false));
return;
} else {
MxFun.acutPrintf('编组 ' + str + ' 未定义');
continue;
}
} else if (getString.getStatus() === MrxDbgUiPrBaseReturn.kKeyWord) {
// 查询组
getString.setMessage("请输入要列出的编码组名" + "<*>")
getString.setKeyWords("")
const name = await getString.go();
if (getString.getStatus() === MrxDbgUiPrBaseReturn.kOk) {
if (name && name !== "*") {
const groupId = groupDict.getAt(name)
const group = groupId.getMcDbObject() as McDbGroup
MxFun.acutPrintf(`\n 定义的编组:`)
if (group) {
MxFun.acutPrintf(`\n${group.name}`)
}
} else if (name === "*") {
const groupIds = groupDict.getAllObject()
MxFun.acutPrintf(`\n 定义的编组:`)
groupIds.forEach((groupId) => {
const group = groupId.getMcDbObject() as McDbGroup
group && MxFun.acutPrintf(`\n ${group.name}`)
})
}
} else if (getString.getStatus() === MrxDbgUiPrBaseReturn.kNone) {
const groupIds = groupDict.getAllObject()
MxFun.acutPrintf(`\n 定义的编组:`)
groupIds.forEach((groupId) => {
const group = groupId.getMcDbObject() as McDbGroup
group && MxFun.acutPrintf(`\n ${group.name}`)
})
}
continue;
}
}
}
} else if (getEnt.getStatus() === MrxDbgUiPrBaseReturn.kOk) {
if (groupArr.length === 1) {
name = groupArr[0].name
} else if (groupArr.length > 1) {
while (true) {
const getKeys = new MxCADUiPrKeyWord();
getKeys.setMessage('对象是多个组的成员<接受>')
getKeys.setKeyWords('[接受(A)/下一个(N)]');
let key = await getKeys.go();
if (key === "A") {
name = groupArr[index].name;
break;
} else if (key === "N") {
ents.forEach(ent => ent.highlight(false));
ents.length = 0;
index + 1 > groupArr.length - 1 ? index = 0 : index += 1;
const res = groupArr[index];
res.group.getAllEntityId().forEach(id => {
const ent = id.getMcDbEntity();
ent.highlight(true);
ents.push(ent);
});
continue;
} else {
if (ents.length) ents.forEach(ent => ent.highlight(false));
return;
}
}
}
if (name) {
const groupId = groupDict.getAt(name)
const group = groupId.getMcDbObject() as McDbGroup
if (group) {
group.clear();
groupDict.remove(name);
MxPluginContext.useMessage().success(`组 ${name} 已分解`);
} else {
MxPluginContext.useMessage().success('对象不是组成员');
}
} else {
MxPluginContext.useMessage().success('对象不是组成员');
};
if (ents.length) ents.forEach(ent => ent.highlight(false));
}
}
3. 编辑组
编辑图形中已有对象组(Group)的交互式功能。其主要功能是允许用户通过选择对象或输入组名的方式,找到目标组,并对其进行添加成员、删除成员或重命名等操作。
命令启动后,系统首先提示"选择组",并支持通过关键字 [名称(N)]
切换为按名称选择模式。在用户移动鼠标时,系统会启用悬停预览功能:自动检测光标下的对象,查询其所属的组,并高亮显示该组内的所有成员,帮助用户直观判断当前将要操作的对象范围。
如果用户点击了一个对象,系统会获取该对象所属的所有组:
若对象不属于任何组,则提示"对象不是组成员";
若只属于一个组,则直接进入编辑操作;
若属于多个组,则提示"对象是多个组的成员<接受>",并提供
[接受(A)/下一个(N)]
选项,用户可循环切换高亮不同的组,直到确认目标组。
如果用户选择 [名称(N)]
模式,则进入按名称编辑流程:
提示"输入组的名称",并支持
[查询(A)]
关键字;输入
A
后可查看所有组名(输入*
)或查询特定组是否存在;输入有效组名后,若组存在,则加载并高亮其成员,进入编辑;若不存在,则提示"编组 xxx 不存在",并允许重新输入。
确定目标组后,系统弹出操作菜单:[添加对象(A)/删除对象(R)/重命名(REN)]
。
添加对象(A):用户可通过单击或框选方式选择要加入的对象。系统会动态高亮预览可添加的对象(不包括已存在于组内的对象),支持窗口和交叉选择,完成后将所选对象追加到组中,并提示"添加对象成功!"。
删除对象(R):用户选择组内对象进行移除。系统仅允许删除当前组中的成员,选择后会从组中剔除这些对象,并通过清空后重新添加剩余对象的方式更新组内容。
重命名(REN) :提示用户输入新名称。支持再次使用
[查询(A)]
查看现有组名以避免冲突。若新名称已被其他组使用,则提示"编组 xxx 已经存在"并要求重新输入;否则更新组名,并提示"修改组名成功"。
实现上述流程的具体功能代码如下:
ts
import { McDbEntity, McDbGroup, McDbPolyline, McGePoint3d, McObjectId, MxCADSelectionSet, MxCADUiPrKeyWord, MxCADUiPrPoint, MxCADUiPrString, MxCADUtility, MxCpp } from "mxcad";
import { DetailedResult, MxFun, MrxDbgUiPrBaseReturn } from "mxdraw";
// 编辑组
async function Mx_Groupedit() {
const ents: McDbEntity[] = [];//高亮实体数组
let groupArr: GroupObject[] = [];//实体组集合
let index: number = 0;
let name: string = '';
const database = MxCpp.getCurrentDatabase();
const groupDict = database.GetGroupDictionary();
const mxcad = MxCpp.getCurrentMxCAD();
const editGroup = async () => {
// 选中目标组
if (groupArr.length === 1) {
name = groupArr[0].name
} else if (groupArr.length > 1) {
while (true) {
const getKeys = new MxCADUiPrKeyWord();
getKeys.setMessage('对象是多个组的成员<接受>')
getKeys.setKeyWords(`[接受(A)/下一个(N)]`);
let key = await getKeys.go();
if (key === "A") {
name = groupArr[index].name;
break;
} else if (key === "N") {
ents.forEach(ent => ent.highlight(false));
ents.length = 0;
index + 1 > groupArr.length - 1 ? index = 0 : index += 1;
const res = groupArr[index];
res.group.getAllEntityId().forEach(id => {
const ent = id.getMcDbEntity();
ent.highlight(true);
ents.push(ent);
});
continue;
} else {
continue;
}
}
} else {
name = '';
}
// 操作目标组
if (name) {
const groupId = groupDict.getAt(name)
const group = groupId.getMcDbObject() as McDbGroup
if (group) {
// 进入编辑组
const getKey = new MxCADUiPrKeyWord();
getKey.setMessage(t('输入选项'));
getKey.setKeyWords(`[添加对象(A)/删除对象(R)/重命名(REN)]`);
const key = await getKey.go();
if (!key) return;
if (key === 'A') {
const selectIds: McObjectId[] = [];
// 添加对象
while (true) {
const getEntityPt = new MxCADUiPrPoint();
getEntityPt.setMessage('选择要添加到编组的对象');
getEntityPt.setDisableOsnap(true);
getEntityPt.setDisableDynInput(true);
getEntityPt.disableAllTrace(true);
const hoverSelectEnts: McDbEntity[] = [];
getEntityPt.setUserDraw((pt, pw) => {
if (hoverSelectEnts.length) hoverSelectEnts.forEach(ent => {
if (!ents.map(i => i.getObjectID().id).includes(ent.getObjectID().id)) ent.highlight(false);
});
hoverSelectEnts.length = 0;
const entId = MxCADUtility.findEntAtPoint(pt.x, pt.y, pt.z, -1);
if (entId.isValid() && !selectIds.map(item => item.id).includes(entId.id) && !group.has(entId)) {
const ent = entId.getMcDbEntity();
const arr = getGroupForEntity(ent);
if (arr.length) {
const group = arr[0].group;
group.getAllEntityId().forEach(id => {
const ent = id.getMcDbEntity();
ent.highlight(true);
hoverSelectEnts.push(ent)
})
} else {
ent.highlight(true);
hoverSelectEnts.push(ent)
}
}
});
const pt = await getEntityPt.go();
if (!pt) {
if (hoverSelectEnts.length) hoverSelectEnts.forEach(item => item.highlight(false));
break;
} else {
// 判断是否选中实体
if (hoverSelectEnts.length) {
if (hoverSelectEnts.length) {
hoverSelectEnts.forEach(ent => {
selectIds.push(ent.getObjectID());
})
};
} else {
getEntityPt.setUserDraw((point, pw) => {
const pts = [pt, new McGePoint3d(pt.x, point.y), point, new McGePoint3d(point.x, pt.y)]
// 设置范围框颜色即位置
let pl = new McDbPolyline();
pl.isClosed = true;
pts.forEach(pt => pl.addVertexAt(pt));
pw.setColor(0xFFFFFF);
pw.drawMcDbEntity(pl);
// 动态绘制矩形填充框
const geometry = new THREE.BufferGeometry();
geometry.setFromPoints([
new THREE.Vector3(pt.x, pt.y, pt.z),
new THREE.Vector3(pt.x, point.y, point.z),
new THREE.Vector3(point.x, point.y, point.z),
new THREE.Vector3(point.x, pt.y, pt.z)
]);
geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]), 2);
geometry.setIndex([0, 1, 2, 0, 2, 3]);
// 创建材质(半透明的颜色)
const material = new THREE.MeshBasicMaterial({
color: 0x004D00,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geometry, material);
pw.drawEntity(mesh);
});
const nextPt = await getEntityPt.go();
if (!nextPt) break;
const ss = new MxCADSelectionSet();
await ss.crossingSelect(pt.x, pt.y, nextPt.x, nextPt.y);
ss.forEach(id => {
if (!group.has(id) && !selectIds.map(i => i.id).includes(id.id)) {
const ent = id.getMcDbEntity();
const arr = getGroupForEntity(ent);
if (arr.length) {
const group = arr[0].group;
group.getAllEntityId().forEach(id => {
id.getMcDbEntity()?.highlight(true);
selectIds.push(id);
})
} else {
id.getMcDbEntity()?.highlight(true);
selectIds.push(id);
}
}
});
};
continue;
}
};
if (selectIds.length) {
selectIds.forEach(id => {
id.getMcDbEntity().highlight(false);
group.append(id);
});
MxPluginContext.useMessage().success('添加对象成功!');
}
} else if (key === 'R') {
const selectIds: McObjectId[] = [];
while (true) {
const getEntityPt = new MxCADUiPrPoint();
getEntityPt.setMessage('选择要从编组中删除的对象');
getEntityPt.setDisableOsnap(true);
getEntityPt.setDisableDynInput(true);
getEntityPt.disableAllTrace(true);
const hoverSelectEnts: McDbEntity[] = [];
getEntityPt.setUserDraw((pt, pw) => {
const entId = MxCADUtility.findEntAtPoint(pt.x, pt.y, pt.z, -1);
hoverSelectEnts.forEach(e => {
if (!group.has(e.getObjectID())) {
e.highlight(false)
}
});
hoverSelectEnts.length = 0;
if (entId.isValid() && !selectIds.map(i => i.id).includes(entId.id)) {
const ent = entId.getMcDbEntity();
ent.highlight(true);
hoverSelectEnts.push(ent)
}
});
const pt = await getEntityPt.go();
if (!pt) {
break;
} else {
// 判断是否选中实体
if (hoverSelectEnts.length) {
hoverSelectEnts.forEach(ent => {
ent.highlight(false);
if (group.has(ent.getObjectID())) {
selectIds.push(ent.getObjectID())
} else {
MxFun.acutPrintf('对象不是组内元素,无法删除')
}
})
} else {
getEntityPt.setUserDraw((point, pw) => {
const pts = [pt, new McGePoint3d(pt.x, point.y), point, new McGePoint3d(point.x, pt.y)]
// 设置范围框颜色即位置
let pl = new McDbPolyline();
pl.isClosed = true;
pts.forEach(pt => pl.addVertexAt(pt));
pw.setColor(0xFFFFFF);
pw.drawMcDbEntity(pl);
// 动态绘制矩形填充框
const geometry = new THREE.BufferGeometry();
geometry.setFromPoints([
new THREE.Vector3(pt.x, pt.y, pt.z),
new THREE.Vector3(pt.x, point.y, point.z),
new THREE.Vector3(point.x, point.y, point.z),
new THREE.Vector3(point.x, pt.y, pt.z)
]);
geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]), 2);
geometry.setIndex([0, 1, 2, 0, 2, 3]);
// 创建材质(半透明的颜色)
const material = new THREE.MeshBasicMaterial({
color: 0x004D00,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geometry, material);
pw.drawEntity(mesh);
});
const nextPt = await getEntityPt.go();
if (!nextPt) break;
const ss = new MxCADSelectionSet();
await ss.crossingSelect(pt.x, pt.y, nextPt.x, nextPt.y);
ss.forEach(id => {
if (group.has(id)) {
const ent = id.getMcDbEntity();
ent.highlight(false);
selectIds.push(ent.getObjectID());
}
});
};
continue;
}
};
if (selectIds.length) {
const newIds = ents.filter(ent => !selectIds.map(i => i.id).includes(ent.getObjectID().id)).map(ent => ent.getObjectID());
group.clear();
group.appendArray(newIds);
}
} else if (key === 'REN') {
while (true) {
const getName = new MxCADUiPrString()
getName.setMessage("输入组的新名称" + `<${group.name}>`)
getName.setKeyWords('查询(A)]')
const str = await getName.go();
if (getName.getStatus() === MrxDbgUiPrBaseReturn.kKeyWord) {
if (getName.isKeyWordPicked("A")) {
getName.setMessage("请输入要列出的编码组名" + "<*>")
const name = await getName.go();
if (getName.getStatus() === MrxDbgUiPrBaseReturn.kOk) {
if (name && name !== "*") {
const groupId = groupDict.getAt(name)
const group = groupId.getMcDbObject() as McDbGroup
MxFun.acutPrintf('定义的编组')
if (group) {
MxFun.acutPrintf(`\n${group.name}`)
}
} else if (name === "*") {
const groupIds = groupDict.getAllObject()
MxFun.acutPrintf(`\n 定义的编组:`)
groupIds.forEach((groupId) => {
const group = groupId.getMcDbObject() as McDbGroup
group && MxFun.acutPrintf(`\n ${group.name}`)
})
}
} else {
const groupIds = groupDict.getAllObject()
MxFun.acutPrintf(`\n 定义的编组:`)
groupIds.forEach((groupId) => {
const group = groupId.getMcDbObject() as McDbGroup
group && MxFun.acutPrintf(`\n ${group.name}`)
})
}
continue;
}
} else if (getName.getStatus() === MrxDbgUiPrBaseReturn.kOk) {
const groupId = groupDict.getAt(str)
const _group = groupId.getMcDbObject() as McDbGroup
if (_group && groupId.isValid()) {
MxFun.acutPrintf(`编组 ${str} 已经存在}`);
continue;
} else {
group.name = str;
MxPluginContext.useMessage().success('修改组名成功');
break;
}
} else {
break;
}
}
}
} else {
MxPluginContext.useMessage().success('对象不是组成员');
}
}
if (ents.length) ents.forEach(ent => ent.highlight(false));
mxcad.updateDisplay();
}
const getEnt = new MxCADUiPrEntity();
getEnt.setMessage('选择组');
getEnt.setKeyWords('[名称(N)]');
getEnt.setUserDraw((pt, pw) => {
ents.forEach(ent => ent.highlight(false));
ents.length = 0;
const entId = MxCADUtility.findEntAtPoint(pt.x, pt.y, pt.z, -1);
if (entId.isValid()) {
const ent = entId.getMcDbEntity();
groupArr = getGroupForEntity(ent);
if (groupArr.length) {
const group = groupArr[index].group;
group.getAllEntityId().forEach(id => {
const entity = id.getMcDbEntity();
entity.highlight(true);
ents.push(entity);
})
}
}
});
const entId = await getEnt.go();
if (getEnt.getStatus() === MrxDbgUiPrBaseReturn.kKeyWord) {
if (getEnt.isKeyWordPicked('N')) {
// 选择关键字
while (true) {
const getName = new MxCADUiPrString()
getName.setMessage("输入组的名称")
getName.setKeyWords('[查询(A)]')
const str = await getName.go();
if (getName.getStatus() === MrxDbgUiPrBaseReturn.kKeyWord) {
if (getName.isKeyWordPicked("A")) {
getName.setMessage("请输入要列出的编码组名" + "<*>")
getName.setKeyWords("")
const name = await getName.go();
if (getName.getStatus() === MrxDbgUiPrBaseReturn.kOk) {
if (name && name !== "*") {
const groupId = groupDict.getAt(name)
const group = groupId.getMcDbObject() as McDbGroup
MxFun.acutPrintf('定义的编组')
if (group) {
MxFun.acutPrintf(`\n${group.name}`)
}
} else if (name === "*") {
const groupIds = groupDict.getAllObject()
MxFun.acutPrintf(`\n 定义的编组:`)
groupIds.forEach((groupId) => {
const group = groupId.getMcDbObject() as McDbGroup
group && MxFun.acutPrintf(`\n ${group.name}`)
})
}
} else {
const groupIds = groupDict.getAllObject()
MxFun.acutPrintf(`\n 定义的编组:`)
groupIds.forEach((groupId) => {
const group = groupId.getMcDbObject() as McDbGroup
group && MxFun.acutPrintf(`\n ${group.name}`)
})
}
continue;
}
} else if (getName.getStatus() === MrxDbgUiPrBaseReturn.kOk) {
const groupId = groupDict.getAt(str)
const group = groupId.getMcDbObject() as McDbGroup
if (group && groupId.isValid()) {
group.getAllEntityId().forEach(id => {
const ent = id.getMcDbEntity();
ent.highlight(true);
ents.push(ent);
})
groupArr.push({ name: group.name, group });
editGroup()
break;
} else {
MxFun.acutPrintf(`编组 ${str} 不存在`);
continue;
};
} else {
break;
}
}
}
} else if (getEnt.getStatus() === MrxDbgUiPrBaseReturn.kOk) {
editGroup();
} else {
if (ents.length) ents.forEach(ent => ent.highlight(false));
}
}
4. 启用或禁用组选择
启用指定对象组的选择功能其执行过程如下:首先提示用户"请选择目标组",并在鼠标悬停时自动检测光标下的对象,若该对象属于某个组,则实时高亮显示该组的所有成员,提供可视化反馈。用户点击对象后,系统获取其所属的第一个组,并将该组的 isSelectable
属性设置为 true
,从而允许后续通过点击组内任意成员来选中整个组。最后清除高亮并刷新显示,完成设置。该方法提升了组对象的操作便捷性,适用于需要快速选中成组元素的场景。其完整代码如下:
ts
import { MxCADUiPrEntity, MxCADUtility, MxCpp} from "mxcad";
// 启用/禁用组选择
async function Mx_SetGroupSelection() {
const ents: McDbEntity[] = [];
let groupArr: GroupObject[] = [];
const getEnt = new MxCADUiPrEntity();
getEnt.setMessage('请选择目标组');
getEnt.setUserDraw((pt, pw) => {
ents.forEach(ent => ent.highlight(false));
ents.length = 0;
const entId = MxCADUtility.findEntAtPoint(pt.x, pt.y, pt.z, -1);
if (entId.isValid()) {
const ent = entId.getMcDbEntity();
groupArr = getGroupForEntity(ent);
if (groupArr.length) {
const group = groupArr[0].group;
group.getAllEntityId().forEach(id => {
const entity = id.getMcDbEntity();
entity.highlight(true);
ents.push(entity);
})
}
}
});
const entId = await getEnt.go();
if (groupArr.length) {
const group = groupArr[0].group;
group.isSelectable = true;
ents.forEach(ent => {
ent.highlight(false);
})
MxCpp.getCurrentMxCAD().updateDisplay();
};
}
三、功能演示
