Compare commits

...

62 Commits

Author SHA1 Message Date
d43b49a12f fix: Reset dimension properties when defining new circle
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 15:42:19 -07:00
2407957fb6 fix: Clear CircleTool dimension properties on activation
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 15:35:11 -07:00
8a90b17b1f fix: Adjust v-axis for XY and XZ sketch planes to match preview
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 15:30:56 -07:00
e8aef2427b fix: Correct CircleTool XY/XZ plane drawing logic
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 15:27:04 -07:00
2ab4bbf805 fix: Calculate circle radius from point distance
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 15:20:17 -07:00
7f6c01c8a0 feat: Implement circle drawing tool
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 15:15:24 -07:00
4b0a903052 feat: Implement CircleTool with diameter input and preview
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 15:08:57 -07:00
37399bd7c1 feat: Add CircleTool 2026-02-17 15:08:55 -07:00
9d9c658cda fix: Correct QString to const char* for Qt property methods
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 15:00:08 -07:00
e1327b2caa Refactor: Abstract dimension input and finalize creation logic to SketchTool
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 14:58:02 -07:00
d66f7aaf56 fix: Calculate shortest path for camera rotations
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 14:47:56 -07:00
64b48c7ed1 feat: Add limits for camera and pivot point distances
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 14:41:55 -07:00
7ce318b402 fix: Restore camera rotation and stabilize UI element scaling
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 13:58:58 -07:00
08820659d4 fix: Fix erratic camera distance during rotation
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 13:14:24 -07:00
94304bd2e3 feat: Add mouseReleaseEvent declaration
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 13:07:38 -07:00
88199a9d51 feat: Implement middle-mouse rotation around grid intersection with visual pivot
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 13:04:33 -07:00
68eeeb11ec fix: Adjust camera distance text position in viewport 2026-02-17 13:04:30 -07:00
e00af9a8e3 feat: Scale axis length with grid size
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 12:53:16 -07:00
ef55eac997 feat: Adjust grid scaling thresholds and parameters 2026-02-17 12:53:14 -07:00
483b673229 feat: Display camera zoom level in viewport
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 12:39:24 -07:00
e8afc0a4b4 fix: Correct dynamic grid scaling based on camera distance
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 12:34:54 -07:00
1779725d53 feat: Implement dynamic sketch grid based on camera zoom
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 12:32:02 -07:00
b056ccbfec style: Refine sketch grid line appearance 2026-02-17 12:31:59 -07:00
3a7cd78fb2 style: Lighten major grid lines and rename line vectors
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 12:25:08 -07:00
d274b4f59f refactor: Move axis label drawing logic from ViewportWidget to SketchGrid
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 12:19:46 -07:00
a0dbc537cf fix: Increase far clip plane for greater draw distance
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 12:19:46 -07:00
ddf6f6fd85 feat: Implement pixel-perfect camera panning
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 12:19:39 -07:00
f3a1f73f45 fix: Scale pan speed with zoom level for consistent movement
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:53:07 -07:00
3bb8d65fd4 feat: Adjust zoom speed based on distance for consistent feel
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:48:29 -07:00
6721caca9f fix: Update Qt API usage for QWheelEvent and QMatrix4x4
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:37:42 -07:00
38e931bc79 feat: Implement mouse wheel zoom to cursor position
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:36:44 -07:00
a66dc50daf refactor: Move camera mouse event handling to Camera class
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:34:25 -07:00
2e2f99f2c2 refactor: Move camera restore state animation to Camera
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:27:36 -07:00
f29f40277d refactor: Move sketch mode camera animations to Camera class
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:26:41 -07:00
a5e8257eb4 build: Automate source file discovery for C++ sources in src/
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:22:08 -07:00
ec63a23247 feat: Add sketch, line, rectangle tools and snapping 2026-02-17 11:22:07 -07:00
d708ab9827 refactor: Extract snapping logic into dedicated Snapping class
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:19:29 -07:00
e86a775b46 feat: Add snapping functionality 2026-02-17 11:19:27 -07:00
9334508b67 fix: Ensure viewport updates and consistent key handling in drawing tools
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:09:42 -07:00
b93f6158ef fix: Enable line tool functionality
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 11:02:54 -07:00
35cad74367 fix: Move line tool specific logic from ViewportWidget to LineTool
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 10:57:02 -07:00
2394727724 refactor: Move rectangle tool logic to RectangleTool class
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 10:53:02 -07:00
95a651b6b0 refactor: Move LineTool event handling to LineTool and add tool APIs
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 10:44:56 -07:00
d015d171ec refactor: Move LineTool paintGL and paint2D logic to LineTool
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 10:38:51 -07:00
1584bfd5a0 fix: Deactivate active tool on Escape key press 2026-02-17 10:38:47 -07:00
3e2f464de9 refactor: Introduce SketchTool base class and derived tool skeletons
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 10:18:36 -07:00
e64755ea0c feat: Make rectangle corners snappable
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 10:01:00 -07:00
b144efbe05 feat: Allow editable rectangle dimensions and fix Tab crash
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 09:57:36 -07:00
966ab037b5 fix: Connect rectangleAdded signal to ApplicationController
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 09:52:11 -07:00
246372b847 refactor: Implement Rectangle tool with snapping and dimensions
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 20:45:39 -07:00
2b455f57d4 fix: Set YZ plane line angle reference to horizontal
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 20:37:08 -07:00
ed88730edb fix: Correctly place dimension labels based on angle difference
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 20:32:23 -07:00
7f304bf1f3 fix: Position linear dimension opposite angle to avoid overlap
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 20:28:17 -07:00
f5c7f6d326 fix: Normalize angle for dimension text to align with arc
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 20:23:39 -07:00
c43330fe5e fix: Adjust chained line angle ref dir for free drawing
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 20:13:21 -07:00
9cacbf4e0e fix: Correctly position angle dimension text in all quadrants
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 19:53:48 -07:00
8febc50fec fix: Correct dimensioning arc direction and align text angle snapping
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 19:50:09 -07:00
c9c1b38f45 fix: Redefine angle dimension quadrant snapping
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 19:47:03 -07:00
c117ff3a8a fix: Adjust angle dimension arc direction on quadrant snap
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 19:31:23 -07:00
34cecc38d5 fix: Apply typed dimensions/angles on mouse click for lines
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 19:26:58 -07:00
2f7d2a4189 fix: Ensure angle dimension arrows always point outward
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 19:24:01 -07:00
1fb211cc34 feat: Highlight active dimension input blue by default
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 19:21:12 -07:00
26 changed files with 2756 additions and 1018 deletions

View File

@@ -28,19 +28,10 @@ endif()
message(STATUS "OpenCASCADE_INCLUDE_DIRS: ${OpenCASCADE_INCLUDE_DIRS}")
message(STATUS "OpenCASCADE_LIBRARIES: ${OpenCASCADE_LIBRARIES}")
file(GLOB SOURCES "src/*.cpp")
add_executable(OpenCAD
src/main.cpp
src/MainWindow.cpp
src/ViewportWidget.cpp
src/ViewCube.cpp
src/SketchGrid.cpp
src/Document.cpp
src/Feature.cpp
src/SketchFeature.cpp
src/SketchLine.cpp
src/FeatureBrowser.cpp
src/ApplicationController.cpp
src/Camera.cpp
${SOURCES}
resources.qrc
)

View File

