Compare commits

...

98 Commits

Author SHA1 Message Date
1be782b88d fix: Improve face creation from edges using BRepOffsetAPI_MakeFilling
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-18 12:23:29 -07:00
3444e9e183 chore: Add debug logging for sketch face creation
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-18 12:18:58 -07:00
fdd972b286 fix: Improve face creation for wire-based shapes by inferring plane
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-18 12:16:45 -07:00
768eed2f39 Adjust sketch grid 2026-02-18 12:11:06 -07:00
41639882db fix: Correct face generation and resolve tool preview glitches
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-18 12:11:06 -07:00
fa6e4662a6 fix: Generate faces for sketch objects and fix tool preview state
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-18 12:11:06 -07:00
a7ad78e103 fix: Correct coordinate system for sketch plane geometry and rendering
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-18 12:11:06 -07:00
0798cd2a6c fix: Correct sketch face rendering, orientation, and complex wire generation
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-18 12:11:06 -07:00
ce6975cc44 feat: Add Blinn-Phong shading for faces
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-18 12:10:59 -07:00
b9860f3de0 fix: Prevent crash from failed sketch geometry creation
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 18:12:08 -07:00
6e729183ef fix: Update OpenCASCADE triangulation API usage
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 17:57:13 -07:00
d5d430e80d fix: Add edges to wire builder individually
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 17:56:15 -07:00
95b4db5191 feat: Render OpenCASCADE faces in viewport
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 17:54:59 -07:00
e2dfdf1600 feat: Generate faces from closed sketch wires
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 17:52:17 -07:00
2333a7cdb5 fix: Emulate middle click with left + right mouse button for camera controls
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 17:29:58 -07:00
8a75dab453 Add README with build instructions 2026-02-17 17:28:32 -07:00
6396e49f9b refactor: Revert home button color inversion logic
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 17:28:25 -07:00
22efad4684 style: Adjust ViewCube home button position 2026-02-17 17:28:25 -07:00
d40ae7e670 refactor: DRY home button rect calculation in ViewCube
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 17:28:25 -07:00
2fb73d91ef feat: Invert home button icon color to white
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 17:28:25 -07:00
a6d46a8280 refactor: Update ViewCube home button icon and position 2026-02-17 17:28:12 -07:00
c28c080009 feat: Add animated home button to view cube to reset camera
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 16:56:25 -07:00
405e151f12 style: Increase default view cube opacity 2026-02-17 16:56:22 -07:00
5e20822df4 feat: Add translucent view cube with hover opacity effect
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 16:49:49 -07:00
34ecee0fa2 fix: Scale view cube size by device pixel ratio
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 16:44:54 -07:00
7010d221d0 fix: Correct OpenGL viewport scaling on high-DPI displays
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-17 16:42:01 -07:00
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
b2d5cd19d4 feat: Prevent Tab key from moving focus outside active viewport tools 2026-02-16 18:51:57 -07:00
d89b7e42bc fix: Resolve variable redeclaration and font metrics scope in paintGL
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 17:45:05 -07:00
9d72fe2155 feat: Add editable angle dimensions to line drawing
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 17:42:05 -07:00
93282fce52 feat: Implement interactive dimension input for line drawing
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 17:23:35 -07:00
a18435bb15 style: Reduce radius of unattached sketch vertices 2026-02-16 17:04:13 -07:00
ec75ab41c5 style: Render unattached sketch vertices as filled circles with outline
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 16:51:11 -07:00
aaaf9a44ff feat: Billboard unattached sketch vertices
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 16:48:31 -07:00
bc2e84a537 fix: Replace qPi() with M_PI to resolve build error
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 16:45:59 -07:00
f842555582 style: Render unattached sketch vertices as unfilled circles
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 16:45:22 -07:00
a980ad52be fix: Replace QMap with std::map for custom comparator support
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
2026-02-16 16:41:59 -07:00
36 changed files with 3361 additions and 479 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
)

19
README.md Normal file
View File

@@ -0,0 +1,19 @@
# Unnamed CAD Software
## Development
### Building
On Debian 12:
```bash
$ sudo apt install cmake qt6-base-dev qt6-svg-dev libtbb-dev
$ sudo apt install libocct-foundation-dev libocct-modeling-data-dev libocct-modeling-algorithms-dev libocct-visualization-dev
$ mkdir build
$ cd build/
$ cmake ..
$ make
$ ./OpenCAD
```

42
icons/home.svg Normal file
View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 24 24"
fill="currentColor"
class="size-6"
version="1.1"
id="svg2"
sodipodi:docname="home.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="14.574147"
inkscape:cx="6.9643869"
inkscape:cy="8.6797532"
inkscape:window-width="1440"
inkscape:window-height="831"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<path
d="M11.47 3.841a.75.75 0 0 1 1.06 0l8.69 8.69a.75.75 0 1 0 1.06-1.061l-8.689-8.69a2.25 2.25 0 0 0-3.182 0l-8.69 8.69a.75.75 0 1 0 1.061 1.06l8.69-8.689Z"
id="path1"
style="fill:#ffffff" />
<path
d="m12 5.432 8.159 8.159c.03.03.06.058.091.086v6.198c0 1.035-.84 1.875-1.875 1.875H15a.75.75 0 0 1-.75-.75v-4.5a.75.75 0 0 0-.75-.75h-3a.75.75 0 0 0-.75.75V21a.75.75 0 0 1-.75.75H5.625a1.875 1.875 0 0 1-1.875-1.875v-6.198a2.29 2.29 0 0 0 .091-.086L12 5.432Z"
id="path2"
style="fill:#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -7,11 +7,14 @@
<file>icons/circle.svg</file>
<file>icons/save-sketch.svg</file>
<file>icons/cursor.svg</file>
<file>icons/home.svg</file>
</qresource>
<qresource prefix="/shaders">
<file alias="simple.vert">src/shaders/simple.vert</file>
<file alias="simple.frag">src/shaders/simple.frag</file>
<file alias="texture.vert">src/shaders/texture.vert</file>
<file alias="texture.frag">src/shaders/texture.frag</file>
<file alias="lit.vert">src/shaders/lit.vert</file>
<file alias="lit.frag">src/shaders/lit.frag</file>
</qresource>
</RCC>

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,8 +142,25 @@ 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()
{
if (m_activeSketch) {
m_activeSketch->buildShape();
}
m_activeSketch = nullptr;
setActiveTool(ToolType::None);
emit sketchModeEnded();

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();
}
if (event->buttons() & Qt::MiddleButton) {
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) || (event->buttons() == (Qt::LeftButton | Qt::RightButton))) {
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,264 @@ 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::animateToHomeView()
{
auto* animGroup = new QParallelAnimationGroup(this);
const float full_circle = 360.0f * 16.0f;
float currentXRot = xRotation();
float targetXRot = 30 * 16;
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 = -45 * 16;
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,16 @@ public:
void saveState();
void restoreState();
void animateToPlaneView(int plane);
void animateRestoreState();
void animateToHomeView();
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 +57,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

@@ -17,11 +17,14 @@
#include <QIcon>
#include <QInputDialog>
#include <QStringList>
#include <QKeyEvent>
#include <QApplication>
MainWindow::MainWindow(ApplicationController* appController, QWidget *parent)
: QMainWindow(parent)
, m_appController(appController)
{
qApp->installEventFilter(this);
setWindowTitle("OpenCAD");
resize(1920, 1080);
@@ -143,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); });
@@ -204,3 +209,19 @@ void MainWindow::updateWindowTitle(const QString& filePath)
shownName = "Untitled";
setWindowTitle(tr("%1[*] - %2").arg(QFileInfo(shownName).fileName(), tr("OpenCAD")));
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (m_appController->activeTool() != ApplicationController::ToolType::None && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab) {
if (watched == m_viewport) {
return false; // Let the viewport handle its own event
}
// Forward event to viewport and consume it
QApplication::sendEvent(m_viewport, keyEvent);
return true;
}
}
return QMainWindow::eventFilter(watched, event);
}

