本文介绍的例子是沿一个圆弧实体等间距放置若干个图块,用户拖动光标时圆弧的形状发生变化,同时插入的块参照的位置也会随之变化。
技术路线:
(1)使用ObjectARX向导创建新工程MultipleEntJig
向工程中添加一个普通类CArcBlockJigEntity,将它的父类设置为AcDbEntity
类CArcBlockJigEntity 的头文件:
class CArcBlockJigEntity :
public AcDbEntity
{
public:
//参数:startPoint:起始点;endPoint:终止点;thirdPoint:第三点;
//blockId:块的id;count:插入块的个数
CArcBlockJigEntity(const AcGePoint3d &startPoint,const AcGePoint3d &thirdPoint,const AcGePoint3d &endPoint,
AcDbObjectId blkDefId,int count);
virtual ~CArcBlockJigEntity();
//自定义实体的绘制函数
virtual Adesk::Boolean worldDraw(AcGiWorldDraw* mode);
//设置圆弧终点的位置
void SetEntPoint(const AcGePoint3d &pt);
//将圆弧和块添加到模型空间
void PostToModelSpace();
//获得添加的块参照集合
AcDbObjectIdArray GetBlkRefIds();
private:
//绘制实体或添加到模型空间
void DrawOrAddSubEnts(AcGiWorldDraw* mode);
private:
AcGePoint3d m_startPoint, m_endPoint, m_thirdPoint;
//圆弧的起点、终点和第三点(圆弧上位于起点和终点中间的一点)
AcDbObjectId m_blkDefId; //块定义ID
int m_blockCount; //要布置的块参照的数量
AcDbObjectIdArray m_blkRefIds; //添加的块参照集合
};
各函数的作用:
worldDraw函数 | 完成自定义实体的绘制 |
SetEndPoint函数 | 函数在拖动过程中动态修改圆弧的终点,它会引发自定义实体的绘制,也就是worldDraw函数被调用 |
PostToModelSpace函数 | 函数能将动态显示的子实体添加到模型空间 |
GetBlkRefIds函数 | 将PostToModelSpace函数中添加的块参照ID集合返回到外部调用函数 |
DrawOrAddSubEnts函数 | 将worldDraw和PostModelSpace中公用的代码封装起来便于重用 |
(2)自定义实体的构造函数,会根据外部传递的参数来填充类中的成员变量,析构函数则不做什么事情。
连个函数的实现代码:
CArcBlockJigEntity::CArcBlockJigEntity(const AcGePoint3d &startPoint,const AcGePoint3d &thirdPoint,const AcGePoint3d &endPoint,AcDbObjectId blkDefId,int count)
{
m_startPoint = startPoint;
m_thirdPoint = thirdPoint;
m_endPoint = endPoint;
m_blkDefId = blkDefId;
m_blockCount = count;
}
CArcBlockJigEntity::~CArcBlockJigEntity()
{
}
(3)worldDraw函数会在图形窗口中显示圆弧和块参照子实体,负责拖动过程中的显示更新,PostToModelSpace函数则会将这些子实体添加到模型空间,负责拖动完整之后将子实体添加到模型空间。
DrawOrAddSubEnts函数封装了两个公用的代码。实现代码为:
void CArcBlockJigEntity::DrawOrAddSubEnts(AcGiWorldDraw* mode)
{
//绘制圆弧
AcDbCurve *pCurve = NULL; //计算等分点的曲线
AcGePoint2d startPoint2d = ToPoint2d(m_startPoint);
AcGePoint2d thirdPoint2d = ToPoint2d(m_thirdPoint);
AcGePoint2d endPoint2d = ToPoint2d(m_endPoint);
if (ThreePointIsCollinear(startPoint2d, thirdPoint2d, endPoint2d))
{
AcGePoint3d verts[2];
verts[0] = m_startPoint;
verts[1] = m_endPoint;
if (mode != NULL)
{
mode->geometry().polyline(2, verts);
}
pCurve = new AcDbLine(m_startPoint, m_endPoint);//创建直线段
}
else
{
if (mode != NULL)
{
mode->geometry().circularArc(m_startPoint, m_thirdPoint, m_endPoint);
}
AcGeCircArc2d geArc(startPoint2d, thirdPoint2d, endPoint2d);
//geArc的起始角度始终是0,因此单独计算起始角度和终止角度
AcGeVector2d vecStart = startPoint2d - geArc.center();
AcGeVector2d vecEnd = endPoint2d - geArc.center();
//AcGeArc必须是逆时针,因此需要根据三点的旋转方向,确定正确的起始
//角度
double startAngle = 0;
if (PtInLeftOfLine(startPoint2d, thirdPoint2d, endPoint2d) > 0)
//逆时针
{
startAngle = vecStart.angle();
}
else
{
startAngle = vecEnd.angle();
}
double endAngle = startAngle + (geArc.endAng() - geArc.startAng());
pCurve = new AcDbArc(ToPoint3d(geArc.center()), geArc.radius(),
startAngle, endAngle);
//计算等分点,获得块参照插入的位置
double startParam = 0, endParam = 0; //曲线的起点和终点参数
pCurve->getStartParam(startParam);
pCurve->getEndParam(endParam);
int intervalCount = m_blockCount + 1; //等分间距份数比块参照数量大1
double paramInterval = (endParam - startParam) / intervalCount;
AcGePoint3dArray blkRefPoints; //块参照插入点的集合
for (int i = 1; i < intervalCount; i++) //曲线的起点和终点不需要放置图块
{
double param = startParam + i * paramInterval;
AcGePoint3d pt;
pCurve->getPointAtParam(param, pt);
blkRefPoints.append(pt);
}
if (mode != NULL) //显示子实体
{
delete pCurve; //动态分配的实体,不加入模型空间,使用完毕之后需要释放
}
else //添加子实体的方式
{
PostToModelSpace(pCurve);
}
//绘制几个图块
m_blkRefIds.setLogicalLength(0);
for (int i = 0; i < blkRefPoints.length(); i++)
{
AcDbBlockReference *pBlkRef = new AcDbBlockReference(blkRefPoints[i], m_blkDefId);
if (mode != NULL)
{
pBlkRef->worldDraw(mode);
delete pBlkRef;
}
else
{
m_blkRefIds.append(PostToModelSpace(pBlkRef));
}
}
}
}
①函数ToPoint2d的实现:
AcGePoint2d CArcBlockJigEntity::ToPoint2d(const AcGePoint3d &point3d)
{
return AcGePoint2d(point3d.x, point3d.y);
}
②函数threePointIsCollinear的实现:
bool CArcBlockJigEntity::ThreePointIsCollinear(const AcGePoint2d &pt1, const AcGePoint2d &pt2, const AcGePoint2d &pt3)
{
double xy = pt1.x * pt1.x + pt1.y * pt1.y;
double xyse = xy - pt3.x * pt3.x - pt3.y * pt3.y;
double xysm = xy - pt2.x * pt2.x - pt2.y * pt2.y;
xy = (pt1.x - pt2.x) * (pt1.y - pt3.y) - (pt1.x - pt3.x) * (pt1.y - pt2.y);
return (fabs(xy) < 1.0E-5);
}
③geometry()函数
语法:
virtual AcGiWorldGeometry& geometry() const = 0;
返回对AcGiWorldGeometry对象的引用。AcGiWorldGeometry对象允许用户生成几何图形(polylines、弧、网格等)。
④函数polyline():
语法:
virtual Adesk::Boolean polyline(
const Adesk::UInt32 nbPoints,
const AcGePoint3d* pVertexList,
const AcGeVector3d* pNormal = NULL,
Adesk::LongPtr lBaseSubEntMarker = -1
) const = 0;
参数:
const Adesk::UInt32 nbPoints |
在polyline中顶点的输入数量(最小值为2) |
const AcGePoint3d* pVertexList |
顶点的输入数组(必须是数组中的nbPoints) |
const AcGeVector3d* pNormal = NULL |
输入 normal |
Adesk::LongPtr lBaseSubEntMarker = -1 |
第一部分的子实体标记 |
描述:
从点到点,遍历顶点的列表,从一个点到另一个点 ,画出polylines
mode->geometry().polyline(2, verts);
上面代码表示在图形窗口中直接绘制直线段。
⑥函数circularArc()
语法:
virtual Adesk::Boolean circularArc(
const AcGePoint3d& start,
const AcGePoint3d& point,
const AcGePoint3d& end,
const AcGiArcType arcType = kAcGiArcSimple
) const = 0;
参数:
const AcGePoint3d& start |
输入圆弧的起点 |
const AcGePoint3d& point |
输入圆弧上一点 |
const AcGePoint3d& end |
输入圆弧的终点 |
const AcGiArcType arcType = kAcGiArcSimple |
输入表现圆弧的类型 |
描述:
显示一个由三个点定义的弧primitive:开始、点和结束。
Adesk的返回值::kFalse(即0)表明primitive已被成功地存储在图形数据库中。Adesk的返回值::kTrue表明操作已被终止,应用程序希望尽快获得控制权。
mode->geometry().circularArc(m_startPoint, m_thirdPoint, m_endPoint);
上面代码表示:在图形窗口中直接画出圆弧。
⑦函数 PtInLeftOfLine( ) 的实现:
int CArcBlockJigEntity::PtInLeftOfLine(const AcGePoint2d &ptStart, const AcGePoint2d &ptEnd, const AcGePoint2d &pt, double tol /*= 1.0E-7*/)
{
return PtInLeftOfLine(ptStart.x, ptStart.y, ptEnd.x, ptEnd.y, pt.x, pt.y, tol);
}
int CArcBlockJigEntity::PtInLeftOfLine(double x1, double y1, double x2, double y2, double x3, double y3, double tol /*= 1.0E-7*/)
{
// 两个矢量的叉乘结果是一个,矢量的行列式值是这两个矢量确定的平行四边形的面积
double a = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1);
if (fabs(a) < tol)
{
return 0;
}
else if (a > 0)
{
return 1;
}
else
{
return -1;
}
⑧函数 ToPoint3d( ) 的实现:
AcGePoint3d CArcBlockJigEntity::ToPoint3d(const AcGePoint2d &point2d, double z)
{
return AcGePoint3d(point2d.x, point2d.y, z);
}
(4)worldDraw函数可以直接调用DrawOrAddSubEnts 函数来完成:
Adesk::Boolean CArcBlockJigEntity::worldDraw(AcGiWorldDraw* mode)
{
DrawOrAddSubEnts(mode);
return Adesk::kTrue;
}
上面函数的实现已经在ObjectARX中定义,只需要在 .cpp文件中添加 :
#include "drawable.h"
(5)PostToModelSpace函数同样可以直接调用DrawOrAddSubEnts函数完成:
void CArcBlockJigEntity::PostToModelSpace()
{
DrawOrAddSubEnts(NULL);
}
(6)SetEndPoint 函数用于修改自定义实体中圆弧终点,其实现代码为:
void CArcBlockJigEntity::SetEntPoint(const AcGePoint3d &pt)
{
//这句话能引发worldDraw函数的调用
assertWriteEnabled();
m_endPoint = pt;
}
(7)GetBlkRefIds会将PostToModelSpace中添加到模型空间的块参照集合返回到外部调用函数,其实现代码为:
AcDbObjectIdArray CArcBlockJigEntity::GetBlkRefIds()
{
return m_blkRefIds;
}
(8)在工程中添加普通类CArcBlockJig,从AcEdJig 类继承而来
类的声明:
class CArcBlockJig :
public AcEdJig
{
public:
CArcBlockJig();
virtual ~CArcBlockJig();
//参数startPoint:起始点;endPoint:终止点;thirdPoint第三点;
//blkDefId块的Id; blockCount:插入块的个数
bool doIt(const AcGePoint3d &startPoint, AcGePoint3d &thirdPoint,
AcGePoint3d &endPoint, AcDbObjectId blkDefId, int blockCount);
//此函数将被drag函数调用以获得一个输入
virtual AcEdJig::DragStatus sampler();
virtual Adesk::Boolean update();
virtual AcDbEntity* entity() const;//制定了Jig所操作的对象
//获得Jig操作成功后插入的块的参照集合
AcDbObjectIdArray GetBlkRefIds();
private:
CArcBlockJigEntity* m_pJigEnt;
AcGePoint3d m_curPoint;
AcDbObjectIdArray m_blkRefIds;
};
注意:CArcBlockJigEntity* m_pJigEnt;需要包含头文件
#include "ArcBlockJigEntity.h"
(9)CArcBlockJig 类的构造函数中,对自定义实体的指针进行初始化,析构函数中销毁自定义实体。
实现代码为:
CArcBlockJig::CArcBlockJig()
{
m_pJigEnt = NULL;
}
CArcBlockJig::~CArcBlockJig()
{
if (m_pJigEnt)
{
delete m_pJigEnt;
m_pJigEnt = NULL;
}
}
(10)doIt函数仍然用来处理拖动的整个流程。
实现代码为:
bool CArcBlockJig::doIt(const AcGePoint3d &startPoint,
AcGePoint3d &thirdPoint, AcGePoint3d &endPoint,
AcDbObjectId blkDefId, int blockCount)
{
//拖动之前:创建自定义实体
if (m_pJigEnt != NULL)
{
delete m_pJigEnt;
m_pJigEnt = NULL;
}
m_pJigEnt = new CArcBlockJigEntity(startPoint, thirdPoint, endPoint, blkDefId, blockCount);
//执行拖动绘制
CString prompt = TEXT("\\n指定下一点:");
setDispPrompt(prompt);
AcEdJig::DragStatus stat = drag();
//执行之后:根据需要确定自己的处理方式
bool bRet = false;
if (stat == kNormal)
{
//添加子实体到模型空间
m_pJigEnt->PostToModelSpace();
bRet = true;
}
m_blkRefIds = m_pJigEnt->GetBlkRefIds();
delete m_pJigEnt;
m_pJigEnt = NULL;
return bRet;
}
(11)sampler函数的实现
AcEdJig::DragStatus CArcBlockJig::sampler()
{
setUserInputControls((UserInputControls)
(AcEdJig::kAccept3dCoordinates
| AcEdJig::kNoNegativeResponseAccepted
| AcEdJig::kNullResponseAccepted));
//一定要判断一下点是否发生了变化,否则update函数不停地被调用,实体反而不能被绘制出来
static AcGePoint3d pointTemp;
DragStatus stat = acquirePoint(m_curPoint);
if (pointTemp != m_curPoint)
{
pointTemp = m_curPoint;
}
else if (stat == AcEdJig::kNormal)
{
return AcEdJig::kNoChange;
}
return stat;
}
(12)update函数中更新自定义实体。
实现代码为:
Adesk::Boolean CArcBlockJig::update()
{
m_pJigEnt->SetEntPoint(m_curPoint);
return Adesk::kTrue;
}
(13)entity函数返回AutoCAD需要动态更新的实体。
实现代码为:
AcDbEntity* CArcBlockJig::entity() const
{
return m_pJigEnt;
}
(14)GetBlkRefIds会将Jig过程中创建的块参照集合返回给外部调用函数。
实现代码为:
AcDbObjectIdArray CArcBlockJig::GetBlkRefIds()
{
return m_blkRefIds;
}
(15)注册命令ArcBlockJig,测试本节相关函数。
在acrxEntryPoint.cpp 文件中包含头文件:
#include "ArcBlockJig.h"
注册函数 AAAMyGroupArcBlockJig() 的实现:
static void AAAMyGroupArcBlockJig() {
//选择一个块参照,用于沿圆弧插入
AcDbEntity *pEnt = NULL;
AcDbObjectId blkDefId;
AcGePoint3d pickPoint;
if (CArcBlockJig::PromptSelectEntity(TEXT("\\n 选择一个块参照用于沿圆弧插入:"), AcDbBlockReference::desc(), pEnt, pickPoint))
{
AcDbBlockReference *pBlkRef = AcDbBlockReference::cast(pEnt);
blkDefId = pBlkRef->blockTableRecord();
pEnt->close();
}
if (blkDefId.isNull())
{
return;
}
//提示用户拾取第一点
AcGePoint3d startPoint;
if (!CArcBlockJig::GetPoint(TEXT("\\n拾取第一点:"), startPoint))
{
return;
}
//提示用户拾取第二点
AcGePoint3d secondPoint;
if (!CArcBlockJig::GetPoint(startPoint, TEXT("\\n拾取第二点:"), secondPoint))
{
return;
}
//开始拖动
CArcBlockJig jig;
int blockCount = 4;
jig.doIt(startPoint, secondPoint, secondPoint, blkDefId, blockCount);
}
①函数PromptSelectEntity 的实现:
函数声明:
static bool PromptSelectEntity(const TCHAR* prompt, AcRxClass* classDesc, AcDbEntity *&pEnt,
AcGePoint3d &pickPoint, bool bOpenForWrite = true);
static bool PromptSelectEntity(const TCHAR* prompt, const std::vector<AcRxClass*> &classDescs, AcDbEntity *&pEnt,
AcGePoint3d &pickPoint, bool bOpenForWrite = true);
函数实现:
bool CArcBlockJig::PromptSelectEntity(const TCHAR* prompt, AcRxClass* classDesc, AcDbEntity *&pEnt,
AcGePoint3d &pickPoint, bool bOpenForWrite /*= true*/)
{
std::vector<AcRxClass*> descs; //#include <vector>
descs.push_back(classDesc);
return PromptSelectEntity(prompt, descs, pEnt, pickPoint, bOpenForWrite);
}
bool CArcBlockJig::PromptSelectEntity(const TCHAR* prompt, const std::vector<AcRxClass*> &classDescs, AcDbEntity *&pEnt,
AcGePoint3d &pickPoint, bool bOpenForWrite /*= true*/)
{
ads_name ename;
RETRY:
if (acedEntSel(prompt, ename, asDblArray(pickPoint)) != RTNORM)
{
pEnt = NULL;
return false;
}
AcDbObjectId entId;
acdbGetObjectId(entId, ename);
// 判断选择的实体是否是指定类型的实体
Acad::ErrorStatus es;
if (bOpenForWrite)
{
es = acdbOpenObject(pEnt, entId, AcDb::kForWrite);
}
else
{
es = acdbOpenObject(pEnt, entId, AcDb::kForRead);
}
assert(es == Acad::eOk);
bool bRet = false;
for (int i = 0; i < (int)classDescs.size(); i++)
{
if (pEnt->isKindOf(classDescs[i]))
{
bRet = true;
break;
}
}
if (bRet)
{
return true;
}
else
{
pEnt->close();
acutPrintf(TEXT("\\n选择的实体类型不合要求, 请再次选择..."));
goto RETRY;
}
}
注意:在ArcBlockJig.cpp文件中包含头文件:
#include <vector>
注意:在ArcBlockJig.h文件中包含头文件:
#include <vector>
using namespace std;
否则会出现编译错误:
“vector”: 不是“std”的成员 问题解决
②GetPoint函数实现:
函数声明:
// 提示用户选择一个点(无论当前是否在UCS中工作,直接返回该点的WCS坐标)
// basePoint: 基于WCS的点坐标
// 返回值:与acedGetPoint函数相同
static int GetPointReturnCode(const AcGePoint3d &basePoint, const TCHAR* prompt, AcGePoint3d &point);
static bool GetPoint(const AcGePoint3d &basePoint, const TCHAR* prompt, AcGePoint3d &point);
static int GetPointReturnCode(const TCHAR* prompt, AcGePoint3d &point);
static bool GetPoint(const TCHAR* prompt, AcGePoint3d &point);
// 将一个点从用户坐标系坐标转换到世界坐标系
static AcGePoint3d UcsToWcsPoint(const AcGePoint3d &point);
// 将一个点从世界坐标系坐标转换到显示坐标系
static AcGePoint3d WcsToUcsPoint(const AcGePoint3d &point);
函数实现:
int CArcBlockJig::GetPointReturnCode(const AcGePoint3d &basePoint, const TCHAR* prompt, AcGePoint3d &point)
{
// 将基点转换为UCS坐标
AcGePoint3d ucsBasePoint = CArcBlockJig::WcsToUcsPoint(basePoint);
int nReturn = acedGetPoint(asDblArray(ucsBasePoint), prompt, asDblArray(point));
if (nReturn == RTNORM)
{
// acedGetPoint得到UCS坐标,转换为WCS
point = CArcBlockJig::UcsToWcsPoint(point);
}
return nReturn;
}
int CArcBlockJig::GetPointReturnCode(const TCHAR* prompt, AcGePoint3d &point)
{
int nReturn = acedGetPoint(NULL, prompt, asDblArray(point));
if (nReturn == RTNORM)
{
point = CArcBlockJig::UcsToWcsPoint(point);
}
return nReturn;
}
bool CArcBlockJig::GetPoint(const AcGePoint3d &basePoint, const TCHAR* prompt, AcGePoint3d &point)
{
return (GetPointReturnCode(basePoint, prompt, point) == RTNORM);
}
bool CArcBlockJig::GetPoint(const TCHAR* prompt, AcGePoint3d &point)
{
return (GetPointReturnCode(prompt, point) == RTNORM);
}
AcGePoint3d CArcBlockJig::UcsToWcsPoint(const AcGePoint3d &point)
{
// 转换成世界坐标
AcGePoint3d pt;
struct resbuf rbFrom, rbTo;
rbFrom.restype = RTSHORT;
rbFrom.resval.rint = 1; // from UCS
rbTo.restype = RTSHORT;
rbTo.resval.rint = 0; // to WCS
acedTrans(asDblArray(point), &rbFrom, &rbTo, Adesk::kFalse, asDblArray(pt));
return pt;
}
AcGePoint3d CArcBlockJig::WcsToUcsPoint(const AcGePoint3d &point)
{
// 转换成世界坐标
AcGePoint3d pt;
struct resbuf rbFrom, rbTo;
rbFrom.restype = RTSHORT;
rbFrom.resval.rint = 0; // from WCS
rbTo.restype = RTSHORT;
rbTo.resval.rint = 1; // to UCS
acedTrans(asDblArray(point), &rbFrom, &rbTo, Adesk::kFalse, asDblArray(pt));
return pt;
}
(16)效果:
先创建一个块,然后执行ArcBlockJig命令:
完整的代码下载链接:
https://pan.baidu.com/s/1Slalsxj1f1nRRsoayC4wxA
参考资料:
张帆《AutoCAD ObjectARX(VC)开发基础与实例教程》
暂无评论内容