feat: Add interactive sketch line drawing to viewport

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2026-02-14 19:19:13 -07:00
parent a1cfbc2e3f
commit 8e1ab54cb5
4 changed files with 144 additions and 4 deletions

View File

@@ -1,6 +1,7 @@
#include "ApplicationController.h" #include "ApplicationController.h"
#include "Document.h" #include "Document.h"
#include "SketchFeature.h" #include "SketchFeature.h"
#include "SketchLine.h"
#include "MainWindow.h" #include "MainWindow.h"
#include <QInputDialog> #include <QInputDialog>
@@ -126,6 +127,13 @@ void ApplicationController::beginSketchCreation()
} }
} }
void ApplicationController::addLine(const gp_Pnt& start, const gp_Pnt& end)
{
if (m_activeSketch) {
m_activeSketch->addObject(new SketchLine(start, end));
}
}
void ApplicationController::endSketch() void ApplicationController::endSketch()
{ {
m_activeSketch = nullptr; m_activeSketch = nullptr;

View File

@@ -3,6 +3,7 @@
#include <QObject> #include <QObject>
#include "ViewportWidget.h" // For SketchPlane enum #include "ViewportWidget.h" // For SketchPlane enum
#include <gp_Pnt.hxx>
class Document; class Document;
class MainWindow; class MainWindow;
@@ -30,6 +31,7 @@ public:
public slots: public slots:
void setActiveTool(ToolType tool); void setActiveTool(ToolType tool);
void addLine(const gp_Pnt& start, const gp_Pnt& end);
void newDocument(); void newDocument();
bool openDocument(); bool openDocument();
bool saveDocument(); bool saveDocument();

View File

@@ -3,6 +3,10 @@
#include "SketchGrid.h" #include "SketchGrid.h"
#include "Document.h" #include "Document.h"
#include "FeatureBrowser.h" #include "FeatureBrowser.h"
#include "SketchFeature.h"
#include "SketchLine.h"
#include "SketchObject.h"
#include "ApplicationController.h"
#include <QMouseEvent> #include <QMouseEvent>
#include <QWheelEvent> #include <QWheelEvent>
#include <QApplication> #include <QApplication>
@@ -18,6 +22,7 @@ ViewportWidget::ViewportWidget(QWidget *parent)
m_viewCube = new ViewCube(); m_viewCube = new ViewCube();
m_sketchGrid = new SketchGrid(); m_sketchGrid = new SketchGrid();
m_featureBrowser = new FeatureBrowser(); m_featureBrowser = new FeatureBrowser();
setMouseTracking(true);
} }
ViewportWidget::~ViewportWidget() ViewportWidget::~ViewportWidget()
@@ -29,6 +34,7 @@ ViewportWidget::~ViewportWidget()
void ViewportWidget::setDocument(Document* document) void ViewportWidget::setDocument(Document* document)
{ {
m_document = document;
m_featureBrowser->setDocument(document); m_featureBrowser->setDocument(document);
} }
@@ -112,6 +118,23 @@ void ViewportWidget::paintGL()
m_sketchGrid->paintGL(static_cast<SketchGrid::SketchPlane>(m_currentPlane), projection, model); m_sketchGrid->paintGL(static_cast<SketchGrid::SketchPlane>(m_currentPlane), projection, model);
} }
if (m_document) {
for (Feature* feature : m_document->features()) {
if (auto sketch = dynamic_cast<SketchFeature*>(feature)) {
drawSketch(sketch);
}
}
}
if (m_isDefiningLine && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) {
QVector3D worldPos = unproject(m_currentMousePos);
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();
}
// View cube rendering // View cube rendering
QMatrix4x4 viewCubeModel; QMatrix4x4 viewCubeModel;
viewCubeModel.rotate(m_xRot / 16.0f, 1, 0, 0); viewCubeModel.rotate(m_xRot / 16.0f, 1, 0, 0);
@@ -136,11 +159,26 @@ void ViewportWidget::resizeGL(int w, int h)
void ViewportWidget::mousePressEvent(QMouseEvent *event) void ViewportWidget::mousePressEvent(QMouseEvent *event)
{ {
lastPos = event->pos(); if (event->button() == Qt::LeftButton && m_currentPlane != SketchPlane::NONE && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) {
QVector3D worldPos = unproject(event->pos());
gp_Pnt p(worldPos.x(), worldPos.y(), worldPos.z());
if (!m_isDefiningLine) {
m_firstLinePoint = p;
m_isDefiningLine = true;
} else {
emit lineAdded(m_firstLinePoint, p);
m_isDefiningLine = false;
}
update();
} else {
lastPos = event->pos();
}
} }
void ViewportWidget::mouseMoveEvent(QMouseEvent *event) void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
{ {
m_currentMousePos = event->pos();
int dx = event->pos().x() - lastPos.x(); int dx = event->pos().x() - lastPos.x();
int dy = event->pos().y() - lastPos.y(); int dy = event->pos().y() - lastPos.y();
@@ -150,9 +188,11 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
m_panX += dx / 100.0f; m_panX += dx / 100.0f;
m_panY -= dy / 100.0f; m_panY -= dy / 100.0f;
} else { } else {
// Rotate if (m_currentPlane == SketchPlane::NONE) {
m_xRot += 8 * dy; // Rotate
m_yRot += 8 * dx; m_xRot += 8 * dy;
m_yRot += 8 * dx;
}
} }
} }
lastPos = event->pos(); lastPos = event->pos();
@@ -332,3 +372,79 @@ void ViewportWidget::drawAxisLabels(QPainter& painter, const QMatrix4x4& modelVi
} }
} }
void ViewportWidget::onActiveToolChanged(int tool)
{
m_activeTool = tool;
m_isDefiningLine = false;
}
QVector3D ViewportWidget::unproject(const QPoint& screenPos)
{
QMatrix4x4 model;
model.translate(m_panX, m_panY, m_zoom);
model.rotate(m_xRot / 16.0f, 1, 0, 0);
model.rotate(m_yRot / 16.0f, 0, 1, 0);
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 (m_currentPlane) {
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);
glColor3f(1.0, 1.0, 1.0);
for (const auto& obj : sketch->objects()) {
if (obj->type() == SketchObject::ObjectType::Line) {
auto line = static_cast<const SketchLine*>(obj);
const auto& start = line->startPoint();
const auto& end = line->endPoint();
glBegin(GL_LINES);
glVertex3d(start.X(), start.Y(), start.Z());
glVertex3d(end.X(), end.Y(), end.Z());
glEnd();
}
}
glEnable(GL_DEPTH_TEST);
}

