(42)ObjectARX2015 + vs2012 JIG-一拖多

1. 说明

        基本的 Jig 拖动支持在创建/编辑一个实体的过程中,移动光标的同时实体的外观同步变化,但是很多情况下我们希望能有多个实体同时变化. 

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

2. 思路

Jig 一拖多可以考虑两种实现思路:

(1) 在entity 函数中返回一个长度为0的直线(欺骗AutoCAD,让AutoCAD更新这条直线的行为不会影响到图形窗口中的显示),在 update 函数中,根据需要打开多个需要同步变化的实体,对其参数进行修改,然后关闭它。

(2) 创建一个自定义实体,entity 函数中返回这个自定义实体的指针,在update函数中,修改自定义实体的一些参数,引起自定义实体的重新操作,在自定义实体的 worldDraw 函数中调用子实体(子实体就是需要拖动的多个实体)完成拖动绘制。

3. 步骤

(1) 添加 CArcBlockJigEntity 新类, 父类设置为 AcDbEntity

MyWorldDraw函数               完成自定义实体的绘制
SetEndPoint函数                 函数在拖动过程中动态修改圆弧的终点,它会引发自定义实体的绘                                                  制,也就是 MyWorldDraw 函数被调用
PostToModelSpace函数      函数能将动态显示的子实体添加到模型空间
GetBlkRefIds函数                将PostToModelSpace函数中添加的块参照ID集合返回到外部调用函数
DrawOrAddSubEnts函数    将worldDraw和PostModelSpace中公用的代码封装起来便于重用

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 MyWorldDraw(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;  // 添加的块参照集合

};

(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(void)
{
}

(3) 绘制

MyWorldDraw 函数会在图形窗口中显示圆弧和块参照子实体,负责拖动过程中的显示更新,        

PostToModelSpace函数则会将这些子实体添加到模型空间,负责拖动完整之后将子实体添加到模型空间。

 这两个函数的处理逻辑完全一致,不同的在于结果体现不一样,一个是在图形窗口中绘制,另外一个添加到模型空间.DrawOrAddSubEnts 函数封装了两个函数中公用的代码,这样就可以将相同的逻辑用一个函数来体现.

A . 判断给定的三点是否共线,如果共线,就要用直线段来代替圆弧

    // 判断给定的三点是否共线
    static bool ThreePointIsCollinear(const AcGePoint2d &pt1, const AcGePoint2d &pt2, const AcGePoint2d &pt3); // 判断给定的三点是否共线
// 判断给定的三点是否共线
bool CGeometryOper::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);
}

B. 在图形窗口中直接绘制圆弧/直线段的方法:

mode->geometry().polyline(2, verts);
mode->geometry().circularArc(m_startPoint, m_thirdPoint, m_endPoint);

C. 为了计算圆弧或直线段的等分点, 我们使用AcDbCurve提供的一些成员函数,如果三点共线,创建一条直线段,否则就创建圆弧实体.

        创建直线段的代码为:

pCurve = new AcDbLine(m_startPoint, m_endPoint);

D. 判断顺时针还是逆时针(AcDbArc对象构建时默认是逆时针)

判断方式: 终点在起点-中间点连线的左侧还是右侧

    // 判断绘画圆弧时是顺时针还是逆时针
    static int PtInLeftOfLine(const AcGePoint3d &ptStart, const AcGePoint3d &ptEnd, const AcGePoint3d &pt, double tol = 1.0E-7);
    static int PtInLeftOfLine(const AcGePoint2d &ptStart, const AcGePoint2d &ptEnd, const AcGePoint2d &pt, double tol = 1.0E-7);
    static int PtInLeftOfLine(double x1, double y1, double x2, double y2, double x3, double y3, double tol = 1.0E-7);
// 判断绘画圆弧时是顺时针还是逆时针
int CGeometryOper::PtInLeftOfLine(const AcGePoint3d &ptStart, const AcGePoint3d &ptEnd, const AcGePoint3d &pt, double tol)
{
    return PtInLeftOfLine(ptStart.x, ptStart.y, ptEnd.x, ptEnd.y, pt.x, pt.y, tol);
}

int CGeometryOper::PtInLeftOfLine(const AcGePoint2d &ptStart, const AcGePoint2d &ptEnd, const AcGePoint2d &pt, double tol)
{
    return PtInLeftOfLine(ptStart.x, ptStart.y, ptEnd.x, ptEnd.y, pt.x, pt.y, tol);
}

int CGeometryOper::PtInLeftOfLine(double x1, double y1, double x2, double y2, double x3, double y3, double tol)
{
    // 两个矢量的叉乘结果是一个,矢量的行列式值是这两个矢量确定的平行四边形的面积
    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;
    }
}

E. 二维三维点转换

    static AcGePoint2d ToPoint2d(const AcGePoint3d &point3d);
    static AcGePoint3d ToPoint3d(const AcGePoint2d &point2d, double z = 0)
AcGePoint2d CGeometryOper::ToPoint2d(const AcGePoint3d &point3d)
{
    return AcGePoint2d(point3d.x, point3d.y);
}

AcGePoint3d CGeometryOper::ToPoint3d(const AcGePoint2d &point2d, double z)
{
    return AcGePoint3d(point2d.x, point2d.y, z);
}

F. 准备函数完成, 

