问题描述:
在 中望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
