问题描述: 

在 中望CAD 二次开发中,需要通过 ZcDbWipeout 创建遮罩实体,用于遮挡指定区域内的图形。常见需求包括:

- **基础需求**:根据拾取的两个角点创建矩形遮罩。
- **拓展需求**:创建「回」字形遮罩(外围为遮罩框,中间口不遮罩)。

实现思路:

一、最简单的矩形 Wipeout 实现:

 实现思路

Wipeout 基于 AcDbRasterImage,裁剪边界需在**图像局部(像素)坐标系**下给出。若直接传入图纸坐标,容易出现定位偏差。正确做法是:

1. 先设置 Wipeout 的 **orientation**(原点 + U/V 向量);
2. 用 **getPixelToModelTransform** 取得像素→模型变换,取其**逆矩阵**将模型空间边界点变换到像素空间;
3. 再调用 **setClipBoundary** 传入像素空间下的 2D 点集。

步骤指引

1. 确保图像定义存在并创建 Wipeout 实体

```cpp
if (AcDbWipeout::createImageDefinition() != Acad::eOk)
    return;  // 失败处理
AcDbWipeout* pWipe = new AcDbWipeout();
```

2. 根据矩形范围设置 orientation

以矩形最小角为原点,宽度/高度取 `max(dx, dy)` 作为缩放,保证图像覆盖该矩形:

```cpp
double x1 = ..., y1 = ..., x2 = ..., y2 = ...;  // 两角点得到的矩形
double dx = x2 - x1, dy = y2 - y1;
double w = max(dx, dy);
if (w < 1e-10) w = 1.0;

AcGePoint3d originPnt(x1, y1, 0.0);
AcGeVector3d uDir(w, 0.0, 0.0), vDir(0.0, w, 0.0);
pWipe->setOrientation(originPnt, uDir, vDir);
```

3. 将模型空间边界点变换到像素空间**

矩形 5 点(四角 + 闭合):(x1,y1) → (x2,y1) → (x2,y2) → (x1,y2) → (x1,y1),逐一用逆变换得到像素坐标后填入 `AcGePoint2dArray`:

```cpp
AcGeMatrix3d pixelToModel, modelToPixel;
pWipe->getPixelToModelTransform(pixelToModel);
modelToPixel = pixelToModel.invert();

AcGePoint2dArray point2ds;
AcGePoint3dArray point3ds;  // 已按闭合顺序填入矩形 5 点
for (int i = 0; i < point3ds.length(); i++) {
    AcGePoint3d pt = point3ds.at(i);
    pt.transformBy(modelToPixel);
    point2ds.append(AcGePoint2d(pt.x, pt.y));
}
```

4. 设置裁剪与显示并写入图纸**

```cpp
pWipe->setDisplayOpt(AcDbRasterImage::kTransparent, Adesk::kTrue);
pWipe->setDisplayOpt(AcDbRasterImage::kShow, true);
pWipe->setClipBoundaryToWholeImage();
pWipe->setClipBoundary(AcDbRasterImage::kPoly, point2ds);
pWipe->setShowImage(false);
pWipe->setShowClipped(true);
pWipe->setClipInverted(false);   // 矩形内部遮罩

// 将 pWipe 追加到模型空间并 REGEN
```

按上述流程即可得到与拾取矩形完全对齐的矩形 Wipeout。


二、拓展:回字形 Wipeout 实现

### 需求说明

回字形效果:**外框与内框之间的“框带”为遮罩,中间口不遮罩**。  
`setClipBoundary` 仅接受**单条闭合多段线**点集,不能直接传“外环+内环”两套环,否则会报错。因此需要用**一组点**描出“回”字外围轮廓,允许存在一段重合边(例如从内框回到外框起点的对角线走两次)。

### 11 点顺序(从外框左上角绕一圈再回到左上角)

采用** 11 个点**描述一条闭合路径,与常见“回”字示意图一致:

