ObjectARX简单自定义实体的实现

在AutoCAD的二次开发中,最有用和最难的当属自定义实体技术,这应当说是二次开发中最有魅力的部分。在本文中,我通过一个简单的自定义实体,向大家介绍ObjectARX Wizard的安装使用方法,以及自定义实体中一些常用接口的写法。
本文以VS2017+ObjectARX2019+cad2020进行演示说明,最终实体效果如下图所示。本文包含所有源码(除了向导生成的以外),同时包含一些重要的成员函数讲解。
整个解决方案可以从https://download.csdn.net/download/mary288267/87243676进行下载。
在这里插入图片描述

1. ObjectARX向导(Wizard)的安装

首先,下载ObjectARXWizard2020.msi,可以从CAD的官网下载,若官网网速慢或者无法连接,可以通过该链接下载https://download.csdn.net/download/mary288267/87233949

  1. 下载完成后,直接双击安装,在弹出对话框单击下一步。
    在这里插入图片描述
  2. 输入注册开发者符号,可以随便写。
    ObjectARX SDK地址:输入ObjectARX2019开发包的地址
    AutoCAD location:输入cad2020的安装地址
    在这里插入图片描述
  3. 点击Install Now
    在这里插入图片描述
    安装完毕后,再次打开VS2017会出现AutoDesk的项目类型,这说明我们的向导安装成功!
    在这里插入图片描述

2. 创建自定义实体工程CustomStair

在ObjectARX中,一般建议将自定义实体和使用这个实体的工程分开,接下来,我们分别创建两个工程。首先创建自定义实体工程CustomStair。

  1. 创建一个空的解决方案,名称为DemoProject.
    在这里插入图片描述
  2. 在这个空的解决方案下添加一个AutoDesk项目,选中图中 ARX/DBX Project for AutoCAD 2020的选项,项目名称输入“CustomStair”,点击确定。
    在这里插入图片描述
  3. 之后弹出一些项目设置向导对话框,输入RDS名称(自定),这里写的是Zdf。
    在这里插入图片描述
  4. 接下来,我们选择第二项,自定义实体的项目类型。
    在这里插入图片描述
  5. 选择MFC支持(考虑到您将来可能在这个工程上扩展对话框接口,因此选择了MFC支持),点击Finish.
    在这里插入图片描述
    至此,第一个项目创建完毕,向导为我们生成一套必要的代码,我们试着去编译.
    在这里插入图片描述
    出错了!原因是编译器设置了数据截断的检测(/RTCc),和C++标准库有冲突,我们把它关闭
    在这里插入图片描述再次编译,成功。

3. 添加实体类

3.1 利用向导添加自定义类

  1. 在刚刚创建的CustomStair工程中,添加自定义实体类。选中CustomStair工程,右键:添加/新建项,选中下图所示类型,点击添加。
    在这里插入图片描述

  2. 在下面向导对话框中,输入自定义实体类的名称,自定义实体的DXF名称,基类名称(我们选择AcDbEntity),应用程序的名称等,然后点击Finish,完成创建。
    在这里插入图片描述
    这样,向导自动为我们创建了实体类CZdfCustomStair,可以看到有一些基本的成员函数,我们之后会扩充完善类成员函数。
    在这里插入图片描述

3.2 “初始化”自定义类

创建好这个类之后,我们不要着急去重写基类的各个虚函数。我们需要“初始化”这个自定义类,什么意思呢?ObjectARX中,每个自定义类都有一个静态的AcRxClass对象,这个AcRxClass对象是ObjectARX类层次结构树的一个节点,表示这个自定义类在层次结构树的位置(这样,就能找到其父对象和子对象)。这实际上是ObjectARX实现RTTI(运行时类型识别)的方式。参见以下帮助原文。

AcRxClass objects (actually objects of an internal class derived from AcRxClass, since AcRxClass is an abstract base class) are used as the nodes in the ObjectARX runtime class hierarchical tree. Each instance of an AcRxClass represents a C++ class derived from AcRxObject (or AcRxObject itself). The run-time information about such C++ classes and their hierarchical relationships is available through the AcRxClass objects that make up the runtime tree.

