Skip to content

DWG 数据库进阶与用户坐标系交互

从ZRX程序向CAD发送命令创建数据库对象

为什么要通过发送命令来操作CAD

作用

  1. 更简单: 对于新手很友好,直接就像用户操作一样输入命令
  2. 更稳定: 可以采用已有的命令逻辑,避免重复实现
  3. 否认“低级”: 通过命令类方式操作不是技术低级,是调用系统自带的高效程序

实际场景对应

  • 需要快速创建形状/拷贝/移动/绘制时
  • 例如自动处理多个图形的复制操作

zcedCommand 和 resbuf 进阶说明

zcedCommand 是什么?

它是一个函数,用于 在 ZRX 程序里模拟用户手动输入 CAD 命令 的效果。

简单来说,就是用 C++ 把很复杂的 CAD 命令动作用程序化。

与用户操作对应

在 CAD 界面手动输入:

cpp
_.Copy

等同于 C++ 程序里:

cpp
zcedCommand(RTSTR, _T("_.Copy"), ...);

示例

cpp
zcedCommand(
  RTSTR, _T("_.Copy"), // 扩展名 + Copy
  RTENAME, en,         // 选择对象
  RTSTR, _T(""),       // 确定
  RTSTR, _T("_Mode"),
  RTSTR, _T("_Single"),
  RT3DPOINT, apt,      // 指定点
  RTSTR, _T("100,100,0"),
  RTNONE);
  • RTSTR 表示一个字符串

  • RTENAME 表示一个对象名 (选择一条线)

  • RT3DPOINT 是三维点

  • RTNONE 结束命令

_:调用global名称 . :无视Undefine调用原本的命令名称

基础结构名词对应
简写说明
zds_pointC 格式的 3D 坐标,类型是 double[3]
ZcGePoint3d官方更方便用的 3D 点类
zds_nameCAD 内部对象编号 (后续用于 zced/数据库操作)
ZcDbObjectIdZRX 数据库对象标识
数据结构转换
cpp
ZcGePoint3d pt = asPnt3d(zds_point);
zds_point pt2 = asDblArray(pt);
resbuf 介绍

是 CAD 系统内部用来 表示多种类型的数据类型链表结构,用于和系统命令进行交换

  • 创建表示数据的链表

  • 用在 zcedCommand、输入交互、存储扩展数据等场景

  • 装一系列参数,传给 zcedCommand

  • 存储扩展字典(XData)或块参数等。

  • 常用函数:

    • zcutBuildList(...) 创建 resbuf 链

    • zcutRelRb(rb) 释放 resbuf

结构

cpp
struct resbuf {
  short restype;      // 数据类型标志
  union {             // 数据值
	int    rint;
	double rreal;
	char*  rstring;
	...
  } resval;
  resbuf* rbnext;     // 指向下一个节点
};
resbuf 遍历示例
cpp
resbuf* pTmp = pRb;
while (pTmp)
{
  ...
  pTmp = pTmp->rbnext;
}

创建/释放 resbuf

cpp
resbuf* pRb = zcutBuildList(RTSTR, _T("Line"), RTNONE); // 创建
zcutRelRb(pRb); // 释放

如何进行用户交互

选点

cpp
// 语法-选点
zcedGetPoint(
  const zds_point* pBase,
  LPCTSTR prompt,
  zds_point outPt);
// 语法-选角点
// 在指定第一个对角点后,提示用户输入对角范围的第二点,用于画矩形、多边形等。
zds_point p1, p2;
zcedGetPoint(NULL, _T("\n输入第一个角点:"), p1);
zcedGetCorner(p1, _T("\n输入对角点:"), p2);