@@ -2,6 +2,8 @@
#include "Document.h"
#include "SketchFeature.h"
#include "SketchLine.h"
#include "SketchRectangle.h"
#include "SketchCircle.h"
#include "MainWindow.h"
#include <QInputDialog>
@@ -140,6 +142,20 @@ 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::addCircle(const gp_Pnt& center, double radius)
{
if (m_activeSketch) {
m_activeSketch->addObject(new SketchCircle(center, radius));
}
}
void ApplicationController::endSketch()
{
m_activeSketch = nullptr;

View File

@@ -32,6 +32,8 @@ 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 addCircle(const gp_Pnt& center, double radius);
void newDocument();
bool openDocument();
bool saveDocument();

View File

@@ -1,9 +1,14 @@
#include "Camera.h"
#include "ViewportWidget.h"
#include <QApplication>
#include <QWheelEvent>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QtMath>
Camera::Camera(QObject *parent) : QObject(parent)
{
m_lastPos = QPoint();
// Set initial view to an isometric angle on the XY plane
m_xRot = 30 * 16;
m_yRot = -45 * 16;
@@ -12,29 +17,71 @@ Camera::Camera(QObject *parent) : QObject(parent)
m_panY = 0.0f;
}
void Camera::processMouseMovement(QMouseEvent* event, const QPoint& lastPos)
void Camera::mousePressEvent(QMouseEvent* event)
{
int dx = event->pos().x() - lastPos.x();
int dy = event->pos().y() - lastPos.y();
m_lastPos = event->pos();
}
void Camera::mouseMoveEvent(QMouseEvent* event, int viewportHeight)
{
int dx = event->pos().x() - m_lastPos.x();
int dy = event->pos().y() - m_lastPos.y();
if (event->buttons() & Qt::MiddleButton) {
if (QApplication::keyboardModifiers() & Qt::ShiftModifier) {
// Pan
setPanX(m_panX + dx / 100.0f);
setPanY(m_panY - dy / 100.0f);
if (viewportHeight == 0) viewportHeight = 1;
// This logic is based on a 45-degree field of view.
float fov = 45.0f;
float dist = -m_zoom;
float world_height = 2.0f * dist * tan(qDegreesToRadians(fov / 2.0f));
float pixels_to_world = world_height / viewportHeight;
setPanX(m_panX + dx * pixels_to_world);
setPanY(m_panY - dy * pixels_to_world);
} else {
// Rotate
setXRotation(m_xRot + 8 * dy);
setYRotation(m_yRot + 8 * dx);
}
}
m_lastPos = event->pos();
}
void Camera::processWheel(QWheelEvent* event)
void Camera::wheelEvent(QWheelEvent* event, const QVector3D& worldPos)
{
QPoint numDegrees = event->angleDelta() / 8;
if (!numDegrees.isNull()) {
setZoom(m_zoom + numDegrees.y() / 5.0f);
// Make zoom speed proportional to distance, with a minimum speed and a cap.
// The factors are chosen to match the original zoom speed at the default zoom level.
float dist = -m_zoom;
dist = qMin(dist, 200.0f); // Cap distance to avoid crazy fast zoom out.
float zoomFactor = dist * 0.009f + 0.02f;
float zoomAmount = numDegrees.y() * zoomFactor;
float oldZoom = m_zoom;
float newZoom = oldZoom + zoomAmount;
QMatrix4x4 rotation;
rotation.rotate(m_xRot / 16.0f, 1, 0, 0);
rotation.rotate(m_yRot / 16.0f, 0, 1, 0);
QVector3D p_camera = rotation.map(worldPos);
if (std::abs(p_camera.z() + oldZoom) < 1e-6) {
setZoom(newZoom);
return;
}
float ratio = (p_camera.z() + newZoom) / (p_camera.z() + oldZoom);
float newPanX = (p_camera.x() + m_panX) * ratio - p_camera.x();
float newPanY = (p_camera.y() + m_panY) * ratio - p_camera.y();
setZoom(newZoom);
setPanX(newPanX);
setPanY(newPanY);
}
}
@@ -42,8 +89,15 @@ QMatrix4x4 Camera::modelViewMatrix() const
{
QMatrix4x4 model;
model.translate(m_panX, m_panY, m_zoom);
if (m_isRotating) {
model.translate(m_rotationPivot);
}
model.rotate(m_xRot / 16.0f, 1, 0, 0);
model.rotate(m_yRot / 16.0f, 0, 1, 0);
if (m_isRotating) {
model.translate(-m_rotationPivot);
}
return model;
}
@@ -68,6 +122,14 @@ void Camera::setYRotation(float angle)
float Camera::zoom() const { return m_zoom; }
void Camera::setZoom(float value)
{
const float max_dist = 5000.0f;
float remaining_dist_sq = max_dist * max_dist - m_panX * m_panX - m_panY * m_panY;
if (remaining_dist_sq < 0) {
remaining_dist_sq = 0;
}
float max_val = qSqrt(remaining_dist_sq);
value = qBound(-max_val, value, 0.0f); // Zoom is negative or zero
if (value != m_zoom) {
m_zoom = value;
emit cameraChanged();
@@ -77,6 +139,14 @@ void Camera::setZoom(float value)
float Camera::panX() const { return m_panX; }
void Camera::setPanX(float value)
{
const float max_dist = 5000.0f;
float remaining_dist_sq = max_dist * max_dist - m_panY * m_panY - m_zoom * m_zoom;
if (remaining_dist_sq < 0) {
remaining_dist_sq = 0;
}
float max_val = qSqrt(remaining_dist_sq);
value = qBound(-max_val, value, max_val);
if (value != m_panX) {
m_panX = value;
emit cameraChanged();
@@ -86,6 +156,14 @@ void Camera::setPanX(float value)
float Camera::panY() const { return m_panY; }
void Camera::setPanY(float value)
{
const float max_dist = 5000.0f;
float remaining_dist_sq = max_dist * max_dist - m_panX * m_panX - m_zoom * m_zoom;
if (remaining_dist_sq < 0) {
remaining_dist_sq = 0;
}
float max_val = qSqrt(remaining_dist_sq);
value = qBound(-max_val, value, max_val);
if (value != m_panY) {
m_panY = value;
emit cameraChanged();
@@ -109,3 +187,200 @@ void Camera::restoreState()
setPanY(m_savedPanY);
setZoom(m_savedZoom);
}
void Camera::animateToPlaneView(int plane)
{
float targetXRot = xRotation();
float targetYRot = yRotation();
switch (static_cast<ViewportWidget::SketchPlane>(plane)) {
case ViewportWidget::SketchPlane::XY: // Top view
targetXRot = 90 * 16;
targetYRot = 0;
break;
case ViewportWidget::SketchPlane::XZ: // Front view
targetXRot = 0;
targetYRot = 0;
break;
case ViewportWidget::SketchPlane::YZ: // Right view
targetXRot = 0;
targetYRot = -90 * 16;
break;
case ViewportWidget::SketchPlane::NONE:
break;
}
auto* animGroup = new QParallelAnimationGroup(this);
const float full_circle = 360.0f * 16.0f;
float currentXRot = xRotation();
float diffX = targetXRot - currentXRot;
diffX = fmod(diffX, full_circle);
if (diffX > full_circle / 2.0f) {
diffX -= full_circle;
} else if (diffX < -full_circle / 2.0f) {
diffX += full_circle;
}
auto* xRotAnim = new QPropertyAnimation(this, "xRotation");
xRotAnim->setDuration(300);
xRotAnim->setStartValue(currentXRot);
xRotAnim->setEndValue(currentXRot + diffX);
xRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(xRotAnim);
float currentYRot = yRotation();
float diffY = targetYRot - currentYRot;
diffY = fmod(diffY, full_circle);
if (diffY > full_circle / 2.0f) {
diffY -= full_circle;
} else if (diffY < -full_circle / 2.0f) {
diffY += full_circle;
}
auto* yRotAnim = new QPropertyAnimation(this, "yRotation");
yRotAnim->setDuration(300);
yRotAnim->setStartValue(currentYRot);
yRotAnim->setEndValue(currentYRot + diffY);
yRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(yRotAnim);
auto* panXAnim = new QPropertyAnimation(this, "panX");
panXAnim->setDuration(300);
panXAnim->setStartValue(panX());
panXAnim->setEndValue(0.0f);
panXAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(panXAnim);
auto* panYAnim = new QPropertyAnimation(this, "panY");
panYAnim->setDuration(300);
panYAnim->setStartValue(panY());
panYAnim->setEndValue(0.0f);
panYAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(panYAnim);
auto* zoomAnim = new QPropertyAnimation(this, "zoom");
zoomAnim->setDuration(300);
zoomAnim->setStartValue(zoom());
zoomAnim->setEndValue(-20.0f);
zoomAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(zoomAnim);
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
}
void Camera::startRotation(const QVector3D& pivot)
{
m_rotationPivot = pivot;
if (m_rotationPivot.length() > 1000.0f) {
m_rotationPivot = m_rotationPivot.normalized() * 1000.0f;
}
m_stableZoom = m_zoom;
QMatrix4x4 rotation;
rotation.rotate(m_xRot / 16.0f, 1, 0, 0);
rotation.rotate(m_yRot / 16.0f, 0, 1, 0);
QVector3D p_rotated = rotation.map(m_rotationPivot);
QVector3D p_diff = p_rotated - m_rotationPivot;
setPanX(m_panX + p_diff.x());
setPanY(m_panY + p_diff.y());
setZoom(m_zoom + p_diff.z());
m_isRotating = true;
}
void Camera::stopRotation()
{
if (!m_isRotating) {
return;
}
QMatrix4x4 rotation;
rotation.rotate(m_xRot / 16.0f, 1, 0, 0);
rotation.rotate(m_yRot / 16.0f, 0, 1, 0);
QVector3D p_rotated = rotation.map(m_rotationPivot);
QVector3D p_diff = p_rotated - m_rotationPivot;
setPanX(m_panX - p_diff.x());
setPanY(m_panY - p_diff.y());
setZoom(m_zoom - p_diff.z());
m_isRotating = false;
}
void Camera::animateRestoreState()
{
auto* animGroup = new QParallelAnimationGroup(this);
const float full_circle = 360.0f * 16.0f;
float currentXRot = xRotation();
float targetXRot = savedXRot();
float diffX = targetXRot - currentXRot;
diffX = fmod(diffX, full_circle);
if (diffX > full_circle / 2.0f) {
diffX -= full_circle;
} else if (diffX < -full_circle / 2.0f) {
diffX += full_circle;
}
auto* xRotAnim = new QPropertyAnimation(this, "xRotation");
xRotAnim->setDuration(300);
xRotAnim->setStartValue(currentXRot);
xRotAnim->setEndValue(currentXRot + diffX);
xRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(xRotAnim);
float currentYRot = yRotation();
float targetYRot = savedYRot();
float diffY = targetYRot - currentYRot;
diffY = fmod(diffY, full_circle);
if (diffY > full_circle / 2.0f) {
diffY -= full_circle;
} else if (diffY < -full_circle / 2.0f) {
diffY += full_circle;
}
auto* yRotAnim = new QPropertyAnimation(this, "yRotation");
yRotAnim->setDuration(300);
yRotAnim->setStartValue(currentYRot);
yRotAnim->setEndValue(currentYRot + diffY);
yRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(yRotAnim);
auto* panXAnim = new QPropertyAnimation(this, "panX");
panXAnim->setDuration(300);
panXAnim->setStartValue(panX());
panXAnim->setEndValue(savedPanX());
panXAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(panXAnim);
auto* panYAnim = new QPropertyAnimation(this, "panY");
panYAnim->setDuration(300);
panYAnim->setStartValue(panY());
panYAnim->setEndValue(savedPanY());
panYAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(panYAnim);
auto* zoomAnim = new QPropertyAnimation(this, "zoom");
zoomAnim->setDuration(300);
zoomAnim->setStartValue(zoom());
zoomAnim->setEndValue(savedZoom());
zoomAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(zoomAnim);
connect(animGroup, &QParallelAnimationGroup::finished, this, &Camera::restoreStateAnimationFinished);
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
}
float Camera::uiCameraDistance() const
{
if (m_isRotating) {
return m_stableZoom;
}
return m_zoom;
}

View File

@@ -19,8 +19,9 @@ class Camera : public QObject
public:
explicit Camera(QObject *parent = nullptr);
void processMouseMovement(QMouseEvent* event, const QPoint& lastPos);
void processWheel(QWheelEvent* event);
void mousePressEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event, int viewportHeight);
void wheelEvent(QWheelEvent* event, const QVector3D& worldPos);
QMatrix4x4 modelViewMatrix() const;
@@ -38,6 +39,15 @@ public:
void saveState();
void restoreState();
void animateToPlaneView(int plane);
void animateRestoreState();
void startRotation(const QVector3D& pivot);
void stopRotation();
bool isRotating() const { return m_isRotating; }
const QVector3D& rotationPivot() const { return m_rotationPivot; }
float uiCameraDistance() const;
float savedXRot() const { return m_savedXRot; }
float savedYRot() const { return m_savedYRot; }
float savedZoom() const { return m_savedZoom; }
@@ -46,14 +56,20 @@ public:
signals:
void cameraChanged();
void restoreStateAnimationFinished();
private:
QPoint m_lastPos;
float m_xRot;
float m_yRot;
float m_zoom;
float m_panX;
float m_panY;
QVector3D m_rotationPivot;
bool m_isRotating = false;
float m_stableZoom = 0;
float m_savedXRot = 0;
float m_savedYRot = 0;
float m_savedZoom = -5.0f;

267
src/CircleTool.cpp Normal file
View File

@@ -0,0 +1,267 @@
#include "CircleTool.h"
#include "ViewportWidget.h"
#include "Camera.h"
#include <QMouseEvent>
#include <QKeyEvent>
#include <QPainter>
#include <QVector>
#include <QOpenGLShaderProgram>
#include <cmath>
#include <QtMath>
CircleTool::CircleTool(ViewportWidget* viewport)
: SketchTool(viewport)
{
}
void CircleTool::activate()
{
SketchTool::activate();
m_dimensionModes.clear();
m_dimensionModes << "diameter";
m_dimensionPropertyNames.clear();
m_dimensionPropertyNames["diameter"] = "diameterInput";
m_viewport->setProperty("diameterInput", "");
m_viewport->setProperty("dimensionEditMode", "diameter");
}
void CircleTool::mousePressEvent(QMouseEvent *event)
{
gp_Pnt p;
if (!m_isDefining) {
if (m_viewport->isSnappingOrigin()) {
p.SetCoord(0, 0, 0);
} else if (m_viewport->isSnappingVertex()) {
p = m_viewport->snapVertex();
} else {
QVector3D worldPos = m_viewport->unproject(event->pos(), m_viewport->currentPlane());
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
}
m_centerPoint = p;
m_isDefining = true;
m_dimensionModes.clear();
m_dimensionModes << "diameter";
m_dimensionPropertyNames.clear();
m_dimensionPropertyNames["diameter"] = "diameterInput";
m_viewport->setProperty("diameterInput", "");
m_viewport->setProperty("dimensionEditMode", "diameter");
} else {
QVector3D worldPos;
QVector3D centerPos(m_centerPoint.X(), m_centerPoint.Y(), m_centerPoint.Z());
QString diameterInput = m_viewport->property("diameterInput").toString();
bool diameterFromInput = false;
double inputDiameter = 0;
if (!diameterInput.isEmpty()) {
bool ok;
inputDiameter = diameterInput.toDouble(&ok);
if (ok) diameterFromInput = true;
}
if (diameterFromInput) {
QVector3D mousePos = m_viewport->unproject(event->pos(), m_viewport->currentPlane());
QVector3D mouseDir = mousePos - centerPos;
if (mouseDir.lengthSquared() < 1e-9) {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
mouseDir = QVector3D(1, 0, 0);
} else { // YZ
mouseDir = QVector3D(0, 1, 0);
}
}
double radius = inputDiameter / 2.0;
worldPos = centerPos + mouseDir.normalized() * radius;
} else {
if (m_viewport->isSnappingOrigin()) {
worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0);
} else if (m_viewport->isSnappingVertex()) {
worldPos = QVector3D(m_viewport->snapVertex().X(), m_viewport->snapVertex().Y(), m_viewport->snapVertex().Z());
} else {
worldPos = m_viewport->unproject(event->pos(), m_viewport->currentPlane());
}
}
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
emit m_viewport->circleAdded(m_centerPoint, m_centerPoint.Distance(p));
deactivate();
}
}
void CircleTool::mouseMoveEvent(QMouseEvent *event)
{
// To be implemented
}
void CircleTool::finalizeCreation()
{
QVector3D worldPos;
QVector3D centerPos(m_centerPoint.X(), m_centerPoint.Y(), m_centerPoint.Z());
QString diameterInput = m_viewport->property("diameterInput").toString();
bool diameterFromInput = false;
double inputDiameter = 0;
if (!diameterInput.isEmpty()) {
bool ok;
inputDiameter = diameterInput.toDouble(&ok);
if (ok) diameterFromInput = true;
}
if (diameterFromInput) {
QVector3D mousePos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D mouseDir = mousePos - centerPos;
if (mouseDir.lengthSquared() < 1e-9) {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
mouseDir = QVector3D(1, 0, 0);
} else { // YZ
mouseDir = QVector3D(0, 1, 0);
}
}
double radius = inputDiameter / 2.0;
worldPos = centerPos + mouseDir.normalized() * radius;
} else {
worldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
}
gp_Pnt p;
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
emit m_viewport->circleAdded(m_centerPoint, m_centerPoint.Distance(p));
deactivate();
}
void CircleTool::paintGL()
{
if (m_isDefining) {
QVector<GLfloat> vertices;
QVector3D worldPos;
QVector3D centerPos(m_centerPoint.X(), m_centerPoint.Y(), m_centerPoint.Z());
QString diameterInput = m_viewport->property("diameterInput").toString();
bool diameterFromInput = false;
double inputDiameter = 0;
if (!diameterInput.isEmpty()) {
bool ok;
inputDiameter = diameterInput.toDouble(&ok);
if (ok) diameterFromInput = true;
}
QVector3D mousePos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
double radius;
if (diameterFromInput) {
radius = inputDiameter / 2.0;
} else {
worldPos = mousePos;
if (m_viewport->isSnappingOrigin()) {
worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0);
} else if (m_viewport->isSnappingVertex()) {
worldPos.setX(m_viewport->snapVertex().X()); worldPos.setY(m_viewport->snapVertex().Y()); worldPos.setZ(m_viewport->snapVertex().Z());
}
radius = (worldPos - centerPos).length();
}
const int segments = 64;
for (int i = 0; i < segments; ++i) {
double angle1 = i * 2.0 * M_PI / segments;
double angle2 = (i + 1) * 2.0 * M_PI / segments;
QVector3D p1, p2;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
p1.setX(centerPos.x() + radius * qCos(angle1));
p1.setY(centerPos.y());
p1.setZ(centerPos.z() + radius * qSin(angle1));
p2.setX(centerPos.x() + radius * qCos(angle2));
p2.setY(centerPos.y());
p2.setZ(centerPos.z() + radius * qSin(angle2));
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
p1.setX(centerPos.x() + radius * qCos(angle1));
p1.setY(centerPos.y() + radius * qSin(angle1));
p1.setZ(centerPos.z());
p2.setX(centerPos.x() + radius * qCos(angle2));
p2.setY(centerPos.y() + radius * qSin(angle2));
p2.setZ(centerPos.z());
} else { // YZ
p1.setX(centerPos.x());
p1.setY(centerPos.y() + radius * qCos(angle1));
p1.setZ(centerPos.z() + radius * qSin(angle1));
p2.setX(centerPos.x());
p2.setY(centerPos.y() + radius * qCos(angle2));
p2.setZ(centerPos.z() + radius * qSin(angle2));
}
vertices << p1.x() << p1.y() << p1.z();
vertices << p2.x() << p2.y() << p2.z();
}
m_viewport->shaderProgram()->setUniformValue(m_viewport->colorLoc(), QVector4D(1.0f, 1.0f, 0.0f, 1.0f));
m_viewport->vbo().bind();
m_viewport->vbo().allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, segments * 2);
}
}
void CircleTool::paint2D(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection)
{
if (m_isDefining) {
QVector3D worldPos;
QVector3D centerPos(m_centerPoint.X(), m_centerPoint.Y(), m_centerPoint.Z());
QString diameterInput = m_viewport->property("diameterInput").toString();
bool diameterFromInput = false;
double inputDiameter = 0;
if (!diameterInput.isEmpty()) {
bool ok;
inputDiameter = diameterInput.toDouble(&ok);
if (ok) diameterFromInput = true;
}
QVector3D mousePos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D edgePos;
double diameter;
if (diameterFromInput) {
diameter = inputDiameter;
QVector3D mouseDir = mousePos - centerPos;
if (mouseDir.lengthSquared() < 1e-9) {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
mouseDir = QVector3D(1, 0, 0);
} else { // YZ
mouseDir = QVector3D(0, 1, 0);
}
}
edgePos = centerPos + mouseDir.normalized() * (diameter / 2.0);
} else {
edgePos = mousePos;
if (m_viewport->isSnappingOrigin()) {
edgePos.setX(0); edgePos.setY(0); edgePos.setZ(0);
} else if (m_viewport->isSnappingVertex()) {
edgePos.setX(m_viewport->snapVertex().X()); edgePos.setY(m_viewport->snapVertex().Y()); edgePos.setZ(m_viewport->snapVertex().Z());
}
diameter = (edgePos - centerPos).length() * 2.0;
}
painter.setRenderHint(QPainter::Antialiasing);
QFontMetrics fm(painter.font());
// Diameter dimension
QVector3D diameterTextPos3D = (centerPos + edgePos) / 2.0f;
QVector3D screenPosD = m_viewport->project(diameterTextPos3D, modelView, projection, m_viewport->rect());
if (screenPosD.z() < 1.0f) {
QString diameterText = diameterFromInput ? diameterInput : QString::number(diameter, 'f', 2);
QRect textRect = fm.boundingRect(diameterText + "__");
textRect.moveCenter(screenPosD.toPoint());
if (m_viewport->property("dimensionEditMode").toString() == "diameter") {
painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(64, 128, 255));
} else {
painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(50, 50, 50));
}
painter.setPen(Qt::white);
painter.drawText(textRect, Qt::AlignCenter, diameterText);
}
}
}