为了把我们的自定义实体类加到ObjectARX的类层次结构树上,我们需要在本工程的acrxEntryPoint.cpp文件中找到CCustomStairApp::On_kInitAppMsg成员函数,在这个函数中加入CZdfCustomStair::rxInit()和acrxBuildClassHierarchy()函数:

	virtual AcRx::AppRetCode On_kInitAppMsg (void *pkt) {
		// TODO: Load dependencies here

		// You *must* call On_kInitAppMsg here
		AcRx::AppRetCode retCode =AcRxDbxApp::On_kInitAppMsg (pkt) ;
		
		//这些是加入的代码,目的是使我们的自定义类加入到ObjectARX的类层次结构树中
		CZdfCustomStair::rxInit();		
		acrxBuildClassHierarchy();

		return (retCode) ;
	}

同时,在卸载这个动态库时,也需要删除这个类的AcRxClass对象,删除其在类结构树中的节点。同样在acrxEntryPoint.cpp文件中找到CCustomStairApp::On_kUnloadAppMsg 函数,并在其中调用deleteAcRxClass(CZdfCustomStair::desc())。

	virtual AcRx::AppRetCode On_kUnloadAppMsg (void *pkt) {
		// TODO: Add your code here

		// You *must* call On_kUnloadAppMsg here
		AcRx::AppRetCode retCode =AcRxDbxApp::On_kUnloadAppMsg (pkt) ;

		// TODO: Unload dependencies here
		deleteAcRxClass(CZdfCustomStair::desc());

		return (retCode) ;
	}

至此,我们终于搭完了框架!接下来,我们看看最关键的——如何添加类的成员函数。

3.3 添加自定义实体类的成员函数

这里,我把自定义实体类的头文件和实现文件罗列出来,之后,会对关键的一些函数做出说明。
以下是CZdfCustomStair的头文件

//-----------------------------------------------------------------------------
//----- ZdfCustomStair.h : Declaration of the CZdfCustomStair
//-----------------------------------------------------------------------------
#pragma once

#ifdef CUSTOMSTAIR_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
//----- Note: we don't use __declspec(dllimport) here, because of the
//----- "local vtable" problem with msvc. If you use __declspec(dllimport),
//----- then, when a client dll does a new on the class, the object's
//----- vtable pointer points to a vtable allocated in that client
//----- dll. If the client dll then passes the object to another dll,
//----- and the client dll is then unloaded, the vtable becomes invalid
//----- and any virtual calls on the object will access invalid memory.
//-----
//----- By not using __declspec(dllimport), we guarantee that the
//----- vtable is allocated in the server dll during the ctor and the
//----- client dll does not overwrite the vtable pointer after calling
//----- the ctor. And, since we expect the server dll to remain in
//----- memory indefinitely, there is no problem with vtables unexpectedly
//----- going away.
#define DLLIMPEXP
#endif

//-----------------------------------------------------------------------------
#include "dbmain.h"

//-----------------------------------------------------------------------------
class DLLIMPEXP CZdfCustomStair : public AcDbEntity 
{
public:
	ACRX_DECLARE_MEMBERS(CZdfCustomStair) ;
	CZdfCustomStair () ;
	virtual ~CZdfCustomStair () ;

	//楼梯参数设置
	void SetStepNum(Adesk::Int32 nStepNum);	//楼梯总台阶数
	void SetStepWidth(double dWidth);		//台阶数目
	void SetStepHeight(double dHeight);		//台阶高度
	void SetStairPos(AcGePoint3d pt);		//楼梯起始点
	void SetStairOrient(AcGeVector3d vt);	//楼梯走向
	void SetStairNormal(AcGeVector3d vt);	//楼梯法向量
	void SetStairText(LPCTSTR psz, int iHeight = 4);//楼梯文字高度和内容

	//楼梯参数获取
	int GetStepNum() const;				//获取楼梯总台阶数
	double GetStepWidth() const;		//获取楼梯总宽度
	double GetStepHeight() const;		//获取楼梯总高度
	AcGePoint3d GetStairPos() const;	//获取楼梯的起始位置
	AcGeVector3d GetStairOrient() const;
	AcGeVector3d GetStairNormal() const;
	const CString& GetStairText() const;

