LibreCAD源码阅读:实体捕捉与选择

本文详细阐述了在基于MVC的设计模式中,如何通过实体容器管理图形视图中的实体,以及RS_Snapper和RS_Action类如何协作进行实体捕捉和选择。特征类通过RS_EntityContainer访问文档中的实体,RS_Snapper负责实体的捕捉和距离判断,RS_Selection用于重绘选中的实体。
摘要由CSDN通过智能技术生成

实体捕捉与选择

实体容器

执行一个绘图特征之后,就会在视图中显示一个特定的特征,例如可以通过两点直线特征来绘制两点直线。这些绘制在视图上的图形实际上在内部会有一个实体与之对应,而存放这些实体的地方即为实体容器。在MVC设计模式中,通常将数据(也就是实体容器)存放在Model中,也就是本项目中的文档Document

本项目中的文档类RS_Document继承于容器类RS_EntityContainerRS_EntityContainer类内部有一个QList<RS_Entity *> entities;成员变量(即为实体容器)。

查看工作窗口类QC_MDIWindow的构造函数,在构造中创建了文档,所以实体容器是属于文档Document的组成部分,也表明实体容器本身是存放在工作窗口类QC_MDIWindow中的。其他的对象可以通过传递实体容器的指针来访问实体容器。

QC_MDIWindow::QC_MDIWindow(RS_Document *doc, QWidget *parent, Qt::WindowFlags wflags)
    : QMdiSubWindow(parent, wflags)
    , m_owner{doc == nullptr}
{
    // ...
    if (doc==nullptr) {
        document = new RS_Graphic();	// 创建文档document
        document->newDoc();
    } else {
        document = doc;
    }
	// 创建视图View
    graphicView = new QG_GraphicView(this, {}, document);
    // ...
}

捕捉类RS_Snapper

特征(Action)的基类是RS_ActionInterface,而RS_ActionInterface的基类(之一)是RS_SnapperRS_Snapper类中有一个重要成员是RS_EntityContainer *container,该参数表示当前活动文档中的实体容器,并且从RS_Snapper类构造函数中传入。

RS_Snapper::RS_Snapper(RS_EntityContainer& container, RS_GraphicView& graphicView)
    :container(&container)	// 传入实体容器的指针
    ,graphicView(&graphicView)
    ,pImpData(new ImpData)
    ,snap_indicator(new Indicator)
{}

具体的特征类,例如两点直线类的构造函数为:

RS_ActionDrawLine(RS_EntityContainer& container, RS_GraphicView& graphicView);

表明创建两点直线特征对象时,就会将实体容器指针和当前视图指针传递进来,然后一步一步传递到RS_Snapper类的构造函数中。

也就是说,所有的特征类中都可以访问当前文档中的实体容器

RS_Snapper类中有几个重要的重载函数RS_Entity* catchEntity(...),这一系列函数是为了计算实体容器中是否有实体被选中,具体是通过判断鼠标点击距离和最小判定距离来确定是否有实体被选中。

下面的第一个catchEntity函数首相是将屏幕点转换成视图中的坐标点。

RS_Entity* RS_Snapper::catchEntity(QMouseEvent* e, // Qt事件,主要是为了获取坐标
                                   RS2::EntityType enType,
                                   RS2::ResolveLevel level) {
    return catchEntity({ graphicView->toGraphX(e->x()), // 将Qt窗口坐标转换为视图坐标
                         graphicView->toGraphY(e->y())},
                         enType,
                         level);
}

然后调用第二个catchEntity函数。

RS_Entity* RS_Snapper::catchEntity(const RS_Vector& pos, RS2::ResolveLevel level)
{
    double dist (0.);	// 存放最短距离
    // 查找容器中距离点pos最近的实体,并返回距离dist
    RS_Entity* entity = container->getNearestEntity(pos, &dist, level);

    int idx = -1;
    if (entity != nullptr && entity->getParent()) {
        idx = entity->getParent()->findEntity(entity);	// 选中实体在容器中的索引
    }
    // ...
}

