diff --git a/CMakeLists.txt b/CMakeLists.txt index 167e731..0cca8af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ add_executable(OpenCAD src/Feature.cpp src/SketchFeature.cpp src/SketchLine.cpp + src/SketchRectangle.cpp src/FeatureBrowser.cpp src/ApplicationController.cpp src/Camera.cpp diff --git a/src/ApplicationController.cpp b/src/ApplicationController.cpp index 0c113d5..df5d95a 100644 --- a/src/ApplicationController.cpp +++ b/src/ApplicationController.cpp @@ -2,6 +2,7 @@ #include "Document.h" #include "SketchFeature.h" #include "SketchLine.h" +#include "SketchRectangle.h" #include "MainWindow.h" #include @@ -140,6 +141,13 @@ void ApplicationController::addLine(const gp_Pnt& start, const gp_Pnt& end) } } +void ApplicationController::addRectangle(const gp_Pnt& corner1, const gp_Pnt& corner2) +{ + if (m_activeSketch) { + m_activeSketch->addObject(new SketchRectangle(corner1, corner2)); + } +} + void ApplicationController::endSketch() { m_activeSketch = nullptr; diff --git a/src/ApplicationController.h b/src/ApplicationController.h index 72498ad..5ede770 100644 --- a/src/ApplicationController.h +++ b/src/ApplicationController.h @@ -32,6 +32,7 @@ public: public slots: void setActiveTool(ToolType tool); void addLine(const gp_Pnt& start, const gp_Pnt& end); + void addRectangle(const gp_Pnt& corner1, const gp_Pnt& corner2); void newDocument(); bool openDocument(); bool saveDocument(); diff --git a/src/SketchFeature.cpp b/src/SketchFeature.cpp index 51d2c96..eae6d17 100644 --- a/src/SketchFeature.cpp +++ b/src/SketchFeature.cpp @@ -1,6 +1,7 @@ #include "SketchFeature.h" #include "SketchObject.h" #include "SketchLine.h" +#include "SketchRectangle.h" #include @@ -66,6 +67,10 @@ void SketchFeature::read(const QJsonObject& json) auto line = new SketchLine(); line->read(objectJson); m_objects.append(line); + } else if (type == "Rectangle") { + auto rect = new SketchRectangle(); + rect->read(objectJson); + m_objects.append(rect); } } } diff --git a/src/SketchObject.h b/src/SketchObject.h index 363c070..c7a356c 100644 --- a/src/SketchObject.h +++ b/src/SketchObject.h @@ -7,7 +7,8 @@ class SketchObject { public: enum class ObjectType { - Line + Line, + Rectangle }; SketchObject() = default; diff --git a/src/SketchRectangle.cpp b/src/SketchRectangle.cpp new file mode 100644 index 0000000..254965f --- /dev/null +++ b/src/SketchRectangle.cpp @@ -0,0 +1,61 @@ +#include "SketchRectangle.h" +#include + +namespace +{ + void pointToJson(const gp_Pnt& p, QJsonArray& arr) + { + arr.append(p.X()); + arr.append(p.Y()); + arr.append(p.Z()); + } + + gp_Pnt jsonToPoint(const QJsonArray& arr) + { + return gp_Pnt(arr[0].toDouble(), arr[1].toDouble(), arr[2].toDouble()); + } +} + +SketchRectangle::SketchRectangle() +{ +} + +SketchRectangle::SketchRectangle(const gp_Pnt& corner1, const gp_Pnt& corner2) + : m_corner1(corner1), m_corner2(corner2) +{ +} + +SketchObject::ObjectType SketchRectangle::type() const +{ + return ObjectType::Rectangle; +} + +void SketchRectangle::read(const QJsonObject& json) +{ + if (json.contains("corner1") && json["corner1"].isArray()) { + m_corner1 = jsonToPoint(json["corner1"].toArray()); + } + if (json.contains("corner2") && json["corner2"].isArray()) { + m_corner2 = jsonToPoint(json["corner2"].toArray()); + } +} + +void SketchRectangle::write(QJsonObject& json) const +{ + json["type"] = "Rectangle"; + QJsonArray c1, c2; + pointToJson(m_corner1, c1); + pointToJson(m_corner2, c2); + json["corner1"] = c1; + json["corner2"] = c2; +} + +const gp_Pnt& SketchRectangle::corner1() const +{ + return m_corner1; +} + +const gp_Pnt& SketchRectangle::corner2() const +{ + return m_corner2; +} diff --git a/src/SketchRectangle.h b/src/SketchRectangle.h new file mode 100644 index 0000000..1cf1203 --- /dev/null +++ b/src/SketchRectangle.h @@ -0,0 +1,26 @@ +#ifndef SKETCHRECTANGLE_H +#define SKETCHRECTANGLE_H + +#include "SketchObject.h" +#include + +class SketchRectangle : public SketchObject +{ +public: + SketchRectangle(); + SketchRectangle(const gp_Pnt& corner1, const gp_Pnt& corner2); + + ObjectType type() const override; + + void read(const QJsonObject& json) override; + void write(QJsonObject& json) const override; + + const gp_Pnt& corner1() const; + const gp_Pnt& corner2() const; + +private: + gp_Pnt m_corner1; + gp_Pnt m_corner2; +}; + +#endif // SKETCHRECTANGLE_H diff --git a/src/ViewportWidget.cpp b/src/ViewportWidget.cpp index a49e104..311391c 100644 --- a/src/ViewportWidget.cpp +++ b/src/ViewportWidget.cpp @@ -6,6 +6,7 @@ #include "FeatureBrowser.h" #include "SketchFeature.h" #include "SketchLine.h" +#include "SketchRectangle.h" #include "SketchObject.h" #include "ApplicationController.h" #include @@ -196,6 +197,47 @@ void ViewportWidget::paintGL() glDisable(GL_BLEND); } + if (m_isDefiningRectangle && m_activeTool == static_cast(ApplicationController::ToolType::Rectangle)) { + vertices.clear(); + QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane); + QVector3D startPos(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z()); + + if (m_isSnappingOrigin) { + worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0); + } else if (m_isSnappingVertex) { + worldPos.setX(m_snapVertex.X()); worldPos.setY(m_snapVertex.Y()); worldPos.setZ(m_snapVertex.Z()); + } + + QVector3D p1 = startPos; + QVector3D p2, p3, p4; + p3 = worldPos; + + if (m_currentPlane == SketchPlane::XY) { + p2.setX(p3.x()); p2.setY(p1.y()); p2.setZ(p1.z()); + p4.setX(p1.x()); p4.setY(p1.y()); p4.setZ(p3.z()); + } else if (m_currentPlane == SketchPlane::XZ) { + p2.setX(p3.x()); p2.setY(p1.y()); p2.setZ(p1.z()); + p4.setX(p1.x()); p4.setY(p3.y()); p4.setZ(p1.z()); + } else if (m_currentPlane == SketchPlane::YZ) { + p2.setX(p1.x()); p2.setY(p3.y()); p2.setZ(p1.z()); + p4.setX(p1.x()); p4.setY(p1.y()); p4.setZ(p3.z()); + } + + vertices << p1.x() << p1.y() << p1.z(); + vertices << p2.x() << p2.y() << p2.z(); + vertices << p2.x() << p2.y() << p2.z(); + vertices << p3.x() << p3.y() << p3.z(); + vertices << p3.x() << p3.y() << p3.z(); + vertices << p4.x() << p4.y() << p4.z(); + vertices << p4.x() << p4.y() << p4.z(); + vertices << p1.x() << p1.y() << p1.z(); + + m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 1.0f)); + m_vbo.bind(); + m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(GLfloat)); + glDrawArrays(GL_LINES, 0, 8); + } + if (m_isDefiningLine && m_activeTool == static_cast(ApplicationController::ToolType::Line)) { vertices.clear(); QVector3D worldPos; @@ -544,6 +586,65 @@ void ViewportWidget::paintGL() } m_featureBrowser->paint(painter, width(), height()); + if (m_isDefiningRectangle && m_activeTool == static_cast(ApplicationController::ToolType::Rectangle)) { + QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane); + if (m_isSnappingOrigin) { + worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0); + } else if (m_isSnappingVertex) { + worldPos.setX(m_snapVertex.X()); worldPos.setY(m_snapVertex.Y()); worldPos.setZ(m_snapVertex.Z()); + } + + QVector3D p1_3d(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z()); + QVector3D p3_3d = worldPos; + QVector3D p2_3d, p4_3d; + + double w, h; + + if (m_currentPlane == SketchPlane::XY) { + p2_3d.setX(p3_3d.x()); p2_3d.setY(p1_3d.y()); p2_3d.setZ(p1_3d.z()); + p4_3d.setX(p1_3d.x()); p4_3d.setY(p1_3d.y()); p4_3d.setZ(p3_3d.z()); + w = qAbs(p3_3d.x() - p1_3d.x()); + h = qAbs(p3_3d.z() - p1_3d.z()); + } else if (m_currentPlane == SketchPlane::XZ) { + p2_3d.setX(p3_3d.x()); p2_3d.setY(p1_3d.y()); p2_3d.setZ(p1_3d.z()); + p4_3d.setX(p1_3d.x()); p4_3d.setY(p3_3d.y()); p4_3d.setZ(p1_3d.z()); + w = qAbs(p3_3d.x() - p1_3d.x()); + h = qAbs(p3_3d.y() - p1_3d.y()); + } else { // YZ + p2_3d.setX(p1_3d.x()); p2_3d.setY(p3_3d.y()); p2_3d.setZ(p1_3d.z()); + p4_3d.setX(p1_3d.x()); p4_3d.setY(p1_3d.y()); p4_3d.setZ(p3_3d.z()); + w = qAbs(p3_3d.y() - p1_3d.y()); + h = qAbs(p3_3d.z() - p1_3d.z()); + } + + painter.setRenderHint(QPainter::Antialiasing); + QFontMetrics fm(painter.font()); + + // Width dimension + QVector3D widthTextPos3D = (p1_3d + p2_3d) / 2.0f; + QVector3D screenPosW = project(widthTextPos3D, model, projection, rect()); + if (screenPosW.z() < 1.0f) { + QString widthText = QString::number(w, 'f', 2); + QRect textRect = fm.boundingRect(widthText + "__"); + textRect.moveCenter(screenPosW.toPoint() + QPoint(0, (p3_3d.z() > p1_3d.z() || p3_3d.y() > p1_3d.y()) ? -15 : 15)); + painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(50, 50, 50)); + painter.setPen(Qt::white); + painter.drawText(textRect, Qt::AlignCenter, widthText); + } + + // Height dimension + QVector3D heightTextPos3D = (p2_3d + p3_3d) / 2.0f; + QVector3D screenPosH = project(heightTextPos3D, model, projection, rect()); + if (screenPosH.z() < 1.0f) { + QString heightText = QString::number(h, 'f', 2); + QRect textRect = fm.boundingRect(heightText + "__"); + textRect.moveCenter(screenPosH.toPoint() + QPoint((p3_3d.x() > p1_3d.x()) ? 15 : -15, 0)); + painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(50, 50, 50)); + painter.setPen(Qt::white); + painter.drawText(textRect, Qt::AlignCenter, heightText); + } + } + if (m_isDefiningLine && m_activeTool == static_cast(ApplicationController::ToolType::Line)) { QVector3D worldPos; QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z()); @@ -802,6 +903,28 @@ void ViewportWidget::mousePressEvent(QMouseEvent *event) return; } + if (m_currentPlane != SketchPlane::NONE && m_activeTool == static_cast(ApplicationController::ToolType::Rectangle)) { + 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); + p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z()); + } + + if (!m_isDefiningRectangle) { + m_firstRectanglePoint = p; + m_isDefiningRectangle = true; + } else { + emit rectangleAdded(m_firstRectanglePoint, p); + m_isDefiningRectangle = false; + } + update(); + return; + } + if (m_currentPlane != SketchPlane::NONE && m_activeTool == static_cast(ApplicationController::ToolType::Line)) { gp_Pnt p; QString dimInput = property("dimensionInput").toString(); @@ -1202,6 +1325,12 @@ void ViewportWidget::keyPressEvent(QKeyEvent *event) update(); return; } + if (m_isDefiningRectangle) { + m_isDefiningRectangle = false; + emit toolDeactivated(); + update(); + return; + } if (m_isSelectingPlane) { m_isSelectingPlane = false; m_highlightedPlane = SketchPlane::NONE; @@ -1394,6 +1523,7 @@ void ViewportWidget::onActiveToolChanged(int tool) { m_activeTool = tool; m_isDefiningLine = false; + m_isDefiningRectangle = false; if (tool == static_cast(ApplicationController::ToolType::None)) { unsetCursor(); @@ -1499,6 +1629,36 @@ void ViewportWidget::drawSketch(const SketchFeature* sketch) vertexCounts[start]++; vertexCounts[end]++; + } else if (obj->type() == SketchObject::ObjectType::Rectangle) { + auto rect = static_cast(obj); + const auto& p1 = rect->corner1(); + const auto& p3 = rect->corner2(); + + gp_Pnt p2, p4; + if (sketch->plane() == SketchFeature::SketchPlane::XY) { + p2.SetCoord(p3.X(), p1.Y(), p1.Z()); + p4.SetCoord(p1.X(), p1.Y(), p3.Z()); + } else if (sketch->plane() == SketchFeature::SketchPlane::XZ) { + p2.SetCoord(p3.X(), p1.Y(), p1.Z()); + p4.SetCoord(p1.X(), p3.Y(), p1.Z()); + } else if (sketch->plane() == SketchFeature::SketchPlane::YZ) { + p2.SetCoord(p1.X(), p3.Y(), p1.Z()); + p4.SetCoord(p1.X(), p1.Y(), p3.Z()); + } + + lineVertices << p1.X() << p1.Y() << p1.Z(); + lineVertices << p2.X() << p2.Y() << p2.Z(); + lineVertices << p2.X() << p2.Y() << p2.Z(); + lineVertices << p3.X() << p3.Y() << p3.Z(); + lineVertices << p3.X() << p3.Y() << p3.Z(); + lineVertices << p4.X() << p4.Y() << p4.Z(); + lineVertices << p4.X() << p4.Y() << p4.Z(); + lineVertices << p1.X() << p1.Y() << p1.Z(); + + vertexCounts[p1] += 2; + vertexCounts[p2] += 2; + vertexCounts[p3] += 2; + vertexCounts[p4] += 2; } } diff --git a/src/ViewportWidget.h b/src/ViewportWidget.h index d4fe63b..f934f3f 100644 --- a/src/ViewportWidget.h +++ b/src/ViewportWidget.h @@ -46,6 +46,7 @@ public slots: signals: void lineAdded(const gp_Pnt& start, const gp_Pnt& end); + void rectangleAdded(const gp_Pnt& corner1, const gp_Pnt& corner2); void planeSelected(SketchPlane plane); void toolDeactivated(); @@ -91,6 +92,8 @@ private: int m_activeTool = 0; bool m_isDefiningLine = false; gp_Pnt m_firstLinePoint; + bool m_isDefiningRectangle = false; + gp_Pnt m_firstRectanglePoint; QPoint m_currentMousePos; bool m_isSnappingOrigin = false; bool m_isSnappingVertex = false;