	//保存数据
	virtual Acad::ErrorStatus dwgOutFields(AcDbDwgFiler *pFiler) const;
	//加载数据
	virtual Acad::ErrorStatus dwgInFields(AcDbDwgFiler *pFiler);

protected:
	static Adesk::UInt32 kCurrentVersionNumber;
	//外包矩形
	virtual Acad::ErrorStatus subGetGeomExtents(AcDbExtents& extents) const;
	//分解(炸开)
	virtual Acad::ErrorStatus subExplode(AcDbVoidPtrArray& entitySet) const;
	//sList
	virtual void subList() const;
	//实体绘制
	virtual Adesk::Boolean subWorldDraw(AcGiWorldDraw *mode);
	//捕捉点
	virtual Acad::ErrorStatus subGetOsnapPoints(
		AcDb::OsnapMode osnapMode, 
		Adesk::GsMarker gsSelectionMark,
		const AcGePoint3d &pickPoint, 
		const AcGePoint3d &lastPoint,
		const AcGeMatrix3d &viewXform,
		AcGePoint3dArray &snapPoints, 
		AcDbIntArray &geomIds) const;
	//显示夹点
	virtual Acad::ErrorStatus subGetGripPoints(
		AcGePoint3dArray &gripPoints, 
		AcDbIntArray &osnapModes,
		AcDbIntArray &geomIds) const;
	//移动夹点
	virtual Acad::ErrorStatus subMoveGripPointsAt(const AcDbIntArray &indices, const AcGeVector3d &offset);
	//矩阵转换
	virtual Acad::ErrorStatus subTransformBy(const AcGeMatrix3d& xform);

private:
	void UpdateEntity();
	//调用者删除动态分配的内容
	AcDbPolyline* ConvertToPolyline() const;

	//序列化的数据
	AcGeVector3d m_vtStairOrient;	//楼梯方向,指水平踏步的方向
	AcGeVector3d m_vtStairNormal;	//法向量方向
	AcGePoint3d m_ptStairOrigin;	//楼梯起点
	Adesk::Int32 m_nStepNum;		//踏步总数
	double m_dStepWidth;			//踏步宽度
	double m_dStepHeight;			//踏步高度
	double m_dTextHeight;			//文字高度
	CString m_sTextContent;			//文字内容

	//辅助数据
	AcGePoint3dArray m_arptStairVertex;	//存储楼梯多段线所有顶点坐标
	AcDbText m_dbtextName;			//楼梯下方显示的文字
};

#ifdef CUSTOMSTAIR_MODULE
ACDB_REGISTER_OBJECT_ENTRY_AUTO(CZdfCustomStair)
#endif

以下为CZdfCustomStair的实现文件

//-----------------------------------------------------------------------------
//----- ZdfCustomStair.cpp : Implementation of CZdfCustomStair
//-----------------------------------------------------------------------------
#include "StdAfx.h"
#include "ZdfCustomStair.h"
#define _USE_MATH_DEFINES
#include <math.h>

Adesk::UInt32 CZdfCustomStair::kCurrentVersionNumber =1 ;

ACRX_DXF_DEFINE_MEMBERS (
	CZdfCustomStair, AcDbEntity,
	AcDb::kDHL_CURRENT, AcDb::kMReleaseCurrent, 
	AcDbProxyEntity::kNoOperation, ZDFCUSTOMSTAIR,
ZDFCUSTOMSTAIRAPP
|Product Desc:     A description for your object
|Company:          Your company name
|WEB Address:      Your company WEB site address
)

CZdfCustomStair::CZdfCustomStair() : AcDbEntity()
	, m_vtStairOrient(1, 0, 0)
	, m_vtStairNormal(0, 0, 1)
	, m_ptStairOrigin(0, 0, 0)
	, m_nStepNum(18)
	, m_dStepWidth(300)
	, m_dStepHeight(180)
	, m_dTextHeight(4)
	, m_sTextContent(_T(""))
{
}

CZdfCustomStair::~CZdfCustomStair () {
}

void CZdfCustomStair::SetStepNum(Adesk::Int32 nStepNum)
{
	assertWriteEnabled();

	m_nStepNum = nStepNum;
	UpdateEntity();
}

void CZdfCustomStair::SetStepWidth(double dWidth)
{
	assertWriteEnabled();

	m_dStepWidth = dWidth;
	UpdateEntity();
}

void CZdfCustomStair::SetStepHeight(double dHeight)
{
	assertWriteEnabled();

	m_dStepHeight = dHeight;
	UpdateEntity();
}