28
src/CircleTool.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef CIRCLETOOL_H
#define CIRCLETOOL_H
#include "SketchTool.h"
#include <gp_Pnt.hxx>
class CircleTool : public SketchTool
{
Q_OBJECT
public:
explicit CircleTool(ViewportWidget* viewport);
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void paintGL() override;
void paint2D(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection) override;
void activate() override;
protected:
void finalizeCreation() override;
private:
gp_Pnt m_centerPoint;
};
#endif // CIRCLETOOL_H

852
src/LineTool.cpp Normal file
View File

@@ -0,0 +1,852 @@
#include "LineTool.h"
#include "ViewportWidget.h"
#include "Camera.h"
#include <QMouseEvent>
#include <QKeyEvent>
#include <QPainter>
#include <QOpenGLShaderProgram>
#include <QtMath>
LineTool::LineTool(ViewportWidget* viewport)
: SketchTool(viewport)
{
}
void LineTool::activate()
{
SketchTool::activate();
m_dimensionModes << "length" << "angle";
m_dimensionPropertyNames["length"] = "dimensionInput";
m_dimensionPropertyNames["angle"] = "angleInput";
m_viewport->setProperty("dimensionInput", "");
m_viewport->setProperty("angleInput", "");
m_viewport->setProperty("dimensionEditMode", "length");
m_viewport->setProperty("isChainedLine", false);
}
void LineTool::mousePressEvent(QMouseEvent *event)
{
gp_Pnt p;
QString dimInput = m_viewport->property("dimensionInput").toString();
QString angleInput = m_viewport->property("angleInput").toString();
bool lengthFromInput = false;
bool angleFromInput = false;
double inputLength = 0;
double inputAngleDegrees = 0;
if (m_isDefining) {
if (!dimInput.isEmpty()) {
bool ok;
inputLength = dimInput.toDouble(&ok);
if (ok) lengthFromInput = true;
}
if (!angleInput.isEmpty()) {
bool ok;
inputAngleDegrees = angleInput.toDouble(&ok);
if (ok) angleFromInput = true;
}
}
if (m_isDefining && (lengthFromInput || angleFromInput)) {
QVector3D worldPos;
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
if (angleFromInput) {
QVector3D refDir;
if (m_viewport->property("isChainedLine").toBool()) {
refDir = m_viewport->property("previousLineDirection").value<QVector3D>();
} else {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) refDir = QVector3D(1, 0, 0);
else refDir = QVector3D(0, 0, -1);
}
QVector3D currentMouseWorldPos = m_viewport->unproject(event->pos(), m_viewport->currentPlane());
QVector3D mouseVec = currentMouseWorldPos - startPos;
double mouseAngle;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) mouseAngle = qRadiansToDegrees(atan2(mouseVec.y(), mouseVec.x()));
else mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.y()));
double refAngle;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) refAngle = qRadiansToDegrees(atan2(refDir.y(), refDir.x()));
else refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.y()));
double relativeMouseAngle = mouseAngle - refAngle;
while (relativeMouseAngle <= -180.0) relativeMouseAngle += 360.0;
while (relativeMouseAngle > 180.0) relativeMouseAngle -= 360.0;
double snappedAngle = 0;
if (relativeMouseAngle >= 0 && relativeMouseAngle < 90) { // Quadrant 1
snappedAngle = inputAngleDegrees;
} else if (relativeMouseAngle >= 90 && relativeMouseAngle <= 180) { // Quadrant 2
snappedAngle = 180.0 - inputAngleDegrees;
} else if (relativeMouseAngle < -90) { // Quadrant 3
snappedAngle = -180.0 + inputAngleDegrees;
} else { // Quadrant 4
snappedAngle = -inputAngleDegrees;
}
double finalAngleRad = qDegreesToRadians(refAngle + snappedAngle);
QVector3D finalDir;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) finalDir = QVector3D(cos(finalAngleRad), 0, sin(finalAngleRad));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) finalDir = QVector3D(cos(finalAngleRad), sin(finalAngleRad), 0);
else finalDir = QVector3D(0, cos(finalAngleRad), sin(finalAngleRad));
double lineLength;
if (lengthFromInput) lineLength = inputLength;
else {
lineLength = QVector3D::dotProduct(mouseVec, finalDir);
if (lineLength < 0) lineLength = 0;
}
worldPos = startPos + lineLength * finalDir;
} else if (lengthFromInput) {
QVector3D currentMouseWorldPos = m_viewport->unproject(event->pos(), m_viewport->currentPlane());
QVector3D dir = (currentMouseWorldPos - startPos);
if (dir.length() > 1e-6) {
dir.normalize();
worldPos = startPos + inputLength * dir;
} else {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) worldPos = startPos + QVector3D(inputLength, 0, 0);
else worldPos = startPos + QVector3D(0, inputLength, 0);
}
}
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
} else {
if (m_viewport->isSnappingOrigin()) {
p.SetCoord(0, 0, 0);
} else if (m_viewport->isSnappingVertex()) {
p = m_viewport->snapVertex();
} else {
QVector3D worldPos = m_viewport->unproject(event->pos(), m_viewport->currentPlane());
if (m_viewport->isSnappingHorizontal()) {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) worldPos.setZ(m_firstLinePoint.Z());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) worldPos.setY(m_firstLinePoint.Y());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) worldPos.setZ(m_firstLinePoint.Z());
} else if (m_viewport->isSnappingVertical()) {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) worldPos.setX(m_firstLinePoint.X());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) worldPos.setX(m_firstLinePoint.X());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) worldPos.setY(m_firstLinePoint.Y());
}
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
}
}
if (!m_isDefining) {
m_firstLinePoint = p;
m_isDefining = true;
m_viewport->setProperty("dimensionInput", QVariant(""));
m_viewport->setProperty("angleInput", QVariant(""));
m_viewport->setProperty("dimensionEditMode", "length");
m_viewport->setProperty("isChainedLine", false);
} else {
QVector3D start(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
QVector3D end(p.X(), p.Y(), p.Z());
m_viewport->setProperty("previousLineDirection", QVariant::fromValue((end - start).normalized()));
m_viewport->addLine(m_firstLinePoint, p);
m_firstLinePoint = p;
m_viewport->setProperty("dimensionInput", QVariant(""));
m_viewport->setProperty("angleInput", QVariant(""));
m_viewport->setProperty("dimensionEditMode", "length");
m_viewport->setProperty("isChainedLine", true);
}
}
void LineTool::mouseMoveEvent(QMouseEvent *event)
{
bool oldIsSnappingHorizontal = m_viewport->isSnappingHorizontal();
bool oldIsSnappingVertical = m_viewport->isSnappingVertical();
m_viewport->setSnappingHorizontal(false);
m_viewport->setSnappingVertical(false);
if (m_isDefining && !m_viewport->isSnappingOrigin() && !m_viewport->isSnappingVertex()) {
QVector3D worldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
QVector3D delta = worldPos - startPos;
if (delta.length() > 1e-6) {
const double snapAngleThreshold = qDegreesToRadians(2.0);
double angle = 0;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
angle = atan2(delta.z(), delta.x());
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
angle = atan2(delta.y(), delta.x());
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) {
angle = atan2(delta.z(), delta.y());
}
if (qAbs(sin(angle)) < sin(snapAngleThreshold)) {
m_viewport->setSnappingHorizontal(true);
} else if (qAbs(cos(angle)) < sin(snapAngleThreshold)) {
m_viewport->setSnappingVertical(true);
}
}
}
if (oldIsSnappingHorizontal != m_viewport->isSnappingHorizontal() || oldIsSnappingVertical != m_viewport->isSnappingVertical()) {
m_viewport->update();
}
}
void LineTool::finalizeCreation()
{
QVector3D worldPos;
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
// This is duplicated from paintGL to ensure consistent line creation
QString dimInput = m_viewport->property("dimensionInput").toString();
QString angleInput = m_viewport->property("angleInput").toString();
bool lengthFromInput = false;
bool angleFromInput = false;
double inputLength = 0;
double inputAngleDegrees = 0;
if (!dimInput.isEmpty()) {
bool ok;
inputLength = dimInput.toDouble(&ok);
if (ok) lengthFromInput = true;
}
if (!angleInput.isEmpty()) {
bool ok;
inputAngleDegrees = angleInput.toDouble(&ok);
if (ok) angleFromInput = true;
}
if (angleFromInput) {
QVector3D refDir;
if (m_viewport->property("isChainedLine").toBool()) {
refDir = m_viewport->property("previousLineDirection").value<QVector3D>();
} else {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) refDir = QVector3D(1, 0, 0);
else refDir = QVector3D(0, 0, -1);
}
QVector3D currentMouseWorldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D mouseVec = currentMouseWorldPos - startPos;
double mouseAngle;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) mouseAngle = qRadiansToDegrees(atan2(mouseVec.y(), mouseVec.x()));
else mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.y()));
double refAngle;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) refAngle = qRadiansToDegrees(atan2(refDir.y(), refDir.x()));
else refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.y()));
double relativeMouseAngle = mouseAngle - refAngle;
while (relativeMouseAngle <= -180.0) relativeMouseAngle += 360.0;
while (relativeMouseAngle > 180.0) relativeMouseAngle -= 360.0;
double snappedAngle = 0;
if (relativeMouseAngle >= 0 && relativeMouseAngle < 90) { // Quadrant 1
snappedAngle = inputAngleDegrees;
} else if (relativeMouseAngle >= 90 && relativeMouseAngle <= 180) { // Quadrant 2
snappedAngle = 180.0 - inputAngleDegrees;
} else if (relativeMouseAngle < -90) { // Quadrant 3
snappedAngle = -180.0 + inputAngleDegrees;
} else { // Quadrant 4
snappedAngle = -inputAngleDegrees;
}
double finalAngleRad = qDegreesToRadians(refAngle + snappedAngle);
QVector3D finalDir;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) finalDir = QVector3D(cos(finalAngleRad), 0, sin(finalAngleRad));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) finalDir = QVector3D(cos(finalAngleRad), sin(finalAngleRad), 0);
else finalDir = QVector3D(0, cos(finalAngleRad), sin(finalAngleRad));
double lineLength;
if (lengthFromInput) lineLength = inputLength;
else {
lineLength = QVector3D::dotProduct(mouseVec, finalDir);
if (lineLength < 0) lineLength = 0;
}
worldPos = startPos + lineLength * finalDir;
} else if (lengthFromInput) {
QVector3D currentMouseWorldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D dir = (currentMouseWorldPos - startPos);
if (dir.length() > 1e-6) {
dir.normalize();
worldPos = startPos + inputLength * dir;
} else {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) worldPos = startPos + QVector3D(inputLength, 0, 0);
else worldPos = startPos + QVector3D(0, inputLength, 0);
}
} else {
worldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
}
gp_Pnt p;
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
QVector3D prevDir = (worldPos - startPos).normalized();
m_viewport->setProperty("previousLineDirection", QVariant::fromValue(prevDir));
m_viewport->addLine(m_firstLinePoint, p);
m_firstLinePoint = p;
m_viewport->setProperty("dimensionInput", QVariant(""));
m_viewport->setProperty("angleInput", QVariant(""));
m_viewport->setProperty("dimensionEditMode", "length");
m_viewport->setProperty("isChainedLine", true);
}
void LineTool::paintGL()
{
if (m_isDefining) {
QVector<GLfloat> vertices;
QVector3D worldPos;
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
QString dimInput = m_viewport->property("dimensionInput").toString();
QString angleInput = m_viewport->property("angleInput").toString();
bool lengthFromInput = false;
bool angleFromInput = false;
double inputLength = 0;
double inputAngleDegrees = 0;
if (!dimInput.isEmpty()) {
bool ok;
inputLength = dimInput.toDouble(&ok);
if (ok) lengthFromInput = true;
}
if (!angleInput.isEmpty()) {
bool ok;
inputAngleDegrees = angleInput.toDouble(&ok);
if (ok) angleFromInput = true;
}
if (angleFromInput) {
QVector3D refDir;
if (m_viewport->property("isChainedLine").toBool()) {
refDir = m_viewport->property("previousLineDirection").value<QVector3D>();
} else {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
refDir = QVector3D(1, 0, 0);
} else { // YZ
refDir = QVector3D(0, 0, -1);
}
}
QVector3D currentMouseWorldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D mouseVec = currentMouseWorldPos - startPos;
// Quadrant snapping
double mouseAngle;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) mouseAngle = qRadiansToDegrees(atan2(mouseVec.y(), mouseVec.x()));
else mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.y()));
double refAngle;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) refAngle = qRadiansToDegrees(atan2(refDir.y(), refDir.x()));
else refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.y()));
double relativeMouseAngle = mouseAngle - refAngle;
while (relativeMouseAngle <= -180.0) relativeMouseAngle += 360.0;
while (relativeMouseAngle > 180.0) relativeMouseAngle -= 360.0;
double snappedAngle = 0;
if (relativeMouseAngle >= 0 && relativeMouseAngle < 90) { // Quadrant 1
snappedAngle = inputAngleDegrees;
} else if (relativeMouseAngle >= 90 && relativeMouseAngle <= 180) { // Quadrant 2
snappedAngle = 180.0 - inputAngleDegrees;
} else if (relativeMouseAngle < -90) { // Quadrant 3
snappedAngle = -180.0 + inputAngleDegrees;
} else { // Quadrant 4
snappedAngle = -inputAngleDegrees;
}
double finalAngleRad = qDegreesToRadians(refAngle + snappedAngle);
QVector3D finalDir;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) finalDir = QVector3D(cos(finalAngleRad), 0, sin(finalAngleRad));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) finalDir = QVector3D(cos(finalAngleRad), sin(finalAngleRad), 0);
else finalDir = QVector3D(0, cos(finalAngleRad), sin(finalAngleRad));
double lineLength;
if (lengthFromInput) {
lineLength = inputLength;
} else {
lineLength = QVector3D::dotProduct(mouseVec, finalDir);
if (lineLength < 0) lineLength = 0;
}
worldPos = startPos + lineLength * finalDir;
} else if (lengthFromInput) {
QVector3D currentMouseWorldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D dir = (currentMouseWorldPos - startPos);
if (dir.length() > 1e-6) {
dir.normalize();
worldPos = startPos + inputLength * dir;
} else {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
worldPos = startPos + QVector3D(inputLength, 0, 0);
} else {
worldPos = startPos + QVector3D(0, inputLength, 0);
}
}
} else {
worldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
if (m_viewport->isSnappingOrigin()) {
worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0);
} else if (m_viewport->isSnappingVertex()) {
worldPos.setX(m_viewport->snapVertex().X()); worldPos.setY(m_viewport->snapVertex().Y()); worldPos.setZ(m_viewport->snapVertex().Z());
} else if (m_viewport->isSnappingHorizontal()) {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) worldPos.setZ(m_firstLinePoint.Z());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) worldPos.setY(m_firstLinePoint.Y());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) worldPos.setZ(m_firstLinePoint.Z());
} else if (m_viewport->isSnappingVertical()) {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) worldPos.setX(m_firstLinePoint.X());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) worldPos.setX(m_firstLinePoint.X());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) worldPos.setY(m_firstLinePoint.Y());
}
}
vertices << m_firstLinePoint.X() << m_firstLinePoint.Y() << m_firstLinePoint.Z();
vertices << worldPos.x() << worldPos.y() << worldPos.z();
m_viewport->shaderProgram()->setUniformValue(m_viewport->colorLoc(), QVector4D(1.0f, 1.0f, 0.0f, 1.0f));
m_viewport->vbo().bind();
m_viewport->vbo().allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, 2);
// Draw dimension line
QVector3D lineVec = worldPos - startPos;
float lineLength = lineVec.length();
if (lineLength > 1e-6) {
double refAngle, lineAngle, angleDiff;
{
QVector3D refDir;
if (m_viewport->property("isChainedLine").toBool()) {
refDir = m_viewport->property("previousLineDirection").value<QVector3D>();
} else {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) refDir = QVector3D(1, 0, 0);
else refDir = QVector3D(0, 0, -1);
}
if (angleFromInput) {
QVector3D currentMouseWorldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D mouseVec = currentMouseWorldPos - startPos;
double mouseAngle;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) mouseAngle = qRadiansToDegrees(atan2(mouseVec.y(), mouseVec.x()));
else mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.y()));
double refAngleForQuadrant;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) refAngleForQuadrant = qRadiansToDegrees(atan2(refDir.z(), refDir.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) refAngleForQuadrant = qRadiansToDegrees(atan2(refDir.y(), refDir.x()));
else refAngleForQuadrant = qRadiansToDegrees(atan2(refDir.z(), refDir.y()));
double relativeMouseAngle = mouseAngle - refAngleForQuadrant;
while (relativeMouseAngle <= -180.0) relativeMouseAngle += 360.0;
while (relativeMouseAngle > 180.0) relativeMouseAngle -= 360.0;
if (relativeMouseAngle >= 90 || relativeMouseAngle < -90) {
refDir = -refDir;
}
} else {
if (m_viewport->property("isChainedLine").toBool()) {
refDir = -refDir;
}
}
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
refAngle = atan2(refDir.z(), refDir.x());
lineAngle = atan2(lineVec.z(), lineVec.x());
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
refAngle = atan2(refDir.y(), refDir.x());
lineAngle = atan2(lineVec.y(), lineVec.x());
} else { // YZ
refAngle = atan2(refDir.z(), refDir.y());
lineAngle = atan2(lineVec.z(), lineVec.y());
}
angleDiff = lineAngle - refAngle;
while (angleDiff <= -M_PI) angleDiff += 2 * M_PI;
while (angleDiff > M_PI) angleDiff -= 2 * M_PI;
lineAngle = refAngle + angleDiff;
}
vertices.clear();
QVector3D perpVec;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
perpVec = QVector3D(-lineVec.z(), 0, lineVec.x()).normalized();
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
perpVec = QVector3D(-lineVec.y(), lineVec.x(), 0).normalized();
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) {
perpVec = QVector3D(0, -lineVec.z(), lineVec.y()).normalized();
}
if (angleDiff < 0) {
perpVec = -perpVec;
}
float offset = 0.05f * -m_viewport->camera()->zoom();
QVector3D dimStart = startPos + offset * perpVec;
QVector3D dimEnd = worldPos + offset * perpVec;
vertices << dimStart.x() << dimStart.y() << dimStart.z();
vertices << dimEnd.x() << dimEnd.y() << dimEnd.z();
float arrowLength = 0.02f * -m_viewport->camera()->zoom();
float arrowWidth = 0.005f * -m_viewport->camera()->zoom();
QVector3D lineDir = lineVec.normalized();
QVector3D arrow_base_end = dimEnd - arrowLength * lineDir;
QVector3D arrowP1_end = arrow_base_end + arrowWidth * perpVec;
QVector3D arrowP2_end = arrow_base_end - arrowWidth * perpVec;
vertices << dimEnd.x() << dimEnd.y() << dimEnd.z();
vertices << arrowP1_end.x() << arrowP1_end.y() << arrowP1_end.z();
vertices << dimEnd.x() << dimEnd.y() << dimEnd.z();
vertices << arrowP2_end.x() << arrowP2_end.y() << arrowP2_end.z();
QVector3D arrow_base_start = dimStart + arrowLength * lineDir;
QVector3D arrowP1_start = arrow_base_start + arrowWidth * perpVec;
QVector3D arrowP2_start = arrow_base_start - arrowWidth * perpVec;
vertices << dimStart.x() << dimStart.y() << dimStart.z();
vertices << arrowP1_start.x() << arrowP1_start.y() << arrowP1_start.z();
vertices << dimStart.x() << dimStart.y() << dimStart.z();
vertices << arrowP2_start.x() << arrowP2_start.y() << arrowP2_start.z();
m_viewport->shaderProgram()->setUniformValue(m_viewport->colorLoc(), QVector4D(0.7f, 0.7f, 0.7f, 1.0f));
glLineWidth(1.0f);
m_viewport->vbo().bind();
m_viewport->vbo().allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, vertices.size() / 3);
glLineWidth(2.0f);
// Draw angle dimension
vertices.clear();
const int numSegments = 30;
const float radius = 0.1f * -m_viewport->camera()->zoom();
for (int i = 0; i <= numSegments; ++i) {
double angle = refAngle + (lineAngle - refAngle) * i / numSegments;
QVector3D p;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) p = startPos + radius * QVector3D(cos(angle), 0, sin(angle));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) p = startPos + radius * QVector3D(cos(angle), sin(angle), 0);
else p = startPos + radius * QVector3D(0, cos(angle), sin(angle));
vertices << p.x() << p.y() << p.z();
}
glLineWidth(1.0f);
m_viewport->vbo().bind();
m_viewport->vbo().allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
glDrawArrays(GL_LINE_STRIP, 0, vertices.size() / 3);
// Arrowheads for arc
QVector<GLfloat> arrowVertices;
float arcArrowLength = 0.02f * -m_viewport->camera()->zoom();
float arcArrowWidth = 0.005f * -m_viewport->camera()->zoom();
double sign = (angleDiff >= 0) ? 1.0 : -1.0;
// End arrowhead
QVector3D endPoint(vertices[vertices.size()-3], vertices[vertices.size()-2], vertices[vertices.size()-1]);
double endAngle = lineAngle;
QVector3D radialDir_end, tangentDir_end;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
radialDir_end = QVector3D(cos(endAngle), 0, sin(endAngle));
tangentDir_end = QVector3D(-sin(endAngle), 0, cos(endAngle));
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
radialDir_end = QVector3D(cos(endAngle), sin(endAngle), 0);
tangentDir_end = QVector3D(-sin(endAngle), cos(endAngle), 0);
} else {
radialDir_end = QVector3D(0, cos(endAngle), sin(endAngle));
tangentDir_end = QVector3D(0, -sin(endAngle), cos(endAngle));
}
QVector3D arc_arrow_base_end = endPoint - sign * arcArrowLength * tangentDir_end;
QVector3D arc_arrowP1_end = arc_arrow_base_end + arcArrowWidth * radialDir_end;
QVector3D arc_arrowP2_end = arc_arrow_base_end - arcArrowWidth * radialDir_end;
arrowVertices << endPoint.x() << endPoint.y() << endPoint.z() << arc_arrowP1_end.x() << arc_arrowP1_end.y() << arc_arrowP1_end.z();
arrowVertices << endPoint.x() << endPoint.y() << endPoint.z() << arc_arrowP2_end.x() << arc_arrowP2_end.y() << arc_arrowP2_end.z();
// Start arrowhead
QVector3D startPoint(vertices[0], vertices[1], vertices[2]);
double startAngle = refAngle;
QVector3D radialDir_start, tangentDir_start;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
radialDir_start = QVector3D(cos(startAngle), 0, sin(startAngle));
tangentDir_start = QVector3D(-sin(startAngle), 0, cos(startAngle));
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
radialDir_start = QVector3D(cos(startAngle), sin(startAngle), 0);
tangentDir_start = QVector3D(-sin(startAngle), cos(startAngle), 0);
} else {
radialDir_start = QVector3D(0, cos(startAngle), sin(startAngle));
tangentDir_start = QVector3D(0, -sin(startAngle), cos(startAngle));
}
QVector3D arc_arrow_base_start = startPoint + sign * arcArrowLength * tangentDir_start;
QVector3D arc_arrowP1_start = arc_arrow_base_start + arcArrowWidth * radialDir_start;
QVector3D arc_arrowP2_start = arc_arrow_base_start - arcArrowWidth * radialDir_start;
arrowVertices << startPoint.x() << startPoint.y() << startPoint.z() << arc_arrowP1_start.x() << arc_arrowP1_start.y() << arc_arrowP1_start.z();
arrowVertices << startPoint.x() << startPoint.y() << startPoint.z() << arc_arrowP2_start.x() << arc_arrowP2_start.y() << arc_arrowP2_start.z();
m_viewport->vbo().bind();
m_viewport->vbo().allocate(arrowVertices.constData(), arrowVertices.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, arrowVertices.size() / 3);
glLineWidth(2.0f);
}
if (!lengthFromInput && !angleFromInput && (m_viewport->isSnappingHorizontal() || m_viewport->isSnappingVertical())) {
vertices.clear();
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
QVector3D midPoint = (startPos + worldPos) / 2.0;
const float indicatorSize = 0.02f * -m_viewport->camera()->zoom();
const float indicatorOffset = 0.02f * -m_viewport->camera()->zoom();
if (m_viewport->isSnappingHorizontal()) {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
vertices << midPoint.x() - indicatorSize << midPoint.y() << midPoint.z() + indicatorOffset;
vertices << midPoint.x() + indicatorSize << midPoint.y() << midPoint.z() + indicatorOffset;
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
vertices << midPoint.x() - indicatorSize << midPoint.y() + indicatorOffset << midPoint.z();
vertices << midPoint.x() + indicatorSize << midPoint.y() + indicatorOffset << midPoint.z();
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) {
vertices << midPoint.x() << midPoint.y() - indicatorSize << midPoint.z() + indicatorOffset;
vertices << midPoint.x() << midPoint.y() + indicatorSize << midPoint.z() + indicatorOffset;
}
} else { // m_isSnappingVertical
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
vertices << midPoint.x() + indicatorOffset << midPoint.y() << midPoint.z() - indicatorSize;
vertices << midPoint.x() + indicatorOffset << midPoint.y() << midPoint.z() + indicatorSize;
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
vertices << midPoint.x() + indicatorOffset << midPoint.y() - indicatorSize << midPoint.z();
vertices << midPoint.x() + indicatorOffset << midPoint.y() + indicatorSize << midPoint.z();
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) {
vertices << midPoint.x() << midPoint.y() + indicatorOffset << midPoint.z() - indicatorSize;
vertices << midPoint.x() << midPoint.y() + indicatorOffset << midPoint.z() + indicatorSize;
}
}
m_viewport->vbo().bind();
m_viewport->vbo().allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, 2);
}
}
}
void LineTool::paint2D(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection)
{
if (m_isDefining) {
QVector3D worldPos;
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
QString dimText;
QString angleText;
QString dimInput = m_viewport->property("dimensionInput").toString();
QString angleInput = m_viewport->property("angleInput").toString();
bool lengthFromInput = false;
bool angleFromInput = false;
double inputLength = 0;
double inputAngleDegrees = 0;
double lineLength = 0;
if (!dimInput.isEmpty()) {
bool ok;
inputLength = dimInput.toDouble(&ok);
if (ok) lengthFromInput = true;
}
if (!angleInput.isEmpty()) {
bool ok;
inputAngleDegrees = angleInput.toDouble(&ok);
if (ok) angleFromInput = true;
}
if (angleFromInput) {
QVector3D refDir;
if (m_viewport->property("isChainedLine").toBool()) {
refDir = m_viewport->property("previousLineDirection").value<QVector3D>();
} else {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
refDir = QVector3D(1, 0, 0);
} else { // YZ
refDir = QVector3D(0, 0, -1);
}
}
QVector3D currentMouseWorldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D mouseVec = currentMouseWorldPos - startPos;
double mouseAngle;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) mouseAngle = qRadiansToDegrees(atan2(mouseVec.y(), mouseVec.x()));
else mouseAngle = qRadiansToDegrees(atan2(mouseVec.z(), mouseVec.y()));
double refAngle;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) refAngle = qRadiansToDegrees(atan2(refDir.y(), refDir.x()));
else refAngle = qRadiansToDegrees(atan2(refDir.z(), refDir.y()));
double relativeMouseAngle = mouseAngle - refAngle;
while (relativeMouseAngle <= -180.0) relativeMouseAngle += 360.0;
while (relativeMouseAngle > 180.0) relativeMouseAngle -= 360.0;
double snappedAngle = 0;
if (relativeMouseAngle >= 0 && relativeMouseAngle < 90) { // Quadrant 1
snappedAngle = inputAngleDegrees;
} else if (relativeMouseAngle >= 90 && relativeMouseAngle <= 180) { // Quadrant 2
snappedAngle = 180.0 - inputAngleDegrees;
} else if (relativeMouseAngle < -90) { // Quadrant 3
snappedAngle = -180.0 + inputAngleDegrees;
} else { // Quadrant 4
snappedAngle = -inputAngleDegrees;
}
double finalAngleRad = qDegreesToRadians(refAngle + snappedAngle);
QVector3D finalDir;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) finalDir = QVector3D(cos(finalAngleRad), 0, sin(finalAngleRad));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) finalDir = QVector3D(cos(finalAngleRad), sin(finalAngleRad), 0);
else finalDir = QVector3D(0, cos(finalAngleRad), sin(finalAngleRad));
if (lengthFromInput) {
lineLength = inputLength;
} else {
lineLength = QVector3D::dotProduct(mouseVec, finalDir);
if (lineLength < 0) lineLength = 0;
}
worldPos = startPos + lineLength * finalDir;
} else if (lengthFromInput) {
lineLength = inputLength;
QVector3D currentMouseWorldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D dir = (currentMouseWorldPos - startPos);
if (dir.length() > 1e-6) {
dir.normalize();
worldPos = startPos + inputLength * dir;
} else {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
worldPos = startPos + QVector3D(inputLength, 0, 0);
} else {
worldPos = startPos + QVector3D(0, inputLength, 0);
}
}
} else {
worldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
if (m_viewport->isSnappingOrigin()) {
worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0);
} else if (m_viewport->isSnappingVertex()) {
worldPos.setX(m_viewport->snapVertex().X()); worldPos.setY(m_viewport->snapVertex().Y()); worldPos.setZ(m_viewport->snapVertex().Z());
} else if (m_viewport->isSnappingHorizontal()) {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) worldPos.setZ(m_firstLinePoint.Z());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) worldPos.setY(m_firstLinePoint.Y());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) worldPos.setZ(m_firstLinePoint.Z());
} else if (m_viewport->isSnappingVertical()) {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) worldPos.setX(m_firstLinePoint.X());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) worldPos.setX(m_firstLinePoint.X());
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) worldPos.setY(m_firstLinePoint.Y());
}
lineLength = (worldPos - startPos).length();
}
QVector3D lineVec = worldPos - startPos;
if (lineVec.length() > 1e-6) {
double refAngle, lineAngle;
QVector3D refDir;
if (m_viewport->property("isChainedLine").toBool()) {
refDir = m_viewport->property("previousLineDirection").value<QVector3D>();
} else {
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY || m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) refDir = QVector3D(1, 0, 0);
else refDir = QVector3D(0, 0, -1);
}
QVector3D currentMouseWorldPosForText = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D mouseVecForText = currentMouseWorldPosForText - startPos;
if (angleFromInput) {
if (mouseVecForText.length() > 1e-6) {
double mouseAngle;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) mouseAngle = qRadiansToDegrees(atan2(mouseVecForText.z(), mouseVecForText.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) mouseAngle = qRadiansToDegrees(atan2(mouseVecForText.y(), mouseVecForText.x()));
else mouseAngle = qRadiansToDegrees(atan2(mouseVecForText.z(), mouseVecForText.y()));
double refAngleForQuadrant;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) refAngleForQuadrant = qRadiansToDegrees(atan2(refDir.z(), refDir.x()));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) refAngleForQuadrant = qRadiansToDegrees(atan2(refDir.y(), refDir.x()));
else refAngleForQuadrant = qRadiansToDegrees(atan2(refDir.z(), refDir.y()));
double relativeMouseAngle = mouseAngle - refAngleForQuadrant;
while (relativeMouseAngle <= -180.0) relativeMouseAngle += 360.0;
while (relativeMouseAngle > 180.0) relativeMouseAngle -= 360.0;
if (relativeMouseAngle >= 90 || relativeMouseAngle < -90) {
refDir = -refDir;
}
}
} else {
if (m_viewport->property("isChainedLine").toBool()) {
refDir = -refDir;
}
}
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
refAngle = atan2(refDir.z(), refDir.x());
lineAngle = atan2(lineVec.z(), lineVec.x());
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
refAngle = atan2(refDir.y(), refDir.x());
lineAngle = atan2(lineVec.y(), lineVec.x());
} else { // YZ
refAngle = atan2(refDir.z(), refDir.y());
lineAngle = atan2(lineVec.z(), lineVec.y());
}
double angleDiff = lineAngle - refAngle;
while (angleDiff <= -M_PI) angleDiff += 2 * M_PI;
while (angleDiff > M_PI) angleDiff -= 2 * M_PI;
lineAngle = refAngle + angleDiff;
QVector3D perpVec;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
perpVec = QVector3D(-lineVec.z(), 0, lineVec.x()).normalized();
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
perpVec = QVector3D(-lineVec.y(), lineVec.x(), 0).normalized();
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) {
perpVec = QVector3D(0, -lineVec.z(), lineVec.y()).normalized();
}
if (angleDiff < 0) {
perpVec = -perpVec;
}
float offset = 0.05f * -m_viewport->camera()->zoom();
QVector3D dimStart = startPos + offset * perpVec;
QVector3D dimEnd = worldPos + offset * perpVec;
QVector3D textPos3D = (dimStart + dimEnd) / 2.0f + 0.015f * -m_viewport->camera()->zoom() * perpVec;
QVector3D screenPos = m_viewport->project(textPos3D, modelView, projection, m_viewport->rect());
painter.setRenderHint(QPainter::Antialiasing);
QFontMetrics fm(painter.font());
if (screenPos.z() < 1.0f) {
dimText = lengthFromInput ? dimInput : QString::number(lineLength, 'f', 2);
QRect textRect = fm.boundingRect(dimText + "_");
textRect.moveCenter(screenPos.toPoint());
if (m_viewport->property("dimensionEditMode").toString() == "length") {
painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(64, 128, 255));
} else {
painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(50, 50, 50));
}
painter.setPen(Qt::white);
painter.drawText(textRect, Qt::AlignCenter, dimText);
}
// Angle dimension text
double angleDiffDegrees = qRadiansToDegrees(angleDiff);
while (angleDiffDegrees <= -180.0) angleDiffDegrees += 360.0;
while (angleDiffDegrees > 180.0) angleDiffDegrees -= 360.0;
angleText = angleFromInput ? angleInput : QString::number(qAbs(angleDiffDegrees), 'f', 1) + QChar(0x00B0);
const float radius = 0.1f * -m_viewport->camera()->zoom();
double midAngle = refAngle + (lineAngle - refAngle) / 2.0;
QVector3D textPos3DAngle;
float textOffset = 0.035f * -m_viewport->camera()->zoom();
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) textPos3DAngle = startPos + (radius + textOffset) * QVector3D(cos(midAngle), 0, sin(midAngle));
else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) textPos3DAngle = startPos + (radius + textOffset) * QVector3D(cos(midAngle), sin(midAngle), 0);
else textPos3DAngle = startPos + (radius + textOffset) * QVector3D(0, cos(midAngle), sin(midAngle));
QVector3D screenPosAngle = m_viewport->project(textPos3DAngle, modelView, projection, m_viewport->rect());
if (screenPosAngle.z() < 1.0f) {
QRect angleTextRect = fm.boundingRect(angleText + "_");
angleTextRect.moveCenter(screenPosAngle.toPoint());
if (m_viewport->property("dimensionEditMode").toString() == "angle") {
painter.fillRect(angleTextRect.adjusted(-4, -2, 4, 2), QColor(64, 128, 255));
} else {
painter.fillRect(angleTextRect.adjusted(-4, -2, 4, 2), QColor(50, 50, 50));
}
painter.setPen(Qt::white);
painter.drawText(angleTextRect, Qt::AlignCenter, angleText);
}
}
}
}

