基于C++与Qt的CAD开发入门:构建交互式绘图框架与基础图元实现 1. 项目概述为什么选择 C 与 Qt 来开启 CAD 开发之旅如果你对 CAD计算机辅助设计软件的内部运作感到好奇或者想亲手打造一个属于自己的绘图工具那么恭喜你你找到了一个绝佳的起点。市面上成熟的 CAD 软件如 AutoCAD、中望CAD 等其核心引擎大多由 C 编写这并非偶然。C 以其卓越的运行效率、对硬件的直接控制能力以及成熟的面向对象特性成为处理复杂图形计算和构建大型桌面应用的首选。而 Qt作为一个久经考验的跨平台 C 应用框架它提供的丰富 GUI 组件、强大的图形视图框架以及便捷的信号槽机制能让我们从繁琐的底层窗口和绘图 API 中解放出来专注于 CAD 应用本身的逻辑。这个系列教程的第一步我们的目标非常明确搭建一个最基础的 CAD 框架实现一个可以交互的画布并完成点、直线等基础图元的绘制。这听起来简单但却是所有复杂功能的基石。画布是用户与 CAD 系统交互的主战场而基础绘图则是构建更复杂几何图形如圆、圆弧、多段线和编辑操作如移动、旋转、缩放的前提。很多新手在入门时容易陷入某个具体算法细节却忽略了整体框架的搭建导致后续功能难以集成。我们将采用“先搭骨架再填血肉”的方式确保每一步都稳固可靠。2. 开发环境准备与第一个 Qt 窗口2.1 工具链的选择与安装避坑工欲善其事必先利其器。对于 C/Qt 开发一个顺手的工具链至关重要。1. 编译器与 Qt 版本选择我强烈建议在 Windows 平台上使用MSVCMicrosoft Visual C编译器因为它与 Qt 的集成度最高稳定性最好。避免使用 MinGW 作为初学选择尤其是在涉及复杂图形和第三方库时可能会遇到一些兼容性问题。Qt 版本方面推荐使用Qt 5.15 LTS或Qt 6.2及以上版本。Qt 6 是未来但 Qt 5.15 拥有最广泛的社区支持和稳定性。本教程基于 Qt 5.15 MSVC2019 进行讲解原理通用。2. 安装 Qt前往 Qt 官网下载在线安装器qt-unified-windows-x64-online.exe。运行后在组件选择页面务必展开Qt-Qt 5.15.2或你选择的版本勾选MSVC 2019 64-bit套件。同时为了后续开发方便强烈建议勾选Qt-Qt 5.15.2-SourcesQt源码便于调试和Qt-Developer and Designer Tools-Qt Creator X.X.X (CDB Debugger Support)。Qt Creator 是 Qt 官方的集成开发环境对 Qt 项目支持非常好。注意安装过程中如果遇到网络问题导致某些组件下载失败可以尝试切换安装镜像源。安装路径不要包含中文或空格这是一个老生常谈但总有人踩坑的点。3. 配置 IDE虽然你可以使用 Visual Studio 并安装 Qt VS Tools 插件但对于 Qt 开发Qt Creator往往是更轻量、更专注的选择。打开 Qt Creator在工具-选项-Kits中确保自动检测到了你的 MSVC 编译器和 Qt 版本套件并且调试器通常是 CDB也已配置好。2.2 创建项目与理解基础结构打开 Qt Creator点击新建项目选择Application-Qt Widgets Application。项目名称可以定为SimpleCAD。在类信息页面基类选择QMainWindow类名可以保持MainWindow。这将为我们生成一个带菜单栏、工具栏和状态栏的主窗口应用。创建完成后项目结构如下SimpleCAD.pro # Qt 项目配置文件 Headers/ mainwindow.h Sources/ main.cpp mainwindow.cpp Forms/ mainwindow.ui # Qt Designer 界面文件让我们快速理解几个关键文件SimpleCAD.pro这是 Qt 的工程文件类似于 CMakeLists.txt。它定义了项目包含的源文件、头文件、需要的 Qt 模块等。后续我们添加自定义的绘图类都需要在这里修改。main.cpp程序入口通常不需要改动。mainwindow.h/cpp主窗口的类定义和实现。mainwindow.ui这是一个 XML 文件用于通过 Qt Designer 以拖拽方式设计界面。对于 CAD 应用核心画布区域我们通常用代码创建但菜单、工具栏可以用 Designer 快速搭建。编译并运行这个项目你应该能看到一个空白的窗口。恭喜你的 CAD 开发环境已经就绪。3. 构建 CAD 核心自定义画布视图3.1 为什么是 QGraphicsView/QGraphicsSceneQt 提供了QPainter用于直接在窗口部件上绘图但对于一个需要支持大量图形项、缩放、平移、选择等交互的 CAD 画布直接使用QPainter会非常吃力。Qt 的Graphics View 框架正是为此类应用而生的。这个框架采用经典的MVC模型-视图-控制器变体模型 (Model):QGraphicsScene。它是一个容器管理所有图形项QGraphicsItem的集合。它不负责显示只负责存储和管理项之间的逻辑如碰撞检测。视图 (View):QGraphicsView。它是一个窗口部件用于将QGraphicsScene的内容可视化。它处理用户的视图变换缩放、平移、滚动条以及部分用户输入。项 (Items):QGraphicsItem及其子类。它们是场景中的具体对象如我们即将创建的直线、圆等。每个项知道如何绘制自己并可以处理自己的鼠标、键盘事件。对于 CAD 应用我们将创建一个自定义的QGraphicsScene来管理我们的图元并创建一个自定义的QGraphicsView来实现 CAD 特有的交互如正交模式、捕捉等。3.2 创建自定义画布视图类首先在 Qt Creator 中右键点击项目添加新文件-C-C Class。类名设为CadGraphicsView基类选择QGraphicsView。在cadgraphicsview.h中我们进行初步定义#ifndef CADGRAPHICSVIEW_H #define CADGRAPHICSVIEW_H #include QGraphicsView #include QMouseEvent class CadGraphicsView : public QGraphicsView { Q_OBJECT public: explicit CadGraphicsView(QWidget *parent nullptr); // 设置绘图模式 enum DrawMode { Select, Line, Circle, Rectangle }; void setCurrentDrawMode(DrawMode mode) { m_currentDrawMode mode; } protected: // 重写鼠标事件处理函数实现交互绘图 void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; // 重写绘制事件可以用于绘制临时图形如橡皮筋线 void drawForeground(QPainter *painter, const QRectF rect) override; private: DrawMode m_currentDrawMode Select; // 当前绘图模式 bool m_isDrawing false; // 是否正在绘制中 QPointF m_startPoint; // 绘图的起始点场景坐标 QPointF m_endPoint; // 绘图的当前点/结束点场景坐标 // 临时图形项指针用于预览 QGraphicsLineItem* m_tempLineItem nullptr; }; #endif // CADGRAPHICSVIEW_H在cadgraphicsview.cpp中我们先实现构造函数和基本的鼠标事件#include cadgraphicsview.h #include QGraphicsLineItem #include QDebug CadGraphicsView::CadGraphicsView(QWidget *parent) : QGraphicsView(parent) { // 设置视图属性 setRenderHint(QPainter::Antialiasing, true); // 抗锯齿 setDragMode(QGraphicsView::RubberBandDrag); // 默认框选模式 setViewportUpdateMode(QGraphicsView::FullViewportUpdate); // 避免残影 setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // 缩放时以鼠标为中心 setResizeAnchor(QGraphicsView::AnchorViewCenter); // 创建一个场景并设置 QGraphicsScene *scene new QGraphicsScene(this); scene-setSceneRect(-10000, -10000, 20000, 20000); // 设置一个很大的场景范围 setScene(scene); } void CadGraphicsView::mousePressEvent(QMouseEvent *event) { if (event-button() Qt::LeftButton) { QPointF scenePos mapToScene(event-pos()); // 将视图坐标转换为场景坐标 switch (m_currentDrawMode) { case Line: if (!m_isDrawing) { // 开始画线 m_isDrawing true; m_startPoint scenePos; m_endPoint scenePos; // 创建临时线项用于预览 m_tempLineItem new QGraphicsLineItem(QLineF(m_startPoint, m_endPoint)); m_tempLineItem-setPen(QPen(Qt::blue, 1, Qt::DashLine)); scene()-addItem(m_tempLineItem); } break; case Select: default: // 交给父类处理选择等操作 QGraphicsView::mousePressEvent(event); break; } } } void CadGraphicsView::mouseMoveEvent(QMouseEvent *event) { if (m_isDrawing m_currentDrawMode Line m_tempLineItem) { m_endPoint mapToScene(event-pos()); // 更新临时线的终点 m_tempLineItem-setLine(QLineF(m_startPoint, m_endPoint)); } else { QGraphicsView::mouseMoveEvent(event); } } void CadGraphicsView::mouseReleaseEvent(QMouseEvent *event) { if (event-button() Qt::LeftButton m_isDrawing) { if (m_currentDrawMode Line m_tempLineItem) { // 结束画线创建最终的线图形项 QGraphicsLineItem *permanentLine new QGraphicsLineItem(QLineF(m_startPoint, m_endPoint)); permanentLine-setPen(QPen(Qt::black, 2)); // 最终线为黑色实线 scene()-addItem(permanentLine); // 清理临时项 scene()-removeItem(m_tempLineItem); delete m_tempLineItem; m_tempLineItem nullptr; m_isDrawing false; } } else { QGraphicsView::mouseReleaseEvent(event); } }现在我们需要在主窗口中集成这个自定义视图。修改mainwindow.cpp的构造函数#include mainwindow.h #include ui_mainwindow.h #include cadgraphicsview.h // 包含自定义视图头文件 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui-setupUi(this); // 创建我们自定义的CAD视图并设置为中央部件 CadGraphicsView *cadView new CadGraphicsView(this); setCentralWidget(cadView); // 创建简单的工具栏来切换模式后续完善 QToolBar *drawToolBar addToolBar(绘图); QAction *selectAction drawToolBar-addAction(选择); QAction *lineAction drawToolBar-addAction(直线); // 连接信号槽 connect(selectAction, QAction::triggered, [cadView]() { cadView-setCurrentDrawMode(CadGraphicsView::Select); }); connect(lineAction, QAction::triggered, [cadView]() { cadView-setCurrentDrawMode(CadGraphicsView::Line); }); }编译运行点击工具栏的“直线”按钮然后在画布上点击并拖动你应该能看到一条蓝色的虚线跟随鼠标橡皮筋效果松开鼠标后一条黑色的实线被创建并添加到场景中。这就是交互式绘图的基本雏形。实操心得这里有一个关键点坐标转换。QMouseEvent::pos()给出的是相对于QGraphicsView窗口部件的坐标。而图形项是存在于QGraphicsScene的场景坐标系中的。我们必须使用mapToScene()将视图坐标转换为场景坐标才能正确放置图形项。反之从场景坐标到视图坐标则使用mapFromScene()。理解这两个坐标系是操作 Graphics View 框架的基础。4. 实现基础绘图功能从直线到自定义图元4.1 封装自定义图形项类直接使用QGraphicsLineItem、QGraphicsEllipseItem虽然快捷但不利于我们未来扩展图元的自定义属性如线型、图层、颜色和序列化。因此我们需要创建自己的图元类。以直线为例创建CadLineItem类继承自QGraphicsLineItem。cadlineitem.h:#ifndef CADLINEITEM_H #define CADLINEITEM_H #include QGraphicsLineItem #include QPen class CadLineItem : public QGraphicsLineItem { public: explicit CadLineItem(const QLineF line, QGraphicsItem *parent nullptr); // 重写类型枚举用于qgraphicsitem_cast enum { Type UserType 1 }; int type() const override { return Type; } // 自定义属性设置与获取 void setLineStyle(Qt::PenStyle style); Qt::PenStyle lineStyle() const; void setLineWidth(qreal width); qreal lineWidth() const; // 可以重写paint函数实现更复杂的绘制逻辑 // void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget nullptr) override; private: // 可以添加更多私有成员如图层ID、唯一标识符等 }; #endif // CADLINEITEM_Hcadlineitem.cpp:#include cadlineitem.h CadLineItem::CadLineItem(const QLineF line, QGraphicsItem *parent) : QGraphicsLineItem(line, parent) { // 设置默认样式 setPen(QPen(Qt::black, 2.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); } void CadLineItem::setLineStyle(Qt::PenStyle style) { QPen p pen(); p.setStyle(style); setPen(p); } Qt::PenStyle CadLineItem::lineStyle() const { return pen().style(); } void CadLineItem::setLineWidth(qreal width) { QPen p pen(); p.setWidthF(width); setPen(p); } qreal CadLineItem::lineWidth() const { return pen().widthF(); }然后修改CadGraphicsView::mouseReleaseEvent中创建最终线段的代码使用我们自己的CadLineItem// 替换原来的 QGraphicsLineItem *permanentLine new QGraphicsLineItem(...); CadLineItem *permanentLine new CadLineItem(QLineF(m_startPoint, m_endPoint)); // 可以在这里设置自定义属性例如 // permanentLine-setLineStyle(Qt::DashLine); // permanentLine-setLineWidth(3.0); scene()-addItem(permanentLine);4.2 扩展其他基础图元点与圆遵循同样的模式我们可以创建CadPointItem和CadCircleItem。对于点我们可以用一个小的实心圆来表示。CadPointItem可以继承自QGraphicsEllipseItem。cadpointitem.h:#ifndef CADPOINTITEM_H #define CADPOINTITEM_H #include QGraphicsEllipseItem class CadPointItem : public QGraphicsEllipseItem { public: explicit CadPointItem(const QPointF ¢er, qreal radius 3.0, QGraphicsItem *parent nullptr); enum { Type UserType 2 }; int type() const override { return Type; } QPointF center() const; void setCenter(const QPointF ¢er); }; #endifcadpointitem.cpp:#include cadpointitem.h CadPointItem::CadPointItem(const QPointF ¢er, qreal radius, QGraphicsItem *parent) : QGraphicsEllipseItem(center.x() - radius, center.y() - radius, radius*2, radius*2, parent) { setBrush(Qt::black); // 实心填充 setPen(QPen(Qt::NoPen)); // 无边框 } QPointF CadPointItem::center() const { return rect().center(); } void CadPointItem::setCenter(const QPointF ¢er) { qreal r rect().width() / 2.0; setRect(center.x() - r, center.y() - r, r*2, r*2); }对于圆我们需要两个点圆心和圆周上的一点或直接圆心和半径。CadCircleItem同样继承自QGraphicsEllipseItem。cadcircleitem.h:#ifndef CADCIRCLEITEM_H #define CADCIRCLEITEM_H #include QGraphicsEllipseItem class CadCircleItem : public QGraphicsEllipseItem { public: // 通过圆心和半径构造 explicit CadCircleItem(const QPointF ¢er, qreal radius, QGraphicsItem *parent nullptr); // 通过圆心和圆上一点构造 explicit CadCircleItem(const QPointF ¢er, const QPointF pointOnCircle, QGraphicsItem *parent nullptr); enum { Type UserType 3 }; int type() const override { return Type; } QPointF center() const; qreal radius() const; void setCenter(const QPointF ¢er); void setRadius(qreal radius); }; #endif实现时需要根据参数计算外接矩形。然后在CadGraphicsView中添加对Circle绘图模式的支持逻辑与画线类似只是临时项和最终项换成了CadCircleItem。注意事项在实现圆的橡皮筋效果时通常有两种交互方式1) 点击确定圆心拖动确定半径2) 点击确定圆上一点拖动确定直径。你需要根据常见的 CAD 交互习惯来选择。这里建议采用第一种更直观。在mouseMoveEvent中根据当前鼠标位置和圆心计算半径并更新临时圆图形项的矩形。4.3 完善绘图模式切换与状态管理现在我们需要在CadGraphicsView中完整支持Select,Line,Circle,Point等模式。我们需要修改鼠标事件处理函数为每种模式编写相应的开始、更新、结束绘制的逻辑。一个更清晰的做法是引入一个“绘图状态机”。我们可以定义一个DrawingState结构体或类来管理当前正在绘制的临时项、起始点、当前模式等。但为了教程清晰我们暂时用switch-case配合枚举来管理。同时我们需要更新主窗口的工具栏为每种模式添加按钮并确保点击按钮后视图的绘图模式被正确设置并且视图的鼠标光标可能也需要相应改变例如画线时变成十字光标。// 在 CadGraphicsView 的构造函数中设置默认光标 setCursor(Qt::ArrowCursor); // 在 setCurrentDrawMode 函数中改变光标 void CadGraphicsView::setCurrentDrawMode(DrawMode mode) { m_currentDrawMode mode; switch(mode) { case Select: setCursor(Qt::ArrowCursor); setDragMode(QGraphicsView::RubberBandDrag); break; case Line: case Circle: case Rectangle: setCursor(Qt::CrossCursor); // 十字光标 setDragMode(QGraphicsView::NoDrag); // 绘图时禁用框选拖拽 break; } }5. 画布交互与视图控制5.1 实现缩放与平移一个专业的 CAD 画布必须支持流畅的缩放和平移。Qt Graphics View 已经提供了基础支持。缩放通常通过鼠标滚轮实现。我们可以重写wheelEvent。void CadGraphicsView::wheelEvent(QWheelEvent *event) { // 缩放因子 double scaleFactor 1.15; // 每次滚轮缩放15% if (event-angleDelta().y() 0) { // 滚轮向上放大 scale(scaleFactor, scaleFactor); } else { // 滚轮向下缩小 scale(1.0 / scaleFactor, 1.0 / scaleFactor); } // 阻止事件继续传递 event-accept(); }平移通常通过按住鼠标中键或右键或空格键鼠标左键拖动实现。我们可以通过设置DragMode为ScrollHandDrag来快速启用。// 可以在键盘事件或鼠标事件中触发平移模式 void CadGraphicsView::keyPressEvent(QKeyEvent *event) { if (event-key() Qt::Key_Space !event-isAutoRepeat()) { setDragMode(QGraphicsView::ScrollHandDrag); setCursor(Qt::OpenHandCursor); // 需要模拟一个鼠标按下事件来开始拖拽实际上ScrollHandDrag会自动处理。 // 更常见的做法是在鼠标按下时如果按下了空格键则进入平移模式。 } QGraphicsView::keyPressEvent(event); } void CadGraphicsView::keyReleaseEvent(QKeyEvent *event) { if (event-key() Qt::Key_Space !event-isAutoRepeat()) { setDragMode(QGraphicsView::RubberBandDrag); // 恢复默认模式 setCursor(Qt::ArrowCursor); } QGraphicsView::keyReleaseEvent(event); }更常见的 CAD 操作是按住鼠标中键平移。这需要重写鼠标事件来判断中键按下然后调用QGraphicsView的translate方法或直接操作视口。5.2 添加网格与坐标显示网格是 CAD 绘图的重要参考。我们可以在CadGraphicsView的drawBackground方法中绘制网格。void CadGraphicsView::drawBackground(QPainter *painter, const QRectF rect) { // 调用父类方法绘制默认背景通常是白色 QGraphicsView::drawBackground(painter, rect); // 设置网格属性 qreal gridSize 20.0; // 网格间距场景单位 QPen gridPen(QColor(220, 220, 220), 1, Qt::SolidLine); // 浅灰色网格线 painter-setPen(gridPen); painter-setBrush(Qt::NoBrush); // 计算需要绘制网格线的范围略大于视图区域避免滚动时出现空白 QRectF sceneRect rect.adjusted(-gridSize, -gridSize, gridSize, gridSize); // 绘制垂直线 qreal left int(sceneRect.left() / gridSize) * gridSize; qreal right sceneRect.right(); for (qreal x left; x right; x gridSize) { painter-drawLine(QPointF(x, sceneRect.top()), QPointF(x, sceneRect.bottom())); } // 绘制水平线 qreal top int(sceneRect.top() / gridSize) * gridSize; qreal bottom sceneRect.bottom(); for (qreal y top; y bottom; y gridSize) { painter-drawLine(QPointF(sceneRect.left(), y), QPointF(sceneRect.right(), y)); } }坐标显示可以在状态栏实时显示鼠标在场景中的坐标。这需要在CadGraphicsView的mouseMoveEvent中发射一个信号。在cadgraphicsview.h中添加信号signals: void mouseMovedTo(const QPointF scenePos);在mouseMoveEvent中发射emit mouseMovedTo(mapToScene(event-pos()));在主窗口类中连接这个信号到一个更新状态栏标签的槽函数即可。5.3 实现简单的捕捉功能Snap捕捉是 CAD 的核心功能之一用于将点精确对齐到网格、端点、中点等。这是一个进阶话题但我们可以实现最简单的网格捕捉。思路是在鼠标移动或释放时将获取到的场景坐标(x, y)对齐到最近的网格点上。QPointF CadGraphicsView::snapToGrid(const QPointF inputPoint) const { qreal gridSize 20.0; qreal snappedX qRound(inputPoint.x() / gridSize) * gridSize; qreal snappedY qRound(inputPoint.y() / gridSize) * gridSize; return QPointF(snappedX, snappedY); }然后在mousePressEvent、mouseMoveEvent和mouseReleaseEvent中对获取到的scenePos调用这个函数使用捕捉后的坐标进行绘图。可以添加一个布尔标志m_snapEnabled来控制是否启用捕捉。更复杂的端点、中点捕捉需要遍历场景中所有相关图形项计算距离找到最近的特征点。这涉及到空间索引和几何计算我们将在后续教程中展开。6. 项目结构优化与代码管理6.1 合理组织头文件与源文件随着图元类的增加项目文件会变多。建议创建专门的目录来组织SimpleCAD/ ├── cadcore/ # 核心类 │ ├── cadgraphicsview.h/cpp │ ├── cadscene.h/cpp (可选未来管理图层、块等) │ └── ... ├── caditems/ # 图元类 │ ├── cadlineitem.h/cpp │ ├── cadpointitem.h/cpp │ ├── cadcircleitem.h/cpp │ └── ... └── ...然后在.pro文件中正确包含这些目录HEADERS \ cadcore/cadgraphicsview.h \ caditems/cadlineitem.h \ caditems/cadpointitem.h \ caditems/cadcircleitem.h SOURCES \ cadcore/cadgraphicsview.cpp \ caditems/cadlineitem.cpp \ caditems/cadpointitem.cpp \ caditems/cadcircleitem.cpp6.2 使用智能指针管理内存在图形场景中当图元被移除或场景销毁时需要确保内存被正确释放。虽然QGraphicsScene会在销毁时删除所有子项但在动态创建和删除项的过程中使用智能指针可以避免悬空指针和内存泄漏。对于临时项如橡皮筋线我们使用原始指针并在删除后置空是可以的。但对于可能被反复引用和操作的核心图元考虑使用QSharedPointer或QScopedPointer进行管理或者确保其生命周期由场景严格管理。一个更“Qt”的方式是让图元对象本身成为QObject的子对象利用 Qt 的对象树父子关系自动管理内存。但QGraphicsItem本身不是QObject。通常的做法是将图元添加到场景后场景就获得了其所有权我们不应再手动delete它除非先将它从场景中移除。常见问题运行时崩溃提示“QGraphicsScene::removeItem: item 0x... is not in this scene”。这通常是因为你试图删除一个已经被场景删除的项或者重复删除。确保你的临时项指针在删除后立即置为nullptr并在操作前检查指针有效性。7. 编译、调试与发布注意事项7.1 解决常见的编译与链接错误1. “unknown module(s) in qt: core5compat”这个错误通常出现在 Qt 6 项目中如果你在.pro文件里写了QT core5compat但你的 Qt 6 安装没有包含这个模块。在 Qt 6 中许多 Qt 5 的兼容类被移到了core5compat模块。解决方案确保在安装 Qt 6 时勾选了该模块或者在.pro文件中移除这行并修改代码使用 Qt 6 的新 API。2. 发布程序时缺失 DLL使用 Qt Creator 的“Release”模式构建后生成的可执行文件需要一系列 Qt 的 DLL 文件才能在其他电脑上运行。最简单的发布方法是使用 Qt 自带的命令行工具windeployqt。打开 Qt 5.15.2 (MSVC 2019 64-bit) 的命令行环境。切换到你的可执行文件.exe所在的目录。执行命令windeployqt your_app_name.exe该工具会自动将所需的 Qt DLL、插件等复制到当前目录。你可能还需要手动复制MSVC的运行时库如msvcp140.dll,vcruntime140.dll。7.2 性能优化初步思考当场景中有成千上万个图形项时性能会成为问题。以下是一些初步优化方向设置 ItemIndexMethod: 在QGraphicsScene构造函数或setScene后调用scene-setItemIndexMethod(QGraphicsScene::NoIndex);对于静态场景或项很少的场景可以关闭索引以提升简单操作的性能。对于大量项的动态场景可以使用BspTreeIndex。视图更新模式: 我们已经设置了FullViewportUpdate这能避免残影但可能性能不是最优。对于复杂场景可以尝试MinimalViewportUpdate或SmartViewportUpdate。细节层次 (LOD): 对于复杂的自定义图元可以在paint函数中根据视图的缩放级别绘制不同细节程度的图形。避免在 paint 函数中进行复杂计算:paint函数会被频繁调用应只用于绘图。7.3 下一步规划至此我们已经完成了一个具备基础绘图点、线、圆、画布交互缩放、平移、网格的 CAD 框架第一步。这个框架虽然简陋但五脏俱全为后续功能打下了坚实基础。接下来的步骤可能包括图元编辑实现图元的选择、移动、旋转、缩放变换。图层管理引入图层概念每个图元属于一个图层可以控制图层的显示、锁定、打印等属性。命令模式实现撤销/重做Undo/Redo功能这是专业 CAD 的必备。文件 IO实现将场景保存为自定义格式或导出为 DXF 等通用格式。更复杂的图元与捕捉实现多段线、样条曲线、图案填充以及端点、中点、圆心、交点等对象捕捉。每一步都可以基于当前框架进行迭代开发。记住CAD 开发是一个系统工程保持代码的模块化和可扩展性至关重要。先从这个小框架开始亲手实现每一个功能点你会对 CAD 软件的工作原理有越来越深刻的理解。