void CZdfCustomStair::SetStairPos(AcGePoint3d pt)
{
	assertWriteEnabled();

	m_ptStairOrigin = pt;
	UpdateEntity();
}

void CZdfCustomStair::SetStairOrient(AcGeVector3d vt)
{
	assertWriteEnabled();

	m_vtStairOrient = vt.normal();
	UpdateEntity();
}

void CZdfCustomStair::SetStairNormal(AcGeVector3d vt)
{
	assertWriteEnabled();

	m_vtStairNormal = vt;
	UpdateEntity();
}

void CZdfCustomStair::SetStairText(LPCTSTR psz, int iHeight)
{
	assertWriteEnabled();
	m_sTextContent = psz;
	m_dTextHeight = iHeight;
	UpdateEntity();
}

double CZdfCustomStair::GetStepWidth() const
{
	assertReadEnabled();
	return m_dStepWidth;
}

double CZdfCustomStair::GetStepHeight() const
{
	assertReadEnabled();
	return m_dStepHeight;
}

AcGePoint3d CZdfCustomStair::GetStairPos() const
{
	assertReadEnabled();
	return m_ptStairOrigin;
}

AcGeVector3d CZdfCustomStair::GetStairOrient() const
{
	assertReadEnabled();
	return m_vtStairOrient;
}

AcGeVector3d CZdfCustomStair::GetStairNormal() const
{
	assertReadEnabled();
	return m_vtStairNormal;
}

const CString& CZdfCustomStair::GetStairText() const
{
	assertReadEnabled();
	return m_sTextContent;
}


//-----------------------------------------------------------------------------
//----- AcDbObject protocols
//- Dwg Filing protocol
Acad::ErrorStatus CZdfCustomStair::dwgOutFields(AcDbDwgFiler *pFiler) const {
	assertReadEnabled();
	//----- Save parent class information first.
	Acad::ErrorStatus es = AcDbEntity::dwgOutFields(pFiler);
	if (es != Acad::eOk)
		return (es);
	//----- Object version number needs to be saved first
	if ((es = pFiler->writeUInt32(CZdfCustomStair::kCurrentVersionNumber)) != Acad::eOk)
		return (es);

	//向dwg文件写入数据
	es = pFiler->writeItem(m_vtStairOrient);
	if (Acad::eOk != es) return es;
	es = pFiler->writeItem(m_vtStairNormal);
	if (Acad::eOk != es) return es;
	es = pFiler->writeItem(m_ptStairOrigin);
	if (Acad::eOk != es) return es;
	es = pFiler->writeItem(m_nStepNum);
	if (Acad::eOk != es) return es;
	es = pFiler->writeItem(m_dStepWidth);
	if (Acad::eOk != es) return es;
	es = pFiler->writeItem(m_dStepHeight);
	if (Acad::eOk != es) return es;
	es = pFiler->writeItem(m_dTextHeight);
	if (Acad::eOk != es) return es;
	es = pFiler->writeItem(m_sTextContent.GetString());
	if (Acad::eOk != es) return es;

	return (pFiler->filerStatus());
}

Acad::ErrorStatus CZdfCustomStair::dwgInFields(AcDbDwgFiler *pFiler) {
	assertWriteEnabled();
	//----- Read parent class information first.
	Acad::ErrorStatus es = AcDbEntity::dwgInFields(pFiler);
	if (es != Acad::eOk)
		return (es);
	//----- Object version number needs to be read first
	Adesk::UInt32 version = 0;
	if ((es = pFiler->readUInt32(&version)) != Acad::eOk)
		return (es);
	if (version > CZdfCustomStair::kCurrentVersionNumber)
		return (Acad::eMakeMeProxy);
	//- Uncomment the 2 following lines if your current object implementation cannot
	//- support previous version of that object.
	//if ( version < CZdfCustomStair::kCurrentVersionNumber )
	//	return (Acad::eMakeMeProxy) ;
	//----- Read params
	//从dwg文件读取文件
	if (version >= 1)
	{
		es = pFiler->readItem(&m_vtStairOrient);
		if (Acad::eOk != es) return es;
		es = pFiler->readItem(&m_vtStairNormal);
		if (Acad::eOk != es) return es;
		es = pFiler->readItem(&m_ptStairOrigin);
		if (Acad::eOk != es) return es;
		es = pFiler->readItem(&m_nStepNum);
		if (Acad::eOk != es) return es;
		es = pFiler->readItem(&m_dStepWidth);
		if (Acad::eOk != es) return es;
		es = pFiler->readItem(&m_dStepHeight);
		if (Acad::eOk != es) return es;
		es = pFiler->readItem(&m_dTextHeight);
		if (Acad::eOk != es) return es;
		TCHAR *psz=NULL;
		es = pFiler->readItem(&psz);
		if (Acad::eOk != es)	{
			delete psz;
			return es;
		}
		m_sTextContent = psz;
		delete psz;
	}

	UpdateEntity();

	return (pFiler->filerStatus());
}

