feat: Implement in-viewport sketch plane selection
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
@@ -102,29 +102,32 @@ bool ApplicationController::saveDocumentAs()
|
||||
|
||||
void ApplicationController::beginSketchCreation()
|
||||
{
|
||||
QStringList items;
|
||||
items << "Top (XY)" << "Front (XZ)" << "Right (YZ)";
|
||||
emit planeSelectionModeStarted();
|
||||
}
|
||||
|
||||
bool ok;
|
||||
QString item = QInputDialog::getItem(m_mainWindow, "Select Sketch Plane",
|
||||
"Plane:", items, 0, false, &ok);
|
||||
if (ok && !item.isEmpty()) {
|
||||
auto feature = new SketchFeature("Sketch");
|
||||
m_activeSketch = feature;
|
||||
ViewportWidget::SketchPlane plane;
|
||||
if (item == "Top (XY)") {
|
||||
plane = ViewportWidget::SketchPlane::XY;
|
||||
void ApplicationController::onPlaneSelected(ViewportWidget::SketchPlane plane)
|
||||
{
|
||||
auto feature = new SketchFeature("Sketch");
|
||||
m_activeSketch = feature;
|
||||
|
||||
switch (plane) {
|
||||
case ViewportWidget::SketchPlane::XY:
|
||||
feature->setPlane(SketchFeature::SketchPlane::XY);
|
||||
} else if (item == "Front (XZ)") {
|
||||
plane = ViewportWidget::SketchPlane::XZ;
|
||||
break;
|
||||
case ViewportWidget::SketchPlane::XZ:
|
||||
feature->setPlane(SketchFeature::SketchPlane::XZ);
|
||||
} else { // "Right (YZ)"
|
||||
plane = ViewportWidget::SketchPlane::YZ;
|
||||
break;
|
||||
case ViewportWidget::SketchPlane::YZ:
|
||||
feature->setPlane(SketchFeature::SketchPlane::YZ);
|
||||
}
|
||||
m_document->addFeature(feature);
|
||||
emit sketchModeStarted(plane);
|
||||
break;
|
||||
case ViewportWidget::SketchPlane::NONE:
|
||||
delete feature;
|
||||
m_activeSketch = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
m_document->addFeature(feature);
|
||||
emit sketchModeStarted(plane);
|
||||
}
|
||||
|
||||
void ApplicationController::addLine(const gp_Pnt& start, const gp_Pnt& end)
|
||||
|
||||
@@ -38,9 +38,11 @@ public slots:
|
||||
bool saveDocumentAs();
|
||||
|
||||
void beginSketchCreation();
|
||||
void onPlaneSelected(ViewportWidget::SketchPlane plane);
|
||||
void endSketch();
|
||||
|
||||
signals:
|
||||
void planeSelectionModeStarted();
|
||||
void sketchModeStarted(ViewportWidget::SketchPlane plane);
|
||||
void sketchModeEnded();
|
||||
void currentFileChanged(const QString& path);
|
||||
|
||||
@@ -135,6 +135,10 @@ void ViewportWidget::paintGL()
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadMatrixf(model.constData());
|
||||
|
||||
if (m_isSelectingPlane) {
|
||||
drawSelectionPlanes();
|
||||
}
|
||||
|
||||
if (m_currentPlane != SketchPlane::NONE) {
|
||||
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)) {
|
||||
QVector3D worldPos = unproject(m_currentMousePos);
|
||||
QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
|
||||
if (m_isSnappingOrigin) {
|
||||
worldPos.setX(0);
|
||||
worldPos.setY(0);
|
||||
@@ -286,16 +290,27 @@ void ViewportWidget::resizeGL(int w, int h)
|
||||
|
||||
void ViewportWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton && m_currentPlane != SketchPlane::NONE && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) {
|
||||
gp_Pnt p;
|
||||
if (m_isSnappingOrigin) {
|
||||
p.SetCoord(0, 0, 0);
|
||||
} else if (m_isSnappingVertex) {
|
||||
p = m_snapVertex;
|
||||
} else {
|
||||
QVector3D worldPos = unproject(event->pos());
|
||||
if (m_isSnappingHorizontal) {
|
||||
if (m_currentPlane == SketchPlane::XY) worldPos.setZ(m_firstLinePoint.Z());
|
||||
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;
|
||||
if (m_isSnappingOrigin) {
|
||||
p.SetCoord(0, 0, 0);
|
||||
} else if (m_isSnappingVertex) {
|
||||
p = m_snapVertex;
|
||||
} else {
|
||||
QVector3D worldPos = unproject(event->pos(), m_currentPlane);
|
||||
if (m_isSnappingHorizontal) {
|
||||
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::YZ) worldPos.setZ(m_firstLinePoint.Z());
|
||||
} else if (m_isSnappingVertical) {
|
||||
@@ -312,8 +327,9 @@ void ViewportWidget::mousePressEvent(QMouseEvent *event)
|
||||
} else {
|
||||
emit lineAdded(m_firstLinePoint, p);
|
||||
m_firstLinePoint = p;
|
||||
}
|
||||
update();
|
||||
}
|
||||
update();
|
||||
} else {
|
||||
lastPos = event->pos();
|
||||
}
|
||||
@@ -323,9 +339,17 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
m_currentMousePos = event->pos();
|
||||
|
||||
if (m_isSelectingPlane) {
|
||||
SketchPlane newHighlight = checkPlaneSelection(m_currentMousePos);
|
||||
if (newHighlight != m_highlightedPlane) {
|
||||
m_highlightedPlane = newHighlight;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldSnap = false;
|
||||
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;
|
||||
|
||||
switch (m_currentPlane) {
|
||||
@@ -354,7 +378,7 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
bool oldIsSnappingVertex = m_isSnappingVertex;
|
||||
m_isSnappingVertex = false;
|
||||
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;
|
||||
|
||||
for (Feature* feature : m_document->features()) {
|
||||
@@ -402,7 +426,7 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
m_isSnappingVertical = false;
|
||||
|
||||
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 delta = worldPos - startPos;
|
||||
|
||||
@@ -538,6 +562,13 @@ void ViewportWidget::onSketchModeStarted(SketchPlane plane)
|
||||
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
|
||||
}
|
||||
|
||||
void ViewportWidget::onPlaneSelectionModeStarted()
|
||||
{
|
||||
m_isSelectingPlane = true;
|
||||
m_highlightedPlane = SketchPlane::NONE;
|
||||
update();
|
||||
}
|
||||
|
||||
void ViewportWidget::onSketchModeEnded()
|
||||
{
|
||||
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;
|
||||
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 planeNormal;
|
||||
switch (m_currentPlane) {
|
||||
switch (plane) {
|
||||
case SketchPlane::XY: planeNormal = QVector3D(0, 1, 0); break;
|
||||
case SketchPlane::XZ: planeNormal = QVector3D(0, 0, 1); break;
|
||||
case SketchPlane::YZ: planeNormal = QVector3D(1, 0, 0); break;
|
||||
@@ -754,3 +785,105 @@ void ViewportWidget::drawSketch(const SketchFeature* sketch)
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
public slots:
|
||||
void onSketchModeStarted(SketchPlane plane);
|
||||
void onSketchModeEnded();
|
||||
void onPlaneSelectionModeStarted();
|
||||
void onActiveToolChanged(int tool);
|
||||
|
||||
float xRotation() const;
|
||||
@@ -62,6 +63,7 @@ public slots:
|
||||
|
||||
signals:
|
||||
void lineAdded(const gp_Pnt& start, const gp_Pnt& end);
|
||||
void planeSelected(SketchPlane plane);
|
||||
|
||||
protected:
|
||||
void initializeGL() override;
|
||||
@@ -75,9 +77,11 @@ protected:
|
||||
|
||||
private:
|
||||
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 drawSketch(const SketchFeature* sketch);
|
||||
void drawSelectionPlanes();
|
||||
ViewportWidget::SketchPlane checkPlaneSelection(const QPoint& screenPos);
|
||||
|
||||
QMatrix4x4 projection;
|
||||
ViewCube* m_viewCube;
|
||||
@@ -86,6 +90,9 @@ private:
|
||||
Document* m_document = nullptr;
|
||||
SketchPlane m_currentPlane = SketchPlane::NONE;
|
||||
|
||||
bool m_isSelectingPlane = false;
|
||||
SketchPlane m_highlightedPlane = SketchPlane::NONE;
|
||||
|
||||
int m_activeTool = 0;
|
||||
bool m_isDefiningLine = false;
|
||||
gp_Pnt m_firstLinePoint;
|
||||
|
||||
Reference in New Issue
Block a user