From 6e64856f2389308b81daa93b63ec16f3102fe2b8 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Sat, 14 Feb 2026 22:16:14 -0700 Subject: [PATCH] feat: Add vertex snapping and draw line endpoints Co-authored-by: aider (gemini/gemini-2.5-pro) --- src/ViewportWidget.cpp | 84 ++++++++++++++++++++++++++++++++++++++++++ src/ViewportWidget.h | 2 + 2 files changed, 86 insertions(+) diff --git a/src/ViewportWidget.cpp b/src/ViewportWidget.cpp index a413ef0..7de33bb 100644 --- a/src/ViewportWidget.cpp +++ b/src/ViewportWidget.cpp @@ -161,6 +161,30 @@ void ViewportWidget::paintGL() } glEnd(); glDisable(GL_BLEND); + } else if (m_isSnappingVertex) { + const float rectSize = 0.0075f * -m_zoom; + glColor4f(1.0, 1.0, 0.0, 0.5f); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBegin(GL_LINE_LOOP); + if (m_currentPlane == SketchPlane::XY) { + glVertex3f(m_snapVertex.X() - rectSize, m_snapVertex.Y() - rectSize, m_snapVertex.Z()); + glVertex3f(m_snapVertex.X() + rectSize, m_snapVertex.Y() - rectSize, m_snapVertex.Z()); + glVertex3f(m_snapVertex.X() + rectSize, m_snapVertex.Y() + rectSize, m_snapVertex.Z()); + glVertex3f(m_snapVertex.X() - rectSize, m_snapVertex.Y() + rectSize, m_snapVertex.Z()); + } else if (m_currentPlane == SketchPlane::XZ) { + glVertex3f(m_snapVertex.X() - rectSize, m_snapVertex.Y(), m_snapVertex.Z() - rectSize); + glVertex3f(m_snapVertex.X() + rectSize, m_snapVertex.Y(), m_snapVertex.Z() - rectSize); + glVertex3f(m_snapVertex.X() + rectSize, m_snapVertex.Y(), m_snapVertex.Z() + rectSize); + glVertex3f(m_snapVertex.X() - rectSize, m_snapVertex.Y(), m_snapVertex.Z() + rectSize); + } else if (m_currentPlane == SketchPlane::YZ) { + glVertex3f(m_snapVertex.X(), m_snapVertex.Y() - rectSize, m_snapVertex.Z() - rectSize); + glVertex3f(m_snapVertex.X(), m_snapVertex.Y() + rectSize, m_snapVertex.Z() - rectSize); + glVertex3f(m_snapVertex.X(), m_snapVertex.Y() + rectSize, m_snapVertex.Z() + rectSize); + glVertex3f(m_snapVertex.X(), m_snapVertex.Y() - rectSize, m_snapVertex.Z() + rectSize); + } + glEnd(); + glDisable(GL_BLEND); } if (m_isDefiningLine && m_activeTool == static_cast(ApplicationController::ToolType::Line)) { @@ -169,6 +193,10 @@ void ViewportWidget::paintGL() 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()); } glBegin(GL_LINES); glColor3f(1.0, 1.0, 0.0); @@ -209,6 +237,8 @@ void ViewportWidget::mousePressEvent(QMouseEvent *event) gp_Pnt p; if (m_isSnappingOrigin) { p.SetCoord(0, 0, 0); + } else if (m_isSnappingVertex) { + p = m_snapVertex; } else { QVector3D worldPos = unproject(event->pos()); p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z()); @@ -253,6 +283,54 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent *event) if (shouldSnap != m_isSnappingOrigin) { m_isSnappingOrigin = shouldSnap; + if (m_isSnappingOrigin) { + m_isSnappingVertex = false; + } + update(); + } + + bool oldIsSnappingVertex = m_isSnappingVertex; + m_isSnappingVertex = false; + if (!m_isSnappingOrigin && m_document && m_currentPlane != SketchPlane::NONE) { + QVector3D worldPos = unproject(m_currentMousePos); + const float snapRectHalfSize = 0.0075f * -m_zoom; + + for (Feature* feature : m_document->features()) { + if (auto sketch = dynamic_cast(feature)) { + for (const auto& obj : sketch->objects()) { + if (obj->type() == SketchObject::ObjectType::Line) { + auto line = static_cast(obj); + const gp_Pnt vertices[] = {line->startPoint(), line->endPoint()}; + for (const auto& vertex : vertices) { + bool isClose = false; + switch (m_currentPlane) { + case SketchPlane::XY: + isClose = qAbs(worldPos.x() - vertex.X()) < snapRectHalfSize && qAbs(worldPos.y() - vertex.Y()) < snapRectHalfSize; + break; + case SketchPlane::XZ: + isClose = qAbs(worldPos.x() - vertex.X()) < snapRectHalfSize && qAbs(worldPos.z() - vertex.Z()) < snapRectHalfSize; + break; + case SketchPlane::YZ: + isClose = qAbs(worldPos.y() - vertex.Y()) < snapRectHalfSize && qAbs(worldPos.z() - vertex.Z()) < snapRectHalfSize; + break; + case SketchPlane::NONE: + break; + } + + if (isClose) { + m_isSnappingVertex = true; + m_snapVertex = vertex; + goto end_snap_check; + } + } + } + } + } + } + end_snap_check:; + } + + if (oldIsSnappingVertex != m_isSnappingVertex) { update(); } @@ -560,6 +638,7 @@ void ViewportWidget::drawSketch(const SketchFeature* sketch) glDisable(GL_DEPTH_TEST); glLineWidth(2.0f); glColor3f(1.0, 1.0, 1.0); + glPointSize(5.0f); for (const auto& obj : sketch->objects()) { if (obj->type() == SketchObject::ObjectType::Line) { @@ -570,6 +649,11 @@ void ViewportWidget::drawSketch(const SketchFeature* sketch) glVertex3d(start.X(), start.Y(), start.Z()); glVertex3d(end.X(), end.Y(), end.Z()); glEnd(); + + glBegin(GL_POINTS); + glVertex3d(start.X(), start.Y(), start.Z()); + glVertex3d(end.X(), end.Y(), end.Z()); + glEnd(); } } glEnable(GL_DEPTH_TEST); diff --git a/src/ViewportWidget.h b/src/ViewportWidget.h index 9cdd08f..62f2bc8 100644 --- a/src/ViewportWidget.h +++ b/src/ViewportWidget.h @@ -91,6 +91,8 @@ private: gp_Pnt m_firstLinePoint; QPoint m_currentMousePos; bool m_isSnappingOrigin = false; + bool m_isSnappingVertex = false; + gp_Pnt m_snapVertex; QMap m_toolIcons; QSvgRenderer* m_cursorRenderer = nullptr;