【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)開發基礎與例項教程》