| 序号 | 含义           | 说明                    |
|------|----------------|-------------------------|
| 1    | 外框左上 OTL   | 起点                    |
| 2    | 外框右上 OTR   |                         |
| 3    | 外框右下 OBR   |                         |
| 4    | 外框左下 OBL   |                         |
| 5    | 外框左上 OTL   | 外框闭合                |
| 6    | 内框左上 ITL   | 对角线进入内框          |
| 7    | 内框右上 ITR   |                         |
| 8    | 内框右下 IBR   |                         |
| 9    | 内框左下 IBL   |                         |
| 10   | 内框左上 ITL   | 内框闭合                |
| 11   | 外框左上 OTL   | 对角线回到起点,闭合整条路径 |

坐标约定:外矩形 (x1,y1)~(x2,y2),内矩形在内缩 margin 后为 (xi1,yi1)~(xi2,yi2)。则:

- OTL=(x1,y2),OTR=(x2,y2),OBR=(x2,y1),OBL=(x1,y1)
- ITL=(xi1,yi2),ITR=(xi2,yi2),IBR=(xi2,yi1),IBL=(xi1,yi1)

### 关键代码示例

```cpp
// 外矩形与内矩形(内缩 15%)
double margin = min(dx, dy) * 0.15;
if (margin < 1e-6) margin = min(dx, dy) * 0.3;
double xi1 = x1 + margin, yi1 = y1 + margin;
double xi2 = x2 - margin, yi2 = y2 - margin;

AcGePoint3dArray point3ds;
point3ds.append(AcGePoint3d(x1, y2, 0.0));    // 1 OTL
point3ds.append(AcGePoint3d(x2, y2, 0.0));    // 2 OTR
point3ds.append(AcGePoint3d(x2, y1, 0.0));    // 3 OBR
point3ds.append(AcGePoint3d(x1, y1, 0.0));    // 4 OBL
point3ds.append(AcGePoint3d(x1, y2, 0.0));   // 5 OTL
point3ds.append(AcGePoint3d(xi1, yi2, 0.0));  // 6 ITL
point3ds.append(AcGePoint3d(xi2, yi2, 0.0)); // 7 ITR
point3ds.append(AcGePoint3d(xi2, yi1, 0.0));  // 8 IBR
point3ds.append(AcGePoint3d(xi1, yi1, 0.0));  // 9 IBL
point3ds.append(AcGePoint3d(xi1, yi2, 0.0));  // 10 ITL
point3ds.append(AcGePoint3d(x1, y2, 0.0));   // 11 OTL
```

后续与矩形实现相同:**setOrientation → getPixelToModelTransform 求逆 → 将上述 11 点变换到像素空间 → setClipBoundary(kPoly, point2ds)**,并设置 **setClipInverted(false)**,即可得到回字形遮罩(框带遮罩、中间口不遮罩)。

### 小结

- 矩形 Wipeout:模型空间矩形 5 点 → 像素空间 → setClipBoundary;setClipInverted(false)。
- 回字形 Wipeout:用 11 点单条闭合路径描述“回”字外围,同样经像素空间变换后 setClipBoundary;setClipInverted(false)。  
- 两种方式均依赖「先 setOrientation,再模型→像素变换,再 setClipBoundary」这一流程,才能保证遮罩位置与形状正确。


完整代码:

void createFrameWipeout()
{
	ads_point ap1, ap2;
	if (acedGetPoint(nullptr, _T("\n指定矩形第一角点: "), ap1) != RTNORM) return;
	if (acedGetPoint(ap1, _T("\n指定对角点: "), ap2) != RTNORM) return;

	double x1 = min(ap1[X], ap2[X]), x2 = max(ap1[X], ap2[X]);
	double y1 = min(ap1[Y], ap2[Y]), y2 = max(ap1[Y], ap2[Y]);
	double dx = x2 - x1, dy = y2 - y1;
	if (dx < 1e-10 || dy < 1e-10) { acutPrintf(_T("\n矩形尺寸过小。")); return; }

	// 内框:外框内缩 15%
	double margin = min(dx, dy) * 0.15;
	double xi1 = x1 + margin, yi1 = y1 + margin;
	double xi2 = x2 - margin, yi2 = y2 - margin;
	if (xi1 >= xi2 || yi1 >= yi2) {
		margin = min(dx, dy) * 0.1;
		xi1 = x1 + margin; yi1 = y1 + margin;
		xi2 = x2 - margin; yi2 = y2 - margin;
	}

	// 11 点:外框左上起绕外框→回左上→对角线到内框左上→绕内框→回内框左上→对角线回外框左上
	AcGePoint3dArray pts;
	pts.append(AcGePoint3d(x1, y2, 0.0));    // 1 外框左上
	pts.append(AcGePoint3d(x2, y2, 0.0));   // 2 外框右上
	pts.append(AcGePoint3d(x2, y1, 0.0));   // 3 外框右下
	pts.append(AcGePoint3d(x1, y1, 0.0));   // 4 外框左下
	pts.append(AcGePoint3d(x1, y2, 0.0));   // 5 外框左上
	pts.append(AcGePoint3d(xi1, yi2, 0.0)); // 6 内框左上
	pts.append(AcGePoint3d(xi2, yi2, 0.0)); // 7 内框右上
	pts.append(AcGePoint3d(xi2, yi1, 0.0)); // 8 内框右下
	pts.append(AcGePoint3d(xi1, yi1, 0.0)); // 9 内框左下
	pts.append(AcGePoint3d(xi1, yi2, 0.0)); // 10 内框左上
	pts.append(AcGePoint3d(x1, y2, 0.0));   // 11 外框左上

	if (AcDbWipeout::createImageDefinition() != Acad::eOk) return;
	AcDbWipeout* pWipe = new AcDbWipeout();
	if (!pWipe) return;

	double w = max(dx, dy);
	AcGePoint3d origin(x1, y1, 0.0);
	AcGeVector3d uVec(w, 0.0, 0.0), vVec(0.0, w, 0.0);
	ZSoft::Boolean b = pWipe->setOrientation(origin, uVec, vVec);
	if (b) {
		delete pWipe; return;
	}

	AcGeMatrix3d pixelToModel, modelToPixel;
	if (pWipe->getPixelToModelTransform(pixelToModel) != Acad::eOk) {
		delete pWipe; return;
	}
	modelToPixel = pixelToModel.invert();

	AcGePoint2dArray pt2d;
	for (int i = 0; i < pts.length(); i++) {
		AcGePoint3d p = pts.at(i);
		p.transformBy(modelToPixel);
		pt2d.append(AcGePoint2d(p.x, p.y));
	}

	pWipe->setDisplayOpt(AcDbRasterImage::kTransparent, Adesk::kTrue);
	pWipe->setDisplayOpt(AcDbRasterImage::kShow, true);
	pWipe->setClipBoundaryToWholeImage();
	if (pWipe->setClipBoundary(AcDbRasterImage::kPoly, pt2d) != Acad::eOk) {
		delete pWipe; return;
	}
	pWipe->setShowImage(false);
	pWipe->setShowClipped(true);
	pWipe->setClipInverted(false);

	AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
	if (!pDb) { delete pWipe; return; }
	AcDbBlockTable* pBT = nullptr;
	if (pDb->getSymbolTable(pBT, AcDb::kForRead) != Acad::eOk || !pBT) { delete pWipe; return; }
	AcDbBlockTableRecord* pMS = nullptr;
	if (pBT->getAt(ACDB_MODEL_SPACE, pMS, AcDb::kForWrite) != Acad::eOk || !pMS) {
		pBT->close(); delete pWipe; return;
	}
	AcDbObjectId id;
	if (pMS->appendAcDbEntity(id, pWipe) != Acad::eOk) {
		pMS->close(); pBT->close(); delete pWipe; return;
	}
	pWipe->close();
	pMS->close();
	pBT->close();
	acedCommandS(RTSTR, _T("._REGEN"), NULL);
	acutPrintf(_T("\n回字形 Wipeout 已创建。"));
}

效果:


PS:如果想取消遮罩的边框可以用WIPEOUTFRAME系统变量控制。


英文空间链接


★★ 中望CAD 二次开发:创建 Wipeout 遮罩的技术实现https://confluence.zwcad.com/pages/viewpage.action?pageId=319951116

  • No labels