LibreCAD源码阅读:实体绘制流程

本文详细解释了在LibreCAD中如何通过RS_ActionDrawLine类绘制两点直线,涉及实体创建、事件处理、视图重绘以及Qt和QWidget在图形绘制中的作用。还讨论了实体容器和实体设计的潜在问题。
摘要由CSDN通过智能技术生成

实体绘制流程

以绘制两点直线为例进行说明。两点直线的特征类是RS_ActionDrawLine,在这个类里面实现了直线对象的创建。创建完成后就会通知视图进行重绘,然后就将实体显示在界面上了。

创建实体

进入绘制两点直线命令后,在void RS_ActionDrawLine::coordinateEvent(RS_CoordinateEvent* e)中,当接收到第二个点的事件后,就会调用trigger()函数,表示当前这个实体已经绘制完毕,需要生成实体,然后添加到文档(Document)中,最后进行重绘,以显示在桌面上。

void RS_ActionDrawLine::coordinateEvent(RS_CoordinateEvent* e)
{
	...
    switch (getStatus()) {
    case SetStartpoint:	// 起点,第一个点
		...
        break;

    case SetEndpoint:	// 终点,第二个点
        if ((mouse-pPoints->data.startpoint).squared() > RS_TOLERANCE2) {
            ...
            trigger();	// 绘制完毕,生成实体
            ...
        }
        break;
    }
	...
}

绘制事件

trigger()函数中,最重要的是调用了视图的redraw()函数进行视图重绘。重绘函数中实际上只调用了update()函数,这个函数是QtQWidget提供的界面刷新函数,调用这个函数后,会自动执行对应窗口的paintEvent()函数。

void RS_ActionDrawLine::trigger()
{
    RS_PreviewActionInterface::trigger();

    RS_Line* line = new RS_Line(container, pPoints->data);	// 直线实体对象
    line->setLayerToActive();
    line->setPenToActive();
    container->addEntity(line);		// 添加到容器中
	...
    graphicView->redraw(RS2::RedrawDrawing);	// 重绘视图
    ...
}

paintEvent函数

首先是一个窗口中有三种类别的图层,实体在PixmapLayer2图层上。在绘制实体前,需要将绘图设备(实际上是一个QPainter)传递给绘图函数。这里的绘图设备还是RS_PainterQt对象。

void QG_GraphicView::paintEvent(QPaintEvent *)
{
    getPixmapForView(PixmapLayer1);	// 背景,包括栅格
    getPixmapForView(PixmapLayer2);	// 实体
    getPixmapForView(PixmapLayer3);	// 预览,例如橡皮线

    
    if (redrawMethod & RS2::RedrawGrid)	// Draw Layer 1
	{ ... }
    if (redrawMethod & RS2::RedrawDrawing)	// DRaw layer 2
    {
        ...
        PixmapLayer2->fill(Qt::transparent);
        RS_PainterQt painter2(PixmapLayer2.get());	// 获取绘图对象
		...
        painter2.setDrawingMode(drawingMode);
        painter2.setDrawSelectedOnly(false);
        drawLayer2((RS_Painter*)&painter2);	// 将绘图设备传递给实体绘制函数
        painter2.setDrawSelectedOnly(true);
        drawLayer2((RS_Painter*)&painter2);
        painter2.end();
    }
    if (redrawMethod & RS2::RedrawOverlay)	// Draw Layer 3
    { ... }

    // Finally paint the layers back on the screen, bitblk to the rescue!
    RS_PainterQt wPainter(this);
    wPainter.drawPixmap(0,0,*PixmapLayer1);
    wPainter.drawPixmap(0,0,*PixmapLayer2);
    wPainter.drawPixmap(0,0,*PixmapLayer3);
    wPainter.end();

    redrawMethod=RS2::RedrawNone;
}

实际的绘图函数是drawLayer2(),这个函数里面简单调用了RS_GraphicView::drawEntity()

void RS_GraphicView::drawLayer2(RS_Painter *painter)
{
	drawEntity(painter, container);	//	Draw all entities.
	...
}

然后在drawEntity()中又调用了RS_GraphicView::drawEntityPlain(),这个函数的重点是调用draw()函数。drawEntityPlain()函数中的第二个参数RS_Entity* e实际上就是drawEntity(painter, container);中的container,也就是说此时的参数e表示一个实体容器,而并非一个单纯的实体对象。从而表明上面标注*的行最终会调用RS_EntityContainer::draw()

void RS_GraphicView::drawEntityPlain(RS_Painter *painter, RS_Entity* e, double& patternOffset) {
	if (!e) { return; }
	if (!e->isContainer() && (e->isSelected()!=painter->shouldDrawSelected()))
    { return; }

	e->draw(painter, this, patternOffset);	// *
}

RS_EntityContainer::draw()函数中,遍历容器中的所有实体,执行各个实体的绘制工作。这里还是调用了RS_GraphicView::drawEntity(),也就是说,还是会调用RS_GraphicView::drawEntityPlain(),但是此时这个e的实际类型已经不是一个容器,而是一个实体,例如直线、圆。

void RS_EntityContainer::draw(RS_Painter* painter, RS_GraphicView* view, double& /*patternOffset*/)
{
    if (painter == nullptr || view == nullptr)
        return;

    foreach (auto* e, entities)
        view->drawEntity(painter, e);
}

实体绘制

在第二次调用void RS_GraphicView::drawEntityPlain()函数时,此时该函数的第二个参数e就是表示待绘制的实体,在这里是直线实体。所以就会调用RS_Line::draw()函数,绘制直线。这里调用了RS_PainterQt类进行绘制。RS_PainterQt类是一个使用QtQPainter实现了各种实体(直线、圆弧、矩形)的绘图类。

void RS_Line::draw(RS_Painter* painter, RS_GraphicView* view, double& patternOffset) {
    ...
	painter->drawLine(view->toGui(getStartpoint()), view->toGui(getEndpoint()));
    ...
}
void RS_PainterQt::drawLine(const RS_Vector& p1, const RS_Vector& p2)
{
    PainterGuard painterGuard{*this};
    QPainter::drawLine(toScreenX(p1.x), toScreenY(p1.y),
                       toScreenX(p2.x), toScreenY(p2.y));
}

总结

LibreCAD中,绘图的画布是QWidget的派生类,绘图设备是QPainter的派生类,利用Qt提供的事件机制进行重绘事件通知。

有一点需要吐槽一下,容器类RS_EntityContainer继承于实体类RS_Entity,而直线类RS_Line、圆类RS_Circle等绘图实体类型也是继承于RS_Entity,然后RS_EntityContainer可以包含若干RS_Entity对象。这种奇怪的设计不符合一般认知,造成了源码阅读困难,应予以修正。

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值