feat: Implement in-viewport sketch plane selection

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2026-02-15 11:09:28 -07:00
parent d2fe4226ea
commit 8424097127
4 changed files with 181 additions and 36 deletions

View File

@@ -102,29 +102,32 @@ bool ApplicationController::saveDocumentAs()
void ApplicationController::beginSketchCreation() void ApplicationController::beginSketchCreation()
{ {
QStringList items; emit planeSelectionModeStarted();
items << "Top (XY)" << "Front (XZ)" << "Right (YZ)"; }
bool ok; void ApplicationController::onPlaneSelected(ViewportWidget::SketchPlane plane)
QString item = QInputDialog::getItem(m_mainWindow, "Select Sketch Plane", {
"Plane:", items, 0, false, &ok);
if (ok && !item.isEmpty()) {
auto feature = new SketchFeature("Sketch"); auto feature = new SketchFeature("Sketch");
m_activeSketch = feature; m_activeSketch = feature;
ViewportWidget::SketchPlane plane;
if (item == "Top (XY)") { switch (plane) {
plane = ViewportWidget::SketchPlane::XY; case ViewportWidget::SketchPlane::XY:
feature->setPlane(SketchFeature::SketchPlane::XY); feature->setPlane(SketchFeature::SketchPlane::XY);
} else if (item == "Front (XZ)") { break;
plane = ViewportWidget::SketchPlane::XZ; case ViewportWidget::SketchPlane::XZ:
feature->setPlane(SketchFeature::SketchPlane::XZ); feature->setPlane(SketchFeature::SketchPlane::XZ);
} else { // "Right (YZ)" break;
plane = ViewportWidget::SketchPlane::YZ; case ViewportWidget::SketchPlane::YZ:
feature->setPlane(SketchFeature::SketchPlane::YZ); feature->setPlane(SketchFeature::SketchPlane::YZ);
break;
case ViewportWidget::SketchPlane::NONE:
delete feature;
m_activeSketch = nullptr;
return;
} }
m_document->addFeature(feature); m_document->addFeature(feature);
emit sketchModeStarted(plane); emit sketchModeStarted(plane);
}
} }
void ApplicationController::addLine(const gp_Pnt& start, const gp_Pnt& end) void ApplicationController::addLine(const gp_Pnt& start, const gp_Pnt& end)

View File

@@ -38,9 +38,11 @@ public slots:
bool saveDocumentAs(); bool saveDocumentAs();
void beginSketchCreation(); void beginSketchCreation();
void onPlaneSelected(ViewportWidget::SketchPlane plane);
void endSketch(); void endSketch();
signals: signals:
void planeSelectionModeStarted();
void sketchModeStarted(ViewportWidget::SketchPlane plane); void sketchModeStarted(ViewportWidget::SketchPlane plane);
void sketchModeEnded(); void sketchModeEnded();
void currentFileChanged(const QString& path); void currentFileChanged(const QString& path);

View File