相关新闻

最新新闻

基于MUD游戏的低成本LLM评估框架:动态测试与实战指南

基于MUD游戏的低成本LLM评估框架:动态测试与实战指南

今天来看一个很有意思的项目:用 MUD(多用户地下城游戏)来评估大语言模型(LLMs)的能力。这个项目来自开源社区,核心思路是把传统的游戏环境改造成一个低成本、可复现的 LLM 测试平台,整个验证方案…

2026/7/26 4:56:32
遮挡、低质、跨年龄段——人脸识别的“死穴“到底怎么破

遮挡、低质、跨年龄段——人脸识别的“死穴“到底怎么破

一、遮挡——口罩帽子墨镜,把脸遮了一半还怎么认?2020年疫情之后,口罩人脸识别成了一个迫在眉睫的问题。你辛辛苦苦训出来的ArcFace模型,是针对"全脸"设计的——眼睛、鼻子、嘴巴、额头全都在特征空间里有对应的特征响应…

2026/7/26 4:56:32
向日葵远程Ubuntu,支持隐私屏

向日葵远程Ubuntu,支持隐私屏

向日葵远程Ubuntu时,对Ubuntu的版本、桌面环境的法要求。1.Ubuntu24.04登录时选Ubuntu on Xorg,安装后就有,进入系统后就可以登陆乐2.Ubuntu24.04这些版本需要使用xorg、x11,可以安装xfce、kde等桌面3.隐私屏目前Linxu版本的向日葵不支持隐私…

