From 9d72fe2155a5e86ed94059a2c9454e9e1fa1677c Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Mon, 16 Feb 2026 17:42:05 -0700 Subject: [PATCH] feat: Add editable angle dimensions to line drawing Co-authored-by: aider (gemini/gemini-2.5-pro) --- src/ViewportWidget.cpp | 473 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 418 insertions(+), 55 deletions(-) diff --git a/src/ViewportWidget.cpp b/src/ViewportWidget.cpp index e3ccd96..8789806 100644 --- a/src/ViewportWidget.cpp +++ b/src/ViewportWidget.cpp @@ -202,28 +202,93 @@ void ViewportWidget::paintGL() QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z()); QString dimInput = property("dimensionInput").toString(); + QString angleInput = property("angleInput").toString(); bool lengthFromInput = false; + bool angleFromInput = false; + double inputLength = 0; + double inputAngleDegrees = 0; + if (!dimInput.isEmpty()) { bool ok; - double inputLength = dimInput.toDouble(&ok); - if (ok) { - lengthFromInput = true; - QVector3D currentMouseWorldPos = unproject(m_currentMousePos, m_currentPlane); - QVector3D dir = (currentMouseWorldPos - startPos); - if (dir.length() > 1e-6) { - dir.normalize(); - worldPos = startPos + inputLength * dir; - } else { - if (m_currentPlane == SketchPlane::XY || m_currentPlane == SketchPlane::XZ) { - worldPos = startPos + QVector3D(inputLength, 0, 0); - } else { - worldPos = startPos + QVector3D(0, inputLength, 0); - } - } - } + inputLength = dimInput.toDouble(&ok); + if (ok) lengthFromInput = true; + } + if (!angleInput.isEmpty()) { + bool ok; + inputAngleDegrees = angleInput.toDouble(&ok); + if (ok) angleFromInput = true; } - if (!lengthFromInput) { + if (angleFromInput) { + QVector3D refDir; + if (property("isChainedLine").toBool()) { + refDir = property("previousLineDirection").value(); + } else { + if (m_currentPlane == SketchPlane::XY || m_currentPlane == SketchPlane::XZ) { + refDir = QVector3D(1, 0, 0); + } else { // YZ + refDir = QVector3D(0, 1, 0); + } + } + + QVector3D currentMouseWorldPos = unproject(m_currentMousePos, m_currentPlane); + QVector3D mouseVec = currentMouseWorldPos - startPos; + + // Quadrant snapping + double mouseAngle; + if (m_currentPlane == SketchPlane::XY) mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.x())); + else if (m_currentPlane == SketchPlane::XZ) mouseAngle = qRadiansToDegrees(atan2(mouseVec.y(), mouseVec.x())); + else mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.y())); + + double refAngle; + if (m_currentPlane == SketchPlane::XY) refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.x())); + else if (m_currentPlane == SketchPlane::XZ) refAngle = qRadiansToDegrees(atan2(refDir.y(), refDir.x())); + else refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.y())); + + double relativeMouseAngle = mouseAngle - refAngle; + while (relativeMouseAngle <= -180.0) relativeMouseAngle += 360.0; + while (relativeMouseAngle > 180.0) relativeMouseAngle -= 360.0; + + double snappedAngle = 0; + if (relativeMouseAngle >= -45 && relativeMouseAngle < 45) { + snappedAngle = inputAngleDegrees; + } else if (relativeMouseAngle >= 45 && relativeMouseAngle < 135) { + snappedAngle = 180 - inputAngleDegrees; + } else if (relativeMouseAngle >= 135 || relativeMouseAngle < -135) { + snappedAngle = inputAngleDegrees + 180.0; + } else { // -135 to -45 + snappedAngle = 360 - inputAngleDegrees; + } + + double finalAngleRad = qDegreesToRadians(refAngle + snappedAngle); + QVector3D finalDir; + if (m_currentPlane == SketchPlane::XY) finalDir = QVector3D(cos(finalAngleRad), 0, sin(finalAngleRad)); + else if (m_currentPlane == SketchPlane::XZ) finalDir = QVector3D(cos(finalAngleRad), sin(finalAngleRad), 0); + else finalDir = QVector3D(0, cos(finalAngleRad), sin(finalAngleRad)); + + double lineLength; + if (lengthFromInput) { + lineLength = inputLength; + } else { + lineLength = QVector3D::dotProduct(mouseVec, finalDir); + if (lineLength < 0) lineLength = 0; + } + worldPos = startPos + lineLength * finalDir; + + } else if (lengthFromInput) { + QVector3D currentMouseWorldPos = unproject(m_currentMousePos, m_currentPlane); + QVector3D dir = (currentMouseWorldPos - startPos); + if (dir.length() > 1e-6) { + dir.normalize(); + worldPos = startPos + inputLength * dir; + } else { + if (m_currentPlane == SketchPlane::XY || m_currentPlane == SketchPlane::XZ) { + worldPos = startPos + QVector3D(inputLength, 0, 0); + } else { + worldPos = startPos + QVector3D(0, inputLength, 0); + } + } + } else { worldPos = unproject(m_currentMousePos, m_currentPlane); if (m_isSnappingOrigin) { worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0); @@ -297,9 +362,101 @@ void ViewportWidget::paintGL() m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(GLfloat)); glDrawArrays(GL_LINES, 0, vertices.size() / 3); glLineWidth(2.0f); + + // Draw angle dimension + vertices.clear(); + QVector3D refDir; + if (property("isChainedLine").toBool()) { + refDir = property("previousLineDirection").value(); + } else { + if (m_currentPlane == SketchPlane::XY || m_currentPlane == SketchPlane::XZ) refDir = QVector3D(1, 0, 0); + else refDir = QVector3D(0, 1, 0); + } + + double refAngle, lineAngle; + if (m_currentPlane == SketchPlane::XY) { + refAngle = atan2(refDir.z(), refDir.x()); + lineAngle = atan2(lineVec.z(), lineVec.x()); + } else if (m_currentPlane == SketchPlane::XZ) { + refAngle = atan2(refDir.y(), refDir.x()); + lineAngle = atan2(lineVec.y(), lineVec.x()); + } else { // YZ + refAngle = atan2(refDir.z(), refDir.y()); + lineAngle = atan2(lineVec.z(), lineVec.y()); + } + + double angleDiff = lineAngle - refAngle; + while (angleDiff <= -M_PI) angleDiff += 2 * M_PI; + while (angleDiff > M_PI) angleDiff -= 2 * M_PI; + lineAngle = refAngle + angleDiff; + + const int numSegments = 30; + const float radius = 0.1f * -m_camera->zoom(); + for (int i = 0; i <= numSegments; ++i) { + double angle = refAngle + (lineAngle - refAngle) * i / numSegments; + QVector3D p; + if (m_currentPlane == SketchPlane::XY) p = startPos + radius * QVector3D(cos(angle), 0, sin(angle)); + else if (m_currentPlane == SketchPlane::XZ) p = startPos + radius * QVector3D(cos(angle), sin(angle), 0); + else p = startPos + radius * QVector3D(0, cos(angle), sin(angle)); + vertices << p.x() << p.y() << p.z(); + } + glLineWidth(1.0f); + m_vbo.bind(); + m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(GLfloat)); + glDrawArrays(GL_LINE_STRIP, 0, vertices.size() / 3); + + // Arrowheads for arc + QVector arrowVertices; + float arrowLength = 0.02f * -m_camera->zoom(); + float arrowWidth = 0.005f * -m_camera->zoom(); + + // End arrowhead + QVector3D endPoint(vertices[vertices.size()-3], vertices[vertices.size()-2], vertices[vertices.size()-1]); + double endAngle = lineAngle; + QVector3D radialDir_end, tangentDir_end; + if (m_currentPlane == SketchPlane::XY) { + radialDir_end = QVector3D(cos(endAngle), 0, sin(endAngle)); + tangentDir_end = QVector3D(-sin(endAngle), 0, cos(endAngle)); + } else if (m_currentPlane == SketchPlane::XZ) { + radialDir_end = QVector3D(cos(endAngle), sin(endAngle), 0); + tangentDir_end = QVector3D(-sin(endAngle), cos(endAngle), 0); + } else { + radialDir_end = QVector3D(0, cos(endAngle), sin(endAngle)); + tangentDir_end = QVector3D(0, -sin(endAngle), cos(endAngle)); + } + QVector3D arrow_base_end = endPoint - arrowLength * tangentDir_end; + QVector3D arrowP1_end = arrow_base_end + arrowWidth * radialDir_end; + QVector3D arrowP2_end = arrow_base_end - arrowWidth * radialDir_end; + arrowVertices << endPoint.x() << endPoint.y() << endPoint.z() << arrowP1_end.x() << arrowP1_end.y() << arrowP1_end.z(); + arrowVertices << endPoint.x() << endPoint.y() << endPoint.z() << arrowP2_end.x() << arrowP2_end.y() << arrowP2_end.z(); + + // Start arrowhead + QVector3D startPoint(vertices[0], vertices[1], vertices[2]); + double startAngle = refAngle; + QVector3D radialDir_start, tangentDir_start; + if (m_currentPlane == SketchPlane::XY) { + radialDir_start = QVector3D(cos(startAngle), 0, sin(startAngle)); + tangentDir_start = QVector3D(-sin(startAngle), 0, cos(startAngle)); + } else if (m_currentPlane == SketchPlane::XZ) { + radialDir_start = QVector3D(cos(startAngle), sin(startAngle), 0); + tangentDir_start = QVector3D(-sin(startAngle), cos(startAngle), 0); + } else { + radialDir_start = QVector3D(0, cos(startAngle), sin(startAngle)); + tangentDir_start = QVector3D(0, -sin(startAngle), cos(startAngle)); + } + QVector3D arrow_base_start = startPoint + arrowLength * tangentDir_start; + QVector3D arrowP1_start = arrow_base_start + arrowWidth * radialDir_start; + QVector3D arrowP2_start = arrow_base_start - arrowWidth * radialDir_start; + arrowVertices << startPoint.x() << startPoint.y() << startPoint.z() << arrowP1_start.x() << arrowP1_start.y() << arrowP1_start.z(); + arrowVertices << startPoint.x() << startPoint.y() << startPoint.z() << arrowP2_start.x() << arrowP2_start.y() << arrowP2_start.z(); + + m_vbo.bind(); + m_vbo.allocate(arrowVertices.constData(), arrowVertices.size() * sizeof(GLfloat)); + glDrawArrays(GL_LINES, 0, arrowVertices.size() / 3); + glLineWidth(2.0f); } - if (!lengthFromInput && (m_isSnappingHorizontal || m_isSnappingVertical)) { + if (!lengthFromInput && !angleFromInput && (m_isSnappingHorizontal || m_isSnappingVertical)) { vertices.clear(); QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z()); QVector3D midPoint = (startPos + worldPos) / 2.0; @@ -356,33 +513,96 @@ void ViewportWidget::paintGL() QVector3D worldPos; QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z()); QString dimText; + QString angleText; QString dimInput = property("dimensionInput").toString(); + QString angleInput = property("angleInput").toString(); bool lengthFromInput = false; + bool angleFromInput = false; + double inputLength = 0; + double inputAngleDegrees = 0; double lineLength = 0; if (!dimInput.isEmpty()) { bool ok; - double inputLength = dimInput.toDouble(&ok); - if (ok) { - lengthFromInput = true; - lineLength = inputLength; - QVector3D currentMouseWorldPos = unproject(m_currentMousePos, m_currentPlane); - QVector3D dir = (currentMouseWorldPos - startPos); - if (dir.length() > 1e-6) { - dir.normalize(); - worldPos = startPos + inputLength * dir; - } else { - if (m_currentPlane == SketchPlane::XY || m_currentPlane == SketchPlane::XZ) { - worldPos = startPos + QVector3D(inputLength, 0, 0); - } else { - worldPos = startPos + QVector3D(0, inputLength, 0); - } - } - } + inputLength = dimInput.toDouble(&ok); + if (ok) lengthFromInput = true; + } + if (!angleInput.isEmpty()) { + bool ok; + inputAngleDegrees = angleInput.toDouble(&ok); + if (ok) angleFromInput = true; } - if (!lengthFromInput) { + if (angleFromInput) { + QVector3D refDir; + if (property("isChainedLine").toBool()) { + refDir = property("previousLineDirection").value(); + } else { + if (m_currentPlane == SketchPlane::XY || m_currentPlane == SketchPlane::XZ) { + refDir = QVector3D(1, 0, 0); + } else { // YZ + refDir = QVector3D(0, 1, 0); + } + } + + QVector3D currentMouseWorldPos = unproject(m_currentMousePos, m_currentPlane); + QVector3D mouseVec = currentMouseWorldPos - startPos; + + double mouseAngle; + if (m_currentPlane == SketchPlane::XY) mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.x())); + else if (m_currentPlane == SketchPlane::XZ) mouseAngle = qRadiansToDegrees(atan2(mouseVec.y(), mouseVec.x())); + else mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.y())); + + double refAngle; + if (m_currentPlane == SketchPlane::XY) refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.x())); + else if (m_currentPlane == SketchPlane::XZ) refAngle = qRadiansToDegrees(atan2(refDir.y(), refDir.x())); + else refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.y())); + + double relativeMouseAngle = mouseAngle - refAngle; + while (relativeMouseAngle <= -180.0) relativeMouseAngle += 360.0; + while (relativeMouseAngle > 180.0) relativeMouseAngle -= 360.0; + + double snappedAngle = 0; + if (relativeMouseAngle >= -45 && relativeMouseAngle < 45) { + snappedAngle = inputAngleDegrees; + } else if (relativeMouseAngle >= 45 && relativeMouseAngle < 135) { + snappedAngle = 180 - inputAngleDegrees; + } else if (relativeMouseAngle >= 135 || relativeMouseAngle < -135) { + snappedAngle = inputAngleDegrees + 180.0; + } else { // -135 to -45 + snappedAngle = 360 - inputAngleDegrees; + } + + double finalAngleRad = qDegreesToRadians(refAngle + snappedAngle); + QVector3D finalDir; + if (m_currentPlane == SketchPlane::XY) finalDir = QVector3D(cos(finalAngleRad), 0, sin(finalAngleRad)); + else if (m_currentPlane == SketchPlane::XZ) finalDir = QVector3D(cos(finalAngleRad), sin(finalAngleRad), 0); + else finalDir = QVector3D(0, cos(finalAngleRad), sin(finalAngleRad)); + + if (lengthFromInput) { + lineLength = inputLength; + } else { + lineLength = QVector3D::dotProduct(mouseVec, finalDir); + if (lineLength < 0) lineLength = 0; + } + worldPos = startPos + lineLength * finalDir; + + } else if (lengthFromInput) { + lineLength = inputLength; + QVector3D currentMouseWorldPos = unproject(m_currentMousePos, m_currentPlane); + QVector3D dir = (currentMouseWorldPos - startPos); + if (dir.length() > 1e-6) { + dir.normalize(); + worldPos = startPos + inputLength * dir; + } else { + if (m_currentPlane == SketchPlane::XY || m_currentPlane == SketchPlane::XZ) { + worldPos = startPos + QVector3D(inputLength, 0, 0); + } else { + worldPos = startPos + QVector3D(0, inputLength, 0); + } + } + } else { worldPos = unproject(m_currentMousePos, m_currentPlane); if (m_isSnappingOrigin) { worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0); @@ -426,7 +646,7 @@ void ViewportWidget::paintGL() QRect textRect = fm.boundingRect(dimText + "_"); textRect.moveCenter(screenPos.toPoint()); - if (lengthFromInput) { + if (lengthFromInput && property("dimensionEditMode").toString() == "length") { painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(64, 128, 255)); } else { painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(50, 50, 50)); @@ -434,6 +654,55 @@ void ViewportWidget::paintGL() painter.setPen(Qt::white); painter.drawText(textRect, Qt::AlignCenter, dimText); } + + // Angle dimension text + QVector3D refDir; + if (property("isChainedLine").toBool()) { + refDir = property("previousLineDirection").value(); + } else { + if (m_currentPlane == SketchPlane::XY || m_currentPlane == SketchPlane::XZ) refDir = QVector3D(1, 0, 0); + else refDir = QVector3D(0, 1, 0); + } + + double refAngle, lineAngle; + if (m_currentPlane == SketchPlane::XY) { + refAngle = atan2(refDir.z(), refDir.x()); + lineAngle = atan2(lineVec.z(), lineVec.x()); + } else if (m_currentPlane == SketchPlane::XZ) { + refAngle = atan2(refDir.y(), refDir.x()); + lineAngle = atan2(lineVec.y(), lineVec.x()); + } else { // YZ + refAngle = atan2(refDir.z(), refDir.y()); + lineAngle = atan2(lineVec.z(), lineVec.y()); + } + + double angleDiffDegrees = qRadiansToDegrees(lineAngle - refAngle); + while (angleDiffDegrees <= -180.0) angleDiffDegrees += 360.0; + while (angleDiffDegrees > 180.0) angleDiffDegrees -= 360.0; + + angleText = angleFromInput ? angleInput : QString::number(qAbs(angleDiffDegrees), 'f', 1) + QChar(0x00B0); + + const float radius = 0.1f * -m_camera->zoom(); + double midAngle = refAngle + (lineAngle - refAngle) / 2.0; + QVector3D textPos3DAngle; + float textOffset = 0.035f * -m_camera->zoom(); + + if (m_currentPlane == SketchPlane::XY) textPos3DAngle = startPos + (radius + textOffset) * QVector3D(cos(midAngle), 0, sin(midAngle)); + else if (m_currentPlane == SketchPlane::XZ) textPos3DAngle = startPos + (radius + textOffset) * QVector3D(cos(midAngle), sin(midAngle), 0); + else textPos3DAngle = startPos + (radius + textOffset) * QVector3D(0, cos(midAngle), sin(midAngle)); + + QVector3D screenPosAngle = project(textPos3DAngle, model, projection, rect()); + if (screenPosAngle.z() < 1.0f) { + QRect angleTextRect = fm.boundingRect(angleText + "_"); + angleTextRect.moveCenter(screenPosAngle.toPoint()); + if (angleFromInput && property("dimensionEditMode").toString() == "angle") { + painter.fillRect(angleTextRect.adjusted(-4, -2, 4, 2), QColor(64, 128, 255)); + } else { + painter.fillRect(angleTextRect.adjusted(-4, -2, 4, 2), QColor(50, 50, 50)); + } + painter.setPen(Qt::white); + painter.drawText(angleTextRect, Qt::AlignCenter, angleText); + } } } @@ -483,10 +752,20 @@ void ViewportWidget::mousePressEvent(QMouseEvent *event) m_firstLinePoint = p; m_isDefiningLine = true; setProperty("dimensionInput", QVariant("")); + setProperty("angleInput", QVariant("")); + setProperty("dimensionEditMode", "length"); + setProperty("isChainedLine", false); } else { + QVector3D start(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z()); + QVector3D end(p.X(), p.Y(), p.Z()); + setProperty("previousLineDirection", QVariant::fromValue((end - start).normalized())); + emit lineAdded(m_firstLinePoint, p); m_firstLinePoint = p; setProperty("dimensionInput", QVariant("")); + setProperty("angleInput", QVariant("")); + setProperty("dimensionEditMode", "length"); + setProperty("isChainedLine", true); } update(); } @@ -629,45 +908,128 @@ void ViewportWidget::wheelEvent(QWheelEvent *event) void ViewportWidget::keyPressEvent(QKeyEvent *event) { if (m_isDefiningLine && m_activeTool == static_cast(ApplicationController::ToolType::Line)) { - QString currentInput = property("dimensionInput").toString(); + if (event->key() == Qt::Key_Tab) { + QString currentMode = property("dimensionEditMode").toString(); + if (currentMode == "length") { + setProperty("dimensionEditMode", "angle"); + } else { + setProperty("dimensionEditMode", "length"); + } + update(); + return; + } + + QString editMode = property("dimensionEditMode").toString(); + const char* propertyName = (editMode == "length") ? "dimensionInput" : "angleInput"; + QString currentInput = property(propertyName).toString(); + if (event->key() >= Qt::Key_0 && event->key() <= Qt::Key_9) { currentInput += event->text(); - setProperty("dimensionInput", currentInput); + setProperty(propertyName, currentInput); update(); return; } else if (event->key() == Qt::Key_Period) { if (!currentInput.contains('.')) { currentInput += '.'; - setProperty("dimensionInput", currentInput); + setProperty(propertyName, currentInput); update(); return; } } else if (event->key() == Qt::Key_Backspace) { if (!currentInput.isEmpty()) { currentInput.chop(1); - setProperty("dimensionInput", currentInput); + setProperty(propertyName, currentInput); update(); return; } } else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { - bool ok; - double length = currentInput.toDouble(&ok); - if (ok && length > 0) { - QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane); - QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z()); - QVector3D dir = (worldPos - startPos); + QVector3D worldPos; + QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z()); + // This is duplicated from paintGL to ensure consistent line creation + QString dimInput = property("dimensionInput").toString(); + QString angleInput = property("angleInput").toString(); + bool lengthFromInput = false; + bool angleFromInput = false; + double inputLength = 0; + double inputAngleDegrees = 0; + + if (!dimInput.isEmpty()) { + bool ok; + inputLength = dimInput.toDouble(&ok); + if (ok) lengthFromInput = true; + } + if (!angleInput.isEmpty()) { + bool ok; + inputAngleDegrees = angleInput.toDouble(&ok); + if (ok) angleFromInput = true; + } + + if (angleFromInput) { + QVector3D refDir; + if (property("isChainedLine").toBool()) { + refDir = property("previousLineDirection").value(); + } else { + if (m_currentPlane == SketchPlane::XY || m_currentPlane == SketchPlane::XZ) refDir = QVector3D(1, 0, 0); + else refDir = QVector3D(0, 1, 0); + } + QVector3D currentMouseWorldPos = unproject(m_currentMousePos, m_currentPlane); + QVector3D mouseVec = currentMouseWorldPos - startPos; + double mouseAngle; + if (m_currentPlane == SketchPlane::XY) mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.x())); + else if (m_currentPlane == SketchPlane::XZ) mouseAngle = qRadiansToDegrees(atan2(mouseVec.y(), mouseVec.x())); + else mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.y())); + double refAngle; + if (m_currentPlane == SketchPlane::XY) refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.x())); + else if (m_currentPlane == SketchPlane::XZ) refAngle = qRadiansToDegrees(atan2(refDir.y(), refDir.x())); + else refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.y())); + double relativeMouseAngle = mouseAngle - refAngle; + while (relativeMouseAngle <= -180.0) relativeMouseAngle += 360.0; + while (relativeMouseAngle > 180.0) relativeMouseAngle -= 360.0; + double snappedAngle = 0; + if (relativeMouseAngle >= -45 && relativeMouseAngle < 45) snappedAngle = inputAngleDegrees; + else if (relativeMouseAngle >= 45 && relativeMouseAngle < 135) snappedAngle = 180 - inputAngleDegrees; + else if (relativeMouseAngle >= 135 || relativeMouseAngle < -135) snappedAngle = inputAngleDegrees + 180.0; + else snappedAngle = 360 - inputAngleDegrees; + double finalAngleRad = qDegreesToRadians(refAngle + snappedAngle); + QVector3D finalDir; + if (m_currentPlane == SketchPlane::XY) finalDir = QVector3D(cos(finalAngleRad), 0, sin(finalAngleRad)); + else if (m_currentPlane == SketchPlane::XZ) finalDir = QVector3D(cos(finalAngleRad), sin(finalAngleRad), 0); + else finalDir = QVector3D(0, cos(finalAngleRad), sin(finalAngleRad)); + double lineLength; + if (lengthFromInput) lineLength = inputLength; + else { + lineLength = QVector3D::dotProduct(mouseVec, finalDir); + if (lineLength < 0) lineLength = 0; + } + worldPos = startPos + lineLength * finalDir; + } else if (lengthFromInput) { + QVector3D currentMouseWorldPos = unproject(m_currentMousePos, m_currentPlane); + QVector3D dir = (currentMouseWorldPos - startPos); if (dir.length() > 1e-6) { dir.normalize(); - QVector3D endPos = startPos + length * dir; - gp_Pnt p; - p.SetCoord(endPos.x(), endPos.y(), endPos.z()); - emit lineAdded(m_firstLinePoint, p); - m_firstLinePoint = p; // for chained lines - setProperty("dimensionInput", QVariant("")); - update(); - return; + worldPos = startPos + inputLength * dir; + } else { + if (m_currentPlane == SketchPlane::XY || m_currentPlane == SketchPlane::XZ) worldPos = startPos + QVector3D(inputLength, 0, 0); + else worldPos = startPos + QVector3D(0, inputLength, 0); } + } else { + worldPos = unproject(m_currentMousePos, m_currentPlane); } + + gp_Pnt p; + p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z()); + + QVector3D prevDir = (worldPos - startPos).normalized(); + setProperty("previousLineDirection", QVariant::fromValue(prevDir)); + + emit lineAdded(m_firstLinePoint, p); + m_firstLinePoint = p; + setProperty("dimensionInput", QVariant("")); + setProperty("angleInput", QVariant("")); + setProperty("dimensionEditMode", "length"); + setProperty("isChainedLine", true); + update(); + return; } } @@ -675,6 +1037,7 @@ void ViewportWidget::keyPressEvent(QKeyEvent *event) if (m_isDefiningLine) { m_isDefiningLine = false; setProperty("dimensionInput", QVariant("")); + setProperty("angleInput", QVariant("")); emit toolDeactivated(); update(); return;