28
src/LineTool.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef LINETOOL_H
#define LINETOOL_H
#include "SketchTool.h"
#include <gp_Pnt.hxx>
class LineTool : public SketchTool
{
Q_OBJECT
public:
explicit LineTool(ViewportWidget* viewport);
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void paintGL() override;
void paint2D(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection) override;
void activate() override;
protected:
void finalizeCreation() override;
private:
gp_Pnt m_firstLinePoint;
};
#endif // LINETOOL_H

View File

@@ -146,6 +146,8 @@ MainWindow::MainWindow(ApplicationController* appController, QWidget *parent)
connect(m_appController, &ApplicationController::planeSelectionModeStarted, m_viewport, &ViewportWidget::onPlaneSelectionModeStarted);
connect(m_viewport, &ViewportWidget::lineAdded, m_appController, &ApplicationController::addLine);
connect(m_viewport, &ViewportWidget::rectangleAdded, m_appController, &ApplicationController::addRectangle);
connect(m_viewport, &ViewportWidget::circleAdded, m_appController, &ApplicationController::addCircle);
connect(m_viewport, &ViewportWidget::planeSelected, m_appController, &ApplicationController::onPlaneSelected);
connect(m_viewport, &ViewportWidget::toolDeactivated, m_appController, [this]() { m_appController->setActiveTool(ApplicationController::ToolType::None); });