Acad::ErrorStatus CZdfCustomStair::subGetGeomExtents(AcDbExtents& extents) const
{
	assertReadEnabled();

	for (int i = 0; i < m_arptStairVertex.length(); i++)
		extents.addPoint(m_arptStairVertex[i]);

	AcDbExtents extText;
	m_dbtextName.getGeomExtents(extText);
	extents.addExt(extText);

	return Acad::eOk;
}

Acad::ErrorStatus CZdfCustomStair::subExplode(AcDbVoidPtrArray& entitySet) const
{
	assertReadEnabled();

	for (int i = 0; i + 1 < m_arptStairVertex.length(); i++)
	{
		AcDbLine* pLine = new AcDbLine(m_arptStairVertex[i], m_arptStairVertex[i + 1]);
		pLine->setPropertiesFrom(this);
		entitySet.append(pLine);
	}

	AcDbText* pText = AcDbText::cast(m_dbtextName.clone());
	if (pText)
		entitySet.append(pText);

	return Acad::eOk;
}

Adesk::Boolean CZdfCustomStair::subWorldDraw(AcGiWorldDraw *mode)
{
	assertReadEnabled();
	if (!m_arptStairVertex.isEmpty())
		mode->geometry().polyline(m_arptStairVertex.length(), m_arptStairVertex.asArrayPtr());
	m_dbtextName.worldDraw(mode);

	return Adesk::kTrue;
}

//- Osnap points protocol
Acad::ErrorStatus CZdfCustomStair::subGetOsnapPoints(
	AcDb::OsnapMode osnapMode,
	Adesk::GsMarker gsSelectionMark,
	const AcGePoint3d &pickPoint,
	const AcGePoint3d &lastPoint,
	const AcGeMatrix3d &viewXform,
	AcGePoint3dArray &snapPoints,
	AcDbIntArray &geomIds) const
{
	assertReadEnabled();
	Acad::ErrorStatus es = Acad::eOk;
	AcDbPolyline* pPoly = NULL;
	pPoly = ConvertToPolyline();
	if (pPoly)
	{
		pPoly->getOsnapPoints(osnapMode, gsSelectionMark, pickPoint, lastPoint,
			viewXform, snapPoints, geomIds);
		delete pPoly;
	}

	m_dbtextName.getOsnapPoints(osnapMode, gsSelectionMark, pickPoint, lastPoint,
		viewXform, snapPoints, geomIds);

	return Acad::eOk;
}

//- Grip points protocol
Acad::ErrorStatus CZdfCustomStair::subGetGripPoints(
	AcGePoint3dArray &gripPoints, AcDbIntArray &osnapModes, AcDbIntArray &geomIds) const
{
	assertReadEnabled();
	//----- This method is never called unless you return eNotImplemented 
	//----- from the new getGripPoints() method below (which is the default implementation)

	gripPoints.append(m_ptStairOrigin);
	gripPoints.append(m_arptStairVertex.last());

	return Acad::eOk;
}

