【CAD二次开发】-ObjectARX-JIG 一拖多 (MultipleEntJig)

  本文介绍的例子是沿一个圆弧实体等间距放置若干个图块,用户拖动光标时圆弧的形状发生变化,同时插入的块参照的位置也会随之变化。

技术路线:

 

(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)开发基础与实例教程》

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容