392
src/RectangleTool.cpp Normal file
View File

@@ -0,0 +1,392 @@
#include "RectangleTool.h"
#include "ViewportWidget.h"
#include "Camera.h"
#include <QMouseEvent>
#include <QKeyEvent>
#include <QPainter>
#include <QVector>
#include <QOpenGLShaderProgram>
#include <cmath>
#include <QtMath>
RectangleTool::RectangleTool(ViewportWidget* viewport)
: SketchTool(viewport)
{
}
void RectangleTool::activate()
{
SketchTool::activate();
m_dimensionModes << "height" << "width";
m_dimensionPropertyNames["height"] = "heightInput";
m_dimensionPropertyNames["width"] = "widthInput";
m_viewport->setProperty("widthInput", "");
m_viewport->setProperty("heightInput", "");
m_viewport->setProperty("dimensionEditMode", "height");
}
void RectangleTool::mousePressEvent(QMouseEvent *event)
{
gp_Pnt p;
if (!m_isDefining) {
if (m_viewport->isSnappingOrigin()) {
p.SetCoord(0, 0, 0);
} else if (m_viewport->isSnappingVertex()) {
p = m_viewport->snapVertex();
} else {
QVector3D worldPos = m_viewport->unproject(event->pos(), m_viewport->currentPlane());
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
}
m_firstRectanglePoint = p;
m_isDefining = true;
m_viewport->setProperty("widthInput", "");
m_viewport->setProperty("heightInput", "");
m_viewport->setProperty("dimensionEditMode", "height");
} else {
QVector3D worldPos;
QVector3D startPos(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z());
QString widthInput = m_viewport->property("widthInput").toString();
QString heightInput = m_viewport->property("heightInput").toString();
bool widthFromInput = false, heightFromInput = false;
double inputWidth = 0, inputHeight = 0;
if (!widthInput.isEmpty()) {
bool ok;
inputWidth = widthInput.toDouble(&ok);
if (ok) widthFromInput = true;
}
if (!heightInput.isEmpty()) {
bool ok;
inputHeight = heightInput.toDouble(&ok);
if (ok) heightFromInput = true;
}
if (widthFromInput || heightFromInput) {
QVector3D mousePos = m_viewport->unproject(event->pos(), m_viewport->currentPlane());
QVector3D mouseDir = mousePos - startPos;
double current_w, current_h;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
current_w = qAbs(mouseDir.x());
current_h = qAbs(mouseDir.z());
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
current_w = qAbs(mouseDir.x());
current_h = qAbs(mouseDir.y());
} else { // YZ
current_w = qAbs(mouseDir.y());
current_h = qAbs(mouseDir.z());
}
double rect_w = widthFromInput ? inputWidth : current_w;
double rect_h = heightFromInput ? inputHeight : current_h;
int signX = (mouseDir.x() >= 0) ? 1 : -1;
int signY = (mouseDir.y() >= 0) ? 1 : -1;
int signZ = (mouseDir.z() >= 0) ? 1 : -1;
worldPos = startPos;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
worldPos.setX(startPos.x() + signX * rect_w);
worldPos.setZ(startPos.z() + signZ * rect_h);
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
worldPos.setX(startPos.x() + signX * rect_w);
worldPos.setY(startPos.y() + signY * rect_h);
} else { // YZ
worldPos.setY(startPos.y() + signY * rect_w);
worldPos.setZ(startPos.z() + signZ * rect_h);
}
} else {
if (m_viewport->isSnappingOrigin()) {
worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0);
} else if (m_viewport->isSnappingVertex()) {
worldPos = QVector3D(m_viewport->snapVertex().X(), m_viewport->snapVertex().Y(), m_viewport->snapVertex().Z());
} else {
worldPos = m_viewport->unproject(event->pos(), m_viewport->currentPlane());
}
}
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
emit m_viewport->rectangleAdded(m_firstRectanglePoint, p);
deactivate();
}
}
void RectangleTool::mouseMoveEvent(QMouseEvent *event)
{
// To be implemented
}
void RectangleTool::finalizeCreation()
{
QVector3D worldPos;
QVector3D startPos(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z());
QString widthInput = m_viewport->property("widthInput").toString();
QString heightInput = m_viewport->property("heightInput").toString();
bool widthFromInput = false, heightFromInput = false;
double inputWidth = 0, inputHeight = 0;
if (!widthInput.isEmpty()) {
bool ok;
inputWidth = widthInput.toDouble(&ok);
if (ok) widthFromInput = true;
}
if (!heightInput.isEmpty()) {
bool ok;
inputHeight = heightInput.toDouble(&ok);
if (ok) heightFromInput = true;
}
if (widthFromInput || heightFromInput) {
QVector3D mousePos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
QVector3D mouseDir = mousePos - startPos;
double current_w, current_h;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
current_w = qAbs(mouseDir.x());
current_h = qAbs(mouseDir.z());
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
current_w = qAbs(mouseDir.x());
current_h = qAbs(mouseDir.y());
} else { // YZ
current_w = qAbs(mouseDir.y());
current_h = qAbs(mouseDir.z());
}
double rect_w = widthFromInput ? inputWidth : current_w;
double rect_h = heightFromInput ? inputHeight : current_h;
int signX = (mouseDir.x() >= 0) ? 1 : -1;
int signY = (mouseDir.y() >= 0) ? 1 : -1;
int signZ = (mouseDir.z() >= 0) ? 1 : -1;
worldPos = startPos;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
worldPos.setX(startPos.x() + signX * rect_w);
worldPos.setZ(startPos.z() + signZ * rect_h);
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
worldPos.setX(startPos.x() + signX * rect_w);
worldPos.setY(startPos.y() + signY * rect_h);
} else { // YZ
worldPos.setY(startPos.y() + signY * rect_w);
worldPos.setZ(startPos.z() + signZ * rect_h);
}
} else {
worldPos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
}
gp_Pnt p;
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
emit m_viewport->rectangleAdded(m_firstRectanglePoint, p);
deactivate();
}
void RectangleTool::paintGL()
{
if (m_isDefining) {
QVector<GLfloat> vertices;
QVector3D worldPos;
QVector3D startPos(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z());
QString widthInput = m_viewport->property("widthInput").toString();
QString heightInput = m_viewport->property("heightInput").toString();
bool widthFromInput = false;
bool heightFromInput = false;
double inputWidth = 0, inputHeight = 0;
if (!widthInput.isEmpty()) {
bool ok;
inputWidth = widthInput.toDouble(&ok);
if (ok) widthFromInput = true;
}
if (!heightInput.isEmpty()) {
bool ok;
inputHeight = heightInput.toDouble(&ok);
if (ok) heightFromInput = true;
}
QVector3D mousePos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
if (widthFromInput || heightFromInput) {
QVector3D mouseDir = mousePos - startPos;
double current_w, current_h;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
current_w = qAbs(mouseDir.x());
current_h = qAbs(mouseDir.z());
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
current_w = qAbs(mouseDir.x());
current_h = qAbs(mouseDir.y());
} else { // YZ
current_w = qAbs(mouseDir.y());
current_h = qAbs(mouseDir.z());
}
double rect_w = widthFromInput ? inputWidth : current_w;
double rect_h = heightFromInput ? inputHeight : current_h;
int signX = (mouseDir.x() >= 0) ? 1 : -1;
int signY = (mouseDir.y() >= 0) ? 1 : -1;
int signZ = (mouseDir.z() >= 0) ? 1 : -1;
worldPos = startPos;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
worldPos.setX(startPos.x() + signX * rect_w);
worldPos.setZ(startPos.z() + signZ * rect_h);
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
worldPos.setX(startPos.x() + signX * rect_w);
worldPos.setY(startPos.y() + signY * rect_h);
} else { // YZ
worldPos.setY(startPos.y() + signY * rect_w);
worldPos.setZ(startPos.z() + signZ * rect_h);
}
} else {
worldPos = mousePos;
if (m_viewport->isSnappingOrigin()) {
worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0);
} else if (m_viewport->isSnappingVertex()) {
worldPos.setX(m_viewport->snapVertex().X()); worldPos.setY(m_viewport->snapVertex().Y()); worldPos.setZ(m_viewport->snapVertex().Z());
}
}
QVector3D p1 = startPos;
QVector3D p2, p3, p4;
p3 = worldPos;
if (m_viewport->currentPlane() == ViewportWidget::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_viewport->currentPlane() == ViewportWidget::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_viewport->currentPlane() == ViewportWidget::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_viewport->shaderProgram()->setUniformValue(m_viewport->colorLoc(), QVector4D(1.0f, 1.0f, 0.0f, 1.0f));
m_viewport->vbo().bind();
m_viewport->vbo().allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, 8);
}
}
void RectangleTool::paint2D(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection)
{
if (m_isDefining) {
QVector3D worldPos;
QVector3D p1_3d(m_firstRectanglePoint.X(), m_firstRectanglePoint.Y(), m_firstRectanglePoint.Z());
QString widthInput = m_viewport->property("widthInput").toString();
QString heightInput = m_viewport->property("heightInput").toString();
bool widthFromInput = false;
bool heightFromInput = false;
double inputWidth = 0, inputHeight = 0;
if (!widthInput.isEmpty()) {
bool ok;
inputWidth = widthInput.toDouble(&ok);
if (ok) widthFromInput = true;
}
if (!heightInput.isEmpty()) {
bool ok;
inputHeight = heightInput.toDouble(&ok);
if (ok) heightFromInput = true;
}
QVector3D mousePos = m_viewport->unproject(m_viewport->currentMousePos(), m_viewport->currentPlane());
if (widthFromInput || heightFromInput) {
QVector3D mouseDir = mousePos - p1_3d;
double current_w, current_h;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
current_w = qAbs(mouseDir.x());
current_h = qAbs(mouseDir.z());
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
current_w = qAbs(mouseDir.x());
current_h = qAbs(mouseDir.y());
} else { // YZ
current_w = qAbs(mouseDir.y());
current_h = qAbs(mouseDir.z());
}
double rect_w = widthFromInput ? inputWidth : current_w;
double rect_h = heightFromInput ? inputHeight : current_h;
int signX = (mouseDir.x() >= 0) ? 1 : -1;
int signY = (mouseDir.y() >= 0) ? 1 : -1;
int signZ = (mouseDir.z() >= 0) ? 1 : -1;
worldPos = p1_3d;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
worldPos.setX(p1_3d.x() + signX * rect_w);
worldPos.setZ(p1_3d.z() + signZ * rect_h);
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
worldPos.setX(p1_3d.x() + signX * rect_w);
worldPos.setY(p1_3d.y() + signY * rect_h);
} else { // YZ
worldPos.setY(p1_3d.y() + signY * rect_w);
worldPos.setZ(p1_3d.z() + signZ * rect_h);
}
} else {
worldPos = mousePos;
if (m_viewport->isSnappingOrigin()) {
worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0);
} else if (m_viewport->isSnappingVertex()) {
worldPos.setX(m_viewport->snapVertex().X()); worldPos.setY(m_viewport->snapVertex().Y()); worldPos.setZ(m_viewport->snapVertex().Z());
}
}
QVector3D p3_3d = worldPos;
QVector3D p2_3d, p4_3d;
double w, h;
if (m_viewport->currentPlane() == ViewportWidget::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_viewport->currentPlane() == ViewportWidget::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 = m_viewport->project(widthTextPos3D, modelView, projection, m_viewport->rect());
if (screenPosW.z() < 1.0f) {
QString widthText = widthFromInput ? widthInput : 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));
if (m_viewport->property("dimensionEditMode").toString() == "width") {
painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(64, 128, 255));
} else {
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 = m_viewport->project(heightTextPos3D, modelView, projection, m_viewport->rect());
if (screenPosH.z() < 1.0f) {
QString heightText = heightFromInput ? heightInput : QString::number(h, 'f', 2);
QRect textRect = fm.boundingRect(heightText + "__");
textRect.moveCenter(screenPosH.toPoint() + QPoint((p3_3d.x() > p1_3d.x()) ? 15 : -15, 0));
if (m_viewport->property("dimensionEditMode").toString() == "height") {
painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(64, 128, 255));
} else {
painter.fillRect(textRect.adjusted(-4, -2, 4, 2), QColor(50, 50, 50));
}
painter.setPen(Qt::white);
painter.drawText(textRect, Qt::AlignCenter, heightText);
}
}
}