@@ -135,6 +135,10 @@ void ViewportWidget::paintGL()
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(model.constData()); glLoadMatrixf(model.constData());
if (m_isSelectingPlane) {
drawSelectionPlanes();
}
if (m_currentPlane != SketchPlane::NONE) { if (m_currentPlane != SketchPlane::NONE) {
m_sketchGrid->paintGL(static_cast<SketchGrid::SketchPlane>(m_currentPlane), projection, model); m_sketchGrid->paintGL(static_cast<SketchGrid::SketchPlane>(m_currentPlane), projection, model);
} }
@@ -198,7 +202,7 @@ void ViewportWidget::paintGL()
} }
if (m_isDefiningLine && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) { if (m_isDefiningLine && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) {
QVector3D worldPos = unproject(m_currentMousePos); QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
if (m_isSnappingOrigin) { if (m_isSnappingOrigin) {
worldPos.setX(0); worldPos.setX(0);
worldPos.setY(0); worldPos.setY(0);
@@ -286,14 +290,25 @@ void ViewportWidget::resizeGL(int w, int h)
void ViewportWidget::mousePressEvent(QMouseEvent *event) void ViewportWidget::mousePressEvent(QMouseEvent *event)
{ {
if (event->button() == Qt::LeftButton && m_currentPlane != SketchPlane::NONE && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) { if (event->button() == Qt::LeftButton) {
if (m_isSelectingPlane) {
if (m_highlightedPlane != SketchPlane::NONE) {
emit planeSelected(m_highlightedPlane);
m_isSelectingPlane = false;
m_highlightedPlane = SketchPlane::NONE;
update();
}
return;
}
if (m_currentPlane != SketchPlane::NONE && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) {
gp_Pnt p; gp_Pnt p;
if (m_isSnappingOrigin) { if (m_isSnappingOrigin) {
p.SetCoord(0, 0, 0); p.SetCoord(0, 0, 0);
} else if (m_isSnappingVertex) { } else if (m_isSnappingVertex) {
p = m_snapVertex; p = m_snapVertex;
} else { } else {
QVector3D worldPos = unproject(event->pos()); QVector3D worldPos = unproject(event->pos(), m_currentPlane);
if (m_isSnappingHorizontal) { if (m_isSnappingHorizontal) {
if (m_currentPlane == SketchPlane::XY) worldPos.setZ(m_firstLinePoint.Z()); if (m_currentPlane == SketchPlane::XY) worldPos.setZ(m_firstLinePoint.Z());
else if (m_currentPlane == SketchPlane::XZ) worldPos.setY(m_firstLinePoint.Y()); else if (m_currentPlane == SketchPlane::XZ) worldPos.setY(m_firstLinePoint.Y());
@@ -314,6 +329,7 @@ void ViewportWidget::mousePressEvent(QMouseEvent *event)
m_firstLinePoint = p; m_firstLinePoint = p;
} }
update(); update();
}
} else { } else {
lastPos = event->pos(); lastPos = event->pos();
} }
@@ -323,9 +339,17 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
{ {
m_currentMousePos = event->pos(); m_currentMousePos = event->pos();
if (m_isSelectingPlane) {
SketchPlane newHighlight = checkPlaneSelection(m_currentMousePos);
if (newHighlight != m_highlightedPlane) {
m_highlightedPlane = newHighlight;
update();
}
}
bool shouldSnap = false; bool shouldSnap = false;
if (m_currentPlane != SketchPlane::NONE && m_activeTool != static_cast<int>(ApplicationController::ToolType::None)) { if (m_currentPlane != SketchPlane::NONE && m_activeTool != static_cast<int>(ApplicationController::ToolType::None)) {
QVector3D worldPos = unproject(m_currentMousePos); QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
const float snapRectHalfSize = 0.0075f * -m_zoom; const float snapRectHalfSize = 0.0075f * -m_zoom;
switch (m_currentPlane) { switch (m_currentPlane) {
@@ -354,7 +378,7 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
bool oldIsSnappingVertex = m_isSnappingVertex; bool oldIsSnappingVertex = m_isSnappingVertex;
m_isSnappingVertex = false; m_isSnappingVertex = false;
if (!m_isSnappingOrigin && m_document && m_currentPlane != SketchPlane::NONE && m_activeTool != static_cast<int>(ApplicationController::ToolType::None)) { if (!m_isSnappingOrigin && m_document && m_currentPlane != SketchPlane::NONE && m_activeTool != static_cast<int>(ApplicationController::ToolType::None)) {
QVector3D worldPos = unproject(m_currentMousePos); QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
const float snapRectHalfSize = 0.0075f * -m_zoom; const float snapRectHalfSize = 0.0075f * -m_zoom;
for (Feature* feature : m_document->features()) { for (Feature* feature : m_document->features()) {
@@ -402,7 +426,7 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
m_isSnappingVertical = false; m_isSnappingVertical = false;
if (m_isDefiningLine && !m_isSnappingOrigin && !m_isSnappingVertex) { if (m_isDefiningLine && !m_isSnappingOrigin && !m_isSnappingVertex) {
QVector3D worldPos = unproject(m_currentMousePos); QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z()); QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
QVector3D delta = worldPos - startPos; QVector3D delta = worldPos - startPos;
@@ -538,6 +562,13 @@ void ViewportWidget::onSketchModeStarted(SketchPlane plane)
animGroup->start(QAbstractAnimation::DeleteWhenStopped); animGroup->start(QAbstractAnimation::DeleteWhenStopped);
} }
void ViewportWidget::onPlaneSelectionModeStarted()
{
m_isSelectingPlane = true;
m_highlightedPlane = SketchPlane::NONE;
update();
}
void ViewportWidget::onSketchModeEnded() void ViewportWidget::onSketchModeEnded()
{ {
auto* animGroup = new QParallelAnimationGroup(this); auto* animGroup = new QParallelAnimationGroup(this);
@@ -678,7 +709,7 @@ void ViewportWidget::onActiveToolChanged(int tool)
} }
} }
QVector3D ViewportWidget::unproject(const QPoint& screenPos) QVector3D ViewportWidget::unproject(const QPoint& screenPos, SketchPlane plane)
{ {
QMatrix4x4 model; QMatrix4x4 model;
model.translate(m_panX, m_panY, m_zoom); model.translate(m_panX, m_panY, m_zoom);
@@ -711,7 +742,7 @@ QVector3D ViewportWidget::unproject(const QPoint& screenPos)
QVector3D rayDir = (QVector3D(farPoint_world) - rayOrigin).normalized(); QVector3D rayDir = (QVector3D(farPoint_world) - rayOrigin).normalized();
QVector3D planeNormal; QVector3D planeNormal;
switch (m_currentPlane) { switch (plane) {
case SketchPlane::XY: planeNormal = QVector3D(0, 1, 0); break; case SketchPlane::XY: planeNormal = QVector3D(0, 1, 0); break;
case SketchPlane::XZ: planeNormal = QVector3D(0, 0, 1); break; case SketchPlane::XZ: planeNormal = QVector3D(0, 0, 1); break;
case SketchPlane::YZ: planeNormal = QVector3D(1, 0, 0); break; case SketchPlane::YZ: planeNormal = QVector3D(1, 0, 0); break;
@@ -754,3 +785,105 @@ void ViewportWidget::drawSketch(const SketchFeature* sketch)
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
} }
void ViewportWidget::drawSelectionPlanes()
{
const float planeSize = 5.0f;
const float planeOffset = 1.0f;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glLineWidth(2.0f);
glDisable(GL_DEPTH_TEST);
// Draw back to front for proper blending
// XY Plane (Top), normal is Y (green)
glColor4f(0.0f, 1.0f, 0.0f, m_highlightedPlane == SketchPlane::XY ? 0.5f : 0.2f);
if (m_highlightedPlane == SketchPlane::XY) {
glBegin(GL_QUADS);
glVertex3f(planeOffset, 0, planeOffset);
glVertex3f(planeOffset + planeSize, 0, planeOffset);
glVertex3f(planeOffset + planeSize, 0, planeOffset + planeSize);
glVertex3f(planeOffset, 0, planeOffset + planeSize);
glEnd();
}
glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
glBegin(GL_LINE_LOOP);
glVertex3f(planeOffset, 0, planeOffset);
glVertex3f(planeOffset + planeSize, 0, planeOffset);
glVertex3f(planeOffset + planeSize, 0, planeOffset + planeSize);
glVertex3f(planeOffset, 0, planeOffset + planeSize);
glEnd();
// XZ Plane (Front), normal is Z (blue)
glColor4f(0.0f, 0.0f, 1.0f, m_highlightedPlane == SketchPlane::XZ ? 0.5f : 0.2f);
if (m_highlightedPlane == SketchPlane::XZ) {
glBegin(GL_QUADS);
glVertex3f(planeOffset, planeOffset, 0);
glVertex3f(planeOffset + planeSize, planeOffset, 0);
glVertex3f(planeOffset + planeSize, planeOffset + planeSize, 0);
glVertex3f(planeOffset, planeOffset + planeSize, 0);
glEnd();
}
glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
glBegin(GL_LINE_LOOP);
glVertex3f(planeOffset, planeOffset, 0);
glVertex3f(planeOffset + planeSize, planeOffset, 0);
glVertex3f(planeOffset + planeSize, planeOffset + planeSize, 0);
glVertex3f(planeOffset, planeOffset + planeSize, 0);
glEnd();
// YZ Plane (Right), normal is X (red)
glColor4f(1.0f, 0.0f, 0.0f, m_highlightedPlane == SketchPlane::YZ ? 0.5f : 0.2f);
if (m_highlightedPlane == SketchPlane::YZ) {
glBegin(GL_QUADS);
glVertex3f(0, planeOffset, planeOffset);
glVertex3f(0, planeOffset + planeSize, planeOffset);
glVertex3f(0, planeOffset + planeSize, planeOffset + planeSize);
glVertex3f(0, planeOffset, planeOffset + planeSize);
glEnd();
}
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glBegin(GL_LINE_LOOP);
glVertex3f(0, planeOffset, planeOffset);
glVertex3f(0, planeOffset + planeSize, planeOffset);
glVertex3f(0, planeOffset + planeSize, planeOffset + planeSize);
glVertex3f(0, planeOffset, planeOffset + planeSize);
glEnd();
glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
}
ViewportWidget::SketchPlane ViewportWidget::checkPlaneSelection(const QPoint& screenPos)
{
const float planeSize = 5.0f;
const float planeOffset = 1.0f;
QVector3D intersection;
// Check front to back to handle overlaps
// YZ plane (Right)
intersection = unproject(screenPos, SketchPlane::YZ);
if (intersection.y() >= planeOffset && intersection.y() <= planeOffset + planeSize &&
intersection.z() >= planeOffset && intersection.z() <= planeOffset + planeSize) {
return SketchPlane::YZ;
}
// XZ plane (Front)
intersection = unproject(screenPos, SketchPlane::XZ);
if (intersection.x() >= planeOffset && intersection.x() <= planeOffset + planeSize &&
intersection.y() >= planeOffset && intersection.y() <= planeOffset + planeSize) {
return SketchPlane::XZ;
}
// XY plane (Top)
intersection = unproject(screenPos, SketchPlane::XY);
if (intersection.x() >= planeOffset && intersection.x() <= planeOffset + planeSize &&
intersection.z() >= planeOffset && intersection.z() <= planeOffset + planeSize) {
return SketchPlane::XY;
}
return SketchPlane::NONE;
}

View File

@@ -43,6 +43,7 @@ public:
public slots: public slots:
void onSketchModeStarted(SketchPlane plane); void onSketchModeStarted(SketchPlane plane);
void onSketchModeEnded(); void onSketchModeEnded();
void onPlaneSelectionModeStarted();
void onActiveToolChanged(int tool); void onActiveToolChanged(int tool);
float xRotation() const; float xRotation() const;
@@ -62,6 +63,7 @@ public slots:
signals: signals:
void lineAdded(const gp_Pnt& start, const gp_Pnt& end); void lineAdded(const gp_Pnt& start, const gp_Pnt& end);
void planeSelected(SketchPlane plane);
protected: protected:
void initializeGL() override; void initializeGL() override;
@@ -75,9 +77,11 @@ 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); QVector3D unproject(const QPoint& screenPos, SketchPlane plane);
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); void drawSketch(const SketchFeature* sketch);
void drawSelectionPlanes();
ViewportWidget::SketchPlane checkPlaneSelection(const QPoint& screenPos);
QMatrix4x4 projection; QMatrix4x4 projection;
ViewCube* m_viewCube; ViewCube* m_viewCube;
@@ -86,6 +90,9 @@ private:
Document* m_document = nullptr; Document* m_document = nullptr;
SketchPlane m_currentPlane = SketchPlane::NONE; SketchPlane m_currentPlane = SketchPlane::NONE;
bool m_isSelectingPlane = false;
SketchPlane m_highlightedPlane = SketchPlane::NONE;
int m_activeTool = 0; int m_activeTool = 0;
bool m_isDefiningLine = false; bool m_isDefiningLine = false;
gp_Pnt m_firstLinePoint; gp_Pnt m_firstLinePoint;