//绘制实体或添加到模型空间,封装
void CArcBlockJigEntity::DrawOrAddSubEnts(AcGiWorldDraw* mode)
{
    //绘制圆弧
    AcDbCurve *pCurve = NULL;  //计算等分点的曲线
    AcGePoint2d startPoint2d = CGeometryOper::ToPoint2d(m_startPoint);
    AcGePoint2d thirdPoint2d = CGeometryOper::ToPoint2d(m_thirdPoint);
    AcGePoint2d endPoint2d = CGeometryOper::ToPoint2d(m_endPoint);

    if (CGeometryOper::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 (CGeometryOper::PtInLeftOfLine(startPoint2d, thirdPoint2d, endPoint2d) > 0)
            //逆时针
        {
            startAngle = vecStart.angle();
        }
        else
        {
            startAngle = vecEnd.angle();
        }
        double endAngle = startAngle + (geArc.endAng() - geArc.startAng());
        pCurve = new AcDbArc(CGeometryOper::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  //添加子实体的方式
        {
            CCreateEnt::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(CCreateEnt::PostToModelSpace(pBlkRef));
            }
        }
    }
}

(4) MyWorldDraw() 函数 PostToModelSpace() 函数

// 自定义实体的绘制函数
Adesk::Boolean CArcBlockJigEntity::MyWorldDraw(AcGiWorldDraw* mode)
{
    DrawOrAddSubEnts(mode);

    return Adesk::kTrue;
}


// 将圆弧和块添加到模型空间
void CArcBlockJigEntity::PostToModelSpace()
{
    DrawOrAddSubEnts(NULL);
}

(5) 设置圆弧终点的位置 用于修改自定义实体中的圆弧终点

// 设置圆弧终点的位置 用于修改自定义实体中的圆弧终点
void CArcBlockJigEntity::SetEntPoint(const AcGePoint3d &pt)
{
    //这句代码能引发MyWorldDraw函数的调用
    assertWriteEnabled();

    m_endPoint = pt;
}

(6) 将PostToModelSpace 中添加到模型空间的块参照集合返回到外部调用函数
        获得添加的块参照集合

// 将PostToModelSpace 中添加到模型空间的块参照集合返回到外部调用函数
// 获得添加的块参照集合
AcDbObjectIdArray CArcBlockJigEntity::GetBlkRefIds()
{
    return m_blkRefIds;
}

(7) 添加新类 CArcBlockJig , 从 AcEdJig 类继承而来

#include "ArcBlockJigEntity.h"
class CArcBlockJig : public AcEdJig
{
public:
    CArcBlockJig(void);
    virtual ~CArcBlockJig(void);

    //参数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;

};

(8) 构造函数中, 对自定义实体的指针进行初始化,

        析构函数中, 销毁自定义实体,

CArcBlockJig::CArcBlockJig(void)
{
    m_pJigEnt = NULL;
}


CArcBlockJig::~CArcBlockJig(void)
{
    if (m_pJigEnt)
    {
        delete m_pJigEnt;
        m_pJigEnt = NULL;
    }
}

(9) doIt() 函数

// 处理拖动的整个流程
// 参数startPoint:起始点;endPoint:终止点;thirdPoint第三点;
// blkDefId块的Id; blockCount:插入块的个数
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 = _T("\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;
}

(10) sampler() 函数

// 此函数将被drag函数调用以获得一个输入
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;
}

(11) update() 函数

// 更新自定义实体
Adesk::Boolean CArcBlockJig::update()
{
    m_pJigEnt->SetEntPoint(m_curPoint);
    return Adesk::kTrue;
}

(12) entity() 函数

// 返回 AutoCAD 需要动态更新的实体
AcDbEntity* CArcBlockJig::entity() const
{
    return m_pJigEnt;
}

(13) GetBlkRefIds() 函数

// 将Jig过程中创建的块参照集合返回给外部调用函数
AcDbObjectIdArray CArcBlockJig::GetBlkRefIds()
{
    return m_blkRefIds;
}

(14) 添加一个测试函数进行测试

    static void ArcBlockJig(); //测试函数
// 测试
void CArcBlockJig::ArcBlockJig()
{
    //选择一个块参照,用于沿圆弧插入
    AcDbEntity *pEnt = NULL;
    AcDbObjectId blkDefId;
    AcGePoint3d pickPoint;
    if (CArcBlockJig::PromptSelectEntity(_T("\n 选择一个块参照用于沿圆弧插入:"), AcDbBlockReference::desc(), pEnt, pickPoint))
    {
        AcDbBlockReference *pBlkRef = AcDbBlockReference::cast(pEnt);
        blkDefId = pBlkRef->blockTableRecord();
        pEnt->close();
    }
    if (blkDefId.isNull())
    {
        return;
    }

    //提示用户拾取第一点
    AcGePoint3d startPoint;
    if (!CArcBlockJig::GetPoint(_T("\n拾取第一点:"), startPoint))
    {
        return;
    }

    //提示用户拾取第二点
    AcGePoint3d secondPoint;
    if (!CArcBlockJig::GetPoint(startPoint, _T("\n拾取第二点:"), secondPoint))
    {
        return;
    }

    //开始拖动
    CArcBlockJig jig;
    int blockCount = 4;
    jig.doIt(startPoint, secondPoint, secondPoint, blkDefId, blockCount);
}

(15) 需要的函数

    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);

    // 提示用户选择一个点(无论当前是否在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);
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(_T("\n选择的实体类型不合要求, 请再次选择..."));
        goto RETRY;
    }
}


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) 在acrxEntryPoint.cpp中

ACED_ARXCOMMAND_ENTRY_AUTO(CArxConfigApp, MidasMyGroup, MyArcBlockJig, MyArcBlockJig, ACRX_CMD_MODAL, NULL) //Jig圆弧上分段画选择的块
    //当前项目中注册命令 ArcBlockJig (Jig圆弧上分段画选择的块)
    static void MidasMyGroupMyArcBlockJig()
    {
        CDrawSquareJig::DrawSequareJig();
    }

效果展示:

1. 创建块

 2. 输入命令MyArcBlockJig

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值