28
src/RectangleTool.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef RECTANGLETOOL_H
#define RECTANGLETOOL_H
#include "SketchTool.h"
#include <gp_Pnt.hxx>
class RectangleTool : public SketchTool
{
Q_OBJECT
public:
explicit RectangleTool(ViewportWidget* viewport);
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void paintGL() override;
void paint2D(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection) override;
void activate() override;
protected:
void finalizeCreation() override;
private:
gp_Pnt m_firstRectanglePoint;
};
#endif // RECTANGLETOOL_H

56
src/SketchCircle.cpp Normal file
View File

@@ -0,0 +1,56 @@
#include "SketchCircle.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());
}
}
SketchCircle::SketchCircle() : m_radius(0.0)
{
}
SketchCircle::SketchCircle(const gp_Pnt& center, double radius)
: m_center(center), m_radius(radius)
{
}
SketchObject::ObjectType SketchCircle::type() const
{
return ObjectType::Circle;
}
void SketchCircle::read(const QJsonObject& json)
{
m_center = jsonToPoint(json["center"].toArray());
m_radius = json["radius"].toDouble();
}
void SketchCircle::write(QJsonObject& json) const
{
QJsonArray centerArr;
pointToJson(m_center, centerArr);
json["center"] = centerArr;
json["radius"] = m_radius;
json["type"] = "Circle";
}
const gp_Pnt& SketchCircle::center() const
{
return m_center;
}
double SketchCircle::radius() const
{
return m_radius;
}

