From b144efbe050f8ce546c14a8448a4da24f77bff0a Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Tue, 17 Feb 2026 09:57:36 -0700 Subject: [PATCH] feat: Allow editable rectangle dimensions and fix Tab crash Co-authored-by: aider (gemini/gemini-2.5-pro) --- src/ViewportWidget.cpp | 324 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 300 insertions(+), 24 deletions(-) diff --git a/src/ViewportWidget.cpp b/src/ViewportWidget.cpp index 311391c..26a470d 100644 --- a/src/ViewportWidget.cpp +++ b/src/ViewportWidget.cpp @@ -199,13 +199,64 @@ void ViewportWidget::paintGL() if (m_isDefiningRectangle && m_activeTool == static_cast(ApplicationController::ToolType::Rectangle)) { vertices.clear(); - QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane); + QVector3D worldPos; QVector3D startPos(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z()); - if (m_isSnappingOrigin) { - worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0); - } else if (m_isSnappingVertex) { - worldPos.setX(m_snapVertex.X()); worldPos.setY(m_snapVertex.Y()); worldPos.setZ(m_snapVertex.Z()); + QString widthInput = property("widthInput").toString(); + QString heightInput = property("heightInput").toString(); + bool widthFromInput = false; + bool heightFromInput = false; + double inputWidth = 0, inputHeight = 0; + + if (!widthInput.isEmpty()) { + bool ok; + inputWidth = widthInput.toDouble(&ok); + if (ok) widthFromInput = true; + } + if (!heightInput.isEmpty()) { + bool ok; + inputHeight = heightInput.toDouble(&ok); + if (ok) heightFromInput = true; + } + + QVector3D mousePos = unproject(m_currentMousePos, m_currentPlane); + + if (widthFromInput || heightFromInput) { + QVector3D mouseDir = mousePos - startPos; + double current_w, current_h; + if (m_currentPlane == SketchPlane::XY) { + current_w = qAbs(mouseDir.x()); + current_h = qAbs(mouseDir.z()); + } else if (m_currentPlane == SketchPlane::XZ) { + current_w = qAbs(mouseDir.x()); + current_h = qAbs(mouseDir.y()); + } else { // YZ + current_w = qAbs(mouseDir.y()); + current_h = qAbs(mouseDir.z()); + } + double rect_w = widthFromInput ? inputWidth : current_w; + double rect_h = heightFromInput ? inputHeight : current_h; + int signX = (mouseDir.x() >= 0) ? 1 : -1; + int signY = (mouseDir.y() >= 0) ? 1 : -1; + int signZ = (mouseDir.z() >= 0) ? 1 : -1; + worldPos = startPos; + if (m_currentPlane == SketchPlane::XY) { + worldPos.setX(startPos.x() + signX * rect_w); + worldPos.setZ(startPos.z() + signZ * rect_h); + } else if (m_currentPlane == SketchPlane::XZ) { + worldPos.setX(startPos.x() + signX * rect_w); + worldPos.setY(startPos.y() + signY * rect_h); + } else { // YZ + worldPos.setY(startPos.y() + signY * rect_w); + worldPos.setZ(startPos.z() + signZ * rect_h); + } + } else { + worldPos = mousePos; + if (m_isSnappingOrigin) { + worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0); + } else if (m_isSnappingVertex) { + worldPos.setX(m_snapVertex.X()); worldPos.setY(m_snapVertex.Y()); worldPos.setZ(m_snapVertex.Z()); + } } QVector3D p1 = startPos; @@ -587,14 +638,65 @@ void ViewportWidget::paintGL() m_featureBrowser->paint(painter, width(), height()); if (m_isDefiningRectangle && m_activeTool == static_cast(ApplicationController::ToolType::Rectangle)) { - QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane); - if (m_isSnappingOrigin) { - worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0); - } else if (m_isSnappingVertex) { - worldPos.setX(m_snapVertex.X()); worldPos.setY(m_snapVertex.Y()); worldPos.setZ(m_snapVertex.Z()); + QVector3D worldPos; + QVector3D p1_3d(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z()); + + QString widthInput = property("widthInput").toString(); + QString heightInput = property("heightInput").toString(); + bool widthFromInput = false; + bool heightFromInput = false; + double inputWidth = 0, inputHeight = 0; + + if (!widthInput.isEmpty()) { + bool ok; + inputWidth = widthInput.toDouble(&ok); + if (ok) widthFromInput = true; + } + if (!heightInput.isEmpty()) { + bool ok; + inputHeight = heightInput.toDouble(&ok); + if (ok) heightFromInput = true; } - QVector3D p1_3d(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z()); + QVector3D mousePos = unproject(m_currentMousePos, m_currentPlane); + + if (widthFromInput || heightFromInput) { + QVector3D mouseDir = mousePos - p1_3d; + double current_w, current_h; + if (m_currentPlane == SketchPlane::XY) { + current_w = qAbs(mouseDir.x()); + current_h = qAbs(mouseDir.z()); + } else if (m_currentPlane == SketchPlane::XZ) { + current_w = qAbs(mouseDir.x()); + current_h = qAbs(mouseDir.y()); + } else { // YZ + current_w = qAbs(mouseDir.y()); + current_h = qAbs(mouseDir.z()); + } + double rect_w = widthFromInput ? inputWidth : current_w; + double rect_h = heightFromInput ? inputHeight : current_h; + int signX = (mouseDir.x() >= 0) ? 1 : -1; + int signY = (mouseDir.y() >= 0) ? 1 : -1; + int signZ = (mouseDir.z() >= 0) ? 1 : -1; + worldPos = p1_3d; + if (m_currentPlane == SketchPlane::XY) { + worldPos.setX(p1_3d.x() + signX * rect_w); + worldPos.setZ(p1_3d.z() + signZ * rect_h); + } else if (m_currentPlane == SketchPlane::XZ) { + worldPos.setX(p1_3d.x() + signX * rect_w); + worldPos.setY(p1_3d.y() + signY * rect_h); + } else { // YZ + worldPos.setY(p1_3d.y() + signY * rect_w); + worldPos.setZ(p1_3d.z() + signZ * rect_h); + } + } else { + worldPos = mousePos; + if (m_isSnappingOrigin) { + worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0); + } else if (m_isSnappingVertex) { + worldPos.setX(m_snapVertex.X()); worldPos.setY(m_snapVertex.Y()); worldPos.setZ(m_snapVertex.Z()); + } + } QVector3D p3_3d = worldPos; QVector3D p2_3d, p4_3d; @@ -624,10 +726,14 @@ void ViewportWidget::paintGL() QVector3D widthTextPos3D = (p1_3d + p2_3d) / 2.0f; QVector3D screenPosW = project(widthTextPos3D, model, projection, rect()); if (screenPosW.z() < 1.0f) { - QString widthText = QString::number(w, 'f', 2); + QString widthText = widthFromInput ? widthInput : QString::number(w, 'f', 2); QRect textRect = fm.boundingRect(widthText + "__"); textRect.moveCenter(screenPosW.toPoint() + QPoint(0, (p3_3d.z() > p1_3d.z() || p3_3d.y() > p1_3d.y()) ? -15 : 15)); - painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(50, 50, 50)); + if (property("dimensionEditMode").toString() == "width") { + 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)); + } painter.setPen(Qt::white); painter.drawText(textRect, Qt::AlignCenter, widthText); } @@ -636,10 +742,14 @@ void ViewportWidget::paintGL() QVector3D heightTextPos3D = (p2_3d + p3_3d) / 2.0f; QVector3D screenPosH = project(heightTextPos3D, model, projection, rect()); if (screenPosH.z() < 1.0f) { - QString heightText = QString::number(h, 'f', 2); + QString heightText = heightFromInput ? heightInput : QString::number(h, 'f', 2); QRect textRect = fm.boundingRect(heightText + "__"); textRect.moveCenter(screenPosH.toPoint() + QPoint((p3_3d.x() > p1_3d.x()) ? 15 : -15, 0)); - painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(50, 50, 50)); + if (property("dimensionEditMode").toString() == "height") { + 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)); + } painter.setPen(Qt::white); painter.drawText(textRect, Qt::AlignCenter, heightText); } @@ -905,21 +1015,84 @@ void ViewportWidget::mousePressEvent(QMouseEvent *event) if (m_currentPlane != SketchPlane::NONE && m_activeTool == static_cast(ApplicationController::ToolType::Rectangle)) { gp_Pnt p; - if (m_isSnappingOrigin) { - p.SetCoord(0, 0, 0); - } else if (m_isSnappingVertex) { - p = m_snapVertex; - } else { - QVector3D worldPos = unproject(event->pos(), m_currentPlane); - p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z()); - } - if (!m_isDefiningRectangle) { + if (m_isSnappingOrigin) { + p.SetCoord(0, 0, 0); + } else if (m_isSnappingVertex) { + p = m_snapVertex; + } else { + QVector3D worldPos = unproject(event->pos(), m_currentPlane); + p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z()); + } m_firstRectanglePoint = p; m_isDefiningRectangle = true; + setProperty("widthInput", ""); + setProperty("heightInput", ""); + setProperty("dimensionEditMode", "height"); } else { + QVector3D worldPos; + QVector3D startPos(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z()); + + QString widthInput = property("widthInput").toString(); + QString heightInput = property("heightInput").toString(); + bool widthFromInput = false, heightFromInput = false; + double inputWidth = 0, inputHeight = 0; + + if (!widthInput.isEmpty()) { + bool ok; + inputWidth = widthInput.toDouble(&ok); + if (ok) widthFromInput = true; + } + if (!heightInput.isEmpty()) { + bool ok; + inputHeight = heightInput.toDouble(&ok); + if (ok) heightFromInput = true; + } + + if (widthFromInput || heightFromInput) { + QVector3D mousePos = unproject(event->pos(), m_currentPlane); + QVector3D mouseDir = mousePos - startPos; + double current_w, current_h; + if (m_currentPlane == SketchPlane::XY) { + current_w = qAbs(mouseDir.x()); + current_h = qAbs(mouseDir.z()); + } else if (m_currentPlane == SketchPlane::XZ) { + current_w = qAbs(mouseDir.x()); + current_h = qAbs(mouseDir.y()); + } else { // YZ + current_w = qAbs(mouseDir.y()); + current_h = qAbs(mouseDir.z()); + } + double rect_w = widthFromInput ? inputWidth : current_w; + double rect_h = heightFromInput ? inputHeight : current_h; + int signX = (mouseDir.x() >= 0) ? 1 : -1; + int signY = (mouseDir.y() >= 0) ? 1 : -1; + int signZ = (mouseDir.z() >= 0) ? 1 : -1; + worldPos = startPos; + if (m_currentPlane == SketchPlane::XY) { + worldPos.setX(startPos.x() + signX * rect_w); + worldPos.setZ(startPos.z() + signZ * rect_h); + } else if (m_currentPlane == SketchPlane::XZ) { + worldPos.setX(startPos.x() + signX * rect_w); + worldPos.setY(startPos.y() + signY * rect_h); + } else { // YZ + worldPos.setY(startPos.y() + signY * rect_w); + worldPos.setZ(startPos.z() + signZ * rect_h); + } + } else { + if (m_isSnappingOrigin) { + worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0); + } else if (m_isSnappingVertex) { + worldPos = QVector3D(m_snapVertex.X(), m_snapVertex.Y(), m_snapVertex.Z()); + } else { + worldPos = unproject(event->pos(), m_currentPlane); + } + } + p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z()); emit rectangleAdded(m_firstRectanglePoint, p); m_isDefiningRectangle = false; + setProperty("widthInput", ""); + setProperty("heightInput", ""); } update(); return; @@ -1316,6 +1489,107 @@ void ViewportWidget::keyPressEvent(QKeyEvent *event) } } + if (m_isDefiningRectangle && m_activeTool == static_cast(ApplicationController::ToolType::Rectangle)) { + if (event->key() == Qt::Key_Tab) { + QString currentMode = property("dimensionEditMode").toString(); + if (currentMode == "width") { + setProperty("dimensionEditMode", "height"); + } else { + setProperty("dimensionEditMode", "width"); + } + update(); + return; + } + + QString editMode = property("dimensionEditMode").toString(); + const char* propertyName = (editMode == "width") ? "widthInput" : "heightInput"; + QString currentInput = property(propertyName).toString(); + + if (event->key() >= Qt::Key_0 && event->key() <= Qt::Key_9) { + currentInput += event->text(); + setProperty(propertyName, currentInput); + update(); + return; + } else if (event->key() == Qt::Key_Period) { + if (!currentInput.contains('.')) { + currentInput += '.'; + setProperty(propertyName, currentInput); + update(); + return; + } + } else if (event->key() == Qt::Key_Backspace) { + if (!currentInput.isEmpty()) { + currentInput.chop(1); + setProperty(propertyName, currentInput); + update(); + return; + } + } else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { + QVector3D worldPos; + QVector3D startPos(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z()); + + QString widthInput = property("widthInput").toString(); + QString heightInput = property("heightInput").toString(); + bool widthFromInput = false, heightFromInput = false; + double inputWidth = 0, inputHeight = 0; + + if (!widthInput.isEmpty()) { + bool ok; + inputWidth = widthInput.toDouble(&ok); + if (ok) widthFromInput = true; + } + if (!heightInput.isEmpty()) { + bool ok; + inputHeight = heightInput.toDouble(&ok); + if (ok) heightFromInput = true; + } + + if (widthFromInput || heightFromInput) { + QVector3D mousePos = unproject(m_currentMousePos, m_currentPlane); + QVector3D mouseDir = mousePos - startPos; + double current_w, current_h; + if (m_currentPlane == SketchPlane::XY) { + current_w = qAbs(mouseDir.x()); + current_h = qAbs(mouseDir.z()); + } else if (m_currentPlane == SketchPlane::XZ) { + current_w = qAbs(mouseDir.x()); + current_h = qAbs(mouseDir.y()); + } else { // YZ + current_w = qAbs(mouseDir.y()); + current_h = qAbs(mouseDir.z()); + } + double rect_w = widthFromInput ? inputWidth : current_w; + double rect_h = heightFromInput ? inputHeight : current_h; + int signX = (mouseDir.x() >= 0) ? 1 : -1; + int signY = (mouseDir.y() >= 0) ? 1 : -1; + int signZ = (mouseDir.z() >= 0) ? 1 : -1; + worldPos = startPos; + if (m_currentPlane == SketchPlane::XY) { + worldPos.setX(startPos.x() + signX * rect_w); + worldPos.setZ(startPos.z() + signZ * rect_h); + } else if (m_currentPlane == SketchPlane::XZ) { + worldPos.setX(startPos.x() + signX * rect_w); + worldPos.setY(startPos.y() + signY * rect_h); + } else { // YZ + worldPos.setY(startPos.y() + signY * rect_w); + worldPos.setZ(startPos.z() + signZ * rect_h); + } + } else { + worldPos = unproject(m_currentMousePos, m_currentPlane); + } + + gp_Pnt p; + p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z()); + + emit rectangleAdded(m_firstRectanglePoint, p); + m_isDefiningRectangle = false; + setProperty("widthInput", ""); + setProperty("heightInput", ""); + update(); + return; + } + } + if (event->key() == Qt::Key_Escape) { if (m_isDefiningLine) { m_isDefiningLine = false; @@ -1327,6 +1601,8 @@ void ViewportWidget::keyPressEvent(QKeyEvent *event) } if (m_isDefiningRectangle) { m_isDefiningRectangle = false; + setProperty("widthInput", ""); + setProperty("heightInput", ""); emit toolDeactivated(); update(); return;