feat: Add horizontal/vertical line snapping with visual indicator

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2026-02-14 22:26:38 -07:00
parent 6e64856f23
commit 6e5a819c42
2 changed files with 90 additions and 0 deletions

View File

@@ -19,6 +19,8 @@
#include <QApplication>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <cmath>
#include <QtMath>
ViewportWidget::ViewportWidget(QWidget *parent)
: QOpenGLWidget(parent)
@@ -197,12 +199,55 @@ void ViewportWidget::paintGL()
worldPos.setX(m_snapVertex.X());
worldPos.setY(m_snapVertex.Y());
worldPos.setZ(m_snapVertex.Z());
} else if (m_isSnappingHorizontal) {
if (m_currentPlane == SketchPlane::XY) worldPos.setY(m_firstLinePoint.Y());
else if (m_currentPlane == SketchPlane::XZ) worldPos.setZ(m_firstLinePoint.Z());
else if (m_currentPlane == SketchPlane::YZ) worldPos.setZ(m_firstLinePoint.Z());
} else if (m_isSnappingVertical) {
if (m_currentPlane == SketchPlane::XY) worldPos.setX(m_firstLinePoint.X());
else if (m_currentPlane == SketchPlane::XZ) worldPos.setX(m_firstLinePoint.X());
else if (m_currentPlane == SketchPlane::YZ) worldPos.setY(m_firstLinePoint.Y());
}
glBegin(GL_LINES);
glColor3f(1.0, 1.0, 0.0);
glVertex3d(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
glVertex3d(worldPos.x(), worldPos.y(), worldPos.z());
glEnd();
if (m_isSnappingHorizontal || m_isSnappingVertical) {
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
QVector3D midPoint = (startPos + worldPos) / 2.0;
const float indicatorSize = 0.02f * -m_zoom;
const float indicatorOffset = 0.02f * -m_zoom;
glColor3f(1.0, 1.0, 0.0);
glBegin(GL_LINES);
if (m_isSnappingHorizontal) {
if (m_currentPlane == SketchPlane::XY) {
glVertex3f(midPoint.x() - indicatorSize, midPoint.y() + indicatorOffset, midPoint.z());
glVertex3f(midPoint.x() + indicatorSize, midPoint.y() + indicatorOffset, midPoint.z());
} else if (m_currentPlane == SketchPlane::XZ) {
glVertex3f(midPoint.x() - indicatorSize, midPoint.y(), midPoint.z() + indicatorOffset);
glVertex3f(midPoint.x() + indicatorSize, midPoint.y(), midPoint.z() + indicatorOffset);
} else if (m_currentPlane == SketchPlane::YZ) {
glVertex3f(midPoint.x(), midPoint.y() - indicatorSize, midPoint.z() + indicatorOffset);
glVertex3f(midPoint.x(), midPoint.y() + indicatorSize, midPoint.z() + indicatorOffset);
}
} else { // m_isSnappingVertical
if (m_currentPlane == SketchPlane::XY) {
glVertex3f(midPoint.x() + indicatorOffset, midPoint.y() - indicatorSize, midPoint.z());
glVertex3f(midPoint.x() + indicatorOffset, midPoint.y() + indicatorSize, midPoint.z());
} else if (m_currentPlane == SketchPlane::XZ) {
glVertex3f(midPoint.x() + indicatorOffset, midPoint.y(), midPoint.z() - indicatorSize);
glVertex3f(midPoint.x() + indicatorOffset, midPoint.y(), midPoint.z() + indicatorSize);
} else if (m_currentPlane == SketchPlane::YZ) {
glVertex3f(midPoint.x(), midPoint.y() + indicatorOffset, midPoint.z() - indicatorSize);
glVertex3f(midPoint.x(), midPoint.y() + indicatorOffset, midPoint.z() + indicatorSize);
}
}
glEnd();
}
}
// View cube rendering
@@ -241,6 +286,15 @@ void ViewportWidget::mousePressEvent(QMouseEvent *event)
p = m_snapVertex;
} else {
QVector3D worldPos = unproject(event->pos());
if (m_isSnappingHorizontal) {
if (m_currentPlane == SketchPlane::XY) worldPos.setY(m_firstLinePoint.Y());
else if (m_currentPlane == SketchPlane::XZ) worldPos.setZ(m_firstLinePoint.Z());
else if (m_currentPlane == SketchPlane::YZ) worldPos.setZ(m_firstLinePoint.Z());
} else if (m_isSnappingVertical) {
if (m_currentPlane == SketchPlane::XY) worldPos.setX(m_firstLinePoint.X());
else if (m_currentPlane == SketchPlane::XZ) worldPos.setX(m_firstLinePoint.X());
else if (m_currentPlane == SketchPlane::YZ) worldPos.setY(m_firstLinePoint.Y());
}
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
}
@@ -334,6 +388,40 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
update();
}
bool oldIsSnappingHorizontal = m_isSnappingHorizontal;
bool oldIsSnappingVertical = m_isSnappingVertical;
m_isSnappingHorizontal = false;
m_isSnappingVertical = false;
if (m_isDefiningLine && !m_isSnappingOrigin && !m_isSnappingVertex) {
QVector3D worldPos = unproject(m_currentMousePos);
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
QVector3D delta = worldPos - startPos;
if (delta.length() > 1e-6) {
const double snapAngleThreshold = qDegreesToRadians(2.0);
double angle = 0;
if (m_currentPlane == SketchPlane::XY) {
angle = atan2(delta.y(), delta.x());
} else if (m_currentPlane == SketchPlane::XZ) {
angle = atan2(delta.z(), delta.x());
} else if (m_currentPlane == SketchPlane::YZ) {
angle = atan2(delta.z(), delta.y());
}
if (qAbs(sin(angle)) < sin(snapAngleThreshold)) {
m_isSnappingHorizontal = true;
} else if (qAbs(cos(angle)) < sin(snapAngleThreshold)) {
m_isSnappingVertical = true;
}
}
}
if (oldIsSnappingHorizontal != m_isSnappingHorizontal || oldIsSnappingVertical != m_isSnappingVertical) {
update();
}
int dx = event->pos().x() - lastPos.x();
int dy = event->pos().y() - lastPos.y();

View File

@@ -93,6 +93,8 @@ private:
bool m_isSnappingOrigin = false;
bool m_isSnappingVertex = false;
gp_Pnt m_snapVertex;
bool m_isSnappingHorizontal = false;
bool m_isSnappingVertical = false;
QMap<int, QSvgRenderer*> m_toolIcons;
QSvgRenderer* m_cursorRenderer = nullptr;