在使用CAD工具進行繪圖時,面對複雜的圖形結構,如何高效地管理多個對象成為提升工作效率的關鍵。CAD提供的“組”功能,正是為解決這一問題而設計的實用工具。本文將全面介紹 mxcad 中”組“的概念,以及如何實現組相關的功能開發。
一、什麼是“組”(Group)?
在CAD中,組(Group) 是指將多個圖形對象邏輯地組合在一起,形成一個可被統一操作的集合。組不會創建新的圖元實體,也不會改變對象本身的幾何屬性,僅是一種命名的對象集合,組對象包含特點如下:
- 組內的對象保持獨立,可單獨編輯。
- 選擇組中任意一個對象時,整個組可被選中(取決於系統設置)。
- 每個組有唯一的名稱,便於識別和管理。
- 支持嵌套:一個組可以包含另一個組,形成層級結構。
- 組不作為獨立實體存儲在圖形數據庫中,僅作為對象的邏輯關聯存在。
二、組的核心功能開發
1. 創建組
該功能流程是從用户執行“創建組”命令開始。首先,系統初始化相關變量(如組名、描述和對象列表),並獲取當前圖形數據庫中的組管理字典。
隨後進入主循環,提示用户“選擇對象”。用户可以通過點擊或框選方式選擇一個或多個圖形對象,所選對象的ID將被保存到臨時列表中。
在選擇過程中,用户可隨時輸入關鍵字進行設置:
- 輸入 N(名稱):進入命名流程,系統提示“輸入編組名”。此時可輸入
[查詢(A)]來查看已存在的組名;若輸入*或直接回車,則列出所有組;否則查詢指定組信息。輸入名稱後,系統檢查是否重名,若無衝突則保存名稱並返回選擇狀態。 - 輸入 D(説明):進入説明設置,提示“輸入編組説明”,用户輸入的文本將作為該組的描述信息。
當用户完成選擇並按 回車或空格鍵 確認後,系統開始創建組:
- 首先檢查所選對象中是否有成員已屬於其他組。
- 若存在此類情況,則彈出確認提示:“包含相同對象的組已經存在。仍要創建新的組?<N>”,並提供“是(Y)/否(N)”選項。
- 若用户選擇“否”或取消操作,命令終止。
- 若用户確認繼續或無衝突,則調用底層API創建組,並將之前輸入的描述信息賦值給新組。
最後,組創建完成,系統退出循環,命令執行結束。整個流程支持ESC中斷或新命令打斷,確保操作的安全性和靈活性。
根據上述流程調用 mxcad 內部API接口實現方法如下:
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()從字典中刪除該組; - 提示“組 已分解”或“對象不是組成員”(如未選中有效組)。
操作完成後,清除所有高亮顯示的對象,確保界面恢復整潔,命令結束。其具體實現代碼如下:
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 已經存在”並要求重新輸入;否則更新組名,並提示“修改組名成功”。
實現上述流程的具體功能代碼如下: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,從而允許後續通過點擊組內任意成員來選中整個組。最後清除高亮並刷新顯示,完成設置。該方法提升了組對象的操作便捷性,適用於需要快速選中成組元素的場景。其完整代碼如下:
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();
};
}