Acad::ErrorStatus CZdfCustomStair::subMoveGripPointsAt(const AcDbIntArray &indices, const AcGeVector3d &offset)
{
	assertWriteEnabled();
	//----- This method is never called unless you return eNotImplemented 
	//----- from the new moveGripPointsAt() method below (which is the default implementation)

	for (int i = 0; i < indices.length(); i++)
	{
		switch (indices[i])
		{
		case 0:
			SetStairPos(m_ptStairOrigin + offset);
			break;
		case 1:
			//计算新的楼梯台阶数目
			AcGePoint3d pt;
			AcGePoint3d ptDrag = m_arptStairVertex.last() + offset;
			AcDbLine line(m_ptStairOrigin, m_arptStairVertex.last());
			line.getClosestPointTo(ptDrag, pt, true);	//获取垂足的方法
			double dStepLen = sqrt(pow(m_dStepWidth, 2) + pow(m_dStepHeight, 2));
			int nStepNum = (int)(pt.distanceTo(m_ptStairOrigin) / dStepLen);
			SetStepNum(nStepNum);
			break;
		}
	}

	return Acad::eOk;
}

void CZdfCustomStair::subList() const
{
	assertReadEnabled();
	AcDbEntity::subList();

	acutPrintf(_T("\n\t\t踏步总数: \t\t%g"), m_nStepNum);
	acutPrintf(_T("\n\t\t踏步宽度: \t\t%g"), m_dStepWidth);
	acutPrintf(_T("\n\t\t踏步高度: \t\t%g"), m_dStepHeight);
	acutPrintf(_T("\n\t\t踏步方向: \t\t%g"), m_vtStairOrient.angleTo(AcGeVector3d::kXAxis));
	acutPrintf(_T("\n\t\t楼梯说明: \t\t%g"), m_dbtextName.textStringConst());
}

Acad::ErrorStatus CZdfCustomStair::subTransformBy(const AcGeMatrix3d& xform)
{
	assertWriteEnabled();

	m_vtStairOrient.transformBy(xform);
	m_vtStairNormal.transformBy(xform);
	m_ptStairOrigin.transformBy(xform);
	double dScale = xform.scale();
	m_dStepWidth *= dScale;
	m_dStepHeight *= dScale;
	m_dTextHeight *= dScale;

	UpdateEntity();

	return Acad::eOk;
}

int CZdfCustomStair::GetStepNum() const
{
	assertReadEnabled();
	return m_nStepNum;
}

void CZdfCustomStair::UpdateEntity()
{
	assertWriteEnabled();

	//绘制每一级台阶
	m_arptStairVertex.removeAll();

	int nSteps = GetStepNum();
	if (nSteps < 1)
		return;

	AcGeVector3d vtOrientHor = m_vtStairOrient.normal();
	AcGeVector3d vtOrientVert = m_vtStairOrient;
	vtOrientVert.rotateBy(M_PI_2, m_vtStairNormal).normalize();
	AcGePoint3d pt = m_ptStairOrigin - m_dStepWidth * vtOrientHor;

	for (int i = 0; i < nSteps * 2; i += 2)
	{
		pt += m_dStepWidth * vtOrientHor;
		m_arptStairVertex.append(pt);
		pt += m_dStepHeight * vtOrientVert;
		m_arptStairVertex.append(pt);
	}
	pt += m_dStepWidth * vtOrientHor;
	m_arptStairVertex.append(pt);

	//初始文字位置
	m_dbtextName.setDatabaseDefaults();
	m_dbtextName.setTextString(m_sTextContent);
	m_dbtextName.setHeight(m_dTextHeight);
	m_dbtextName.setPosition(m_arptStairVertex[0]);
	double dRotate = AcGeVector3d::kXAxis.angleTo(vtOrientHor,m_vtStairNormal);
	m_dbtextName.setRotation(dRotate);
}

AcDbPolyline* CZdfCustomStair::ConvertToPolyline() const
{
	assertReadEnabled();

	if (m_arptStairVertex.isEmpty())
		return NULL;

	AcDbPolyline* pLine = new AcDbPolyline();
	for (int i = 0; i < m_arptStairVertex.length(); i++)
		pLine->addVertexAt(i, AcGePoint2d(m_arptStairVertex[i].x, m_arptStairVertex[i].y));

	return pLine;
}