// 示例
zcedGetPoint(NULL, _T("\n输入起点:"), pt1);
zcedGetPoint(pt1, _T("输入终点: "), pt2)
zcedGetCorner(pt1, _T(“\n输入对角点:"), pt2);
  • 参数
参数含义
pBase基点(可用于相对提示),NULL 表示绝对坐标
prompt提示信息
outPt接收用户输入的三维点
  • CAD 对应:命令行中 Specify first point: 或鼠标点击。

选实体

cpp
// 语法:
int zcedEntSel(
  LPCTSTR prompt,
  zds_name outEnt,
  zds_point outPt);

zcedEntSel(_T(“\n选择一个实体: "), en, apt);
  • 返回值RTNORM 表示正常选择。
  • CAD 对应:命令行 Select an object: 并拾取图形。

选择嵌套的实体

cpp
zds_name  en;//选择到的实体
BOOL pickflag = FALSE;
zds_point pt;//如果pickflag为FALSE,则这个参数表示用户选到的点,如果为TRUE,参数传入的点表示从该点选择实体
LPCWSTR prompt = _T(“\n选择一个嵌套的实体: ");
zds_matrix xformres;//把嵌套的实体从ECS转换成WCS的矩阵
resbuf *rb = nullptr;//保存返回的结果
zcedNEntSelP(prompt, en, pt, pickflag, xformres, &rb);
if (rb)//返回的结果要释放掉
{
  zcutRelRb(rb);
}

选择集

  • 定义:一次性获取满足条件的多个实体,返回一个选择集(Selection Set)。
  • 对应场景:批量操作,如同时移动或删除多个对象,或者框选某一范围内的所有图元。
  • 函数zcedSSGet
  • 用法:获取多个实体,如框选、全选。
  • 示例
cpp
zds_name ss;
// 手动逐一选择
zcedSSGet(NULL, NULL, NULL, NULL, ss);
// 全选
zcedSSGet(_T("X"), NULL, NULL, NULL, ss);
模式代码描述CAD 对应命令行选项
NULL手动逐一选择框选 / CTRL+点击等
"X"选取所有实体ALL
"W"窗口选择(完全包含)Window
"C"窗口交叉(包括部分包含)Crossing
  • 遍历
cpp
int len = 0;
zcedSSLength(ss, &len);
for (int i = 0; i < len; i++) {
  zds_name ent;
  zcedSSName(ss, i, ent);
  // 转换到 ZcDbObjectId...
}

// 详细解释:
int len = 0;
zcedSSLength(ss, &len);
for (int i = 0; i < len; ++i) {
    zds_name en;
    zcedSSName(ss, i, en);            // 获取第 i 个实体名
    ZcDbObjectId id;
    zcdbGetObjectId(id, en);          // 转换为对象 ID
    ZcDbEntity* pEnt = nullptr;
    zcdbOpenZcDbEntity(pEnt, id, ZcDb::kForRead);  // 打开实体
    // ... 处理实体,如获取属性、移动等
    pEnt->close();
}

非常复杂,请仔细看文档

cpp
zcedSSGet(NULL, NULL, NULL, NULL, ss);//手动选择
zcedSSGet(_T("X"), NULL, NULL, NULL, ss);//选择所有实体
zcedSSGet(_T("W"), pt1, pt2, NULL, ss);//框选实体(完全包含)
zcedSSGet(_T("C"), pt1, pt2, NULL, ss);//框选实体(包括部分包含)

zcedSSFree

选择集的过滤器

  • 定义:在获取选择集时,先用 resbuf 链表定义筛选条件,只选中符合条件的实体。
  • 作用:大幅提高自动化脚本的可靠性和效率,例如只对特定类型或属性的对象操作。
cpp
zds_name ss;
// 构造筛选条件:选取所有半径 50~100 的圆,或颜色为红色(索引 1)的圆
resbuf* pFilter = zcutBuildList(
    RTDXF0, _T("CIRCLE"),     // 只筛选圆
    -4, _T("<OR"),
      -4, _T("<AND"),
         -4, _T(">"), 40, 50.0,   // 半径 > 50
         -4, _T("<"), 40, 100.0,  // 半径 < 100
      -4, _T("AND>"),
      62, 1,                       // 颜色索引 = 1 (红)
    -4, _T("OR>"),
    RTNONE);
// 使用过滤器获取选择集
zcedSSGet(NULL, NULL, NULL, pFilter, ss);
// 释放过滤器链表
zcutRelRb(pFilter);

// 遍历选择集同上
int len = 0;
zcedSSLength(ss, &len);
for (int i = 0; i < len; ++i) {
    zds_name en;
    zcedSSName(ss, i, en);
    ZcDbObjectId id;
    zcdbGetObjectId(id, en);
    ZcDbEntity* pEnt = nullptr;
    zcdbOpenZcDbEntity(pEnt, id, ZcDb::kForRead);
    // ... 处理实体
    pEnt->close();
}
// 释放选择集
zcedSSFree(ss);

输入字符串

cpp
ZcString str;
zcedGetString(0, _T("\n输入字符串:"), str);

/整数/实数/角度

API用途
zcedGetString输入字符串
zcedGetInt输入整数
zcedGetReal输入实数
zcedGetAngle输入角度(返回弧度)

输入数字

cpp
int n;
zcedGetInt(_T("\n输入整数"), &n);
zds_real r;//也可以用double,不太严谨但更方便
zcedGetReal(_T("\n输入实数"), &r);

输入角度:受ANGBASE和ANGDIR影响

cpp
zds_real ang;
zcedGetAngle(NULL, _T("\n输入角度:"), &ang);

zds_point pt;
zcedGetPoint(NULL, _T("\n选择基点:"), pt);
zds_real ang;
zcedGetAngle(pt, _T("\n输入角度:"), &ang);

输入长度:zcedGetDist(与角度类似)

快捷键 zcedInitGet(RSG_NONULL, _T("Ja NEIN,N _ YES,Y No")); 快捷键由空格分隔 大写的字母是快捷键 逗号后也是快捷键 如有下划线,前面是local名称,后面是global名称,数量不同时从左往右匹配

cpp
zds_point pt;
zcedInitGet(RSG_NONULL, _T("3P,3 2P,2 Ttr"));
auto rc = zcedGetPoint(NULL, _T("Specify the center point of circle or [3P/2P/Ttr (tangency tangency radius)]: "), pt);
if (rc == RTNORM)//输入了点
{
  ...
}
else if (rc == RTKWORD)//输入了关键字
{
  ZcString kword;
  zcedGetInput(kword);
  zcutPrintf(kword);
}

坐标系概述

坐标系定义使用场景
WCS世界坐标系,CAD 默认绝对坐标系所有数据存储、内部计算
UCS用户坐标系,用户可定义的绘图坐标系绘制特殊角度/方向图形,如斜切、旋转图形
DCS显示坐标系,由当前视图/相机位置决定屏幕显示、捕捉光标移动
OCS对象坐标系,对象自身的二维坐标映射二维多段线(POLYLINE)高度 + 法向量存储方式
MCS模型坐标系,大多数情况下等同 WCS块(BLOCK) 插入变换时的参照系
Details

W(orld)CS:世界坐标系,绝对坐标 U(ser)CS:用户坐标系,用户所使用的坐标系 D(isplay)CS:显示坐标系,由当前相机的位置、目标位置、up方向决定 O(bject)CS:对象坐标系,二维多段线,节省存储空间(三维点->二维点+标高+法向量) M(odel)CS:模型坐标系,块,大部分情况下跟WCS一样

所有获取用户输入的接口,如无特殊说明,得到的都是UCS坐标 与用户输入无关的接口,如无特殊说明,输入输出都是WCS坐标

cpp
// WCS -> UCS
zds_point wpt = {10, 20, 0}, upt;
resbuf orig = {RTSHORT}; orig.resval.rint = 0; // WCS
resbuf dest = {RTSHORT}; dest.resval.rint = 1; // UCS
zcedTrans(wpt, &orig, &dest, 0, upt);

// UCS -> WCS
zcedTrans(upt, &dest, &orig, 0, wpt);

课堂习题与作业解析

任务:交互式绘制直线

这是今天的核心练习。它不再是像昨天那样在代码里写死坐标来画图,而是让用户亲自在CAD界面上通过鼠标点击来决定直线的位置。这完美地应用了课堂上讲的用户交互中的“选点”功能。

功能说明: 执行 testLine 命令后,程序会提示用户在图纸上依次点击两点,然后根据用户选择的这两点来创建一条直线。

算法思路

  1. 调用 zcedGetPoint 函数,提示用户选择直线的起点,并获取用户点击的点的坐标。

  2. 再次调用 zcedGetPoint 函数,提示用户选择终点,并将起点作为“基点”(Base Point)传入,这样用户在选择时会看到一条从起点到当前鼠标位置的橡皮筋线,交互体验更好。

  3. 获取到的坐标是 zds_point 类型(C风格的数组),需要将其转换为数据库操作更常用的 ZcGePoint3d 类型(C++类)。

  4. 使用这两个 ZcGePoint3d 点创建一个 ZcDbLine 对象。

  5. 通过标准的数据库操作流程(获取数据库 -> 获取块表 -> 以可写方式获取模型空间),将这条直线添加到模型空间中。

  6. 依次关闭所有打开的对象,释放资源。

代码解析

cpp
void testLine()
{
	// 定义两个 zds_point 类型的变量来接收用户输入的坐标。
	// zds_point 是一个简单的 double[3] 数组,专门用于与 zced* 系列的交互函数配合。
	zds_point pt1, pt2;

	// 第一步:让用户点击起点
	// zcedGetPoint 是核心交互函数,用于获取用户在屏幕上的点选。
	// 参数1 (NULL): 基点。为NULL表示没有基点,用户的选择是绝对的。
	// 参数2 ("..."): 提示字符串,会显示在CAD的命令行。
	// 参数3 (pt1): 用于接收用户点击坐标的输出变量。
	// 返回值 (RTNORM): 表示用户正常点击了一个点。如果用户按了ESC,则返回其他值。
	if (zcedGetPoint(NULL, _T("\n请选择起点:"), pt1) != RTNORM)
		return; // 如果用户取消操作,则直接退出函数

	// 第二步:让用户点击终点
	// 再次调用 zcedGetPoint。
	// 参数1 (pt1): 此时将刚才获取的起点 pt1 作为基点。
	// 效果:CAD界面会显示一条从 pt1 到当前鼠标位置的动态虚线(橡皮筋效果)。
	if (zcedGetPoint(pt1, _T("\n请选择终点:"), pt2) != RTNORM)
		return;

	// 第三步:坐标类型转换
	// 将 C 风格的 zds_point 数组转换为面向对象的 ZcGePoint3d 类。
	// 数据库实体(如 ZcDbLine)的构造函数需要的是 ZcGePoint3d 类型。
	ZcGePoint3d startPt(pt1[0], pt1[1], pt1[2]);
	ZcGePoint3d endPt(pt2[0], pt2[1], pt2[2]);

	// 第四步:创建直线实体对象 (在内存中)
	ZcDbLine* pLine = new ZcDbLine(startPt, endPt);

	// 第五步:将实体添加到数据库 (这部分是昨天的知识)
	// 5.1 获取当前数据库
	ZcDbDatabase* pDb = zcdbHostApplicationServices()->workingDatabase();

	// 5.2 获取块表
	ZcDbBlockTable* pBT = nullptr;
	if (pDb->getBlockTable(pBT, ZcDb::kForRead) != Acad::eOk || !pBT)
		return;

	// 5.3 以可写方式获取模型空间
	ZcDbBlockTableRecord* pBTR = nullptr;
	if (pBT->getAt(ACDB_MODEL_SPACE, pBTR, ZcDb::kForWrite) != Acad::eOk || !pBTR)
	{
		pBT->close();
		return;
	}

	// 5.4 将直线实体追加到模型空间
	ZcDbObjectId lineId;
	pBTR->appendAcDbEntity(lineId, pLine);

	// 第六步:关闭所有打开的数据库对象
	pLine->close();
	pBTR->close();
	pBT->close();
}
  • 核心知识点

    • zcedGetPoint(): 这是实现与用户点交互的关键。通过它,我们的程序可以暂停,等待用户的鼠标输入,从而让程序更加灵活和实用。

    • 坐标系 (UCS vs WCS): 课堂上重点讲了坐标系。需要特别注意的是,所有 zcedGet* 系列的交互函数,默认获取的坐标都是在当前的用户坐标系(UCS)下。而数据库中存储实体几何信息通常使用世界坐标系(WCS)。在这个简单的例子中,我们假设UCS和WCS是重合的,所以没有做坐标转换。但在复杂的应用中,如果用户改变了UCS,就需要使用 zcedTrans 函数将点从UCS转换到WCS,然后再存入数据库。

    • 数据类型转换: 理解 zds_pointZcGePoint3d 之间的区别与联系。前者用于与老的C-API风格的交互函数(zced*)通信,后者是现代C++数据库API(ZcDb*, ZcGe*)的标准。