View File

@@ -3,6 +3,7 @@
#include <QMainWindow>
class QEvent;
class ViewportWidget;
class Document;
class Feature;
@@ -29,6 +30,9 @@ private slots:
void exitSketchMode();
void updateWindowTitle(const QString& filePath);
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
ApplicationController* m_appController;
ViewportWidget *m_viewport;

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,8 +1,25 @@
#include "SketchFeature.h"
#include "SketchObject.h"
#include "SketchLine.h"
#include "SketchRectangle.h"
#include "SketchCircle.h"
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRep_Builder.hxx>
#include <TopoDS_Compound.hxx>
#include <TopoDS.hxx>
#include <gp_Circ.hxx>
#include <gp_Ax2.hxx>
#include <TopExp_Explorer.hxx>
#include <ShapeAnalysis_FreeBounds.hxx>
#include <gp_Pln.hxx>
#include <BRepOffsetAPI_MakeFilling.hxx>
#include <GeomAbs_Shape.hxx>
#include <QJsonArray>
#include <QDebug>
SketchFeature::SketchFeature(const QString& name)
: Feature(name)
@@ -34,6 +51,119 @@ const TopoDS_Shape& SketchFeature::shape() const
return m_shape;
}
void SketchFeature::buildShape()
{
m_shape.Nullify();
QList<TopoDS_Edge> lineEdges;
QList<TopoDS_Face> faces;
gp_Pln sketchPlane;
switch (m_plane) {
case SketchPlane::XY: sketchPlane = gp_Pln(gp::Origin(), gp::DY()); break;
case SketchPlane::XZ: sketchPlane = gp_Pln(gp::Origin(), gp::DZ()); break;
case SketchPlane::YZ: sketchPlane = gp_Pln(gp::Origin(), gp::DX()); break;
}
for (SketchObject* obj : m_objects) {
if (auto line = dynamic_cast<SketchLine*>(obj)) {
BRepBuilderAPI_MakeEdge makeEdge(line->startPoint(), line->endPoint());
if (makeEdge.IsDone()) {
lineEdges.append(makeEdge.Edge());
}
} else if (auto rect = dynamic_cast<SketchRectangle*>(obj)) {
const gp_Pnt& c1 = rect->corner1();
const gp_Pnt& c2 = rect->corner2();
gp_Pnt other_corner1, other_corner2;
if (m_plane == SketchPlane::XY) {
other_corner1.SetCoord(c2.X(), c1.Y(), c1.Z());
other_corner2.SetCoord(c1.X(), c1.Y(), c2.Z());
} else if (m_plane == SketchPlane::XZ) {
other_corner1.SetCoord(c2.X(), c1.Y(), c1.Z());
other_corner2.SetCoord(c1.X(), c2.Y(), c1.Z());
} else { // YZ
other_corner1.SetCoord(c1.X(), c2.Y(), c1.Z());
other_corner2.SetCoord(c1.X(), c1.Y(), c2.Z());
}
BRepBuilderAPI_MakeEdge me1(c1, other_corner1);
BRepBuilderAPI_MakeEdge me2(other_corner1, c2);
BRepBuilderAPI_MakeEdge me3(c2, other_corner2);
BRepBuilderAPI_MakeEdge me4(other_corner2, c1);
if (me1.IsDone() && me2.IsDone() && me3.IsDone() && me4.IsDone()) {
BRepBuilderAPI_MakeWire wireBuilder(me1.Edge(), me2.Edge(), me3.Edge(), me4.Edge());
if (wireBuilder.IsDone()) {
BRepBuilderAPI_MakeFace faceBuilder(sketchPlane, wireBuilder.Wire());
if (faceBuilder.IsDone()) {
faces.append(faceBuilder.Face());
}
}
}
} else if (auto circle = dynamic_cast<SketchCircle*>(obj)) {
const gp_Pnt& center = circle->center();
double radius = circle->radius();
gp_Dir normal;
switch (m_plane) {
case SketchPlane::XY: normal = gp::DY(); break;
case SketchPlane::XZ: normal = gp::DZ(); break;
case SketchPlane::YZ: normal = gp::DX(); break;
}
gp_Ax2 axis(center, normal);
gp_Circ circ(axis, radius);
BRepBuilderAPI_MakeEdge makeEdge(circ);
if (makeEdge.IsDone()) {
TopoDS_Edge edge = makeEdge.Edge();
BRepBuilderAPI_MakeWire wireBuilder(edge);
if(wireBuilder.IsDone()) {
BRepBuilderAPI_MakeFace faceBuilder(sketchPlane, wireBuilder.Wire());
if (faceBuilder.IsDone()) {
faces.append(faceBuilder.Face());
}
}
}
}
}
if (!lineEdges.isEmpty()) {
qDebug() << "buildShape: processing" << lineEdges.size() << "line edges";
BRepOffsetAPI_MakeFilling faceMaker;
for (const TopoDS_Edge& edge : lineEdges) {
faceMaker.Add(edge, GeomAbs_C0);
}
faceMaker.Build();
if (faceMaker.IsDone()) {
TopExp_Explorer explorer(faceMaker.Shape(), TopAbs_FACE);
int facesAdded = 0;
for (; explorer.More(); explorer.Next()) {
faces.append(TopoDS::Face(explorer.Current()));
facesAdded++;
}
qDebug() << "buildShape: added" << facesAdded << "face(s) using MakeFilling";
} else {
qDebug() << "buildShape: MakeFilling failed";
}
}
qDebug() << "buildShape: total faces created:" << faces.size();
if (faces.isEmpty()) {
return;
}
if (faces.size() == 1) {
m_shape = faces.first();
} else {
TopoDS_Compound compound;
BRep_Builder builder;
builder.MakeCompound(compound);
for (const auto& face : faces) {
builder.Add(compound, face);
}
m_shape = compound;
}
}
void SketchFeature::addObject(SketchObject* object)
{
m_objects.append(object);
@@ -66,6 +196,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

@@ -26,6 +26,8 @@ public:
const TopoDS_Shape& shape() const;
void buildShape();
void addObject(SketchObject* object);
const QList<SketchObject*>& objects() const;

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, 1000 };
} else { // zoomed in
return { 0.2f, 1.0f, 1000 };
}
}
} // 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

View File