关于成员函数的实现方法,需要做出以下说明:

  1. 实体打开的三种状态
    在定义新的成员函数或者重写父类成员函数时,为了确保对象以正确的方式打开,应当在函数中首先调用assertReadEnabled(), assertWriteEnabled(), 或者assertNotifyEnabled() 。在这三个函数中,assertWriteEnabled是最为重要,它记录了在成员函数中发生的修改操作,以用于后续的撤销操作。即使你不需要撤销操作,也必须调用下面函数:
    assertWriteEnabled(kFalse, kFalse);
    这个函数使对象可以实现增量保存。如果不按照上述指令,可能会引起错误的绘制。
    下表表示打开对象的三种状态(读、写和通告),并说明了每种状态下每个assert函数是否成功。如果没有按照对应的状态打开对象,AutoCAD将异常终止,并提示用户保存图纸。
    在这里插入图片描述
  2. 从外部读取和写入对象数据
    dwgOutFields:在需要保存时向外部写入成员变量的值;
    dwgInFields:在需要读取时从外部读取成员变量的值。
    注意这两个函数读取和写入变量的顺序必须一致。
  3. 图形绘制
    subWorldDraw是实体显示的关键函数。在本例中,将台阶的所有顶点保存在数组中,再利用AcGiWorldDraw中成员函数将所有点连成的线绘制出来。另外,楼梯中的文字直接用文字对象的worldDraw函数进行绘制。
Adesk::Boolean CZdfCustomStair::subWorldDraw(AcGiWorldDraw *mode)
{
	assertReadEnabled();
	if (!m_arptStairVertex.isEmpty())
		mode->geometry().polyline(m_arptStairVertex.length(), m_arptStairVertex.asArrayPtr());
	m_dbtextName.worldDraw(mode);

	return Adesk::kTrue;
}
  1. 捕捉点设置
    如果在绘制图形的时候,需要捕捉到这个自定义楼梯的某些特征点,就需要重写subGetOsnapPoints函数。为了简单实现,我们动态的创建了一条和原图形重合的多段线,让这条多段线提供捕捉点。这是自定义实体中的一个技巧,利用这种方法,我们可以轻松的在自定义实体中实现填充、块等复杂的行为。
Acad::ErrorStatus CZdfCustomStair::subGetOsnapPoints(
	AcDb::OsnapMode osnapMode,
	Adesk::GsMarker gsSelectionMark,
	const AcGePoint3d &pickPoint,
	const AcGePoint3d &lastPoint,
	const AcGeMatrix3d &viewXform,
	AcGePoint3dArray &snapPoints,
	AcDbIntArray &geomIds) const
{
	assertReadEnabled();
	Acad::ErrorStatus es = Acad::eOk;
	AcDbPolyline* pPoly = NULL;
	pPoly = ConvertToPolyline();
	if (pPoly)
	{
		pPoly->getOsnapPoints(osnapMode, gsSelectionMark, pickPoint, lastPoint,
			viewXform, snapPoints, geomIds);
		delete pPoly;
	}

	m_dbtextName.getOsnapPoints(osnapMode, gsSelectionMark, pickPoint, lastPoint,
		viewXform, snapPoints, geomIds);

	return Acad::eOk;
}
  1. 夹点设置和编辑
    在CAD中,如果用户用鼠标选中实体,会在实体上显示亮显的点,这称作“夹点”,拖动不同夹点,对象会有不同响应。在ObjectARX中,getGripPoints函数返回为实体定义的夹点;在修改夹点后,CAD调用moveGripPointsAt修改实体。
    如果需要自定义实体支持夹点编辑,需要重写subGetGripPoints()和subMoveGripPointsAt() 函数。subGetGripPoints()函数添加实体所有需要编辑的夹点。在本例中,我们添加两个夹点。
Acad::ErrorStatus CZdfCustomStair::subGetGripPoints(
	AcGePoint3dArray &gripPoints, AcDbIntArray &osnapModes, AcDbIntArray &geomIds) const
{
	assertReadEnabled();
	//----- This method is never called unless you return eNotImplemented 
	//----- from the new getGripPoints() method below (which is the default implementation)

	//这个函数添加了所有需要编辑的夹点
	gripPoints.append(m_ptStairOrigin);
	gripPoints.append(m_arptStairVertex.last());

	return Acad::eOk;
}

subMoveGripPointsAt用于根据夹点修改实体外形,它有两个输入参数,第一个参数是指编辑的夹点序号(这个序号是按照subGetGripPoints添加夹点的顺序确定的),第二个参数是指夹点移动的距离。通过执行一个循环,依次取出每个编辑的夹点,对0号夹点,我们整体移动实体;对1号夹点,我们增加或者减少楼梯的台阶数目。

