#include "ViewportWidget.h" #include "Snapping.h" #include "Camera.h" #include "ViewCube.h" #include "SketchGrid.h" #include "Document.h" #include "FeatureBrowser.h" #include "SketchFeature.h" #include "SketchLine.h" #include "SketchRectangle.h" #include "SketchCircle.h" #include "SketchObject.h" #include "ApplicationController.h" #include "RectangleTool.h" #include "LineTool.h" #include "CircleTool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct PntComparator { bool operator()(const gp_Pnt& a, const gp_Pnt& b) const { // A tolerance is needed for floating point comparisons double tol = 1e-9; if (std::abs(a.X() - b.X()) > tol) return a.X() < b.X(); if (std::abs(a.Y() - b.Y()) > tol) return a.Y() < b.Y(); if (std::abs(a.Z() - b.Z()) > tol) return a.Z() < b.Z(); return false; // They are considered equal } }; ViewportWidget::ViewportWidget(QWidget *parent) : QOpenGLWidget(parent) { m_camera = new Camera(this); connect(m_camera, &Camera::cameraChanged, this, QOverload<>::of(&QWidget::update)); connect(m_camera, &Camera::restoreStateAnimationFinished, this, &ViewportWidget::onRestoreStateAnimationFinished); m_viewCube = new ViewCube(); m_sketchGrid = new SketchGrid(this); m_featureBrowser = new FeatureBrowser(); setMouseTracking(true); setFocusPolicy(Qt::StrongFocus); m_currentPlane = SketchPlane::XY; m_toolIcons.insert(static_cast(ApplicationController::ToolType::Line), new QSvgRenderer(QString(":/icons/line.svg"), this)); m_toolIcons.insert(static_cast(ApplicationController::ToolType::Rectangle), new QSvgRenderer(QString(":/icons/rectangle.svg"), this)); m_toolIcons.insert(static_cast(ApplicationController::ToolType::Circle), new QSvgRenderer(QString(":/icons/circle.svg"), this)); m_cursorRenderer = new QSvgRenderer(QString(":/icons/cursor.svg"), this); m_sketchTools.insert(static_cast(ApplicationController::ToolType::Line), new LineTool(this)); m_sketchTools.insert(static_cast(ApplicationController::ToolType::Rectangle), new RectangleTool(this)); m_sketchTools.insert(static_cast(ApplicationController::ToolType::Circle), new CircleTool(this)); m_snapping = new Snapping(this); } ViewportWidget::~ViewportWidget() { makeCurrent(); delete m_shaderProgram; delete m_viewCube; delete m_sketchGrid; m_vbo.destroy(); m_vao.destroy(); doneCurrent(); delete m_featureBrowser; delete m_snapping; } void ViewportWidget::setDocument(Document* document) { m_document = document; m_featureBrowser->setDocument(document); } void ViewportWidget::initializeGL() { initializeOpenGLFunctions(); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); initShaders(); m_vao.create(); QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao); m_vbo.create(); m_vbo.bind(); m_vbo.allocate(nullptr, 0); // Allocate when drawing // Position attribute m_shaderProgram->enableAttributeArray(0); m_shaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 3, 0); m_vbo.release(); glEnable(GL_DEPTH_TEST); m_viewCube->initializeGL(); m_sketchGrid->initializeGL(); } void ViewportWidget::paintGL() { const qreal retinaScale = devicePixelRatio(); // Main scene rendering glViewport(0, 0, width() * retinaScale, height() * retinaScale); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (!m_shaderProgram || !m_shaderProgram->isLinked()) { return; } glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); QMatrix4x4 model = m_camera->modelViewMatrix(); m_shaderProgram->bind(); m_shaderProgram->setUniformValue(m_projMatrixLoc, projection); m_shaderProgram->setUniformValue(m_mvMatrixLoc, model); QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao); // Sketch grid rendering if (m_isSelectingPlane) { if (m_highlightedPlane != SketchPlane::NONE) { m_sketchGrid->paintGL(static_cast(m_highlightedPlane), m_shaderProgram, m_colorLoc); } } if (m_currentPlane != SketchPlane::NONE) { m_sketchGrid->paintGL(static_cast(m_currentPlane), m_shaderProgram, m_colorLoc); } if (m_isSelectingPlane) { drawSelectionPlanes(); } if (m_document) { for (Feature* feature : m_document->features()) { if (auto sketch = dynamic_cast(feature)) { drawSketch(sketch); } } } m_snapping->paintGL(); if (m_camera->isRotating()) { const float radius = 0.004f * -m_camera->uiCameraDistance(); const int numSegments = 16; QMatrix4x4 invModelView = m_camera->modelViewMatrix().inverted(); QVector3D rightVec = invModelView.column(0).toVector3D(); QVector3D upVec = invModelView.column(1).toVector3D(); QVector circleFillVertices; QVector3D center = m_camera->rotationPivot(); circleFillVertices << center.x() << center.y() << center.z(); // Center vertex for fan for (int i = 0; i <= numSegments; ++i) { // <= to close the circle float angle = (2.0f * M_PI * float(i)) / float(numSegments); QVector3D p = center + radius * (cos(angle) * rightVec + sin(angle) * upVec); circleFillVertices << p.x() << p.y() << p.z(); } m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(0.0f, 0.0f, 1.0f, 1.0f)); // Blue m_vbo.bind(); m_vbo.allocate(circleFillVertices.constData(), circleFillVertices.size() * sizeof(GLfloat)); glDrawArrays(GL_TRIANGLE_FAN, 0, circleFillVertices.size() / 3); } if (m_activeSketchTool) { m_activeSketchTool->paintGL(); } m_shaderProgram->release(); // View cube rendering QMatrix4x4 viewCubeModel; viewCubeModel.rotate(m_camera->xRotation() / 16.0f, 1, 0, 0); viewCubeModel.rotate(m_camera->yRotation() / 16.0f, 0, 1, 0); m_viewCube->paintGL(m_shaderProgram, m_colorLoc, viewCubeModel, width() * retinaScale, height() * retinaScale, m_currentMousePos); glViewport(0, 0, width() * retinaScale, height() * retinaScale); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); QPainter painter(this); if (m_currentPlane != SketchPlane::NONE) { m_sketchGrid->paintAxisLabels(painter, static_cast(m_currentPlane), model, projection); } m_featureBrowser->paint(painter, width(), height()); m_viewCube->paint2D(painter, width(), height()); if (m_activeSketchTool) { m_activeSketchTool->paint2D(painter, model, projection); } painter.setPen(Qt::white); painter.drawText(width() - 350, height() - 10, QString("Camera Distance: %1").arg(-m_camera->uiCameraDistance())); painter.end(); } void ViewportWidget::resizeGL(int w, int h) { projection.setToIdentity(); projection.perspective(45.0f, w / float(h), 0.01f, 10000.0f); } void ViewportWidget::mousePressEvent(QMouseEvent *event) { m_camera->mousePressEvent(event); if (event->button() == Qt::MiddleButton && !(QApplication::keyboardModifiers() & Qt::ShiftModifier)) { m_camera->startRotation(unproject(event->pos(), m_currentPlane)); update(); } if (event->button() == Qt::LeftButton) { if (m_viewCube->handleMousePress(event->pos(), width(), height())) { m_camera->animateToHomeView(); update(); return; } if (m_isSelectingPlane) { if (m_highlightedPlane != SketchPlane::NONE) { emit planeSelected(m_highlightedPlane); m_isSelectingPlane = false; m_highlightedPlane = SketchPlane::NONE; update(); } return; } if (m_activeSketchTool) { m_activeSketchTool->mousePressEvent(event); update(); return; } } } void ViewportWidget::mouseMoveEvent(QMouseEvent *event) { m_currentMousePos = event->pos(); if (m_isSelectingPlane) { SketchPlane newHighlight = checkPlaneSelection(m_currentMousePos); if (newHighlight != m_highlightedPlane) { m_highlightedPlane = newHighlight; update(); } } if (m_snapping->update(m_currentMousePos)) { update(); } if (m_activeSketchTool) { m_activeSketchTool->mouseMoveEvent(event); } m_camera->mouseMoveEvent(event, height()); update(); } void ViewportWidget::wheelEvent(QWheelEvent *event) { QVector3D worldPos = unproject(event->position().toPoint(), m_currentPlane); m_camera->wheelEvent(event, worldPos); } void ViewportWidget::keyPressEvent(QKeyEvent *event) { if (m_activeSketchTool) { m_activeSketchTool->keyPressEvent(event); return; } if (event->key() == Qt::Key_Escape) { if (m_isSelectingPlane) { m_isSelectingPlane = false; m_highlightedPlane = SketchPlane::NONE; m_currentPlane = SketchPlane::XY; update(); return; } else if (m_activeTool) { emit toolDeactivated(); update(); return; } } QOpenGLWidget::keyPressEvent(event); } void ViewportWidget::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::MiddleButton) { m_camera->stopRotation(); update(); } QOpenGLWidget::mouseReleaseEvent(event); } void ViewportWidget::addLine(const gp_Pnt& start, const gp_Pnt& end) { emit lineAdded(start, end); } void ViewportWidget::deactivateActiveTool() { emit toolDeactivated(); } bool ViewportWidget::isSnappingOrigin() const { return m_snapping->isSnappingOrigin(); } bool ViewportWidget::isSnappingVertex() const { return m_snapping->isSnappingVertex(); } const gp_Pnt& ViewportWidget::snapVertex() const { return m_snapping->snapVertex(); } void ViewportWidget::setSnappingHorizontal(bool snapping) { m_isSnappingHorizontal = snapping; } void ViewportWidget::setSnappingVertical(bool snapping) { m_isSnappingVertical = snapping; } bool ViewportWidget::focusNextPrevChild(bool next) { if (m_activeTool != static_cast(ApplicationController::ToolType::None)) { return false; } return QOpenGLWidget::focusNextPrevChild(next); } void ViewportWidget::onSketchModeStarted(SketchPlane plane) { m_currentPlane = plane; m_camera->saveState(); m_camera->animateToPlaneView(static_cast(plane)); } void ViewportWidget::onPlaneSelectionModeStarted() { m_isSelectingPlane = true; m_highlightedPlane = SketchPlane::NONE; m_currentPlane = SketchPlane::NONE; update(); } void ViewportWidget::onSketchModeEnded() { m_camera->animateRestoreState(); } void ViewportWidget::onRestoreStateAnimationFinished() { // Return to showing the base XY grid when not in a sketch m_currentPlane = SketchPlane::XY; update(); } QVector3D ViewportWidget::project(const QVector3D& worldCoord, const QMatrix4x4& modelView, const QMatrix4x4& projection, const QRect& viewport) { QVector4D clipCoord = projection * modelView * QVector4D(worldCoord, 1.0); if (qFuzzyCompare(clipCoord.w(), 0.0f)) { return QVector3D(-1, -1, -1); } QVector3D ndc(clipCoord.x() / clipCoord.w(), clipCoord.y() / clipCoord.w(), clipCoord.z() / clipCoord.w()); float winX = viewport.x() + viewport.width() * (ndc.x() + 1.0) / 2.0; float winY = viewport.y() + viewport.height() * (1.0 - (ndc.y() + 1.0) / 2.0); // Y is inverted return QVector3D(winX, winY, (ndc.z() + 1.0) / 2.0); } void ViewportWidget::onActiveToolChanged(int tool) { if (m_activeSketchTool) { m_activeSketchTool->deactivate(); m_activeSketchTool = nullptr; } m_activeTool = tool; if (m_sketchTools.contains(tool)) { m_activeSketchTool = m_sketchTools.value(tool); m_activeSketchTool->activate(); } if (tool == static_cast(ApplicationController::ToolType::None)) { unsetCursor(); } else { if (m_toolIcons.contains(tool)) { QSvgRenderer* renderer = m_toolIcons.value(tool); if (renderer && renderer->isValid()) { const QSize cursorSize(48, 48); QPixmap cursorPixmap(cursorSize); cursorPixmap.fill(Qt::transparent); QPainter painter(&cursorPixmap); painter.setRenderHint(QPainter::Antialiasing); // Render arrow cursor from SVG if (m_cursorRenderer && m_cursorRenderer->isValid()) { m_cursorRenderer->render(&painter, QRectF(0, 0, 14, 22)); } // Render tool icon const QSize iconSize(32, 32); QPixmap iconPixmap(iconSize); iconPixmap.fill(Qt::transparent); QPainter iconPainter(&iconPixmap); renderer->render(&iconPainter); iconPainter.end(); // Invert and draw icon onto cursor pixmap QImage iconImage = iconPixmap.toImage(); iconImage.invertPixels(QImage::InvertRgb); painter.drawImage(QRect(12, 12, iconSize.width(), iconSize.height()), iconImage); painter.end(); setCursor(QCursor(cursorPixmap, 0, 0)); } } else { unsetCursor(); } } } QVector3D ViewportWidget::unproject(const QPoint& screenPos, SketchPlane plane) { QMatrix4x4 model = m_camera->modelViewMatrix(); bool invertible; QMatrix4x4 inv = (projection * model).inverted(&invertible); if (!invertible) { return QVector3D(); } float ndcX = (2.0f * screenPos.x()) / width() - 1.0f; float ndcY = 1.0f - (2.0f * screenPos.y()) / height(); QVector4D nearPoint_ndc(ndcX, ndcY, -1.0f, 1.0f); QVector4D farPoint_ndc(ndcX, ndcY, 1.0f, 1.0f); QVector4D nearPoint_world = inv * nearPoint_ndc; QVector4D farPoint_world = inv * farPoint_ndc; if (qFuzzyCompare(nearPoint_world.w(), 0.0f) || qFuzzyCompare(farPoint_world.w(), 0.0f)) { return QVector3D(); } nearPoint_world /= nearPoint_world.w(); farPoint_world /= farPoint_world.w(); QVector3D rayOrigin(nearPoint_world); QVector3D rayDir = (QVector3D(farPoint_world) - rayOrigin).normalized(); QVector3D planeNormal; switch (plane) { case SketchPlane::XY: planeNormal = QVector3D(0, 0, 1); break; case SketchPlane::XZ: planeNormal = QVector3D(0, 1, 0); break; case SketchPlane::YZ: planeNormal = QVector3D(1, 0, 0); break; case SketchPlane::NONE: return QVector3D(); } float denom = QVector3D::dotProduct(planeNormal, rayDir); if (qAbs(denom) > 1e-6) { QVector3D p0(0,0,0); float t = QVector3D::dotProduct(p0 - rayOrigin, planeNormal) / denom; return rayOrigin + t * rayDir; } return QVector3D(); } void ViewportWidget::drawSketch(const SketchFeature* sketch) { glDisable(GL_DEPTH_TEST); glLineWidth(2.0f); QVector lineVertices; std::map vertexCounts; for (const auto& obj : sketch->objects()) { if (obj->type() == SketchObject::ObjectType::Line) { auto line = static_cast(obj); const auto& start = line->startPoint(); const auto& end = line->endPoint(); lineVertices << start.X() << start.Y() << start.Z(); lineVertices << end.X() << end.Y() << end.Z(); vertexCounts[start]++; vertexCounts[end]++; } else if (obj->type() == SketchObject::ObjectType::Circle) { auto circle = static_cast(obj); const auto& center = circle->center(); double radius = circle->radius(); QVector3D centerPos(center.X(), center.Y(), center.Z()); const int numSegments = 64; QVector3D u_axis, v_axis; switch (sketch->plane()) { case SketchFeature::SketchPlane::XY: // Top u_axis = QVector3D(1, 0, 0); v_axis = QVector3D(0, 1, 0); break; case SketchFeature::SketchPlane::XZ: // Front u_axis = QVector3D(1, 0, 0); v_axis = QVector3D(0, 0, 1); break; case SketchFeature::SketchPlane::YZ: // Right u_axis = QVector3D(0, 1, 0); v_axis = QVector3D(0, 0, 1); break; } for (int i = 0; i < numSegments; ++i) { double angle1 = 2.0 * M_PI * double(i) / double(numSegments); QVector3D p1 = centerPos + radius * (cos(angle1) * u_axis + sin(angle1) * v_axis); double angle2 = 2.0 * M_PI * double(i + 1) / double(numSegments); QVector3D p2 = centerPos + radius * (cos(angle2) * u_axis + sin(angle2) * v_axis); lineVertices << p1.x() << p1.y() << p1.z(); lineVertices << p2.x() << p2.y() << p2.z(); } } else if (obj->type() == SketchObject::ObjectType::Rectangle) { auto rect = static_cast(obj); const auto& p1 = rect->corner1(); const auto& p3 = rect->corner2(); gp_Pnt p2, p4; if (sketch->plane() == SketchFeature::SketchPlane::XY) { p2.SetCoord(p3.X(), p1.Y(), p1.Z()); p4.SetCoord(p1.X(), p3.Y(), p1.Z()); } else if (sketch->plane() == SketchFeature::SketchPlane::XZ) { p2.SetCoord(p3.X(), p1.Y(), p1.Z()); p4.SetCoord(p1.X(), p1.Y(), p3.Z()); } else if (sketch->plane() == SketchFeature::SketchPlane::YZ) { p2.SetCoord(p1.X(), p3.Y(), p1.Z()); p4.SetCoord(p1.X(), p1.Y(), p3.Z()); } lineVertices << p1.X() << p1.Y() << p1.Z(); lineVertices << p2.X() << p2.Y() << p2.Z(); lineVertices << p2.X() << p2.Y() << p2.Z(); lineVertices << p3.X() << p3.Y() << p3.Z(); lineVertices << p3.X() << p3.Y() << p3.Z(); lineVertices << p4.X() << p4.Y() << p4.Z(); lineVertices << p4.X() << p4.Y() << p4.Z(); lineVertices << p1.X() << p1.Y() << p1.Z(); vertexCounts[p1] += 2; vertexCounts[p2] += 2; vertexCounts[p3] += 2; vertexCounts[p4] += 2; } } m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 1.0f, 1.0f)); m_vbo.bind(); if (!lineVertices.isEmpty()) { m_vbo.allocate(lineVertices.constData(), lineVertices.size() * sizeof(GLfloat)); glDrawArrays(GL_LINES, 0, lineVertices.size() / 3); } QVector unattachedVertices; for (const auto& pair : vertexCounts) { if (pair.second == 1) { unattachedVertices.append(pair.first); } } if (!unattachedVertices.isEmpty()) { const float radius = 0.004f * -m_camera->uiCameraDistance(); const int numSegments = 16; QMatrix4x4 invModelView = m_camera->modelViewMatrix().inverted(); QVector3D rightVec = invModelView.column(0).toVector3D(); QVector3D upVec = invModelView.column(1).toVector3D(); QVector circleFillVertices; QVector circleOutlineVertices; for (const auto& centerPnt : unattachedVertices) { QVector3D center(centerPnt.X(), centerPnt.Y(), centerPnt.Z()); circleFillVertices << center.x() << center.y() << center.z(); // Center vertex for fan QVector3D firstCircumferencePoint; for (int i = 0; i < numSegments; ++i) { float angle = (2.0f * M_PI * float(i)) / float(numSegments); QVector3D p = center + radius * (cos(angle) * rightVec + sin(angle) * upVec); if (i == 0) { firstCircumferencePoint = p; } circleFillVertices << p.x() << p.y() << p.z(); circleOutlineVertices << p.x() << p.y() << p.z(); } circleFillVertices << firstCircumferencePoint.x() << firstCircumferencePoint.y() << firstCircumferencePoint.z(); } // Draw fills m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(0.2f, 0.3f, 0.3f, 1.0f)); m_vbo.allocate(circleFillVertices.constData(), circleFillVertices.size() * sizeof(GLfloat)); const int vertsPerFan = numSegments + 2; for (size_t i = 0; i < unattachedVertices.size(); ++i) { glDrawArrays(GL_TRIANGLE_FAN, i * vertsPerFan, vertsPerFan); } // Draw outlines m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 1.0f, 1.0f)); m_vbo.allocate(circleOutlineVertices.constData(), circleOutlineVertices.size() * sizeof(GLfloat)); const int vertsPerLoop = numSegments; for (size_t i = 0; i < unattachedVertices.size(); ++i) { glDrawArrays(GL_LINE_LOOP, i * vertsPerLoop, vertsPerLoop); } } glEnable(GL_DEPTH_TEST); } void ViewportWidget::initShaders() { m_shaderProgram = new QOpenGLShaderProgram(this); if (!m_shaderProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/simple.vert")) { qCritical() << "Vertex shader compilation failed:" << m_shaderProgram->log(); return; } if (!m_shaderProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/simple.frag")) { qCritical() << "Fragment shader compilation failed:" << m_shaderProgram->log(); return; } if (!m_shaderProgram->link()) { qCritical() << "Shader program linking failed:" << m_shaderProgram->log(); return; } m_projMatrixLoc = m_shaderProgram->uniformLocation("projectionMatrix"); m_mvMatrixLoc = m_shaderProgram->uniformLocation("modelViewMatrix"); m_colorLoc = m_shaderProgram->uniformLocation("objectColor"); } void ViewportWidget::drawSelectionPlanes() { const float planeSize = 5.0f; const float planeOffset = 1.0f; glDisable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glLineWidth(2.0f); glDisable(GL_DEPTH_TEST); m_vbo.bind(); QVector vertices; auto drawPlane = [&](const QVector& quadVerts, bool highlighted) { // Draw fill if (highlighted) { m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 0.5f)); m_vbo.allocate(quadVerts.constData(), quadVerts.size() * sizeof(GLfloat)); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } // Draw outline m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 1.0f)); m_vbo.allocate(quadVerts.constData(), quadVerts.size() * sizeof(GLfloat)); glDrawArrays(GL_LINE_LOOP, 0, 4); }; // XY Plane (Top) QVector xyQuad = { planeOffset, planeOffset, 0, planeOffset + planeSize, planeOffset, 0, planeOffset + planeSize, planeOffset + planeSize, 0, planeOffset, planeOffset + planeSize, 0 }; drawPlane(xyQuad, m_highlightedPlane == SketchPlane::XY); // XZ Plane (Front) QVector xzQuad = { planeOffset, 0, planeOffset, planeOffset + planeSize, 0, planeOffset, planeOffset + planeSize, 0, planeOffset + planeSize, planeOffset, 0, planeOffset + planeSize }; drawPlane(xzQuad, m_highlightedPlane == SketchPlane::XZ); // YZ Plane (Right) QVector yzQuad = { 0, planeOffset, planeOffset, 0, planeOffset + planeSize, planeOffset, 0, planeOffset + planeSize, planeOffset + planeSize, 0, planeOffset, planeOffset + planeSize }; drawPlane(yzQuad, m_highlightedPlane == SketchPlane::YZ); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); } ViewportWidget::SketchPlane ViewportWidget::checkPlaneSelection(const QPoint& screenPos) { const float planeSize = 5.0f; const float planeOffset = 1.0f; QVector3D intersection; // Check front to back to handle overlaps // YZ plane (Right) intersection = unproject(screenPos, SketchPlane::YZ); if (intersection.y() >= planeOffset && intersection.y() <= planeOffset + planeSize && intersection.z() >= planeOffset && intersection.z() <= planeOffset + planeSize) { return SketchPlane::YZ; } // XZ plane (Front) intersection = unproject(screenPos, SketchPlane::XZ); if (intersection.x() >= planeOffset && intersection.x() <= planeOffset + planeSize && intersection.z() >= planeOffset && intersection.z() <= planeOffset + planeSize) { return SketchPlane::XZ; } // XY plane (Top) intersection = unproject(screenPos, SketchPlane::XY); if (intersection.x() >= planeOffset && intersection.x() <= planeOffset + planeSize && intersection.y() >= planeOffset && intersection.y() <= planeOffset + planeSize) { return SketchPlane::XY; } return SketchPlane::NONE; }