湖南省网站建设_网站建设公司_会员系统_seo优化
2025/12/18 20:15:17 网站建设 项目流程

我们已经完成了 CAD 基础框架搭建和性能优化,现在你可能会问:“接下来该往哪个方向走?” 新手最忌 “贪多求全”(比如直接上手 3D 建模、复杂约束),也忌 “停滞不前”(只停留在画点线圆)。这篇文章会给你一条循序渐进、可落地、能看到明确成果的开发路线,每个阶段都有具体目标、核心知识点和实操步骤,帮你稳步推进 CAD 框架的功能迭代。

一、先明确核心目标:从 “能画图” 到 “能编辑”

前两步我们实现了 “画点 / 线 / 圆 + 性能优化”,下一步的核心是让框架具备图形编辑能力—— 这是 CAD 软件区别于普通画图工具的关键,也是新手能快速掌握、且能显著提升框架实用性的方向。

整体路线分 3 个阶段(新手建议按顺序推进,每个阶段 1-2 周):

  1. 基础编辑:图形选中、移动、删除;
  2. 进阶编辑:图形缩放、旋转、复制粘贴;
  3. 辅助功能:图层管理、尺寸标注、文件读写。

每个阶段都基于之前的代码优化,不推翻原有逻辑,只做增量开发,降低学习成本。

二、第一阶段:实现图形选中、移动、删除(核心)

1. 核心目标

  • 鼠标点击能选中图形(高亮显示);
  • 选中后拖动鼠标能移动图形;
  • 按 Delete 键删除选中的图形;
  • 保持操作流畅(复用之前的性能优化技巧)。

2. 关键知识点

  • 几何命中测试(判断鼠标是否 “点中” 图形);
  • 选中状态管理(标记选中的图形,绘图时高亮);
  • 事件联动(选中→拖动→释放的完整交互链)。

3. 实操步骤(附核心代码)

步骤 1:给图形添加 “选中状态” 属性

修改之前的图形结构体,增加isSelected标记(用于绘图时高亮):

// 基础图形结构体(新增选中状态) struct BaseShape { ShapeType type; // 图形类型 QColor color; // 颜色 int penWidth; // 线宽 bool isSelected; // 选中状态(新增) // 构造函数(初始化默认值) BaseShape(ShapeType t) : type(t), color(Qt::black), penWidth(2), isSelected(false) {} };
步骤 2:实现 “命中测试”(判断鼠标是否选中图形)

这是编辑功能的核心 —— 新手不用搞复杂的几何算法,先实现 “简化版命中测试”(足够满足基础需求),后续再优化精度。

CanvasWidget中新增命中测试函数(按图形类型分别实现):

// 新增:判断鼠标点是否命中图形(返回命中的图形指针) BaseShape* hitTest(const QPointF& mousePos) { // 按“圆→线→点”的顺序判断(圆的命中区域大,优先判断) // 1. 测试圆 foreach (CircleShape* circle, circleShapes) { // 计算鼠标到圆心的距离,小于半径+5像素视为命中(扩大范围,新手操作更友好) qreal dist = sqrt(pow(mousePos.x() - circle->center.x(), 2) + pow(mousePos.y() - circle->center.y(), 2)); if (dist <= circle->radius + 5) { return circle; } } // 2. 测试直线(简化版:点到直线的距离<5像素视为命中) foreach (LineShape* line, lineShapes) { if (isPointNearLine(mousePos, line->startPos, line->endPos)) { return line; } } // 3. 测试点 foreach (PointShape* point, pointShapes) { qreal dist = sqrt(pow(mousePos.x() - point->pos.x(), 2) + pow(mousePos.y() - point->pos.y(), 2)); if (dist <= 5) { // 5像素范围内视为命中 return point; } } return nullptr; // 未命中任何图形 } // 辅助函数:判断点是否靠近直线(复用之前优化的版本,避免开根号) bool isPointNearLine(const QPointF& point, const QPointF& p1, const QPointF& p2) { QVector2D v1(p2 - p1); QVector2D v2(point - p1); qreal dot = QVector2D::dotProduct(v1, v2); if (dot < 0) return false; qreal lenSq = v1.lengthSquared(); if (dot > lenSq) return false; qreal distSq = v2.lengthSquared() - dot * dot / lenSq; return distSq < pow(5, 2); // 5像素阈值 }
步骤 3:处理鼠标事件,实现 “选中 + 移动”

修改CanvasWidget的鼠标事件,增加选中和移动逻辑:

// CanvasWidget新增成员变量 private: BaseShape* selectedShape = nullptr; // 当前选中的图形 QPointF lastMousePos; // 上次鼠标位置(用于移动计算) bool isDraggingShape = false; // 是否正在拖动图形 // 1. 鼠标按下事件:选中图形 void mousePressEvent(QMouseEvent *event) override { QPointF mousePos = event->pos(); // 先取消之前的选中状态 if (selectedShape != nullptr) { selectedShape->isSelected = false; selectedShape = nullptr; update(); // 刷新画布,取消高亮 } // 如果是右键/中键,走原有逻辑(平移画布) if (event->button() == Qt::MiddleButton) { // 平移画布的逻辑(之前实现的) m_isDragging = true; m_lastMousePos = mousePos; return; } // 左键:命中测试,选中图形 selectedShape = hitTest(mousePos); if (selectedShape != nullptr) { selectedShape->isSelected = true; isDraggingShape = false; // 初始未拖动 lastMousePos = mousePos; // 记录选中时的鼠标位置 update(); // 刷新画布,高亮显示选中的图形 return; } // 未选中图形:走原有绘图逻辑(画点/线/圆) if (currentTool != None) { // 原有绘图逻辑(按下创建临时图形)... } } // 2. 鼠标移动事件:移动选中的图形 void mouseMoveEvent(QMouseEvent *event) override { QPointF mousePos = event->pos(); // 优先处理图形拖动 if (selectedShape != nullptr && event->buttons() & Qt::LeftButton) { if (!isDraggingShape) { isDraggingShape = true; // 开始拖动 } // 计算鼠标移动的偏移量 QPointF delta = mousePos - lastMousePos; // 根据图形类型移动坐标 moveShape(selectedShape, delta); // 记录新的鼠标位置 lastMousePos = mousePos; // 局部重绘(复用之前的性能优化) update(getShapeBoundingRect(selectedShape).adjusted(-5, -5, 5, 5)); return; } // 未拖动图形:走原有逻辑(临时图形/平移画布) if (tempShape != nullptr) { // 原有临时图形拖动逻辑... } else if (m_isDragging) { // 原有平移画布逻辑... } } // 3. 鼠标释放事件:结束拖动 void mouseReleaseEvent(QMouseEvent *event) override { Q_UNUSED(event); isDraggingShape = false; // 结束拖动 // 原有临时图形保存逻辑... } // 新增:移动图形的核心函数(按类型处理坐标) void moveShape(BaseShape* shape, const QPointF& delta) { if (shape == nullptr) return; switch (shape->type) { case Point: { PointShape* point = dynamic_cast<PointShape*>(shape); point->pos += delta; break; } case Line: { LineShape* line = dynamic_cast<LineShape*>(shape); line->startPos += delta; line->endPos += delta; break; } case Circle: { CircleShape* circle = dynamic_cast<CircleShape*>(shape); circle->center += delta; break; } default: break; } }
步骤 4:绘图时高亮选中的图形

修改drawShape(或分类型的绘图函数),选中的图形用 “红色 + 加粗” 显示:

void drawLine(QPainter* painter, LineShape* line) { QPen pen; if (line->isSelected) { // 选中状态:红色、线宽+2、虚线 pen = QPen(Qt::red, line->penWidth + 2, Qt::DashLine); } else { pen = QPen(line->color, line->penWidth); } painter->setPen(pen); painter->setRenderHint(QPainter::Antialiasing, false); painter->drawLine(line->startPos, line->endPos); } // 圆和点的绘图函数做类似修改...
步骤 5:实现删除功能(按 Delete 键删除选中图形)

CanvasWidget中重写键盘事件:

void keyPressEvent(QKeyEvent *event) override { if (event->key() == Qt::Key_Delete && selectedShape != nullptr) { // 根据类型删除图形(分桶存储的优势) switch (selectedShape->type) { case Point: { PointShape* point = dynamic_cast<PointShape*>(selectedShape); pointShapes.removeOne(point); delete point; break; } case Line: { LineShape* line = dynamic_cast<LineShape*>(selectedShape); lineShapes.removeOne(line); delete line; break; } case Circle: { CircleShape* circle = dynamic_cast<CircleShape*>(selectedShape); circleShapes.removeOne(circle); delete circle; break; } default: break; } selectedShape = nullptr; update(); // 刷新画布 } }

4. 测试验证

编译运行后,测试核心功能:

  • 点击画好的直线 / 圆 / 点,图形会变成红色虚线(选中高亮);
  • 选中后拖动鼠标,图形会跟随移动,且只有图形区域刷新(不卡顿);
  • 选中图形后按 Delete 键,图形被删除;
  • 未选中图形时,仍能正常画点 / 线 / 圆。

三、第二阶段:进阶编辑(缩放、旋转、复制粘贴)

完成基础编辑后,可进一步实现 “图形变换” 功能 —— 这是 CAD 编辑的核心能力,新手重点掌握 “坐标变换” 逻辑,复用 Qt 的QTransform简化计算。

1. 核心目标

  • 选中图形后,鼠标滚轮缩放图形(区别于画布缩放);
  • 选中图形后,按快捷键(如 R)旋转图形;
  • 支持复制(Ctrl+C)、粘贴(Ctrl+V)选中的图形。

2. 关键实操(以图形缩放为例)

// CanvasWidget新增:缩放选中的图形 void scaleSelectedShape(qreal scale) { if (selectedShape == nullptr) return; switch (selectedShape->type) { case Line: { LineShape* line = dynamic_cast<LineShape*>(selectedShape); // 以起点为中心缩放直线 QPointF center = line->startPos; line->endPos = center + (line->endPos - center) * scale; break; } case Circle: { CircleShape* circle = dynamic_cast<CircleShape*>(selectedShape); circle->radius *= scale; // 缩放半径 break; } default: break; } // 局部重绘 update(getShapeBoundingRect(selectedShape).adjusted(-5, -5, 5, 5)); } // 重写滚轮事件,区分“画布缩放”和“图形缩放” void wheelEvent(QWheelEvent *event) override { if (selectedShape != nullptr) { // 有选中图形:缩放图形 qreal delta = event->angleDelta().y() > 0 ? 1.1 : 0.9; scaleSelectedShape(delta); } else { // 无选中图形:缩放画布(原有逻辑) qreal delta = event->angleDelta().y() > 0 ? 1.1 : 0.9; scaleFactor *= delta; scaleFactor = qBound(0.1, scaleFactor, 10.0); // 更新变换矩阵... update(); } }

四、第三阶段:辅助功能(图层、标注、文件读写)

当编辑功能稳定后,添加 “提升实用性” 的辅助功能 —— 这些功能不涉及复杂几何,但能让你的 CAD 框架更接近真实软件。

1. 图层管理(核心是 “分类显示 / 隐藏”)

  • 新增Layer结构体:包含图层名称、是否可见、颜色等属性;
  • 每个图形关联一个图层(修改BaseShape,增加QString layerName属性);
  • 菜单栏添加 “图层管理” 窗口,实现 “显示 / 隐藏图层”“修改图层颜色”;
  • 绘图时只绘制 “可见图层” 的图形。

2. 尺寸标注(CAD 的核心辅助功能)

  • 新增DimensionShape图形类型:存储标注的起点、终点、文字位置、标注值;
  • 实现 “长度标注”(直线长度)、“半径标注”(圆的半径):通过几何计算得到长度 / 半径,绘图时显示文字;
  • 标注跟随图形变化:比如移动直线后,标注值自动更新。

3. 文件读写(保存 / 打开绘图成果)

  • 用 Qt 的QJsonDocument将图形数据(类型、坐标、颜色、图层)保存为 JSON 文件(新手易上手,比 DXF 简单);
  • 打开文件时,读取 JSON 数据,重建图形列表并绘制;
  • 菜单栏添加 “新建 / 打开 / 保存” 功能,关联文件操作函数。

五、新手避坑指南(关键提醒)

  1. 不要同时推进多个功能:比如先做完 “选中 + 移动 + 删除”,再做缩放旋转,最后做图层标注 —— 贪多会导致代码混乱,排查问题困难;
  2. 复用已有代码和优化技巧:比如局部重绘、分桶存储、QTransform等,不要重复造轮子;
  3. 优先实现 “简化版”,再优化精度:比如命中测试先做 “扩大范围” 的简化版,能满足基本操作即可,后续再优化 “精准命中”;
  4. 及时测试和重构:每新增一个功能,都要测试性能(比如 100 个图形时是否卡顿),代码冗余时及时重构(比如提取通用的图形操作函数)。

六、后续进阶方向(当你完成以上所有功能后)

当你掌握了 “2D 绘图 + 编辑 + 辅助功能” 后,可根据兴趣选择进阶方向:

  1. 2D 进阶:添加约束功能(比如平行、垂直、等长约束)、批量绘图(阵列、镜像);
  2. 3D 入门:学习 OpenGL/Qt 3D 模块,实现简单的 3D 建模(拉伸、旋转 2D 图形成 3D 模型);
  3. 兼容性:支持读取 / 保存 DXF 格式(用 dxflib 库),兼容 AutoCAD 文件;
  4. 性能极致优化:引入空间索引(比如 R 树),提升大量图形的命中测试效率。

总结

作为新手,下一步的核心是 “从画图到编辑”—— 先实现图形选中、移动、删除,再逐步添加缩放、旋转、图层、标注等功能。这条路线的核心是 “增量开发、先易后难”,每个阶段都能看到明确的成果,既不会因难度过高放弃,也不会因功能单一失去动力。

记住:CAD 开发是一个 “积少成多” 的过程,哪怕每天只实现一个小功能(比如今天做选中,明天做移动),坚持下来你的框架会越来越完善。如果在开发过程中遇到具体问题(比如旋转图形的坐标计算),可以聚焦单个问题深入研究,不用急于求成

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询