2026/7/26 4:56:32
基于MUD游戏环境的大语言模型低成本评估框架实践

基于MUD游戏环境的大语言模型低成本评估框架实践

这次我们来看一个很有意思的项目:用 MUD(多用户地牢游戏)来评估大语言模型(LLMs)。这个项目来自开源社区,核心思路是把传统的游戏环境改造成一个低成本、可复现的测试场,用来检验 LLM 的推理能力…

2026/7/26 4:56:32
Linux线程原理与性能优化实战

Linux线程原理与性能优化实战

1. 线程的本质与设计哲学在Linux系统中,线程这个概念曾让我困惑了很久——它和进程到底有什么区别?为什么要有线程?经过多年内核开发实践,我终于理解了线程设计的精妙之处。想象你正在运营一家餐厅:进程就像是整个餐厅…

2026/7/26 4:56:32
用 Ace Data Cloud 把 API 能力变成可持续的技术内容分发

用 Ace Data Cloud 把 API 能力变成可持续的技术内容分发

做开发者工具、API 服务或 AI 应用时,很多团队都会遇到一个共同问题:产品已经能用了,接口也很稳定,但很难持续把能力讲清楚、发出去,并且沉淀成可复用的内容资产。 尤其是面向开发者的产品,单纯写一句“我们…

2026/7/26 4:51:32

月新闻