Acad::ErrorStatus CZdfCustomStair::subMoveGripPointsAt(const AcDbIntArray &indices, const AcGeVector3d &offset)
{
	assertWriteEnabled();
	//----- This method is never called unless you return eNotImplemented 
	//----- from the new moveGripPointsAt() method below (which is the default implementation)

	for (int i = 0; i < indices.length(); i++)
	{
		switch (indices[i])
		{
		case 0:
			SetStairPos(m_ptStairOrigin + offset);
			break;
		case 1:
			//计算新的楼梯台阶数目
			AcGePoint3d pt;
			AcGePoint3d ptDrag = m_arptStairVertex.last() + offset;
			AcDbLine line(m_ptStairOrigin, m_arptStairVertex.last());
			line.getClosestPointTo(ptDrag, pt, true);	//获取垂足的方法
			double dStepLen = sqrt(pow(m_dStepWidth, 2) + pow(m_dStepHeight, 2));
			int nStepNum = (int)(pt.distanceTo(m_ptStairOrigin) / dStepLen);
			SetStepNum(nStepNum);
			break;
		}
	}

	return Acad::eOk;
}

夹点编辑后效果为:
在这里插入图片描述

4. 使用这个实体类

4.1 创建使用这个实体类的工程

  1. 在本解决方案下添加一个新的工程,名称为UseStair.
    在这里插入图片描述
  2. 输入RDS为Zdf
    在这里插入图片描述
  3. 工程类别为第一项,ObjectARX工程。
    在这里插入图片描述
  4. 下一步,选择MFC支持(考虑您以后可能会在这个工程添加对话框,因此选择MFC支持),单击Finish完成
    在这里插入图片描述
  5. 因为使用实体类的工程需要引用自定义实体工程的导出文件,所以,我们设置附加库目录,把自定义工程生成的ZdfCustomStair.lib所在的目录包含其中。
    在这里插入图片描述
  6. 设置该工程的附加依赖项为:ZdfCustomStair.lib
    在这里插入图片描述
    7.设置项目依赖关系
    由于UseStair工程依赖于CustomStair工程,因此,选中解决方案后右击,在通用属性/项目依赖项中,按下图所示设置项目依赖关系。在这里插入图片描述
    至此,使用实体类的工程创建完毕。

4.2 添加使用这个类的代码

在UseStair工程acrxEntryPoint.cpp文件中,在CUseStairApp类中添加下面静态成员函数。这是调用CZdfCustomStair的命令函数。

	static void ZdfMyGroupAddStair () {
		// Put your command code here
		CZdfCustomStair* pStair = new CZdfCustomStair();

		pStair->SetStairOrient(AcGeVector3d(1, 0, 0));
		pStair->SetStairPos(AcGePoint3d(100, 0, 0));
		pStair->SetStepNum(20);
		pStair->SetStepWidth(300);
		pStair->SetStepHeight(180);
		pStair->SetStairNormal(AcGeVector3d(0, 0, 1));
		pStair->SetStairText(_T("你好,arx!"), 150);

		AcDbBlockTable* pBlkTble = NULL;
		if (Acad::eOk == acdbHostApplicationServices()->workingDatabase()->getSymbolTable(pBlkTble, AcDb::kForRead))
		{
			AcDbBlockTableRecord* pRec = NULL;
			if (Acad::eOk == pBlkTble->getAt(ACDB_MODEL_SPACE, pRec, AcDb::kForWrite))
			{
				if (Acad::eOk != pRec->appendAcDbEntity(pStair))
				{
					delete pStair;
					pStair = NULL;
				}
				else
				{
					pStair->close();
				}

				pRec->close();
			}
			pBlkTble->close();
		}

	}

在acrxEntryPoint.cpp文件的末尾,加上下面宏调用,这是ObjectARX2019注册命令的方法,命令名为AddStair。组名(ZdfMyGroup)+命令名(AddStair)就是命令函数的函数名。

ACED_ARXCOMMAND_ENTRY_AUTO(CUseStairApp, ZdfMyGroup, AddStair, AddStair, ACRX_CMD_MODAL, NULL)

编译这两个工程,得到一个.dbx和一个.arx文件,用ap命令将他们依次加入到cad中,执行AddStair命令即可。

  • 10
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Santiago

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值