refactor: Implement Rectangle tool with snapping and dimensions
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "Document.h"
|
||||
#include "SketchFeature.h"
|
||||
#include "SketchLine.h"
|
||||
#include "SketchRectangle.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include <QInputDialog>
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "SketchFeature.h"
|
||||
#include "SketchObject.h"
|
||||
#include "SketchLine.h"
|
||||
#include "SketchRectangle.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ class SketchObject
|
||||
{
|
||||
public:
|
||||
enum class ObjectType {
|
||||
Line
|
||||
Line,
|
||||
Rectangle
|
||||
};
|
||||
|
||||
SketchObject() = default;
|
||||
|
||||
61
src/SketchRectangle.cpp
Normal file
61
src/SketchRectangle.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "SketchRectangle.h"
|
||||
#include <QJsonArray>
|
||||
|
||||
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;
|
||||
}
|
||||
26
src/SketchRectangle.h
Normal file
26
src/SketchRectangle.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef SKETCHRECTANGLE_H
|
||||
#define SKETCHRECTANGLE_H
|
||||
|
||||
#include "SketchObject.h"
|
||||
#include <gp_Pnt.hxx>
|
||||
|
||||
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
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "FeatureBrowser.h"
|
||||
#include "SketchFeature.h"
|
||||
#include "SketchLine.h"
|
||||
#include "SketchRectangle.h"
|
||||
#include "SketchObject.h"
|
||||
#include "ApplicationController.h"
|
||||
#include <QMouseEvent>
|
||||
@@ -196,6 +197,47 @@ void ViewportWidget::paintGL()
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
if (m_isDefiningRectangle && m_activeTool == static_cast<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<const SketchRectangle*>(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user