26
src/SketchCircle.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef SKETCHCIRCLE_H
#define SKETCHCIRCLE_H
#include "SketchObject.h"
#include <gp_Pnt.hxx>
class SketchCircle : public SketchObject
{
public:
SketchCircle();
SketchCircle(const gp_Pnt& center, double radius);
ObjectType type() const override;
void read(const QJsonObject& json) override;
void write(QJsonObject& json) const override;
const gp_Pnt& center() const;
double radius() const;
private:
gp_Pnt m_center;
double m_radius;
};
#endif // SKETCHCIRCLE_H

View File

@@ -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);
}
}
}

View File

@@ -1,10 +1,36 @@
#include "SketchGrid.h"
#include "ViewportWidget.h"
#include "Camera.h"
#include <QOpenGLContext>
#include <QOpenGLExtraFunctions>
#include <QOpenGLShaderProgram>
#include <QPainter>
#include <QVector>
SketchGrid::SketchGrid()
namespace {
struct GridParams {
float minorIncrement;
float majorIncrement;
int gridSize;
};
GridParams getGridParams(float distance)
{
if (distance > 500.0f) {
return { 20.0f, 100.0f, 1000 };
} else if (distance > 250.0f) {
return { 10.0f, 50.0f, 1000 };
} else if (distance > 50.0f) {
return { 5.0f, 25.0f, 1000 };
} else if (distance > 10.0f) {
return { 1.0f, 5.0f, 100 };
} else { // zoomed in
return { 0.2f, 1.0f, 10 };
}
}
} // namespace
SketchGrid::SketchGrid(ViewportWidget* viewport) : m_viewport(viewport)
{
}
@@ -46,45 +72,51 @@ void SketchGrid::paintGL(SketchPlane plane, QOpenGLShaderProgram* shaderProgram,
void SketchGrid::drawGridLines(SketchPlane plane, QOpenGLShaderProgram* shaderProgram, int colorLoc)
{
const int gridSize = 50;
const float darkFactor = 0.8f;
QVector<GLfloat> lightLines;
QVector<GLfloat> darkLines;
auto params = getGridParams(-m_viewport->camera()->uiCameraDistance());
const float minorIncrement = params.minorIncrement;
const int gridSize = params.gridSize;
for (int i = -gridSize; i <= gridSize; ++i)
{
if (i == 0) continue;
QVector<GLfloat>& current_vector = (i % 5 == 0) ? darkLines : lightLines;
QVector<GLfloat> minorLines;
QVector<GLfloat> majorLines;
int numLines = gridSize / minorIncrement;
for (int i = -numLines; i <= numLines; ++i) {
if (i == 0)
continue;
float pos = i * minorIncrement;
QVector<GLfloat>& current_vector = (i % 5 == 0) ? majorLines : minorLines;
if (plane == XY) {
current_vector << i << 0 << -gridSize << i << 0 << gridSize;
current_vector << -gridSize << 0 << i << gridSize << 0 << i;
current_vector << pos << 0 << -gridSize << pos << 0 << gridSize;
current_vector << -gridSize << 0 << pos << gridSize << 0 << pos;
} else if (plane == XZ) {
current_vector << i << -gridSize << 0 << i << gridSize << 0;
current_vector << -gridSize << i << 0 << gridSize << i << 0;
current_vector << pos << -gridSize << 0 << pos << gridSize << 0;
current_vector << -gridSize << pos << 0 << gridSize << pos << 0;
} else { // YZ
current_vector << 0 << i << -gridSize << 0 << i << gridSize;
current_vector << 0 << -gridSize << i << 0 << gridSize << i;
current_vector << 0 << pos << -gridSize << 0 << pos << gridSize;
current_vector << 0 << -gridSize << pos << 0 << gridSize << pos;
}
}
m_vbo.bind();
// Draw lighter lines
shaderProgram->setUniformValue(colorLoc, QVector4D(0.5f, 0.5f, 0.5f, 1.0f));
// Draw minor lines
shaderProgram->setUniformValue(colorLoc, QVector4D(0.4f, 0.4f, 0.4f, 1.0f));
glLineWidth(1.0f);
m_vbo.allocate(lightLines.constData(), lightLines.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, lightLines.size() / 3);
m_vbo.allocate(minorLines.constData(), minorLines.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, minorLines.size() / 3);
// Draw darker lines
shaderProgram->setUniformValue(colorLoc, QVector4D(0.5f * darkFactor, 0.5f * darkFactor, 0.5f * darkFactor, 1.0f));
glLineWidth(1.5f);
m_vbo.allocate(darkLines.constData(), darkLines.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, darkLines.size() / 3);
// Draw major lines
shaderProgram->setUniformValue(colorLoc, QVector4D(0.6f, 0.6f, 0.6f, 1.0f));
glLineWidth(1.0f);
m_vbo.allocate(majorLines.constData(), majorLines.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, majorLines.size() / 3);
}
void SketchGrid::drawAxes(SketchPlane plane, QOpenGLShaderProgram* shaderProgram, int colorLoc)
{
const int axisLength = 50;
auto params = getGridParams(-m_viewport->camera()->uiCameraDistance());
const int axisLength = params.gridSize;
QVector<GLfloat> vertices;
glLineWidth(2.0f);
@@ -123,3 +155,41 @@ void SketchGrid::drawAxes(SketchPlane plane, QOpenGLShaderProgram* shaderProgram
m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
glDrawArrays(GL_POINTS, 0, 1);
}
void SketchGrid::paintAxisLabels(QPainter& painter, SketchGrid::SketchPlane plane, const QMatrix4x4& modelView, const QMatrix4x4& projection)
{
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 10));
auto params = getGridParams(-m_viewport->camera()->uiCameraDistance());
const float majorIncrement = params.majorIncrement;
const int range = params.gridSize;
auto drawLabelsForAxis = [&](int axis_idx) {
int numLabels = range / majorIncrement;
for (int i = -numLabels; i <= numLabels; ++i) {
if (i == 0)
continue;
float val = i * majorIncrement;
QVector3D worldCoord;
worldCoord[axis_idx] = val;
QVector3D screenPos = m_viewport->project(worldCoord, modelView, projection, m_viewport->rect());
if (screenPos.z() < 1.0f) { // Not clipped
painter.drawText(screenPos.toPoint(), QString::number(val));
}
}
};
if (plane == SketchGrid::XY) {
drawLabelsForAxis(0); // X
drawLabelsForAxis(2); // Y
} else if (plane == SketchGrid::XZ) {
drawLabelsForAxis(0); // X
drawLabelsForAxis(1); // Z
} else if (plane == SketchGrid::YZ) {
drawLabelsForAxis(1); // Y
drawLabelsForAxis(2); // Z
}
}

View File

@@ -7,6 +7,8 @@
#include <QOpenGLBuffer>
class QOpenGLShaderProgram;
class QPainter;
class ViewportWidget;
class SketchGrid : protected QOpenGLFunctions
{
@@ -17,11 +19,12 @@ public:
YZ = 3
};
SketchGrid();
explicit SketchGrid(ViewportWidget* viewport);
~SketchGrid();
void initializeGL();
void paintGL(SketchPlane plane, QOpenGLShaderProgram* shaderProgram, int colorLoc);
void paintAxisLabels(QPainter& painter, SketchPlane plane, const QMatrix4x4& modelView, const QMatrix4x4& projection);
private:
void drawGridLines(SketchPlane plane, QOpenGLShaderProgram* shaderProgram, int colorLoc);
@@ -29,6 +32,7 @@ private:
QOpenGLVertexArrayObject m_vao;
QOpenGLBuffer m_vbo;
ViewportWidget* m_viewport = nullptr;
};
#endif // SKETCHGRID_H

View File

@@ -7,7 +7,9 @@ class SketchObject
{
public:
enum class ObjectType {
Line
Line,
Rectangle,
Circle
};
SketchObject() = default;

61
src/SketchRectangle.cpp Normal file
View 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
View 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

79
src/SketchTool.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include "SketchTool.h"
#include "ViewportWidget.h"
#include <QKeyEvent>
SketchTool::SketchTool(ViewportWidget* viewport)
: QObject(viewport), m_viewport(viewport)
{
}
void SketchTool::activate()
{
m_isDefining = false;
}
void SketchTool::deactivate()
{
m_isDefining = false;
for (const QString& propName : m_dimensionPropertyNames.values()) {
m_viewport->setProperty(propName.toUtf8().constData(), "");
}
m_dimensionModes.clear();
m_dimensionPropertyNames.clear();
}
void SketchTool::keyPressEvent(QKeyEvent *event)
{
if (m_isDefining) {
if (event->key() == Qt::Key_Tab) {
if (m_dimensionModes.size() > 1) {
QString currentMode = m_viewport->property("dimensionEditMode").toString();
int currentIndex = m_dimensionModes.indexOf(currentMode);
if (currentIndex != -1) {
int nextIndex = (currentIndex + 1) % m_dimensionModes.size();
m_viewport->setProperty("dimensionEditMode", m_dimensionModes[nextIndex]);
m_viewport->update();
}
}
return;
}
QString editMode = m_viewport->property("dimensionEditMode").toString();
if (m_dimensionPropertyNames.contains(editMode)) {
QString propertyName = m_dimensionPropertyNames[editMode];
QString currentInput = m_viewport->property(propertyName.toUtf8().constData()).toString();
if (event->key() >= Qt::Key_0 && event->key() <= Qt::Key_9) {
currentInput += event->text();
m_viewport->setProperty(propertyName.toUtf8().constData(), currentInput);
m_viewport->update();
return;
} else if (event->key() == Qt::Key_Period) {
if (!currentInput.contains('.')) {
currentInput += '.';
m_viewport->setProperty(propertyName.toUtf8().constData(), currentInput);
m_viewport->update();
}
return;
} else if (event->key() == Qt::Key_Backspace) {
if (!currentInput.isEmpty()) {
currentInput.chop(1);
m_viewport->setProperty(propertyName.toUtf8().constData(), currentInput);
m_viewport->update();
}
return;
}
}
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
finalizeCreation();
m_viewport->update();
return;
} else if (event->key() == Qt::Key_Escape) {
deactivate();
m_viewport->deactivateActiveTool();
m_viewport->update();
return;
}
}
}