View File

@@ -7,11 +7,13 @@
#include <QPoint> #include <QPoint>
#include <QVector3D> #include <QVector3D>
#include <QRect> #include <QRect>
#include <gp_Pnt.hxx>
class ViewCube; class ViewCube;
class SketchGrid; class SketchGrid;
class Document; class Document;
class FeatureBrowser; class FeatureBrowser;
class SketchFeature;
class ViewportWidget : public QOpenGLWidget, protected QOpenGLFunctions class ViewportWidget : public QOpenGLWidget, protected QOpenGLFunctions
{ {
@@ -39,6 +41,7 @@ public:
public slots: public slots:
void onSketchModeStarted(SketchPlane plane); void onSketchModeStarted(SketchPlane plane);
void onSketchModeEnded(); void onSketchModeEnded();
void onActiveToolChanged(int tool);
float xRotation() const; float xRotation() const;
void setXRotation(float angle); void setXRotation(float angle);
@@ -55,6 +58,9 @@ public slots:
float panY() const; float panY() const;
void setPanY(float value); void setPanY(float value);
signals:
void lineAdded(const gp_Pnt& start, const gp_Pnt& end);
protected: protected:
void initializeGL() override; void initializeGL() override;
void paintGL() override; void paintGL() override;
@@ -66,14 +72,22 @@ protected:
private: private:
QVector3D project(const QVector3D& worldCoord, const QMatrix4x4& modelView, const QMatrix4x4& projection, const QRect& viewport); QVector3D project(const QVector3D& worldCoord, const QMatrix4x4& modelView, const QMatrix4x4& projection, const QRect& viewport);
QVector3D unproject(const QPoint& screenPos);
void drawAxisLabels(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection); void drawAxisLabels(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection);
void drawSketch(const SketchFeature* sketch);
QMatrix4x4 projection; QMatrix4x4 projection;
ViewCube* m_viewCube; ViewCube* m_viewCube;
SketchGrid* m_sketchGrid = nullptr; SketchGrid* m_sketchGrid = nullptr;
FeatureBrowser* m_featureBrowser = nullptr; FeatureBrowser* m_featureBrowser = nullptr;
Document* m_document = nullptr;
SketchPlane m_currentPlane = SketchPlane::NONE; SketchPlane m_currentPlane = SketchPlane::NONE;
int m_activeTool = 0;
bool m_isDefiningLine = false;
gp_Pnt m_firstLinePoint;
QPoint m_currentMousePos;
float m_xRot = 35.264f * 16.0f; // Default to isometric view float m_xRot = 35.264f * 16.0f; // Default to isometric view
float m_yRot = -45.0f * 16.0f; // Default to isometric view float m_yRot = -45.0f * 16.0f; // Default to isometric view
float m_zoom = -5.0f; float m_zoom = -5.0f;