diff --git a/CMakeLists.txt b/CMakeLists.txt index 42c5b49..bd058dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable(OpenCAD src/MainWindow.cpp src/ViewportWidget.cpp src/ViewCube.cpp + src/SketchGrid.cpp resources.qrc ) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 12e17d6..92b853a 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -34,6 +36,7 @@ MainWindow::MainWindow(QWidget *parent) createSketchButton->setIcon(QIcon(":/icons/create-sketch.svg")); createSketchButton->setIconSize(QSize(48, 48)); createSketchButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + connect(createSketchButton, &QToolButton::clicked, this, &MainWindow::createSketch); solidLayout->addWidget(createSketchButton); QToolButton *extrudeButton = new QToolButton(); @@ -53,6 +56,25 @@ MainWindow::MainWindow(QWidget *parent) QWidget *toolsTab = new QWidget(); tabWidget->addTab(toolsTab, "TOOLS"); - ViewportWidget *viewport = new ViewportWidget; - setCentralWidget(viewport); + m_viewport = new ViewportWidget; + 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); + } + } } diff --git a/src/MainWindow.h b/src/MainWindow.h index 180a302..ae29f45 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -3,12 +3,20 @@ #include +class ViewportWidget; + class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); + +private slots: + void createSketch(); + +private: + ViewportWidget *m_viewport; }; #endif // MAINWINDOW_H diff --git a/src/SketchGrid.cpp b/src/SketchGrid.cpp new file mode 100644 index 0000000..1353af4 --- /dev/null +++ b/src/SketchGrid.cpp @@ -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(); +} diff --git a/src/SketchGrid.h b/src/SketchGrid.h new file mode 100644 index 0000000..6027c03 --- /dev/null +++ b/src/SketchGrid.h @@ -0,0 +1,27 @@ +#ifndef SKETCHGRID_H +#define SKETCHGRID_H + +#include +#include + +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 diff --git a/src/ViewportWidget.cpp b/src/ViewportWidget.cpp index 18b1e80..852c709 100644 --- a/src/ViewportWidget.cpp +++ b/src/ViewportWidget.cpp @@ -1,18 +1,24 @@ #include "ViewportWidget.h" #include "ViewCube.h" +#include "SketchGrid.h" #include #include #include +#include +#include +#include ViewportWidget::ViewportWidget(QWidget *parent) : QOpenGLWidget(parent) { m_viewCube = new ViewCube(); + m_sketchGrid = new SketchGrid(); } ViewportWidget::~ViewportWidget() { delete m_viewCube; + delete m_sketchGrid; } void ViewportWidget::initializeGL() @@ -21,6 +27,7 @@ void ViewportWidget::initializeGL() glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glEnable(GL_DEPTH_TEST); m_viewCube->initializeGL(); + m_sketchGrid->initializeGL(); } void ViewportWidget::paintGL() @@ -42,6 +49,10 @@ void ViewportWidget::paintGL() glMatrixMode(GL_MODELVIEW); glLoadMatrixf(model.constData()); + if (m_currentPlane != SketchPlane::NONE) { + m_sketchGrid->paintGL(static_cast(m_currentPlane), projection, model); + } + // View cube rendering QMatrix4x4 viewCubeModel; viewCubeModel.rotate(xRot / 16.0f, 1, 0, 0); @@ -49,6 +60,12 @@ void ViewportWidget::paintGL() m_viewCube->paintGL(viewCubeModel, 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) @@ -90,3 +107,77 @@ void ViewportWidget::wheelEvent(QWheelEvent *event) } 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 + } +} diff --git a/src/ViewportWidget.h b/src/ViewportWidget.h index 400d609..9869e82 100644 --- a/src/ViewportWidget.h +++ b/src/ViewportWidget.h @@ -5,17 +5,29 @@ #include #include #include +#include +#include class ViewCube; +class SketchGrid; class ViewportWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT public: + enum class SketchPlane { + NONE, + XY, + XZ, + YZ + }; + explicit ViewportWidget(QWidget *parent = nullptr); ~ViewportWidget(); + void startSketch(SketchPlane plane); + protected: void initializeGL() override; void paintGL() override; @@ -26,8 +38,13 @@ protected: void wheelEvent(QWheelEvent *event) override; 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; ViewCube* m_viewCube; + SketchGrid* m_sketchGrid = nullptr; + SketchPlane m_currentPlane = SketchPlane::NONE; float xRot = 0; float yRot = 0;