@@ -1,12 +1,30 @@
#include "ViewCube.h"
#include <QGuiApplication>
#include <QPainter>
#include <QFont>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QScreen>
#include <QSvgRenderer>
#include <QVector>
namespace
{
QRect getHomeButtonRect(int widgetWidth) {
const int viewCubeSize = 150; // logical pixels
const int buttonSize = 24;
const int buttonOffsetX = 16;
const int buttonOffsetY = 16;
const int viewCubeX = widgetWidth - viewCubeSize;
return QRect(viewCubeX + buttonOffsetX, buttonOffsetY, buttonSize, buttonSize);
}
}
ViewCube::ViewCube()
{
m_homeButtonRenderer = new QSvgRenderer(QString(":/icons/home.svg"));
for (int i = 0; i < 6; ++i) {
m_faceTextures[i] = nullptr;
}
@@ -18,6 +36,7 @@ ViewCube::~ViewCube()
delete m_faceTextures[i];
}
delete m_textureShaderProgram;
delete m_homeButtonRenderer;
m_cubeVbo.destroy();
m_cubeVao.destroy();
m_axesVbo.destroy();
@@ -32,17 +51,30 @@ void ViewCube::initializeGL()
setupBuffers();
}
void ViewCube::paintGL(QOpenGLShaderProgram* simpleShader, int simpleShaderColorLoc, const QMatrix4x4& viewMatrix, int width, int height)
void ViewCube::paintGL(QOpenGLShaderProgram* simpleShader, int simpleShaderColorLoc, const QMatrix4x4& viewMatrix, int width, int height, const QPoint& mousePos)
{
int viewCubeSize = 150;
int viewCubeSize = 150 * QGuiApplication::primaryScreen()->devicePixelRatio();
glViewport(width - viewCubeSize, height - viewCubeSize, viewCubeSize, viewCubeSize);
QRect viewCubeRect(width - viewCubeSize, 0, viewCubeSize, viewCubeSize);
QPoint physicalMousePos = mousePos * QGuiApplication::primaryScreen()->devicePixelRatio();
m_isHovered = viewCubeRect.contains(physicalMousePos);
float opacity = m_isHovered ? 1.0f : 0.5f;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClear(GL_DEPTH_BUFFER_BIT);
QMatrix4x4 viewCubeProjection;
viewCubeProjection.ortho(-2, 2, -2, 2, -10, 10);
drawViewCube(viewCubeProjection, viewMatrix);
drawViewCube(viewCubeProjection, viewMatrix, opacity);
drawAxes(simpleShader, simpleShaderColorLoc, viewCubeProjection, viewMatrix);
glDisable(GL_BLEND);
}
void ViewCube::createFaceTextures()
@@ -144,7 +176,7 @@ void ViewCube::setupBuffers()
m_axesVbo.release();
}
void ViewCube::drawViewCube(const QMatrix4x4& projection, const QMatrix4x4& view)
void ViewCube::drawViewCube(const QMatrix4x4& projection, const QMatrix4x4& view, float opacity)
{
if (!m_textureShaderProgram || !m_textureShaderProgram->isLinked()) return;
@@ -153,6 +185,7 @@ void ViewCube::drawViewCube(const QMatrix4x4& projection, const QMatrix4x4& view
m_textureShaderProgram->setUniformValue("projectionMatrix", projection);
m_textureShaderProgram->setUniformValue("modelViewMatrix", view);
m_textureShaderProgram->setUniformValue("texture_diffuse1", 0);
m_textureShaderProgram->setUniformValue("opacity", opacity);
QOpenGLVertexArrayObject::Binder vaoBinder(&m_cubeVao);
for (int i = 0; i < 6; ++i) {
@@ -193,3 +226,27 @@ void ViewCube::drawAxes(QOpenGLShaderProgram* simpleShader, int colorLoc, const
glLineWidth(1.0f);
}
void ViewCube::paint2D(QPainter& painter, int widgetWidth, int widgetHeight)
{
if (!m_isHovered) {
return;
}
m_homeButtonRect = getHomeButtonRect(widgetWidth);
if (m_homeButtonRenderer && m_homeButtonRenderer->isValid()) {
m_homeButtonRenderer->render(&painter, m_homeButtonRect);
}
}
bool ViewCube::handleMousePress(const QPoint& pos, int widgetWidth, int widgetHeight)
{
if (!m_isHovered) {
return false;
}
QRect homeButtonRect = getHomeButtonRect(widgetWidth);
return homeButtonRect.contains(pos);
}

View File

@@ -5,9 +5,12 @@
#include <QMatrix4x4>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QPoint>
class QOpenGLShaderProgram;
class QOpenGLTexture;
class QPainter;
class QSvgRenderer;
class ViewCube : protected QOpenGLFunctions
{
@@ -16,15 +19,21 @@ public:
~ViewCube();
void initializeGL();
void paintGL(QOpenGLShaderProgram* simpleShader, int simpleShaderColorLoc, const QMatrix4x4& viewMatrix, int width, int height);
void paintGL(QOpenGLShaderProgram* simpleShader, int simpleShaderColorLoc, const QMatrix4x4& viewMatrix, int width, int height, const QPoint& mousePos);
void paint2D(QPainter& painter, int widgetWidth, int widgetHeight);
bool handleMousePress(const QPoint& pos, int widgetWidth, int widgetHeight);
private:
void createFaceTextures();
void initShaders();
void setupBuffers();
void drawViewCube(const QMatrix4x4& projection, const QMatrix4x4& view);
void drawViewCube(const QMatrix4x4& projection, const QMatrix4x4& view, float opacity);
void drawAxes(QOpenGLShaderProgram* simpleShader, int colorLoc, const QMatrix4x4& projection, const QMatrix4x4& view);
bool m_isHovered = false;
QRect m_homeButtonRect;
QSvgRenderer* m_homeButtonRenderer = nullptr;
QOpenGLTexture* m_faceTextures[6];
QOpenGLShaderProgram* m_textureShaderProgram = nullptr;

View File

@@ -1,4 +1,5 @@
#include "ViewportWidget.h"
#include "Snapping.h"
#include "Camera.h"
#include "ViewCube.h"
#include "SketchGrid.h"
@@ -6,8 +7,13 @@
#include "FeatureBrowser.h"
#include "SketchFeature.h"
#include "SketchLine.h"
#include "SketchRectangle.h"
#include "SketchCircle.h"
#include "SketchObject.h"
#include "ApplicationController.h"
#include "RectangleTool.h"
#include "LineTool.h"
#include "CircleTool.h"
#include <QMouseEvent>
#include <QKeyEvent>
#include <QWheelEvent>
@@ -18,13 +24,20 @@
#include <QSvgRenderer>
#include <QWheelEvent>
#include <QApplication>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <cmath>
#include <QtMath>
#include <QOpenGLShaderProgram>
#include <QVector>
#include <QMap>
#include <map>
#include <BRepMesh_IncrementalMesh.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <Poly_Triangulation.hxx>
#include <BRep_Tool.hxx>
#include <gp_Dir.hxx>
#include <gp_Trsf.hxx>
struct PntComparator {
bool operator()(const gp_Pnt& a, const gp_Pnt& b) const {
@@ -42,8 +55,9 @@ ViewportWidget::ViewportWidget(QWidget *parent)
{
m_camera = new Camera(this);
connect(m_camera, &Camera::cameraChanged, this, QOverload<>::of(&QWidget::update));
connect(m_camera, &Camera::restoreStateAnimationFinished, this, &ViewportWidget::onRestoreStateAnimationFinished);
m_viewCube = new ViewCube();
m_sketchGrid = new SketchGrid();
m_sketchGrid = new SketchGrid(this);
m_featureBrowser = new FeatureBrowser();
setMouseTracking(true);
setFocusPolicy(Qt::StrongFocus);
@@ -55,12 +69,19 @@ ViewportWidget::ViewportWidget(QWidget *parent)
m_toolIcons.insert(static_cast<int>(ApplicationController::ToolType::Circle), new QSvgRenderer(QString(":/icons/circle.svg"), this));
m_cursorRenderer = new QSvgRenderer(QString(":/icons/cursor.svg"), this);
m_sketchTools.insert(static_cast<int>(ApplicationController::ToolType::Line), new LineTool(this));
m_sketchTools.insert(static_cast<int>(ApplicationController::ToolType::Rectangle), new RectangleTool(this));
m_sketchTools.insert(static_cast<int>(ApplicationController::ToolType::Circle), new CircleTool(this));
m_snapping = new Snapping(this);
}
ViewportWidget::~ViewportWidget()
{
makeCurrent();
delete m_shaderProgram;
delete m_litShaderProgram;
delete m_viewCube;
delete m_sketchGrid;
m_vbo.destroy();
@@ -68,6 +89,7 @@ ViewportWidget::~ViewportWidget()
doneCurrent();
delete m_featureBrowser;
delete m_snapping;
}
void ViewportWidget::setDocument(Document* document)
@@ -102,8 +124,10 @@ void ViewportWidget::initializeGL()
void ViewportWidget::paintGL()
{
const qreal retinaScale = devicePixelRatio();
// Main scene rendering
glViewport(0, 0, width(), height());
glViewport(0, 0, width() * retinaScale, height() * retinaScale);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (!m_shaderProgram || !m_shaderProgram->isLinked()) {
@@ -142,117 +166,33 @@ void ViewportWidget::paintGL()
}
}
QVector<GLfloat> vertices;
if (m_isSnappingOrigin) {
const float rectSize = 0.0075f * -m_camera->zoom();
if (m_currentPlane == 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_currentPlane == 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_currentPlane == 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;
m_snapping->paintGL();
if (m_camera->isRotating()) {
const float radius = 0.004f * -m_camera->uiCameraDistance();
const int numSegments = 16;
QMatrix4x4 invModelView = m_camera->modelViewMatrix().inverted();
QVector3D rightVec = invModelView.column(0).toVector3D();
QVector3D upVec = invModelView.column(1).toVector3D();
QVector<GLfloat> circleFillVertices;
QVector3D center = m_camera->rotationPivot();
circleFillVertices << center.x() << center.y() << center.z(); // Center vertex for fan
for (int i = 0; i <= numSegments; ++i) { // <= to close the circle
float angle = (2.0f * M_PI * float(i)) / float(numSegments);
QVector3D p = center + radius * (cos(angle) * rightVec + sin(angle) * upVec);
circleFillVertices << p.x() << p.y() << p.z();
}
m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 0.5f));
m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(0.0f, 0.0f, 1.0f, 1.0f)); // Blue
m_vbo.bind();
m_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);
} else if (m_isSnappingVertex) {
const float rectSize = 0.0075f * -m_camera->zoom();
const auto& v = m_snapVertex;
if (m_currentPlane == 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_currentPlane == 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_currentPlane == 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_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 0.5f));
m_vbo.bind();
m_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);
m_vbo.allocate(circleFillVertices.constData(), circleFillVertices.size() * sizeof(GLfloat));
glDrawArrays(GL_TRIANGLE_FAN, 0, circleFillVertices.size() / 3);
}
if (m_isDefiningLine && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) {
vertices.clear();
QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
if (m_isSnappingOrigin) {
worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0);
} else if (m_isSnappingVertex) {
worldPos.setX(m_snapVertex.X()); worldPos.setY(m_snapVertex.Y()); worldPos.setZ(m_snapVertex.Z());
} else if (m_isSnappingHorizontal) {
if (m_currentPlane == SketchPlane::XY) worldPos.setZ(m_firstLinePoint.Z());
else if (m_currentPlane == SketchPlane::XZ) worldPos.setY(m_firstLinePoint.Y());
else if (m_currentPlane == SketchPlane::YZ) worldPos.setZ(m_firstLinePoint.Z());
} else if (m_isSnappingVertical) {
if (m_currentPlane == SketchPlane::XY) worldPos.setX(m_firstLinePoint.X());
else if (m_currentPlane == SketchPlane::XZ) worldPos.setX(m_firstLinePoint.X());
else if (m_currentPlane == 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_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 1.0f));
m_vbo.bind();
m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, 2);
if (m_isSnappingHorizontal || m_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_camera->zoom();
const float indicatorOffset = 0.02f * -m_camera->zoom();
if (m_isSnappingHorizontal) {
if (m_currentPlane == SketchPlane::XY) {
vertices << midPoint.x() - indicatorSize << midPoint.y() << midPoint.z() + indicatorOffset;
vertices << midPoint.x() + indicatorSize << midPoint.y() << midPoint.z() + indicatorOffset;
} else if (m_currentPlane == SketchPlane::XZ) {
vertices << midPoint.x() - indicatorSize << midPoint.y() + indicatorOffset << midPoint.z();
vertices << midPoint.x() + indicatorSize << midPoint.y() + indicatorOffset << midPoint.z();
} else if (m_currentPlane == 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_currentPlane == SketchPlane::XY) {
vertices << midPoint.x() + indicatorOffset << midPoint.y() << midPoint.z() - indicatorSize;
vertices << midPoint.x() + indicatorOffset << midPoint.y() << midPoint.z() + indicatorSize;
} else if (m_currentPlane == SketchPlane::XZ) {
vertices << midPoint.x() + indicatorOffset << midPoint.y() - indicatorSize << midPoint.z();
vertices << midPoint.x() + indicatorOffset << midPoint.y() + indicatorSize << midPoint.z();
} else if (m_currentPlane == SketchPlane::YZ) {
vertices << midPoint.x() << midPoint.y() + indicatorOffset << midPoint.z() - indicatorSize;
vertices << midPoint.x() << midPoint.y() + indicatorOffset << midPoint.z() + indicatorSize;
}
}
m_vbo.bind();
m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
glDrawArrays(GL_LINES, 0, 2);
}
if (m_activeSketchTool) {
m_activeSketchTool->paintGL();
}
m_shaderProgram->release();
@@ -261,17 +201,26 @@ void ViewportWidget::paintGL()
QMatrix4x4 viewCubeModel;
viewCubeModel.rotate(m_camera->xRotation() / 16.0f, 1, 0, 0);
viewCubeModel.rotate(m_camera->yRotation() / 16.0f, 0, 1, 0);
m_viewCube->paintGL(m_shaderProgram, m_colorLoc, viewCubeModel, width(), height());
m_viewCube->paintGL(m_shaderProgram, m_colorLoc, viewCubeModel, width() * retinaScale, height() * retinaScale, m_currentMousePos);
glViewport(0, 0, width(), height());
glViewport(0, 0, width() * retinaScale, height() * retinaScale);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
QPainter painter(this);
if (m_currentPlane != SketchPlane::NONE) {
drawAxisLabels(painter, model, projection);
m_sketchGrid->paintAxisLabels(painter, static_cast<SketchGrid::SketchPlane>(m_currentPlane), model, projection);
}
m_featureBrowser->paint(painter, width(), height());
m_viewCube->paint2D(painter, width(), height());
if (m_activeSketchTool) {
m_activeSketchTool->paint2D(painter, model, projection);
}
painter.setPen(Qt::white);
painter.drawText(width() - 350, height() - 10, QString("Camera Distance: %1").arg(-m_camera->uiCameraDistance()));
painter.end();
}
@@ -279,12 +228,25 @@ void ViewportWidget::paintGL()
void ViewportWidget::resizeGL(int w, int h)
{
projection.setToIdentity();
projection.perspective(45.0f, w / float(h), 0.01f, 100.0f);
projection.perspective(45.0f, w / float(h), 0.01f, 10000.0f);
}
void ViewportWidget::mousePressEvent(QMouseEvent *event)
{
m_camera->mousePressEvent(event);
if (event->button() == Qt::MiddleButton && !(QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
m_camera->startRotation(unproject(event->pos(), m_currentPlane));
update();
}
if (event->button() == Qt::LeftButton) {
if (m_viewCube->handleMousePress(event->pos(), width(), height())) {
m_camera->animateToHomeView();
update();
return;
}
if (m_isSelectingPlane) {
if (m_highlightedPlane != SketchPlane::NONE) {
emit planeSelected(m_highlightedPlane);
@@ -295,37 +257,12 @@ void ViewportWidget::mousePressEvent(QMouseEvent *event)
return;
}
if (m_currentPlane != SketchPlane::NONE && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) {
gp_Pnt p;
if (m_isSnappingOrigin) {
p.SetCoord(0, 0, 0);
} else if (m_isSnappingVertex) {
p = m_snapVertex;
} else {
QVector3D worldPos = unproject(event->pos(), m_currentPlane);
if (m_isSnappingHorizontal) {
if (m_currentPlane == SketchPlane::XY) worldPos.setZ(m_firstLinePoint.Z());
else if (m_currentPlane == SketchPlane::XZ) worldPos.setY(m_firstLinePoint.Y());
else if (m_currentPlane == SketchPlane::YZ) worldPos.setZ(m_firstLinePoint.Z());
} else if (m_isSnappingVertical) {
if (m_currentPlane == SketchPlane::XY) worldPos.setX(m_firstLinePoint.X());
else if (m_currentPlane == SketchPlane::XZ) worldPos.setX(m_firstLinePoint.X());
else if (m_currentPlane == SketchPlane::YZ) worldPos.setY(m_firstLinePoint.Y());
}
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
if (m_activeSketchTool) {
m_activeSketchTool->mousePressEvent(event);
update();
return;
}
if (!m_isDefiningLine) {
m_firstLinePoint = p;
m_isDefiningLine = true;
} else {
emit lineAdded(m_firstLinePoint, p);
m_firstLinePoint = p;
}
update();
}
} else {
lastPos = event->pos();
}
}
@@ -341,208 +278,106 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
}
}
bool shouldSnap = false;
if (m_currentPlane != SketchPlane::NONE && m_activeTool != static_cast<int>(ApplicationController::ToolType::None)) {
QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
const float snapRectHalfSize = 0.0075f * -m_camera->zoom();
switch (m_currentPlane) {
case SketchPlane::XY:
shouldSnap = qAbs(worldPos.x()) < snapRectHalfSize && qAbs(worldPos.z()) < snapRectHalfSize;
break;
case SketchPlane::XZ:
shouldSnap = qAbs(worldPos.x()) < snapRectHalfSize && qAbs(worldPos.y()) < snapRectHalfSize;
break;
case SketchPlane::YZ:
shouldSnap = qAbs(worldPos.y()) < snapRectHalfSize && qAbs(worldPos.z()) < snapRectHalfSize;
break;
case SketchPlane::NONE:
break;
}
}
if (shouldSnap != m_isSnappingOrigin) {
m_isSnappingOrigin = shouldSnap;
if (m_isSnappingOrigin) {
m_isSnappingVertex = false;
}
if (m_snapping->update(m_currentMousePos)) {
update();
}
bool oldIsSnappingVertex = m_isSnappingVertex;
m_isSnappingVertex = false;
if (!m_isSnappingOrigin && m_document && m_currentPlane != SketchPlane::NONE && m_activeTool != static_cast<int>(ApplicationController::ToolType::None)) {
QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
const float snapRectHalfSize = 0.0075f * -m_camera->zoom();
for (Feature* feature : m_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_currentPlane) {
case SketchPlane::XY:
isClose = qAbs(worldPos.x() - vertex.X()) < snapRectHalfSize && qAbs(worldPos.z() - vertex.Z()) < snapRectHalfSize;
break;
case SketchPlane::XZ:
isClose = qAbs(worldPos.x() - vertex.X()) < snapRectHalfSize && qAbs(worldPos.y() - vertex.Y()) < snapRectHalfSize;
break;
case SketchPlane::YZ:
isClose = qAbs(worldPos.y() - vertex.Y()) < snapRectHalfSize && qAbs(worldPos.z() - vertex.Z()) < snapRectHalfSize;
break;
case SketchPlane::NONE:
break;
}
if (isClose) {
m_isSnappingVertex = true;
m_snapVertex = vertex;
goto end_snap_check;
}
}
}
}
}
}
end_snap_check:;
if (m_activeSketchTool) {
m_activeSketchTool->mouseMoveEvent(event);
}
if (oldIsSnappingVertex != m_isSnappingVertex) {
update();
}
bool oldIsSnappingHorizontal = m_isSnappingHorizontal;
bool oldIsSnappingVertical = m_isSnappingVertical;
m_isSnappingHorizontal = false;
m_isSnappingVertical = false;
if (m_isDefiningLine && !m_isSnappingOrigin && !m_isSnappingVertex) {
QVector3D worldPos = unproject(m_currentMousePos, m_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_currentPlane == SketchPlane::XY) {
angle = atan2(delta.z(), delta.x());
} else if (m_currentPlane == SketchPlane::XZ) {
angle = atan2(delta.y(), delta.x());
} else if (m_currentPlane == SketchPlane::YZ) {
angle = atan2(delta.z(), delta.y());
}
if (qAbs(sin(angle)) < sin(snapAngleThreshold)) {
m_isSnappingHorizontal = true;
} else if (qAbs(cos(angle)) < sin(snapAngleThreshold)) {
m_isSnappingVertical = true;
}
}
}
if (oldIsSnappingHorizontal != m_isSnappingHorizontal || oldIsSnappingVertical != m_isSnappingVertical) {
update();
}
if (event->buttons() & Qt::MiddleButton) {
m_camera->processMouseMovement(event, lastPos);
}
lastPos = event->pos();
m_camera->mouseMoveEvent(event, height());
update();
}
void ViewportWidget::wheelEvent(QWheelEvent *event)
{
m_camera->processWheel(event);
QVector3D worldPos = unproject(event->position().toPoint(), m_currentPlane);
m_camera->wheelEvent(event, worldPos);
}
void ViewportWidget::keyPressEvent(QKeyEvent *event)
{
if (m_activeSketchTool) {
m_activeSketchTool->keyPressEvent(event);
return;
}
if (event->key() == Qt::Key_Escape) {
if (m_isDefiningLine) {
m_isDefiningLine = false;
emit toolDeactivated();
update();
return;
}
if (m_isSelectingPlane) {
if (m_isSelectingPlane) {
m_isSelectingPlane = false;
m_highlightedPlane = SketchPlane::NONE;
m_currentPlane = SketchPlane::XY;
update();
return;
}
else if (m_activeTool) {
emit toolDeactivated();
update();
return;
}
}
QOpenGLWidget::keyPressEvent(event);
}
void ViewportWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::MiddleButton) {
m_camera->stopRotation();
update();
}
QOpenGLWidget::mouseReleaseEvent(event);
}
void ViewportWidget::addLine(const gp_Pnt& start, const gp_Pnt& end)
{
emit lineAdded(start, end);
}
void ViewportWidget::deactivateActiveTool()
{
emit toolDeactivated();
}
bool ViewportWidget::isSnappingOrigin() const
{
return m_snapping->isSnappingOrigin();
}
bool ViewportWidget::isSnappingVertex() const
{
return m_snapping->isSnappingVertex();
}
const gp_Pnt& ViewportWidget::snapVertex() const
{
return m_snapping->snapVertex();
}
void ViewportWidget::setSnappingHorizontal(bool snapping)
{
m_isSnappingHorizontal = snapping;
}
void ViewportWidget::setSnappingVertical(bool snapping)
{
m_isSnappingVertical = snapping;
}
bool ViewportWidget::focusNextPrevChild(bool next)
{
if (m_activeTool != static_cast<int>(ApplicationController::ToolType::None)) {
return false;
}
return QOpenGLWidget::focusNextPrevChild(next);
}
void ViewportWidget::onSketchModeStarted(SketchPlane plane)
{
m_currentPlane = plane;
m_camera->saveState();
float targetXRot = m_camera->xRotation();
float targetYRot = m_camera->yRotation();
switch (plane) {
case SketchPlane::XY: // Top view
targetXRot = 90 * 16;
targetYRot = 0;
break;
case SketchPlane::XZ: // Front view
targetXRot = 0;
targetYRot = 0;
break;
case SketchPlane::YZ: // Right view
targetXRot = 0;
targetYRot = -90 * 16;
break;
case SketchPlane::NONE:
break;
}
auto* animGroup = new QParallelAnimationGroup(this);
auto* xRotAnim = new QPropertyAnimation(m_camera, "xRotation");
xRotAnim->setDuration(300);
xRotAnim->setStartValue(m_camera->xRotation());
xRotAnim->setEndValue(targetXRot);
xRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(xRotAnim);
auto* yRotAnim = new QPropertyAnimation(m_camera, "yRotation");
yRotAnim->setDuration(300);
yRotAnim->setStartValue(m_camera->yRotation());
yRotAnim->setEndValue(targetYRot);
yRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(yRotAnim);
auto* panXAnim = new QPropertyAnimation(m_camera, "panX");
panXAnim->setDuration(300);
panXAnim->setStartValue(m_camera->panX());
panXAnim->setEndValue(0.0f);
panXAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(panXAnim);
auto* panYAnim = new QPropertyAnimation(m_camera, "panY");
panYAnim->setDuration(300);
panYAnim->setStartValue(m_camera->panY());
panYAnim->setEndValue(0.0f);
panYAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(panYAnim);
auto* zoomAnim = new QPropertyAnimation(m_camera, "zoom");
zoomAnim->setDuration(300);
zoomAnim->setStartValue(m_camera->zoom());
zoomAnim->setEndValue(-20.0f);
zoomAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(zoomAnim);
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
m_camera->animateToPlaneView(static_cast<int>(plane));
}
void ViewportWidget::onPlaneSelectionModeStarted()
@@ -555,50 +390,14 @@ void ViewportWidget::onPlaneSelectionModeStarted()
void ViewportWidget::onSketchModeEnded()
{
auto* animGroup = new QParallelAnimationGroup(this);
m_camera->animateRestoreState();
}
auto* xRotAnim = new QPropertyAnimation(m_camera, "xRotation");
xRotAnim->setDuration(300);
xRotAnim->setStartValue(m_camera->xRotation());
xRotAnim->setEndValue(m_camera->savedXRot());
xRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(xRotAnim);
auto* yRotAnim = new QPropertyAnimation(m_camera, "yRotation");
yRotAnim->setDuration(300);
yRotAnim->setStartValue(m_camera->yRotation());
yRotAnim->setEndValue(m_camera->savedYRot());
yRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(yRotAnim);
auto* panXAnim = new QPropertyAnimation(m_camera, "panX");
panXAnim->setDuration(300);
panXAnim->setStartValue(m_camera->panX());
panXAnim->setEndValue(m_camera->savedPanX());
panXAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(panXAnim);
auto* panYAnim = new QPropertyAnimation(m_camera, "panY");
panYAnim->setDuration(300);
panYAnim->setStartValue(m_camera->panY());
panYAnim->setEndValue(m_camera->savedPanY());
panYAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(panYAnim);
auto* zoomAnim = new QPropertyAnimation(m_camera, "zoom");
zoomAnim->setDuration(300);
zoomAnim->setStartValue(m_camera->zoom());
zoomAnim->setEndValue(m_camera->savedZoom());
zoomAnim->setEasingCurve(QEasingCurve::InOutQuad);
animGroup->addAnimation(zoomAnim);
connect(animGroup, &QParallelAnimationGroup::finished, this, [this]() {
// Return to showing the base XY grid when not in a sketch
m_currentPlane = SketchPlane::XY;
update();
});
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
void ViewportWidget::onRestoreStateAnimationFinished()
{
// Return to showing the base XY grid when not in a sketch
m_currentPlane = SketchPlane::XY;
update();
}
QVector3D ViewportWidget::project(const QVector3D& worldCoord, const QMatrix4x4& modelView, const QMatrix4x4& projection, const QRect& viewport)
@@ -616,43 +415,19 @@ QVector3D ViewportWidget::project(const QVector3D& worldCoord, const QMatrix4x4&
return QVector3D(winX, winY, (ndc.z() + 1.0) / 2.0);
}
void ViewportWidget::drawAxisLabels(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection)
{
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 10));
const int range = 50;
const int step = 5;
auto drawLabelsForAxis = [&](int axis_idx) {
for (int i = -range; i <= range; i += step) {
if (i == 0) continue;
QVector3D worldCoord;
worldCoord[axis_idx] = i;
QVector3D screenPos = project(worldCoord, modelView, projection, rect());
if (screenPos.z() < 1.0f) { // Not clipped
painter.drawText(screenPos.toPoint(), QString::number(i));
}
}
};
if (m_currentPlane == SketchPlane::XY) {
drawLabelsForAxis(0); // X
drawLabelsForAxis(2); // Y
} else if (m_currentPlane == SketchPlane::XZ) {
drawLabelsForAxis(0); // X
drawLabelsForAxis(1); // Z
} else if (m_currentPlane == SketchPlane::YZ) {
drawLabelsForAxis(1); // Y
drawLabelsForAxis(2); // Z
}
}
void ViewportWidget::onActiveToolChanged(int tool)
{
if (m_activeSketchTool) {
m_activeSketchTool->deactivate();
m_activeSketchTool = nullptr;
}
m_activeTool = tool;
m_isDefiningLine = false;
if (m_sketchTools.contains(tool)) {
m_activeSketchTool = m_sketchTools.value(tool);
m_activeSketchTool->activate();
}
if (tool == static_cast<int>(ApplicationController::ToolType::None)) {
unsetCursor();
@@ -744,10 +519,9 @@ void ViewportWidget::drawSketch(const SketchFeature* sketch)
{
glDisable(GL_DEPTH_TEST);
glLineWidth(2.0f);
glPointSize(5.0f);
QVector<GLfloat> lineVertices;
QMap<gp_Pnt, int, PntComparator> vertexCounts;
std::map<gp_Pnt, int, PntComparator> vertexCounts;
for (const auto& obj : sketch->objects()) {
if (obj->type() == SketchObject::ObjectType::Line) {
@@ -759,14 +533,69 @@ void ViewportWidget::drawSketch(const SketchFeature* sketch)
vertexCounts[start]++;
vertexCounts[end]++;
}
}
} else if (obj->type() == SketchObject::ObjectType::Circle) {
auto circle = static_cast<const SketchCircle*>(obj);
const auto& center = circle->center();
double radius = circle->radius();
QVector3D centerPos(center.X(), center.Y(), center.Z());
QVector<GLfloat> pointVertices;
for (auto it = vertexCounts.constBegin(); it != vertexCounts.constEnd(); ++it) {
if (it.value() == 1) {
const gp_Pnt& p = it.key();
pointVertices << p.X() << p.Y() << p.Z();
const int numSegments = 64;
QVector3D u_axis, v_axis;
switch (sketch->plane()) {
case SketchFeature::SketchPlane::XY:
u_axis = QVector3D(1, 0, 0);
v_axis = QVector3D(0, 0, 1);
break;
case SketchFeature::SketchPlane::XZ:
u_axis = QVector3D(1, 0, 0);
v_axis = QVector3D(0, 1, 0);
break;
case SketchFeature::SketchPlane::YZ:
u_axis = QVector3D(0, 1, 0);
v_axis = QVector3D(0, 0, 1);
break;
}
for (int i = 0; i < numSegments; ++i) {
double angle1 = 2.0 * M_PI * double(i) / double(numSegments);
QVector3D p1 = centerPos + radius * (cos(angle1) * u_axis + sin(angle1) * v_axis);
double angle2 = 2.0 * M_PI * double(i + 1) / double(numSegments);
QVector3D p2 = centerPos + radius * (cos(angle2) * u_axis + sin(angle2) * v_axis);
lineVertices << p1.x() << p1.y() << p1.z();
lineVertices << p2.x() << p2.y() << p2.z();
}
} else if (obj->type() == SketchObject::ObjectType::Rectangle) {
auto rect = static_cast<const SketchRectangle*>(obj);
const auto& p1 = rect->corner1();
const auto& p3 = rect->corner2();
gp_Pnt p2, p4;
if (sketch->plane() == SketchFeature::SketchPlane::XY) {
p2.SetCoord(p3.X(), p1.Y(), p1.Z());
p4.SetCoord(p1.X(), p1.Y(), p3.Z());
} else if (sketch->plane() == SketchFeature::SketchPlane::XZ) {
p2.SetCoord(p3.X(), p1.Y(), p1.Z());
p4.SetCoord(p1.X(), p3.Y(), p1.Z());
} else if (sketch->plane() == SketchFeature::SketchPlane::YZ) {
p2.SetCoord(p1.X(), p3.Y(), p1.Z());
p4.SetCoord(p1.X(), p1.Y(), p3.Z());
}
lineVertices << p1.X() << p1.Y() << p1.Z();
lineVertices << p2.X() << p2.Y() << p2.Z();
lineVertices << p2.X() << p2.Y() << p2.Z();
lineVertices << p3.X() << p3.Y() << p3.Z();
lineVertices << p3.X() << p3.Y() << p3.Z();
lineVertices << p4.X() << p4.Y() << p4.Z();
lineVertices << p4.X() << p4.Y() << p4.Z();
lineVertices << p1.X() << p1.Y() << p1.Z();
vertexCounts[p1] += 2;
vertexCounts[p2] += 2;
vertexCounts[p3] += 2;
vertexCounts[p4] += 2;
}
}
@@ -775,16 +604,137 @@ void ViewportWidget::drawSketch(const SketchFeature* sketch)
if (!lineVertices.isEmpty()) {
m_vbo.allocate(lineVertices.constData(), lineVertices.size() * sizeof(GLfloat));
m_shaderProgram->enableAttributeArray(0);
m_shaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 3, 0);
m_shaderProgram->disableAttributeArray(1);
glDrawArrays(GL_LINES, 0, lineVertices.size() / 3);
}
if (!pointVertices.isEmpty()) {
glEnable(GL_POINT_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
m_vbo.allocate(pointVertices.constData(), pointVertices.size() * sizeof(GLfloat));
glDrawArrays(GL_POINTS, 0, pointVertices.size() / 3);
glDisable(GL_BLEND);
glDisable(GL_POINT_SMOOTH);
QVector<gp_Pnt> unattachedVertices;
for (const auto& pair : vertexCounts) {
if (pair.second == 1) {
unattachedVertices.append(pair.first);
}
}
if (!unattachedVertices.isEmpty()) {
const float radius = 0.004f * -m_camera->uiCameraDistance();
const int numSegments = 16;
QMatrix4x4 invModelView = m_camera->modelViewMatrix().inverted();
QVector3D rightVec = invModelView.column(0).toVector3D();
QVector3D upVec = invModelView.column(1).toVector3D();
QVector<GLfloat> circleFillVertices;
QVector<GLfloat> circleOutlineVertices;
for (const auto& centerPnt : unattachedVertices) {
QVector3D center(centerPnt.X(), centerPnt.Y(), centerPnt.Z());
circleFillVertices << center.x() << center.y() << center.z(); // Center vertex for fan
QVector3D firstCircumferencePoint;
for (int i = 0; i < numSegments; ++i) {
float angle = (2.0f * M_PI * float(i)) / float(numSegments);
QVector3D p = center + radius * (cos(angle) * rightVec + sin(angle) * upVec);
if (i == 0) {
firstCircumferencePoint = p;
}
circleFillVertices << p.x() << p.y() << p.z();
circleOutlineVertices << p.x() << p.y() << p.z();
}
circleFillVertices << firstCircumferencePoint.x() << firstCircumferencePoint.y() << firstCircumferencePoint.z();
}
// Draw fills
m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(0.2f, 0.3f, 0.3f, 1.0f));
m_vbo.allocate(circleFillVertices.constData(), circleFillVertices.size() * sizeof(GLfloat));
const int vertsPerFan = numSegments + 2;
for (size_t i = 0; i < unattachedVertices.size(); ++i) {
glDrawArrays(GL_TRIANGLE_FAN, i * vertsPerFan, vertsPerFan);
}
// Draw outlines
m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 1.0f, 1.0f));
m_vbo.allocate(circleOutlineVertices.constData(), circleOutlineVertices.size() * sizeof(GLfloat));
const int vertsPerLoop = numSegments;
for (size_t i = 0; i < unattachedVertices.size(); ++i) {
glDrawArrays(GL_LINE_LOOP, i * vertsPerLoop, vertsPerLoop);
}
}
// Draw faces
if (!sketch->shape().IsNull()) {
glDisable(GL_CULL_FACE);
BRepMesh_IncrementalMesh(sketch->shape(), 0.1, Standard_False, 0.5, Standard_True); // Linear deflection, compute normals
QVector<GLfloat> faceData;
TopExp_Explorer explorer(sketch->shape(), TopAbs_FACE);
for (; explorer.More(); explorer.Next()) {
TopoDS_Face face = TopoDS::Face(explorer.Current());
TopLoc_Location location;
Handle(Poly_Triangulation) triangulation = BRep_Tool::Triangulation(face, location);
if (!triangulation.IsNull()) {
gp_Trsf locTrsf = location.Transformation();
if (triangulation->HasNormals()) {
for (int i = 1; i <= triangulation->NbTriangles(); ++i) {
const Poly_Triangle& triangle = triangulation->Triangle(i);
for (int j = 1; j <= 3; ++j) {
int nodeIdx = triangle.Value(j);
gp_Pnt p = triangulation->Node(nodeIdx).Transformed(location);
gp_Dir n = triangulation->Normal(nodeIdx);
n.Transform(locTrsf);
if (face.Orientation() == TopAbs_REVERSED) {
n.Reverse();
}
faceData << p.X() << p.Y() << p.Z();
faceData << n.X() << n.Y() << n.Z();
}
}
} else {
for (int i = 1; i <= triangulation->NbTriangles(); ++i) {
const Poly_Triangle& triangle = triangulation->Triangle(i);
gp_Pnt p1 = triangulation->Node(triangle.Value(1)).Transformed(location);
gp_Pnt p2 = triangulation->Node(triangle.Value(2)).Transformed(location);
gp_Pnt p3 = triangulation->Node(triangle.Value(3)).Transformed(location);
QVector3D v1(p1.X(), p1.Y(), p1.Z());
QVector3D v2(p2.X(), p2.Y(), p2.Z());
QVector3D v3(p3.X(), p3.Y(), p3.Z());
QVector3D faceNormal = QVector3D::crossProduct(v2 - v1, v3 - v1).normalized();
if (face.Orientation() == TopAbs_REVERSED) {
faceNormal = -faceNormal;
}
faceData << p1.X() << p1.Y() << p1.Z() << faceNormal.x() << faceNormal.y() << faceNormal.z();
faceData << p2.X() << p2.Y() << p2.Z() << faceNormal.x() << faceNormal.y() << faceNormal.z();
faceData << p3.X() << p3.Y() << p3.Z() << faceNormal.x() << faceNormal.y() << faceNormal.z();
}
}
}
}
if (!faceData.isEmpty()) {
m_litShaderProgram->bind();
m_litShaderProgram->setUniformValue(m_litProjMatrixLoc, projection);
m_litShaderProgram->setUniformValue(m_litMvMatrixLoc, m_camera->modelViewMatrix());
m_litShaderProgram->setUniformValue(m_litNormalMatrixLoc, m_camera->modelViewMatrix().normalMatrix());
m_vbo.bind();
m_vbo.allocate(faceData.constData(), faceData.size() * sizeof(GLfloat));
m_litShaderProgram->enableAttributeArray(0);
m_litShaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 3, 6 * sizeof(GLfloat));
m_litShaderProgram->enableAttributeArray(1);
m_litShaderProgram->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(GLfloat), 3, 6 * sizeof(GLfloat));
glDrawArrays(GL_TRIANGLES, 0, faceData.size() / 6);
m_litShaderProgram->disableAttributeArray(1);
m_shaderProgram->bind(); // rebind simple shader for subsequent draws
m_shaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 3, 0);
}
glEnable(GL_CULL_FACE);
}
glEnable(GL_DEPTH_TEST);
@@ -792,8 +742,8 @@ void ViewportWidget::drawSketch(const SketchFeature* sketch)
void ViewportWidget::initShaders()
{
// Simple shader for lines and grids
m_shaderProgram = new QOpenGLShaderProgram(this);
if (!m_shaderProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/simple.vert")) {
qCritical() << "Vertex shader compilation failed:" << m_shaderProgram->log();
return;
@@ -806,10 +756,27 @@ void ViewportWidget::initShaders()
qCritical() << "Shader program linking failed:" << m_shaderProgram->log();
return;
}
m_projMatrixLoc = m_shaderProgram->uniformLocation("projectionMatrix");
m_mvMatrixLoc = m_shaderProgram->uniformLocation("modelViewMatrix");
m_colorLoc = m_shaderProgram->uniformLocation("objectColor");
// Lit shader for faces
m_litShaderProgram = new QOpenGLShaderProgram(this);
if (!m_litShaderProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/lit.vert")) {
qCritical() << "Lit vertex shader compilation failed:" << m_litShaderProgram->log();
return;
}
if (!m_litShaderProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/lit.frag")) {
qCritical() << "Lit fragment shader compilation failed:" << m_litShaderProgram->log();
return;
}
if (!m_litShaderProgram->link()) {
qCritical() << "Lit shader program linking failed:" << m_litShaderProgram->log();
return;
}
m_litProjMatrixLoc = m_litShaderProgram->uniformLocation("projectionMatrix");
m_litMvMatrixLoc = m_litShaderProgram->uniformLocation("modelViewMatrix");
m_litNormalMatrixLoc = m_litShaderProgram->uniformLocation("normalMatrix");
}
void ViewportWidget::drawSelectionPlanes()
@@ -824,6 +791,9 @@ void ViewportWidget::drawSelectionPlanes()
glDisable(GL_DEPTH_TEST);
m_vbo.bind();
m_shaderProgram->enableAttributeArray(0);
m_shaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 3, 0);
m_shaderProgram->disableAttributeArray(1);
QVector<GLfloat> vertices;
auto drawPlane = [&](const QVector<GLfloat>& quadVerts, bool highlighted) {

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,30 +69,35 @@ 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;
bool focusNextPrevChild(bool next) override;
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);
QMatrix4x4 projection;
QOpenGLShaderProgram* m_shaderProgram = nullptr;
QOpenGLShaderProgram* m_litShaderProgram = nullptr;
QOpenGLVertexArrayObject m_vao;
QOpenGLBuffer m_vbo;
@@ -77,6 +105,11 @@ private:
int m_projMatrixLoc = -1;
int m_mvMatrixLoc = -1;
int m_colorLoc = -1;
// Lit shader uniform locations
int m_litProjMatrixLoc = -1;
int m_litMvMatrixLoc = -1;
int m_litNormalMatrixLoc = -1;
Camera* m_camera = nullptr;
ViewCube* m_viewCube;
SketchGrid* m_sketchGrid = nullptr;
@@ -88,19 +121,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

33
src/shaders/lit.frag Normal file
View File

@@ -0,0 +1,33 @@
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
void main()
{
vec3 viewPos = vec3(0.0, 0.0, 0.0); // View position is origin in view space
vec3 lightPos = vec3(0.0, 10.0, 5.0); // Light position in view space
vec3 lightColor = vec3(1.0, 1.0, 1.0);
vec3 objectColor = vec3(0.5, 0.5, 1.0);
// Ambient
float ambientStrength = 0.3;
vec3 ambient = ambientStrength * lightColor;
// Diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// Specular
float specularStrength = 0.5;
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}

17
src/shaders/lit.vert Normal file
View File

@@ -0,0 +1,17 @@
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix;
void main()
{
FragPos = vec3(modelViewMatrix * vec4(aPos, 1.0));
Normal = normalMatrix * aNormal;
gl_Position = projectionMatrix * modelViewMatrix * vec4(aPos, 1.0);
}

View File

@@ -5,8 +5,10 @@ out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D texture_diffuse1;
uniform float opacity;
void main()
{
FragColor = texture(texture_diffuse1, TexCoord);
vec4 texColor = texture(texture_diffuse1, TexCoord);
FragColor = vec4(texColor.rgb, texColor.a * opacity);
}