40
src/SketchTool.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef SKETCHTOOL_H
#define SKETCHTOOL_H
#include <QObject>
#include <QStringList>
#include <QMap>
class QMouseEvent;
class QKeyEvent;
class ViewportWidget;
class QOpenGLShaderProgram;
class QPainter;
class QMatrix4x4;
class SketchTool : public QObject
{
Q_OBJECT
public:
explicit SketchTool(ViewportWidget* viewport);
virtual ~SketchTool() = default;
virtual void mousePressEvent(QMouseEvent *event) = 0;
virtual void mouseMoveEvent(QMouseEvent *event) = 0;
virtual void keyPressEvent(QKeyEvent *event);
virtual void paintGL() = 0;
virtual void paint2D(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection) = 0;
virtual void activate();
virtual void deactivate();
protected:
virtual void finalizeCreation() = 0;
ViewportWidget* m_viewport;
bool m_isDefining = false;
QStringList m_dimensionModes;
QMap<QString, QString> m_dimensionPropertyNames;
};
#endif // SKETCHTOOL_H

185
src/Snapping.cpp Normal file
View File

@@ -0,0 +1,185 @@
#include "Snapping.h"
#include "ViewportWidget.h"
#include "Camera.h"
#include "Document.h"
#include "SketchFeature.h"
#include "SketchLine.h"
#include "SketchRectangle.h"
#include "SketchObject.h"
#include "ApplicationController.h"
#include <QOpenGLShaderProgram>
#include <QVector>
#include <QtMath>
Snapping::Snapping(ViewportWidget* viewport) : m_viewport(viewport)
{
}
bool Snapping::update(const QPoint& mousePos)
{
bool oldIsSnappingOrigin = m_isSnappingOrigin;
bool oldIsSnappingVertex = m_isSnappingVertex;
bool shouldSnap = false;
if (m_viewport->currentPlane() != ViewportWidget::SketchPlane::NONE && m_viewport->activeTool() != static_cast<int>(ApplicationController::ToolType::None)) {
QVector3D worldPos = m_viewport->unproject(mousePos, m_viewport->currentPlane());
const float snapRectHalfSize = 0.0075f * -m_viewport->camera()->zoom();
switch (m_viewport->currentPlane()) {
case ViewportWidget::SketchPlane::XY:
shouldSnap = qAbs(worldPos.x()) < snapRectHalfSize && qAbs(worldPos.z()) < snapRectHalfSize;
break;
case ViewportWidget::SketchPlane::XZ:
shouldSnap = qAbs(worldPos.x()) < snapRectHalfSize && qAbs(worldPos.y()) < snapRectHalfSize;
break;
case ViewportWidget::SketchPlane::YZ:
shouldSnap = qAbs(worldPos.y()) < snapRectHalfSize && qAbs(worldPos.z()) < snapRectHalfSize;
break;
case ViewportWidget::SketchPlane::NONE:
break;
}
}
m_isSnappingOrigin = shouldSnap;
if (m_isSnappingOrigin) {
m_isSnappingVertex = false;
}
m_isSnappingVertex = false;
if (!m_isSnappingOrigin && m_viewport->document() && m_viewport->currentPlane() != ViewportWidget::SketchPlane::NONE && m_viewport->activeTool() != static_cast<int>(ApplicationController::ToolType::None)) {
QVector3D worldPos = m_viewport->unproject(mousePos, m_viewport->currentPlane());
const float snapRectHalfSize = 0.0075f * -m_viewport->camera()->zoom();
for (Feature* feature : m_viewport->document()->features()) {
if (auto sketch = dynamic_cast<SketchFeature*>(feature)) {
for (const auto& obj : sketch->objects()) {
if (obj->type() == SketchObject::ObjectType::Line) {
auto line = static_cast<const SketchLine*>(obj);
const gp_Pnt vertices[] = {line->startPoint(), line->endPoint()};
for (const auto& vertex : vertices) {
bool isClose = false;
switch (m_viewport->currentPlane()) {
case ViewportWidget::SketchPlane::XY:
isClose = qAbs(worldPos.x() - vertex.X()) < snapRectHalfSize && qAbs(worldPos.z() - vertex.Z()) < snapRectHalfSize;
break;
case ViewportWidget::SketchPlane::XZ:
isClose = qAbs(worldPos.x() - vertex.X()) < snapRectHalfSize && qAbs(worldPos.y() - vertex.Y()) < snapRectHalfSize;
break;
case ViewportWidget::SketchPlane::YZ:
isClose = qAbs(worldPos.y() - vertex.Y()) < snapRectHalfSize && qAbs(worldPos.z() - vertex.Z()) < snapRectHalfSize;
break;
case ViewportWidget::SketchPlane::NONE:
break;
}
if (isClose) {
m_isSnappingVertex = true;
m_snapVertex = vertex;
goto end_snap_check;
}
}
} 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());
}
const gp_Pnt vertices[] = {p1, p2, p3, p4};
for (const auto& vertex : vertices) {
bool isClose = false;
switch (m_viewport->currentPlane()) {
case ViewportWidget::SketchPlane::XY:
isClose = qAbs(worldPos.x() - vertex.X()) < snapRectHalfSize && qAbs(worldPos.z() - vertex.Z()) < snapRectHalfSize;
break;
case ViewportWidget::SketchPlane::XZ:
isClose = qAbs(worldPos.x() - vertex.X()) < snapRectHalfSize && qAbs(worldPos.y() - vertex.Y()) < snapRectHalfSize;
break;
case ViewportWidget::SketchPlane::YZ:
isClose = qAbs(worldPos.y() - vertex.Y()) < snapRectHalfSize && qAbs(worldPos.z() - vertex.Z()) < snapRectHalfSize;
break;
case ViewportWidget::SketchPlane::NONE:
break;
}
if (isClose) {
m_isSnappingVertex = true;
m_snapVertex = vertex;
goto end_snap_check;
}
}
}
}
}
}
end_snap_check:;
}
return (oldIsSnappingOrigin != m_isSnappingOrigin) || (oldIsSnappingVertex != m_isSnappingVertex);
}
void Snapping::paintGL() const
{
if (!m_isSnappingOrigin && !m_isSnappingVertex) {
return;
}
QVector<GLfloat> vertices;
if (m_isSnappingOrigin) {
const float rectSize = 0.0075f * -m_viewport->camera()->zoom();
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
vertices << -rectSize << 0 << -rectSize << rectSize << 0 << -rectSize;
vertices << rectSize << 0 << -rectSize << rectSize << 0 << rectSize;
vertices << rectSize << 0 << rectSize << -rectSize << 0 << rectSize;
vertices << -rectSize << 0 << rectSize << -rectSize << 0 << -rectSize;
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
vertices << -rectSize << -rectSize << 0 << rectSize << -rectSize << 0;
vertices << rectSize << -rectSize << 0 << rectSize << rectSize << 0;
vertices << rectSize << rectSize << 0 << -rectSize << rectSize << 0;
vertices << -rectSize << rectSize << 0 << -rectSize << -rectSize << 0;
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) {
vertices << 0 << -rectSize << -rectSize << 0 << rectSize << -rectSize;
vertices << 0 << rectSize << -rectSize << 0 << rectSize << rectSize;
vertices << 0 << rectSize << rectSize << 0 << -rectSize << rectSize;
vertices << 0 << -rectSize << rectSize << 0 << -rectSize << -rectSize;
}
} else if (m_isSnappingVertex) {
const float rectSize = 0.0075f * -m_viewport->camera()->zoom();
const auto& v = m_snapVertex;
if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XY) {
vertices << v.X() - rectSize << v.Y() << v.Z() - rectSize << v.X() + rectSize << v.Y() << v.Z() - rectSize;
vertices << v.X() + rectSize << v.Y() << v.Z() - rectSize << v.X() + rectSize << v.Y() << v.Z() + rectSize;
vertices << v.X() + rectSize << v.Y() << v.Z() + rectSize << v.X() - rectSize << v.Y() << v.Z() + rectSize;
vertices << v.X() - rectSize << v.Y() << v.Z() + rectSize << v.X() - rectSize << v.Y() << v.Z() - rectSize;
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::XZ) {
vertices << v.X() - rectSize << v.Y() - rectSize << v.Z() << v.X() + rectSize << v.Y() - rectSize << v.Z();
vertices << v.X() + rectSize << v.Y() - rectSize << v.Z() << v.X() + rectSize << v.Y() + rectSize << v.Z();
vertices << v.X() + rectSize << v.Y() + rectSize << v.Z() << v.X() - rectSize << v.Y() + rectSize << v.Z();
vertices << v.X() - rectSize << v.Y() + rectSize << v.Z() << v.X() - rectSize << v.Y() - rectSize << v.Z();
} else if (m_viewport->currentPlane() == ViewportWidget::SketchPlane::YZ) {
vertices << v.X() << v.Y() - rectSize << v.Z() - rectSize << v.X() << v.Y() + rectSize << v.Z() - rectSize;
vertices << v.X() << v.Y() + rectSize << v.Z() - rectSize << v.X() << v.Y() + rectSize << v.Z() + rectSize;
vertices << v.X() << v.Y() + rectSize << v.Z() + rectSize << v.X() << v.Y() - rectSize << v.Z() + rectSize;
vertices << v.X() << v.Y() - rectSize << v.Z() + rectSize << v.X() << v.Y() - rectSize << v.Z() - rectSize;
}
}
m_viewport->shaderProgram()->setUniformValue(m_viewport->colorLoc(), QVector4D(1.0f, 1.0f, 0.0f, 0.5f));
m_viewport->vbo().bind();
m_viewport->vbo().allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDrawArrays(GL_LINES, 0, vertices.size() / 3);
glDisable(GL_BLEND);
}

28
src/Snapping.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef SNAPPING_H
#define SNAPPING_H
#include <gp_Pnt.hxx>
#include <QPoint>
class ViewportWidget;
class Snapping
{
public:
explicit Snapping(ViewportWidget* viewport);
bool update(const QPoint& mousePos);
void paintGL() const;
bool isSnappingOrigin() const { return m_isSnappingOrigin; }
bool isSnappingVertex() const { return m_isSnappingVertex; }
const gp_Pnt& snapVertex() const { return m_snapVertex; }
private:
ViewportWidget* m_viewport = nullptr;
bool m_isSnappingOrigin = false;
bool m_isSnappingVertex = false;
gp_Pnt m_snapVertex;
};
#endif // SNAPPING_H

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,8 @@ class Document;
class FeatureBrowser;
class SketchFeature;
class Camera;
class SketchTool;
class Snapping;
class ViewportWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
@@ -38,6 +40,27 @@ public:
void setDocument(Document* document);
QVector3D project(const QVector3D& worldCoord, const QMatrix4x4& modelView, const QMatrix4x4& projection, const QRect& viewport);
QVector3D unproject(const QPoint& screenPos, SketchPlane plane);
QOpenGLShaderProgram* shaderProgram() { return m_shaderProgram; }
QOpenGLBuffer& vbo() { return m_vbo; }
int colorLoc() const { return m_colorLoc; }
Camera* camera() const { return m_camera; }
Document* document() const { return m_document; }
SketchPlane currentPlane() const { return m_currentPlane; }
const QPoint& currentMousePos() const { return m_currentMousePos; }
bool isSnappingOrigin() const;
bool isSnappingVertex() const;
const gp_Pnt& snapVertex() const;
int activeTool() const { return m_activeTool; }
bool isSnappingHorizontal() const { return m_isSnappingHorizontal; }
bool isSnappingVertical() const { return m_isSnappingVertical; }
void setSnappingHorizontal(bool snapping);
void setSnappingVertical(bool snapping);
void addLine(const gp_Pnt& start, const gp_Pnt& end);
void deactivateActiveTool();
public slots:
void onSketchModeStarted(SketchPlane plane);
void onSketchModeEnded();
@@ -46,15 +69,21 @@ public slots:
signals:
void lineAdded(const gp_Pnt& start, const gp_Pnt& end);
void rectangleAdded(const gp_Pnt& corner1, const gp_Pnt& corner2);
void circleAdded(const gp_Pnt& center, double radius);
void planeSelected(SketchPlane plane);
void toolDeactivated();
private slots:
void onRestoreStateAnimationFinished();
protected:
void initializeGL() override;
void paintGL() override;
void resizeGL(int w, int h) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
@@ -62,9 +91,6 @@ protected:
private:
void initShaders();
QVector3D project(const QVector3D& worldCoord, const QMatrix4x4& modelView, const QMatrix4x4& projection, const QRect& viewport);
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);
@@ -89,19 +115,15 @@ private:
SketchPlane m_highlightedPlane = SketchPlane::NONE;
int m_activeTool = 0;
bool m_isDefiningLine = false;
gp_Pnt m_firstLinePoint;
SketchTool* m_activeSketchTool = nullptr;
QMap<int, SketchTool*> m_sketchTools;
QPoint m_currentMousePos;
bool m_isSnappingOrigin = false;
bool m_isSnappingVertex = false;
gp_Pnt m_snapVertex;
Snapping* m_snapping = nullptr;
bool m_isSnappingHorizontal = false;
bool m_isSnappingVertical = false;
QMap<int, QSvgRenderer*> m_toolIcons;
QSvgRenderer* m_cursorRenderer = nullptr;
QPoint lastPos;
};
#endif // VIEWPORTWIDGET_H