feat: Add sketch creation with plane selection, oriented grid and labeled axes

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2026-02-09 16:51:15 -07:00
parent b32594a04b
commit ec658360a6
7 changed files with 280 additions and 2 deletions

View File

@@ -15,6 +15,7 @@ add_executable(OpenCAD
src/MainWindow.cpp src/MainWindow.cpp
src/ViewportWidget.cpp src/ViewportWidget.cpp
src/ViewCube.cpp src/ViewCube.cpp
src/SketchGrid.cpp
resources.qrc resources.qrc
) )

View File

@@ -7,6 +7,8 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QToolButton> #include <QToolButton>
#include <QIcon> #include <QIcon>
#include <QInputDialog>
#include <QStringList>
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) : QMainWindow(parent)
@@ -34,6 +36,7 @@ MainWindow::MainWindow(QWidget *parent)
createSketchButton->setIcon(QIcon(":/icons/create-sketch.svg")); createSketchButton->setIcon(QIcon(":/icons/create-sketch.svg"));
createSketchButton->setIconSize(QSize(48, 48)); createSketchButton->setIconSize(QSize(48, 48));
createSketchButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); createSketchButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
connect(createSketchButton, &QToolButton::clicked, this, &MainWindow::createSketch);
solidLayout->addWidget(createSketchButton); solidLayout->addWidget(createSketchButton);
QToolButton *extrudeButton = new QToolButton(); QToolButton *extrudeButton = new QToolButton();
@@ -53,6 +56,25 @@ MainWindow::MainWindow(QWidget *parent)
QWidget *toolsTab = new QWidget(); QWidget *toolsTab = new QWidget();
tabWidget->addTab(toolsTab, "TOOLS"); tabWidget->addTab(toolsTab, "TOOLS");
ViewportWidget *viewport = new ViewportWidget; m_viewport = new ViewportWidget;
setCentralWidget(viewport); setCentralWidget(m_viewport);
}
void MainWindow::createSketch()
{
QStringList items;
items << "XY-Plane" << "XZ-Plane" << "YZ-Plane";
bool ok;
QString item = QInputDialog::getItem(this, "Select Sketch Plane",
"Plane:", items, 0, false, &ok);
if (ok && !item.isEmpty()) {
if (item == "XY-Plane") {
m_viewport->startSketch(ViewportWidget::SketchPlane::XY);
} else if (item == "XZ-Plane") {
m_viewport->startSketch(ViewportWidget::SketchPlane::XZ);
} else if (item == "YZ-Plane") {
m_viewport->startSketch(ViewportWidget::SketchPlane::YZ);
}
}
} }

View File

@@ -3,12 +3,20 @@
#include <QMainWindow> #include <QMainWindow>
class ViewportWidget;
class MainWindow : public QMainWindow class MainWindow : public QMainWindow
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit MainWindow(QWidget *parent = nullptr); explicit MainWindow(QWidget *parent = nullptr);
private slots:
void createSketch();
private:
ViewportWidget *m_viewport;
}; };
#endif // MAINWINDOW_H #endif // MAINWINDOW_H

112
src/SketchGrid.cpp Normal file
View File