需要注意的是,如果在最小判定距离内没有实体,函数RS_Entity* RS_EntityContainer::getNearestEntity()则会返回nullptr

RS_Entity* RS_EntityContainer::getNearestEntity(const RS_Vector& coord,
                                                double* dist,	// *dist = 0.0
                                                RS2::ResolveLevel level) const
{
    RS_Entity* e = nullptr;
	// ...
    double d = getDistanceToPoint(coord, &e, level, solidDist);
	// ...
    if (dist) { *dist = d; }
    
    return e;
}

而在RS_EntityContainer::getDistanceToPoint()函数中,则是遍历实体容器,计算每个实体的距离。

double RS_EntityContainer::getDistanceToPoint(const RS_Vector& coord,
                                              RS_Entity** entity,
                                              RS2::ResolveLevel level,
                                              double solidDist) const{
    double minDist = RS_MAXDOUBLE;      // minimum measured distance
    double curDist;                     // currently measured distance
    RS_Entity* closestEntity = nullptr;    // closest entity found
    RS_Entity* subEntity = nullptr;

    for(auto e: entities){	// 遍历所有实体,计算最短距离
        if (e->isVisible() && (e->getLayer()==nullptr || !e->getLayer()->isLocked())) {
            // 计算具体的实体e到指定点coord的最短距离
            curDist = e->getDistanceToPoint(coord, &subEntity, level, solidDist);
            if (curDist<=minDist) {
                // ...
                closestEntity = e;	// 更新最短距离的实体
                minDist = curDist;	// 更新最短距离
            }
        }
    }

    if (entity) {
        *entity = closestEntity;
    }
    return minDist;
}

实体选择

在没有选择一个特征时,当前的特征为RS_ActionDefault,当鼠标点击视图时,会通过事件处理器eventHander将鼠标弹起时间mouseReleaseEvent转发给默认特征RS_ActionDefault。然后RS_ActionDefault::mouseReleaseEvent()被调用。

RS_ActionDefault::mouseReleaseEvent()函数中,首先会根据鼠标点击位置去判断是否选中了一个实体(catchEntity()函数),如果选中了就会重绘选中的实体。

void RS_ActionDefault::mouseReleaseEvent(QMouseEvent* e) {
    if (e->button()==Qt::LeftButton) {
		pPoints->v2 = graphicView->toGraph(e->x(), e->y());	// 鼠标点转换为视图坐标点
        switch (getStatus()) {
        case Dragging: {
            RS_Entity* en = catchEntity(e);	// 获取鼠标点击选中的实体
            if (en != nullptr) {	// 鼠标点击选中了实体
                deletePreview();
                RS_Selection s(*container, graphicView);
                s.selectSingle(en);	// 重绘选中的实体
                // ...
            } 
        }
        break;
    }
	// ...
}

重绘选中实体

重绘选中实体是通过RS_Selection类的selectSingle()方法来实现的。重绘过程为:首先从视图中删除被选中的实体,然后切换选中的状态(如果该实体未被选中,将状态设置为选中;如果已经是被选中的状态,则将状态设置为取消选中的状态)。最后单独绘制一下该实体。

void RS_Selection::selectSingle(RS_Entity* e)
{
	if (e && (! (e->getLayer() && e->getLayer()->isLocked())))
    {
        if (graphicView)
            graphicView->deleteEntity(e);	// 先从视图中删除该实体

       	e->toggleSelected();	// 切换选中状态,选中->取消选中,没选中->选中

        if (graphicView)
        {
            graphicView->drawEntity(e);	// 单独绘制选中实体
            // ...
        }
    }
}

总结

特征类继承于捕捉类,表明在执行任意一个特征时,都可以使用捕捉和选择的功能。而创建特征类时,传入了当前工作文档中的实体容器给特征类对象了,然后进一步传递给捕捉类,所以捕捉类能够利用这些数据和当前鼠标点来判断是否有实体被选中了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值