1. 程式人生 > >【CAD二次開發】-ObjectARX-JIG 一拖多 (MultipleEntJig)

【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命令:

完整的程式碼下載連結:

參考資料:

  張帆《AutoCAD ObjectARX(VC)開發基礎與例項教程》