@@ -0,0 +1,112 @@
#include "SketchGrid.h"
SketchGrid::SketchGrid()
{
}
SketchGrid::~SketchGrid()
{
}
void SketchGrid::initializeGL()
{
initializeOpenGLFunctions();
}
void SketchGrid::paintGL(SketchPlane plane, const QMatrix4x4& projection, const QMatrix4x4& modelview)
{
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(projection.constData());
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(modelview.constData());
glPushAttrib(GL_LINE_BIT | GL_POINT_BIT | GL_CURRENT_BIT);
glDisable(GL_DEPTH_TEST);
drawGridLines(plane);
drawAxes(plane);
glEnable(GL_DEPTH_TEST);
glPopAttrib();
}
void SketchGrid::drawGridLines(SketchPlane plane)
{
const int gridSize = 50;
const float darkFactor = 0.8f;
// Lighter lines
glColor3f(0.5f, 0.5f, 0.5f);
glLineWidth(1.0f);
glBegin(GL_LINES);
for (int i = -gridSize; i <= gridSize; ++i)
{
if (i == 0 || i % 5 == 0) continue;
if (plane == XY) {
glVertex3f(i, -gridSize, 0); glVertex3f(i, gridSize, 0);
glVertex3f(-gridSize, i, 0); glVertex3f(gridSize, i, 0);
} else if (plane == XZ) {
glVertex3f(i, 0, -gridSize); glVertex3f(i, 0, gridSize);
glVertex3f(-gridSize, 0, i); glVertex3f(gridSize, 0, i);
} else { // YZ
glVertex3f(0, i, -gridSize); glVertex3f(0, i, gridSize);
glVertex3f(0, -gridSize, i); glVertex3f(0, gridSize, i);
}
}
glEnd();
// Darker lines
glColor3f(0.5f * darkFactor, 0.5f * darkFactor, 0.5f * darkFactor);
glLineWidth(1.5f);
glBegin(GL_LINES);
for (int i = -gridSize; i <= gridSize; i += 5)
{
if (i == 0) continue;
if (plane == XY) {
glVertex3f(i, -gridSize, 0); glVertex3f(i, gridSize, 0);
glVertex3f(-gridSize, i, 0); glVertex3f(gridSize, i, 0);
} else if (plane == XZ) {
glVertex3f(i, 0, -gridSize); glVertex3f(i, 0, gridSize);
glVertex3f(-gridSize, 0, i); glVertex3f(gridSize, 0, i);
} else { // YZ
glVertex3f(0, i, -gridSize); glVertex3f(0, i, gridSize);
glVertex3f(0, -gridSize, i); glVertex3f(0, gridSize, i);
}
}
glEnd();
}
void SketchGrid::drawAxes(SketchPlane plane)
{
const int axisLength = 50;
glLineWidth(2.0f);
glBegin(GL_LINES);
// X Axis (Red)
if (plane == XY || plane == XZ) {
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(-axisLength, 0, 0);
glVertex3f(axisLength, 0, 0);
}
// Y Axis (Green)
if (plane == XY || plane == YZ) {
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(0, -axisLength, 0);
glVertex3f(0, axisLength, 0);
}
// Z Axis (Blue)
if (plane == XZ || plane == YZ) {
glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f(0, 0, -axisLength);
glVertex3f(0, 0, axisLength);
}
glEnd();
// Origin dot
glPointSize(5.0f);
glColor3f(1.0f, 1.0f, 1.0f); // White
glBegin(GL_POINTS);
glVertex3f(0, 0, 0);
glEnd();
}

27
src/SketchGrid.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef SKETCHGRID_H
#define SKETCHGRID_H
#include <QOpenGLFunctions>
#include <QMatrix4x4>
class SketchGrid : protected QOpenGLFunctions
{
public:
enum SketchPlane {
XY = 1,
XZ = 2,
YZ = 3
};
SketchGrid();
~SketchGrid();
void initializeGL();
void paintGL(SketchPlane plane, const QMatrix4x4& projection, const QMatrix4x4& modelview);
private:
void drawGridLines(SketchPlane plane);
void drawAxes(SketchPlane plane);
};
#endif // SKETCHGRID_H

View File

@@ -1,18 +1,24 @@
#include "ViewportWidget.h" #include "ViewportWidget.h"
#include "ViewCube.h" #include "ViewCube.h"
#include "SketchGrid.h"
#include <QMouseEvent> #include <QMouseEvent>
#include <QWheelEvent> #include <QWheelEvent>
#include <QApplication> #include <QApplication>
#include <QPainter>
#include <QWheelEvent>
#include <QApplication>
ViewportWidget::ViewportWidget(QWidget *parent) ViewportWidget::ViewportWidget(QWidget *parent)
: QOpenGLWidget(parent) : QOpenGLWidget(parent)
{ {
m_viewCube = new ViewCube(); m_viewCube = new ViewCube();
m_sketchGrid = new SketchGrid();
} }
ViewportWidget::~ViewportWidget() ViewportWidget::~ViewportWidget()
{ {
delete m_viewCube; delete m_viewCube;
delete m_sketchGrid;
} }
void ViewportWidget::initializeGL() void ViewportWidget::initializeGL()
@@ -21,6 +27,7 @@ void ViewportWidget::initializeGL()
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
m_viewCube->initializeGL(); m_viewCube->initializeGL();
m_sketchGrid->initializeGL();
} }
void ViewportWidget::paintGL() void ViewportWidget::paintGL()
@@ -42,6 +49,10 @@ void ViewportWidget::paintGL()
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(model.constData()); glLoadMatrixf(model.constData());
if (m_currentPlane != SketchPlane::NONE) {
m_sketchGrid->paintGL(static_cast<SketchGrid::SketchPlane>(m_currentPlane), projection, model);
}
// View cube rendering // View cube rendering
QMatrix4x4 viewCubeModel; QMatrix4x4 viewCubeModel;
viewCubeModel.rotate(xRot / 16.0f, 1, 0, 0); viewCubeModel.rotate(xRot / 16.0f, 1, 0, 0);
@@ -49,6 +60,12 @@ void ViewportWidget::paintGL()
m_viewCube->paintGL(viewCubeModel, width(), height()); m_viewCube->paintGL(viewCubeModel, width(), height());
glViewport(0, 0, width(), height()); glViewport(0, 0, width(), height());
if (m_currentPlane != SketchPlane::NONE) {
QPainter painter(this);
drawAxisLabels(painter, model, projection);
painter.end();
}
} }
void ViewportWidget::resizeGL(int w, int h) void ViewportWidget::resizeGL(int w, int h)
@@ -90,3 +107,77 @@ void ViewportWidget::wheelEvent(QWheelEvent *event)
} }
update(); update();
} }
void ViewportWidget::startSketch(SketchPlane plane)
{
m_currentPlane = plane;
panX = 0;
panY = 0;
zoom = -20.0f; // Zoom out to see the grid
switch (plane) {
case SketchPlane::XY: // Top view
xRot = -90 * 16;
yRot = 0;
break;
case SketchPlane::XZ: // Front view
xRot = 0;
yRot = 0;
break;
case SketchPlane::YZ: // Right view
xRot = 0;
yRot = 90 * 16;
break;
case SketchPlane::NONE:
break;
}
update();
}
QVector3D ViewportWidget::project(const QVector3D& worldCoord, const QMatrix4x4& modelView, const QMatrix4x4& projection, const QRect& viewport)
{
QVector4D clipCoord = projection * modelView * QVector4D(worldCoord, 1.0);
if (qFuzzyCompare(clipCoord.w(), 0.0f)) {
return QVector3D(-1, -1, -1);
}
QVector3D ndc(clipCoord.x() / clipCoord.w(), clipCoord.y() / clipCoord.w(), clipCoord.z() / clipCoord.w());
float winX = viewport.x() + viewport.width() * (ndc.x() + 1.0) / 2.0;
float winY = viewport.y() + viewport.height() * (1.0 - (ndc.y() + 1.0) / 2.0); // Y is inverted
return QVector3D(winX, winY, (ndc.z() + 1.0) / 2.0);
}
void ViewportWidget::drawAxisLabels(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection)
{
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 10));
const int range = 50;
const int step = 5;
auto drawLabelsForAxis = [&](int axis_idx) {
for (int i = -range; i <= range; i += step) {
if (i == 0) continue;
QVector3D worldCoord;
worldCoord[axis_idx] = i;
QVector3D screenPos = project(worldCoord, modelView, projection, rect());
if (screenPos.z() < 1.0f) { // Not clipped
painter.drawText(screenPos.toPoint(), QString::number(i));
}
}
};
if (m_currentPlane == SketchPlane::XY) {
drawLabelsForAxis(0); // X
drawLabelsForAxis(1); // Y
} else if (m_currentPlane == SketchPlane::XZ) {
drawLabelsForAxis(0); // X
drawLabelsForAxis(2); // Z
} else if (m_currentPlane == SketchPlane::YZ) {
drawLabelsForAxis(1); // Y
drawLabelsForAxis(2); // Z
}
}

View File

@@ -5,17 +5,29 @@
#include <QOpenGLFunctions> #include <QOpenGLFunctions>
#include <QMatrix4x4> #include <QMatrix4x4>
#include <QPoint> #include <QPoint>
#include <QVector3D>
#include <QRect>
class ViewCube; class ViewCube;
class SketchGrid;
class ViewportWidget : public QOpenGLWidget, protected QOpenGLFunctions class ViewportWidget : public QOpenGLWidget, protected QOpenGLFunctions
{ {
Q_OBJECT Q_OBJECT
public: public:
enum class SketchPlane {
NONE,
XY,
XZ,
YZ
};
explicit ViewportWidget(QWidget *parent = nullptr); explicit ViewportWidget(QWidget *parent = nullptr);
~ViewportWidget(); ~ViewportWidget();
void startSketch(SketchPlane plane);
protected: protected:
void initializeGL() override; void initializeGL() override;
void paintGL() override; void paintGL() override;
@@ -26,8 +38,13 @@ protected:
void wheelEvent(QWheelEvent *event) override; void wheelEvent(QWheelEvent *event) override;
private: private:
QVector3D project(const QVector3D& worldCoord, const QMatrix4x4& modelView, const QMatrix4x4& projection, const QRect& viewport);
void drawAxisLabels(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection);
QMatrix4x4 projection; QMatrix4x4 projection;
ViewCube* m_viewCube; ViewCube* m_viewCube;
SketchGrid* m_sketchGrid = nullptr;
SketchPlane m_currentPlane = SketchPlane::NONE;
float xRot = 0; float xRot = 0;
float yRot = 0; float yRot = 0;