From 413f897e66a99da1836d0571047115715320bfa5 Mon Sep 17 00:00:00 2001 From: tristan Date: Sat, 22 Oct 2022 13:35:40 +0200 Subject: [PATCH 01/32] chore: sonar cleanup --- .github/workflows/deploy-windows.yml | 2 +- src/config/config.h.in | 2 ++ src/config/list.h | 4 ++-- src/geometry/bulge.cpp | 12 +++++++----- src/geometry/pocketer.h | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy-windows.yml b/.github/workflows/deploy-windows.yml index acfc7e8..6f5d484 100644 --- a/.github/workflows/deploy-windows.yml +++ b/.github/workflows/deploy-windows.yml @@ -1,9 +1,9 @@ on: + workflow_dispatch: push: # Sequence of patterns matched against refs/tags tags: - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 - workflow_dispatch: jobs: build: diff --git a/src/config/config.h.in b/src/config/config.h.in index a8241d5..a87e799 100644 --- a/src/config/config.h.in +++ b/src/config/config.h.in @@ -104,6 +104,8 @@ public: Config(const Config &other); ~Config(); + Config& operator=(Config &&) = default; + Root& root(); const Root& root() const; diff --git a/src/config/list.h b/src/config/list.h index 9b5318e..fb595ca 100644 --- a/src/config/list.h +++ b/src/config/list.h @@ -30,8 +30,8 @@ class List : public Node else { // Create items from yaml node for (const auto &pair : m_yamlNode) { - const std::string &name = pair.first.as(); - m_children.emplace(name, Child(name, pair.second)); + const std::string &itemName = pair.first.as(); + m_children.emplace(itemName, Child(itemName, pair.second)); } } } diff --git a/src/geometry/bulge.cpp b/src/geometry/bulge.cpp index 17ee9d7..33da315 100644 --- a/src/geometry/bulge.cpp +++ b/src/geometry/bulge.cpp @@ -43,13 +43,15 @@ QVector2D Bulge::relativeArcCenter() const Point2DList Bulge::arcBoundingPoints() const { + using QVector2D4 = std::array; + Point2DList points; const QVector2D relativeCenter = relativeArcCenter(); const QVector2D line = m_end - m_start; const float radius = arcRadius(); - static const QVector2D quadrantPointsUnit[4] = { + static const QVector2D4 quadrantPointsUnit = { QVector2D(1, 0), QVector2D(0, 1), QVector2D(-1, 0), @@ -58,12 +60,12 @@ Point2DList Bulge::arcBoundingPoints() const const bool centerLineSide = std::signbit(CrossProduct(line, relativeCenter)); - QVector2D quadrantPoints[4]; - std::transform(quadrantPointsUnit, quadrantPointsUnit + 4, quadrantPoints, [&relativeCenter, radius](const QVector2D& pointUnit){ + QVector2D4 quadrantPoints; + std::transform(quadrantPointsUnit.begin(), quadrantPointsUnit.end(), quadrantPoints.begin(), [&relativeCenter, radius](const QVector2D& pointUnit){ return relativeCenter + pointUnit * radius; }); - std::copy_if(quadrantPoints, quadrantPoints + 4, std::back_inserter(points), + std::copy_if(quadrantPoints.begin(), quadrantPoints.end(), std::back_inserter(points), [centerLineSide, &line](const QVector2D &point){ const bool pointLineSide = std::signbit(CrossProduct(line, point)); const bool oppositeSideToCenter = (pointLineSide != centerLineSide); @@ -135,7 +137,7 @@ float Bulge::length() const return radius * angle; } -const Rect boundingRectPoints(const Point2DList &points) +Rect boundingRectPoints(const Point2DList &points) { Point2DList::const_iterator it = points.begin(); const QVector2D firstPoint = *(it++); diff --git a/src/geometry/pocketer.h b/src/geometry/pocketer.h index 4c20146..ed61d0f 100644 --- a/src/geometry/pocketer.h +++ b/src/geometry/pocketer.h @@ -27,7 +27,7 @@ class Pocketer static cavc::OffsetLoop polylineToLoop(const Polyline& polyline, bool inverse); static Polyline loopToPolyline(const cavc::OffsetLoop &loop); static Polyline::List loopsToPolylines(const std::vector> &loops); - bool canContinueOffsetting(const cavc::OffsetLoopSet &loopSet); + static bool canContinueOffsetting(const cavc::OffsetLoopSet &loopSet); void pruneSingularities(std::vector> &loops) const; cavc::OffsetLoopSet baseLoopSet() const; From f9789b0465e4625d71956ea4b039098401f08122 Mon Sep 17 00:00:00 2001 From: tristan Date: Sat, 22 Oct 2022 14:02:19 +0200 Subject: [PATCH 02/32] feat: Add unit test for assembler --- test/CMakeLists.txt | 1 + test/assembler.cpp | 101 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 test/assembler.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f8a7e99..1dd4f6d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,6 +11,7 @@ include(GoogleTest) set(SRC arc.cpp + assembler.cpp bulge.cpp dxfplotexporter.cpp dxfplotimporter.cpp diff --git a/test/assembler.cpp b/test/assembler.cpp new file mode 100644 index 0000000..b517be5 --- /dev/null +++ b/test/assembler.cpp @@ -0,0 +1,101 @@ +#include +#include + +constexpr QVector2D point1(1.2, 3.4); +constexpr QVector2D point2(4.5, 6.7); +constexpr QVector2D point3(7.8, 9.1); +constexpr QVector2D point4(11.0, 12.0); +constexpr QVector2D point5(3.5, 1.2); + +static const geometry::Bulge bulge1(point1, point2, 0.2f); +static const geometry::Bulge bulge2(point2, point3, 1.0f); +static const geometry::Bulge bulge3(point3, point4, -0.5f); +static const geometry::Bulge bulgep1(point5, point5, 0.0f); + +TEST(AssemblerTest, ShouldMergeAllPolylines) +{ + geometry::Polyline::List rawPolylines{ + geometry::Polyline({bulge1}), + geometry::Polyline({bulge3}).inverse(), + geometry::Polyline({bulge2}) + }; + + geometry::filter::Assembler assembler(std::move(rawPolylines), 0.01f); + + const geometry::Polyline::List assembledPolylines(assembler.polylines()); + ASSERT_EQ(1, assembledPolylines.size()); + + const geometry::Polyline &polyline = assembledPolylines.front(); + QVector2D lastPoint; + bool isFirstPoint = true; + polyline.forEachBulge([&lastPoint, &isFirstPoint](const geometry::Bulge& bulge){ + if (!isFirstPoint) { + EXPECT_EQ(bulge.start(), lastPoint); + } + lastPoint = bulge.end(); + }); +} + +TEST(AssemblerTest, ShouldMergeAllPolylinesLargeTolerance) +{ + geometry::Polyline::List rawPolylines{ + geometry::Polyline({bulge1}), + geometry::Polyline({bulge3}).inverse(), + geometry::Polyline({bulge2}) + }; + + std::for_each(rawPolylines.begin(), rawPolylines.end(), [](geometry::Polyline &polyline){ + polyline.transformBulge([](geometry::Bulge &bulge){ + bulge.start()[1] += 0.1f; + bulge.end()[1] -= 0.1f; + }); + }); + + geometry::filter::Assembler assembler(std::move(rawPolylines), 0.1f); + + const geometry::Polyline::List assembledPolylines(assembler.polylines()); + ASSERT_EQ(1, assembledPolylines.size()); + + const geometry::Polyline &polyline = assembledPolylines.front(); + QVector2D lastPoint; + bool isFirstPoint = true; + polyline.forEachBulge([&lastPoint, &isFirstPoint](const geometry::Bulge& bulge){ + if (!isFirstPoint) { + EXPECT_EQ(bulge.start(), lastPoint); + } + lastPoint = bulge.end(); + }); +} + +TEST(AssemblerTest, ShouldMergeAllConnectedPolylinesButPoint) +{ + geometry::Polyline::List rawPolylines{ + geometry::Polyline({bulge1}), + geometry::Polyline({bulge2}), + geometry::Polyline({bulgep1}) + }; + + geometry::filter::Assembler assembler(std::move(rawPolylines), 0.01f); + + const geometry::Polyline::List assembledPolylines(assembler.polylines()); + EXPECT_EQ(2, assembledPolylines.size()); +} + +TEST(AssemblerTest, ShouldMergePolylinesResultClosed) +{ + const geometry::Bulge b1(point1, point2, 1.0); + const geometry::Bulge b2(point2, point1, 1.0); + + geometry::Polyline::List rawPolylines{ + geometry::Polyline({b1}), + geometry::Polyline({b2}) + }; + + geometry::filter::Assembler assembler(std::move(rawPolylines), 0.01f); + + const geometry::Polyline::List assembledPolylines(assembler.polylines()); + ASSERT_EQ(1, assembledPolylines.size()); + + const geometry::Polyline &polyline = assembledPolylines.front(); + EXPECT_TRUE(polyline.isClosed()); +} From 9c52a1505d5294eb0dbb0d04e12a2f373f7bfae7 Mon Sep 17 00:00:00 2001 From: tristan Date: Sat, 22 Oct 2022 14:57:37 +0200 Subject: [PATCH 03/32] chore: Add unit test for model::PathGroupSettings --- src/model/renderable.cpp | 5 +++++ src/model/renderable.h | 1 + src/model/task.cpp | 10 ---------- src/model/task.h | 8 +++++--- test/CMakeLists.txt | 1 + test/assembler.cpp | 2 ++ test/pathsettings.cpp | 37 +++++++++++++++++++++++++++++++++++++ 7 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 test/pathsettings.cpp diff --git a/src/model/renderable.cpp b/src/model/renderable.cpp index 2f24d82..9c7935f 100644 --- a/src/model/renderable.cpp +++ b/src/model/renderable.cpp @@ -34,6 +34,11 @@ void Renderable::toggleVisible() setVisible(!m_visible); } +bool Renderable::selected() const +{ + return m_selected; +} + void Renderable::setSelected(bool selected) { if (m_selected != selected) { diff --git a/src/model/renderable.h b/src/model/renderable.h index 9a2b3e2..3fb5f08 100644 --- a/src/model/renderable.h +++ b/src/model/renderable.h @@ -37,6 +37,7 @@ class Renderable : public QObject void setVisible(bool visible); void toggleVisible(); + bool selected() const; void setSelected(bool selected); void deselect(); void toggleSelect(); diff --git a/src/model/task.cpp b/src/model/task.cpp index 4d7c26f..868cd05 100644 --- a/src/model/task.cpp +++ b/src/model/task.cpp @@ -14,16 +14,6 @@ void Task::initPathsFromLayers() // Register selection/deselection on all paths. forEachPath([this](Path &path) { connect(&path, &Path::selectedChanged, this, [this, &path](bool selected){ - const auto &pathIt = std::find(m_selectedPaths.begin(), m_selectedPaths.end(), &path); - const bool exists = (pathIt != m_selectedPaths.end()); - - if (selected && !exists) { - m_selectedPaths.push_back(&path); - } - else if (exists) { - m_selectedPaths.erase(pathIt); - } - emit pathSelectedChanged(path, selected); emit selectionChanged(m_selectedPaths.size()); }); diff --git a/src/model/task.h b/src/model/task.h index 99747f1..b0e9cbe 100644 --- a/src/model/task.h +++ b/src/model/task.h @@ -67,9 +67,11 @@ class Task : public QObject, public common::Aggregable template void forEachSelectedPath(Functor &&functor) const { - const Path::ListPtr selectedPaths(m_selectedPaths); - for (Path *path : selectedPaths) { - functor(*path); + const Path::ListPtr paths(m_paths); + for (Path *path : paths) { + if (path->selected()) { + functor(*path); + } } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1dd4f6d..e494526 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -17,6 +17,7 @@ set(SRC dxfplotimporter.cpp exporterfixture.cpp gcodeexporter.cpp + pathsettings.cpp pocketer.cpp polyline.cpp polylineutils.cpp diff --git a/test/assembler.cpp b/test/assembler.cpp index b517be5..e671e96 100644 --- a/test/assembler.cpp +++ b/test/assembler.cpp @@ -32,6 +32,7 @@ TEST(AssemblerTest, ShouldMergeAllPolylines) if (!isFirstPoint) { EXPECT_EQ(bulge.start(), lastPoint); } + isFirstPoint = false; lastPoint = bulge.end(); }); } @@ -63,6 +64,7 @@ TEST(AssemblerTest, ShouldMergeAllPolylinesLargeTolerance) if (!isFirstPoint) { EXPECT_EQ(bulge.start(), lastPoint); } + isFirstPoint = false; lastPoint = bulge.end(); }); } diff --git a/test/pathsettings.cpp b/test/pathsettings.cpp new file mode 100644 index 0000000..a44969a --- /dev/null +++ b/test/pathsettings.cpp @@ -0,0 +1,37 @@ +#include +#include + +TEST(PathGroupSettingsTest, ShouldShareValueAfterSet) +{ + model::Path::UPtr path1 = std::make_unique(geometry::Polyline(), "1", model::PathSettings(1, 1, 1, 1)); + model::Path::UPtr path2 = std::make_unique(geometry::Polyline(), "2", model::PathSettings(2, 2, 2, 2)); + + path1->setSelected(true); + path2->setSelected(true); + + model::Path::ListUPtr paths; + paths.push_back(std::move(path1)); + paths.push_back(std::move(path2)); + model::Layer::UPtr layer = std::make_unique("layer", std::move(paths)); + + model::Layer::ListUPtr layers; + layers.push_back(std::move(layer)); + model::Task task(std::move(layers)); + + model::PathGroupSettings group(task); + + EXPECT_EQ(std::nullopt, group.planeFeedRate()); + EXPECT_EQ(std::nullopt, group.depthFeedRate()); + EXPECT_EQ(std::nullopt, group.intensity()); + EXPECT_EQ(std::nullopt, group.depth()); + + group.setPlaneFeedRate(3); + group.setDepthFeedRate(3); + group.setIntensity(3); + group.setDepth(3); + + EXPECT_EQ(3, group.planeFeedRate()); + EXPECT_EQ(3, group.depthFeedRate()); + EXPECT_EQ(3, group.intensity()); + EXPECT_EQ(3, group.depth()); +} From 43e38939306c81083793d5393492705b088badc8 Mon Sep 17 00:00:00 2001 From: tristan Date: Sat, 22 Oct 2022 18:08:33 +0200 Subject: [PATCH 04/32] feat: Auto generate changelog --- .chglog/CHANGELOG.tpl.md | 24 +++ .chglog/config.yml | 27 +++ CHANGELOG.md | 437 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 488 insertions(+) create mode 100755 .chglog/CHANGELOG.tpl.md create mode 100755 .chglog/config.yml create mode 100644 CHANGELOG.md diff --git a/.chglog/CHANGELOG.tpl.md b/.chglog/CHANGELOG.tpl.md new file mode 100755 index 0000000..621971e --- /dev/null +++ b/.chglog/CHANGELOG.tpl.md @@ -0,0 +1,24 @@ +{{ range .Versions }} + +## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} + +> {{ datetime "2006-01-02" .Tag.Date }} + +{{ range .CommitGroups -}} +### {{ .Title }} + +{{ range .Commits -}} +* {{ .Subject }} +{{ end }} +{{ end -}} + +{{- if .NoteGroups -}} +{{ range .NoteGroups -}} +### {{ .Title }} + +{{ range .Notes }} +{{ .Body }} +{{ end }} +{{ end -}} +{{ end -}} +{{ end -}} \ No newline at end of file diff --git a/.chglog/config.yml b/.chglog/config.yml new file mode 100755 index 0000000..74ee87e --- /dev/null +++ b/.chglog/config.yml @@ -0,0 +1,27 @@ +style: github +template: CHANGELOG.tpl.md +info: + title: CHANGELOG + repository_url: https://github.com/panzergame/dxfplotter +options: + commits: + # filters: + # Type: + # - feat + # - fix + # - perf + # - refactor + commit_groups: + # title_maps: + # feat: Features + # fix: Bug Fixes + # perf: Performance Improvements + # refactor: Code Refactoring + header: + pattern: "^(\\w*)\\:\\s(.*)$" + pattern_maps: + - Type + - Subject + notes: + keywords: + - BREAKING CHANGE \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fb0465c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,437 @@ + + +## [v1.3.4](https://github.com/panzergame/dxfplotter/compare/v1.3.3...v1.3.4) + +> 2022-10-21 + +### Chore + +* move cleaner/assembler/sorter to filter directory + +### Feat + +* allow manual workflow triggering +* Filter to remove exact duplicate polylines + +### Fix + +* windows ci qt5 install +* Spatial index not update after pruning loop. + + + +## [v1.3.3](https://github.com/panzergame/dxfplotter/compare/v1.3.2...v1.3.3) + +> 2022-09-12 + +### Chore + +* switch back to qt5 + +### Feat + +* add an option to sort path by length +* tool to set origin to a bounding box corner of selected paths + +### Fix + +* qt5 link libraries +* qt5 uic +* no versionless qt_add_resources in qt5 +* qtbase 5 package name +* root config node displayed multiple times +* path not draw properly after transformation +* missing icons + + + +## [v1.3.2](https://github.com/panzergame/dxfplotter/compare/v1.3.1...v1.3.2) + +> 2022-08-06 + +### Chore + +* ignore clangd cache +* skip zero-length bezier +* skip zero-length bezier + +### Feat + +* compute bounding box of polyline +* compile with qt6 + +### Fix + +* fuse needed to run appimage +* qt6 svg missing during appimage build +* visibility icons in layer and path lists +* viewport not updated when transforming paths +* compiling with qt6.2 +* install qt6 svg +* install qt6 svg +* disable pocket action when no document opened +* request qt svg at compilation for icons +* pruning of small biarc +* disable auto uic +* ci for qt6 +* ci for qt6 +* ci for qt6 +* ci install qt6 +* ci install qt6 +* ci install qt6 +* minor compilation issues + + + +## [v1.3.1](https://github.com/panzergame/dxfplotter/compare/v1.3...v1.3.1) + +> 2022-04-02 + +### Chore + +* suffix artifact by OS name +* update pocket gif to a more realistic example + +### Fix + +* prune small biarc before polyline conversion +* prune small biarc before polyline conversion + + + +## [v1.3](https://github.com/panzergame/dxfplotter/compare/v1.2...v1.3) + +> 2022-03-18 + +### Chore + +* add logo and icons +* centre logo image +* add README logo +* document new feature and update screenshots +* manage pocket cutting direction +* Make pocket polylines retain original border orientation +* hide polyline to/from cavc conversion by making them private +* fix code smells +* simplify cmake configure and build procedure + +### Feat + +* UI and logic to create pocket +* pocket POC using cavc +* compute polyline orientation (cw/ccw) + +### Fix + +* README gif width + + + +## [v1.2](https://github.com/panzergame/dxfplotter/compare/v1-windows...v1.2) + +> 2022-02-15 + +### Chore + +* update badges + +### Fix + +* github ci action apt update +* point not positionned + + + +## [v1-windows](https://github.com/panzergame/dxfplotter/compare/v1.1...v1-windows) + +> 2022-02-11 + +### Chore + +* use a script for windows deploying +* deploy on tag +* use cmake variable to setup /MT +* avoid building tests during deploy +* debug +* embed icons as ressources +* embbed dark theme +* disable export and save action when none file loaded +* versionning dxfplot classes +* remove external dependencies in CI + error on warning +* update yamlcpp import in cmake +* update libdxfrw +* update badges + +### Feat + +* deploy windows binaries +* introduce windows ci +* use dark palette +* duplicate a configuration item +* Cutting direction setting +* sort path by increasing length +* mirror operator +* display info message for 2second when saving a file + +### Fix + +* deploy in release mode +* windeployqt invocation +* copy bynary to release directory +* release file +* windows deploy script +* window deploy script +* windows deploy +* windows deploy +* windows deploy +* multiline script +* multithreading library linkage +* jinja2 install for windows build +* cmake target name +* config tree model root parent +* missing windows include +* cmake target name +* windows compilation compatibility +* cmake wextra flag on windows +* windows ci +* install msbuild +* windows ci +* cleanup +* windows ci +* windows ci +* windows ci +* icons +* invalid name popup when cancelling operation +* code smells +* exclude thirdparty from static analysis +* exitting setting panel with ESC +* move to polyline real start when polyline is inverted depending on cutting direction +* polyline self merging + cavc precision +* yaml include dir +* point not positionned +* saving dxfplot and ngc filename cache + +### Test + +* windeployqt + + + +## [v1.1](https://github.com/panzergame/dxfplotter/compare/v1.0...v1.1) + +> 2021-10-30 + +### Chore + +* rename namespace without upper cases +* remove build and test workflow +* use project base dir +* try sonar gcov +* sonar scanner in build script + +### Feat + +* print configuration at beginning of gcode files +* add dialog to transform (offset + angle) selected paths +* add version in appimage file name + +### Fix + +* use last opened or saved file to construct filename for futur files +* gcov path +* do not export config for test +* sonar scan +* uic include +* deploy file name +* release name generation + + + +## [v1.0](https://github.com/panzergame/dxfplotter/compare/v0.1...v1.0) + +> 2021-10-12 + +### Fix + +* deploy file name +* release name generation + + + +## [v0.1](https://github.com/panzergame/dxfplotter/compare/v0.3...v0.1) + +> 2021-10-12 + +### Chore + +* use cereal as submodule +* use fetch content for cereal +* update workflow +* update workflow +* checkout submodule +* split workflow for test and sonarcloud +* code review +* add dxfplot import exporter tests +* serialize Model::Renderable +* move serializing function in serialier module +* migrate to serializer library +* add untracked files +* fix code smells +* include cereal in thidrparty folder +* add cereal dependencies in travis CI +* code smells +* code smells +* throw exception when spline degree not implemented +* update README +* use system libfmt +* try CI targetting ubuntu bionic + +### Feat + +* add version in appimage file name +* add workflow to deploy +* select/deselect all in viewport with shortcut Ctrl+A/Esc +* add github workflow +* serialize offsetted path +* save visibility and config profile/tool in dxfplot +* save path stack in dxfplot +* save to file without asking file name +* implement save as action +* add save button in UI +* serialize QVector2D +* add document class +* import quadratic splines +* display error message when gcode formatting failed +* cut a first layer at 0 depth + +### Fix + +* do not import dxf entity block +* close at Ctrl+Q +* name column width + tooltip to display full name +* disable cereal unittest building +* sonar secrets +* sonar scan +* sonar scan +* sonar scan +* try to fiw sonar cloud analysis +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* sonar github action +* Model::PathSettings serialization +* layer and path visibility while importing dxfplot files +* gcode exporter test pointer to task +* export test +* do not link to cereal as it's a header only library +* use cmake modules to find libcereal +* exclude executable from thirdparty libraries +* quadratic bezier point count +* offsetted point must be a point +* print error when file doesn't exists when started from command line +* use fmt::to_string for more compatbility over different fmt version +* build CI on ubuntu focal... +* hide action related to opened file +* adapt test to first cut pass +* icon in AppImage +* display window icon + +### Refactor + +* own path in layer instead of task +* separate function for creating task +* simplify layer construction + + + +## [v0.3](https://github.com/panzergame/dxfplotter/compare/untagged-921068d2376f1ef1e911...v0.3) + +> 2021-07-15 + +### Chore + +* try fix deploy +* move deploy out of jobs +* deploy only for master and skip cleanup +* deploy only for tags + +### Fix + +* try deploy on tags + + + +## [untagged-921068d2376f1ef1e911](https://github.com/panzergame/dxfplotter/compare/untagged-ba3527e65c761e7268ad...untagged-921068d2376f1ef1e911) + +> 2021-07-14 + + + +## [untagged-ba3527e65c761e7268ad](https://github.com/panzergame/dxfplotter/compare/v0.2...untagged-ba3527e65c761e7268ad) + +> 2021-07-14 + +### Chore + +* rename offseted by offsetted and use OffsettedPath class to store offsetted polylines and direction. +* cleanup some code smells +* complete sonarcloud properties +* try setup sonarcloud +* rename cut depth by depth per cut +* test exporter and fix task path order +* introduce tree model for layers + +### Feat + +* manage cutting direction to reduce burrs +* control path visibility based on parent layer +* display layer tree with paths +* add paths tab and layers tab +* convert layer and name paths based on their layer name + +### Fix + +* sonar cloud project root to look for sonar-project.properties +* uic-settings dependencies in view target +* script for sonarcloud analyze +* icon for right cutter compensation + +### Refactor + +* makes task return Path reference instead of pointer + + + +## [v0.2](https://github.com/panzergame/dxfplotter/compare/untagged-7ed6ab255cb68746c6e9...v0.2) + +> 2020-10-02 + + + +## untagged-7ed6ab255cb68746c6e9 + +> 2020-10-02 + From 770fa04c01cf879c58950ebb31ef8c8e1900172e Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 13 Nov 2022 13:13:15 +0100 Subject: [PATCH 05/32] chore: add unit tests for path selection. --- test/CMakeLists.txt | 11 +- test/duplicate.dxf | 3040 ------------------------------------------- test/path.cpp | 41 + test/task.cpp | 25 + 4 files changed, 76 insertions(+), 3041 deletions(-) delete mode 100644 test/duplicate.dxf create mode 100644 test/path.cpp create mode 100644 test/task.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e494526..6f7b086 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,6 +9,10 @@ FetchContent_MakeAvailable(googletest) include(GoogleTest) +find_package(Qt5 COMPONENTS REQUIRED + Test +) + set(SRC arc.cpp assembler.cpp @@ -17,11 +21,13 @@ set(SRC dxfplotimporter.cpp exporterfixture.cpp gcodeexporter.cpp + path.cpp pathsettings.cpp pocketer.cpp polyline.cpp polylineutils.cpp serializer.cpp + task.cpp verticalspeed.cpp exporterfixture.h @@ -29,7 +35,10 @@ set(SRC ) add_executable(dxfplotter-test ${SRC} main.cpp) -target_link_libraries(dxfplotter-test ${LINK_LIBRARIES} gtest_main) + +target_include_directories(dxfplotter-test PRIVATE ${Qt5Test_INCLUDE_DIRS}) +target_link_libraries(dxfplotter-test ${LINK_LIBRARIES} Qt5::Test gtest_main) + add_coverage(dxfplotter-test) enable_testing() diff --git a/test/duplicate.dxf b/test/duplicate.dxf deleted file mode 100644 index d6f268a..0000000 --- a/test/duplicate.dxf +++ /dev/null @@ -1,3040 +0,0 @@ -999 -dxfrw 0.6.3 - 0 -SECTION - 2 -HEADER - 9 -$ACADVER - 1 -AC1021 - 9 -$DWGCODEPAGE - 3 -ANSI_1252 - 9 -$INSBASE - 10 -0 - 20 -0 - 30 -0 - 9 -$EXTMIN - 10 -20.00000000000001 - 20 -10 - 30 -0 - 9 -$EXTMAX - 10 -230 - 20 -120.5172658143639 - 30 -0 - 9 -$LIMMIN - 10 -0 - 20 -0 - 9 -$LIMMAX - 10 -420 - 20 -297 - 9 -$ORTHOMODE - 70 - 0 - 9 -$REGENMODE - 70 - 1 - 9 -$FILLMODE - 70 - 1 - 9 -$QTEXTMODE - 70 - 0 - 9 -$MIRRTEXT - 70 - 0 - 9 -$LTSCALE - 40 -1 - 9 -$ATTMODE - 70 - 0 - 9 -$TEXTSIZE - 40 -2.5 - 9 -$TRACEWID - 40 -15.68 - 9 -$TEXTSTYLE - 7 -STANDARD - 9 -$CLAYER - 8 -0 - 9 -$CELTYPE - 6 -BYLAYER - 9 -$CECOLOR - 62 - 256 - 9 -$CELTSCALE - 40 -1 - 9 -$DISPSILH - 70 - 0 - 9 -$DIMSCALE - 40 -2.5 - 9 -$DIMASZ - 40 -2.5 - 9 -$DIMEXO - 40 -0.625 - 9 -$DIMDLI - 40 -3.75 - 9 -$DIMRND - 40 -0 - 9 -$DIMDLE - 40 -0 - 9 -$DIMEXE - 40 -1.25 - 9 -$DIMTP - 40 -0 - 9 -$DIMTM - 40 -0 - 9 -$DIMTXT - 40 -2.5 - 9 -$DIMCEN - 40 -2.5 - 9 -$DIMTSZ - 40 -0 - 9 -$DIMTOL - 70 - 0 - 9 -$DIMLIM - 70 - 0 - 9 -$DIMTIH - 70 - 0 - 9 -$DIMTOH - 70 - 0 - 9 -$DIMSE1 - 70 - 0 - 9 -$DIMSE2 - 70 - 0 - 9 -$DIMTAD - 70 - 1 - 9 -$DIMZIN - 70 - 8 - 9 -$DIMBLK - 1 - - 9 -$DIMASO - 70 - 1 - 9 -$DIMSHO - 70 - 1 - 9 -$DIMPOST - 1 - - 9 -$DIMAPOST - 1 - - 9 -$DIMALT - 70 - 0 - 9 -$DIMALTD - 70 - 3 - 9 -$DIMALTF - 40 -0.03937 - 9 -$DIMLFAC - 40 -1 - 9 -$DIMTOFL - 70 - 1 - 9 -$DIMTVP - 40 -0 - 9 -$DIMTIX - 70 - 0 - 9 -$DIMSOXD - 70 - 0 - 9 -$DIMSAH - 70 - 0 - 9 -$DIMBLK1 - 1 - - 9 -$DIMBLK2 - 1 - - 9 -$DIMSTYLE - 2 -STANDARD - 9 -$DIMCLRD - 70 - 0 - 9 -$DIMCLRE - 70 - 0 - 9 -$DIMCLRT - 70 - 0 - 9 -$DIMTFAC - 40 -1 - 9 -$DIMGAP - 40 -0.625 - 9 -$DIMJUST - 70 - 0 - 9 -$DIMSD1 - 70 - 0 - 9 -$DIMSD2 - 70 - 0 - 9 -$DIMTOLJ - 70 - 0 - 9 -$DIMTZIN - 70 - 8 - 9 -$DIMALTZ - 70 - 0 - 9 -$DIMALTTZ - 70 - 0 - 9 -$DIMUPT - 70 - 0 - 9 -$DIMDEC - 70 - 2 - 9 -$DIMTDEC - 70 - 2 - 9 -$DIMALTU - 70 - 2 - 9 -$DIMALTTD - 70 - 3 - 9 -$DIMTXSTY - 7 -STANDARD - 9 -$DIMAUNIT - 70 - 0 - 9 -$DIMADEC - 70 - 0 - 9 -$DIMALTRND - 40 -0 - 9 -$DIMAZIN - 70 - 0 - 9 -$DIMDSEP - 70 - 44 - 9 -$DIMATFIT - 70 - 3 - 9 -$DIMFRAC - 70 - 0 - 9 -$DIMLDRBLK - 1 -STANDARD - 9 -$DIMLUNIT - 70 - 2 - 9 -$DIMLWD - 70 - -2 - 9 -$DIMLWE - 70 - -2 - 9 -$DIMTMOVE - 70 - 0 - 9 -$DIMFXL - 40 -1 - 9 -$DIMFXLON - 70 - 0 - 9 -$DIMJOGANG - 40 -0.7854 - 9 -$DIMTFILL - 70 - 0 - 9 -$DIMTFILLCLR - 70 - 0 - 9 -$DIMARCSYM - 70 - 0 - 9 -$DIMLTYPE - 6 - - 9 -$DIMLTEX1 - 6 - - 9 -$DIMLTEX2 - 6 - - 9 -$LUNITS - 70 - 2 - 9 -$LUPREC - 70 - 4 - 9 -$SKETCHINC - 40 -1 - 9 -$FILLETRAD - 40 -0 - 9 -$AUNITS - 70 - 0 - 9 -$AUPREC - 70 - 2 - 9 -$MENU - 1 -. - 9 -$ELEVATION - 40 -0 - 9 -$PELEVATION - 40 -0 - 9 -$THICKNESS - 40 -0 - 9 -$LIMCHECK - 70 - 0 - 9 -$CHAMFERA - 40 -0 - 9 -$CHAMFERB - 40 -0 - 9 -$CHAMFERC - 40 -0 - 9 -$CHAMFERD - 40 -0 - 9 -$SKPOLY - 70 - 0 - 9 -$USRTIMER - 70 - 1 - 9 -$ANGBASE - 50 -0 - 9 -$ANGDIR - 70 - 0 - 9 -$PDMODE - 70 - 34 - 9 -$PDSIZE - 40 -0 - 9 -$PLINEWID - 40 -0 - 9 -$SPLFRAME - 70 - 0 - 9 -$SPLINETYPE - 70 - 2 - 9 -$SPLINESEGS - 70 - 8 - 9 -$HANDSEED - 5 -20000 - 9 -$SURFTAB1 - 70 - 6 - 9 -$SURFTAB2 - 70 - 6 - 9 -$SURFTYPE - 70 - 6 - 9 -$SURFU - 70 - 6 - 9 -$SURFV - 70 - 6 - 9 -$UCSBASE - 2 - - 9 -$UCSNAME - 2 - - 9 -$UCSORG - 10 -0 - 20 -0 - 30 -0 - 9 -$UCSXDIR - 10 -1 - 20 -0 - 30 -0 - 9 -$UCSYDIR - 10 -0 - 20 -1 - 30 -0 - 9 -$UCSORTHOREF - 2 - - 9 -$UCSORTHOVIEW - 70 - 0 - 9 -$UCSORGTOP - 10 -0 - 20 -0 - 30 -0 - 9 -$UCSORGBOTTOM - 10 -0 - 20 -0 - 30 -0 - 9 -$UCSORGLEFT - 10 -0 - 20 -0 - 30 -0 - 9 -$UCSORGRIGHT - 10 -0 - 20 -0 - 30 -0 - 9 -$UCSORGFRONT - 10 -0 - 20 -0 - 30 -0 - 9 -$UCSORGBACK - 10 -0 - 20 -0 - 30 -0 - 9 -$PUCSBASE - 2 - - 9 -$PUCSNAME - 2 - - 9 -$PUCSORG - 10 -0 - 20 -0 - 30 -0 - 9 -$PUCSXDIR - 10 -1 - 20 -0 - 30 -0 - 9 -$PUCSYDIR - 10 -0 - 20 -1 - 30 -0 - 9 -$PUCSORTHOREF - 2 - - 9 -$PUCSORTHOVIEW - 70 - 0 - 9 -$PUCSORGTOP - 10 -0 - 20 -0 - 30 -0 - 9 -$PUCSORGBOTTOM - 10 -0 - 20 -0 - 30 -0 - 9 -$PUCSORGLEFT - 10 -0 - 20 -0 - 30 -0 - 9 -$PUCSORGRIGHT - 10 -0 - 20 -0 - 30 -0 - 9 -$PUCSORGFRONT - 10 -0 - 20 -0 - 30 -0 - 9 -$PUCSORGBACK - 10 -0 - 20 -0 - 30 -0 - 9 -$USERI1 - 70 - 0 - 9 -$USERI2 - 70 - 0 - 9 -$USERI3 - 70 - 0 - 9 -$USERI4 - 70 - 0 - 9 -$USERI5 - 70 - 0 - 9 -$USERR1 - 40 -0 - 9 -$USERR2 - 40 -0 - 9 -$USERR3 - 40 -0 - 9 -$USERR4 - 40 -0 - 9 -$USERR5 - 40 -0 - 9 -$WORLDVIEW - 70 - 1 - 9 -$SHADEDGE - 70 - 3 - 9 -$SHADEDIF - 70 - 70 - 9 -$TILEMODE - 70 - 1 - 9 -$MAXACTVP - 70 - 64 - 9 -$PINSBASE - 10 -0 - 20 -0 - 30 -0 - 9 -$PLIMCHECK - 70 - 0 - 9 -$PEXTMIN - 10 -0 - 20 -0 - 30 -0 - 9 -$PEXTMAX - 10 -0 - 20 -0 - 30 -0 - 9 -$GRIDMODE - 70 - 1 - 9 -$SNAPSTYLE - 70 - 0 - 9 -$PLIMMIN - 10 -0 - 20 -0 - 9 -$PLIMMAX - 10 -210 - 20 -297 - 9 -$UNITMODE - 70 - 0 - 9 -$VISRETAIN - 70 - 1 - 9 -$PLINEGEN - 70 - 0 - 9 -$PSLTSCALE - 70 - 1 - 9 -$TREEDEPTH - 70 - 3020 - 9 -$CMLSTYLE - 2 -Standard - 9 -$CMLJUST - 70 - 0 - 9 -$CMLSCALE - 40 -20 - 9 -$PROXYGRAPHICS - 70 - 1 - 9 -$MEASUREMENT - 70 - 1 - 9 -$CELWEIGHT -370 - -1 - 9 -$ENDCAPS -280 - 0 - 9 -$JOINSTYLE -280 - 0 - 9 -$LWDISPLAY -290 - 0 - 9 -$INSUNITS - 70 - 4 - 9 -$HYPERLINKBASE - 1 - - 9 -$STYLESHEET - 1 - - 9 -$XEDIT -290 - 1 - 9 -$CEPSNTYPE -380 - 0 - 9 -$PSTYLEMODE -290 - 1 - 9 -$EXTNAMES -290 - 1 - 9 -$PSVPSCALE - 40 -1 - 9 -$OLESTARTUP -290 - 0 - 9 -$SORTENTS -280 - 127 - 9 -$INDEXCTL -280 - 0 - 9 -$HIDETEXT -280 - 1 - 9 -$XCLIPFRAME -290 - 0 - 9 -$HALOGAP -280 - 0 - 9 -$OBSCOLOR - 70 - 257 - 9 -$OBSLTYPE -280 - 0 - 9 -$INTERSECTIONDISPLAY -280 - 0 - 9 -$INTERSECTIONCOLOR - 70 - 257 - 9 -$DIMASSOC -280 - 1 - 9 -$PROJECTNAME - 1 - - 9 -$CAMERADISPLAY -290 - 0 - 9 -$LENSLENGTH - 40 -50 - 9 -$CAMERAHEIGHT - 40 -0 - 9 -$STEPSPERSEC - 40 -2 - 9 -$STEPSIZE - 40 -50 - 9 -$3DDWFPREC - 40 -2 - 9 -$PSOLWIDTH - 40 -5 - 9 -$PSOLHEIGHT - 40 -80 - 9 -$LOFTANG1 - 40 -1.570796326794897 - 9 -$LOFTANG2 - 40 -1.570796326794897 - 9 -$LOFTMAG1 - 40 -0 - 9 -$LOFTMAG2 - 40 -0 - 9 -$LOFTPARAM - 70 - 7 - 9 -$LOFTNORMALS -280 - 1 - 9 -$LATITUDE - 40 -1 - 9 -$LONGITUDE - 40 -1 - 9 -$NORTHDIRECTION - 40 -0 - 9 -$TIMEZONE - 70 --8000 - 9 -$LIGHTGLYPHDISPLAY -280 - 1 - 9 -$TILEMODELIGHTSYNCH -280 - 1 - 9 -$SOLIDHIST -280 - 1 - 9 -$SHOWHIST -280 - 1 - 9 -$DWFFRAME -280 - 2 - 9 -$DGNFRAME -280 - 0 - 9 -$REALWORLDSCALE -290 - 1 - 9 -$INTERFERECOLOR - 62 - 1 - 9 -$CSHADOW -280 - 0 - 9 -$SHADOWPLANELOCATION - 40 -0 - 0 -ENDSEC - 0 -SECTION - 2 -CLASSES - 0 -ENDSEC - 0 -SECTION - 2 -TABLES - 0 -TABLE - 2 -VPORT - 5 -8 -330 -0 -100 -AcDbSymbolTable - 70 - 1 - 0 -VPORT - 5 -31 -330 -2 -100 -AcDbSymbolTableRecord -100 -AcDbViewportTableRecord - 2 -*ACTIVE - 70 - 0 - 10 -0 - 20 -0 - 11 -1 - 21 -1 - 12 -150.8355321020231 - 22 -73.32893579595437 - 13 -0 - 23 -0 - 14 -10 - 24 -10 - 15 -10 - 25 -10 - 16 -0 - 26 -0 - 36 -1 - 17 -0 - 27 -0 - 37 -0 - 40 -166.8865435356203 - 41 -1.794466403162055 - 42 -50 - 43 -0 - 44 -0 - 50 -0 - 51 -0 - 71 - 0 - 72 - 100 - 73 - 1 - 74 - 3 - 75 - 0 - 76 - 1 - 77 - 0 - 78 - 0 -281 - 0 - 65 - 1 -110 -0 -120 -0 -130 -0 -111 -1 -121 -0 -131 -0 -112 -0 -122 -1 -132 -0 - 79 - 0 -146 -0 -348 -10020 - 60 - 7 - 61 - 5 -292 -1 -282 - 1 -141 -0 -142 -0 - 63 - 250 -421 -3358443 - 0 -ENDTAB - 0 -TABLE - 2 -LTYPE - 5 -5 -330 -0 -100 -AcDbSymbolTable - 70 - 4 - 0 -LTYPE - 5 -14 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -ByBlock - 70 - 0 - 3 - - 72 - 65 - 73 - 0 - 40 -0 - 0 -LTYPE - 5 -15 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -ByLayer - 70 - 0 - 3 - - 72 - 65 - 73 - 0 - 40 -0 - 0 -LTYPE - 5 -16 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -Continuous - 70 - 0 - 3 -Solid line - 72 - 65 - 73 - 0 - 40 -0 - 0 -LTYPE - 5 -32 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DOT - 70 - 0 - 3 -Dot . . . . . . . . . . . . . . . . . . . . . . - 72 - 65 - 73 - 2 - 40 -6.35 - 49 -0 - 74 - 0 - 49 --6.35 - 74 - 0 - 0 -LTYPE - 5 -33 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DOTTINY - 70 - 0 - 3 -Dot (.15x) ..................................... - 72 - 65 - 73 - 2 - 40 -0.9525 - 49 -0 - 74 - 0 - 49 --0.9525 - 74 - 0 - 0 -LTYPE - 5 -34 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DOT2 - 70 - 0 - 3 -Dot (.5x) ..................................... - 72 - 65 - 73 - 2 - 40 -3.175 - 49 -0 - 74 - 0 - 49 --3.175 - 74 - 0 - 0 -LTYPE - 5 -35 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DOTX2 - 70 - 0 - 3 -Dot (2x) . . . . . . . . . . . . . - 72 - 65 - 73 - 2 - 40 -12.7 - 49 -0 - 74 - 0 - 49 --12.7 - 74 - 0 - 0 -LTYPE - 5 -36 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DASHED - 70 - 0 - 3 -Dashed _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - 72 - 65 - 73 - 2 - 40 -19.05 - 49 -12.7 - 74 - 0 - 49 --6.35 - 74 - 0 - 0 -LTYPE - 5 -37 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DASHEDTINY - 70 - 0 - 3 -Dashed (.15x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - 72 - 65 - 73 - 2 - 40 -2.8575 - 49 -1.905 - 74 - 0 - 49 --0.9525 - 74 - 0 - 0 -LTYPE - 5 -38 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DASHED2 - 70 - 0 - 3 -Dashed (.5x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - 72 - 65 - 73 - 2 - 40 -9.524999999999999 - 49 -6.35 - 74 - 0 - 49 --3.175 - 74 - 0 - 0 -LTYPE - 5 -39 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DASHEDX2 - 70 - 0 - 3 -Dashed (2x) ____ ____ ____ ____ ____ ___ - 72 - 65 - 73 - 2 - 40 -38.09999999999999 - 49 -25.4 - 74 - 0 - 49 --12.7 - 74 - 0 - 0 -LTYPE - 5 -3A -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DASHDOT - 70 - 0 - 3 -Dash dot __ . __ . __ . __ . __ . __ . __ . __ - 72 - 65 - 73 - 4 - 40 -25.4 - 49 -12.7 - 74 - 0 - 49 --6.35 - 74 - 0 - 49 -0 - 74 - 0 - 49 --6.35 - 74 - 0 - 0 -LTYPE - 5 -3B -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DASHDOTTINY - 70 - 0 - 3 -Dash dot (.15x) _._._._._._._._._._._._._._._. - 72 - 65 - 73 - 4 - 40 -3.81 - 49 -1.905 - 74 - 0 - 49 --0.9525 - 74 - 0 - 49 -0 - 74 - 0 - 49 --0.9525 - 74 - 0 - 0 -LTYPE - 5 -3C -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DASHDOT2 - 70 - 0 - 3 -Dash dot (.5x) _._._._._._._._._._._._._._._. - 72 - 65 - 73 - 4 - 40 -12.7 - 49 -6.35 - 74 - 0 - 49 --3.175 - 74 - 0 - 49 -0 - 74 - 0 - 49 --3.175 - 74 - 0 - 0 -LTYPE - 5 -3D -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DASHDOTX2 - 70 - 0 - 3 -Dash dot (2x) ____ . ____ . ____ . ___ - 72 - 65 - 73 - 4 - 40 -50.8 - 49 -25.4 - 74 - 0 - 49 --12.7 - 74 - 0 - 49 -0 - 74 - 0 - 49 --12.7 - 74 - 0 - 0 -LTYPE - 5 -3E -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DIVIDE - 70 - 0 - 3 -Divide ____ . . ____ . . ____ . . ____ . . ____ - 72 - 65 - 73 - 6 - 40 -31.75 - 49 -12.7 - 74 - 0 - 49 --6.35 - 74 - 0 - 49 -0 - 74 - 0 - 49 --6.35 - 74 - 0 - 49 -0 - 74 - 0 - 49 --6.35 - 74 - 0 - 0 -LTYPE - 5 -3F -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DIVIDETINY - 70 - 0 - 3 -Divide (.15x) __..__..__..__..__..__..__..__.._ - 72 - 65 - 73 - 6 - 40 -4.7625 - 49 -1.905 - 74 - 0 - 49 --0.9525 - 74 - 0 - 49 -0 - 74 - 0 - 49 --0.9525 - 74 - 0 - 49 -0 - 74 - 0 - 49 --0.9525 - 74 - 0 - 0 -LTYPE - 5 -40 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DIVIDE2 - 70 - 0 - 3 -Divide (.5x) __..__..__..__..__..__..__..__.._ - 72 - 65 - 73 - 6 - 40 -15.875 - 49 -6.35 - 74 - 0 - 49 --3.175 - 74 - 0 - 49 -0 - 74 - 0 - 49 --3.175 - 74 - 0 - 49 -0 - 74 - 0 - 49 --3.175 - 74 - 0 - 0 -LTYPE - 5 -41 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -DIVIDEX2 - 70 - 0 - 3 -Divide (2x) ________ . . ________ . . _ - 72 - 65 - 73 - 6 - 40 -63.5 - 49 -25.4 - 74 - 0 - 49 --12.7 - 74 - 0 - 49 -0 - 74 - 0 - 49 --12.7 - 74 - 0 - 49 -0 - 74 - 0 - 49 --12.7 - 74 - 0 - 0 -LTYPE - 5 -42 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -BORDER - 70 - 0 - 3 -Border __ __ . __ __ . __ __ . __ __ . __ __ . - 72 - 65 - 73 - 6 - 40 -44.45 - 49 -12.7 - 74 - 0 - 49 --6.35 - 74 - 0 - 49 -12.7 - 74 - 0 - 49 --6.35 - 74 - 0 - 49 -0 - 74 - 0 - 49 --6.35 - 74 - 0 - 0 -LTYPE - 5 -43 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -BORDERTINY - 70 - 0 - 3 -Border (.15x) __.__.__.__.__.__.__.__.__.__.__. - 72 - 65 - 73 - 6 - 40 -6.6675 - 49 -1.905 - 74 - 0 - 49 --0.9525 - 74 - 0 - 49 -1.905 - 74 - 0 - 49 --0.9525 - 74 - 0 - 49 -0 - 74 - 0 - 49 --0.9525 - 74 - 0 - 0 -LTYPE - 5 -44 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -BORDER2 - 70 - 0 - 3 -Border (.5x) __.__.__.__.__.__.__.__.__.__.__. - 72 - 65 - 73 - 6 - 40 -22.225 - 49 -6.35 - 74 - 0 - 49 --3.175 - 74 - 0 - 49 -6.35 - 74 - 0 - 49 --3.175 - 74 - 0 - 49 -0 - 74 - 0 - 49 --3.175 - 74 - 0 - 0 -LTYPE - 5 -45 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -BORDERX2 - 70 - 0 - 3 -Border (2x) ____ ____ . ____ ____ . ___ - 72 - 65 - 73 - 6 - 40 -88.89999999999999 - 49 -25.4 - 74 - 0 - 49 --12.7 - 74 - 0 - 49 -25.4 - 74 - 0 - 49 --12.7 - 74 - 0 - 49 -0 - 74 - 0 - 49 --12.7 - 74 - 0 - 0 -LTYPE - 5 -46 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -CENTER - 70 - 0 - 3 -Center ____ _ ____ _ ____ _ ____ _ ____ _ ____ - 72 - 65 - 73 - 4 - 40 -50.8 - 49 -31.75 - 74 - 0 - 49 --6.35 - 74 - 0 - 49 -6.35 - 74 - 0 - 49 --6.35 - 74 - 0 - 0 -LTYPE - 5 -47 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -CENTERTINY - 70 - 0 - 3 -Center (.15x) ___ _ ___ _ ___ _ ___ _ ___ _ ___ - 72 - 65 - 73 - 4 - 40 -7.619999999999999 - 49 -4.7625 - 74 - 0 - 49 --0.9525 - 74 - 0 - 49 -0.9525 - 74 - 0 - 49 --0.9525 - 74 - 0 - 0 -LTYPE - 5 -48 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -CENTER2 - 70 - 0 - 3 -Center (.5x) ___ _ ___ _ ___ _ ___ _ ___ _ ___ - 72 - 65 - 73 - 4 - 40 -28.575 - 49 -19.05 - 74 - 0 - 49 --3.175 - 74 - 0 - 49 -3.175 - 74 - 0 - 49 --3.175 - 74 - 0 - 0 -LTYPE - 5 -49 -330 -5 -100 -AcDbSymbolTableRecord -100 -AcDbLinetypeTableRecord - 2 -CENTERX2 - 70 - 0 - 3 -Center (2x) ________ __ ________ __ _____ - 72 - 65 - 73 - 4 - 40 -101.6 - 49 -63.5 - 74 - 0 - 49 --12.7 - 74 - 0 - 49 -12.7 - 74 - 0 - 49 --12.7 - 74 - 0 - 0 -ENDTAB - 0 -TABLE - 2 -LAYER - 5 -2 -330 -0 -100 -AcDbSymbolTable - 70 - 1 - 0 -LAYER - 5 -10 -330 -2 -100 -AcDbSymbolTableRecord -100 -AcDbLayerTableRecord - 2 -0 - 70 - 0 - 62 - 7 - 6 -CONTINUOUS -370 - 0 -390 -F - 0 -ENDTAB - 0 -TABLE - 2 -STYLE - 5 -3 -330 -0 -100 -AcDbSymbolTable - 70 - 3 - 0 -STYLE - 5 -4A -330 -2 -100 -AcDbSymbolTableRecord -100 -AcDbTextStyleTableRecord - 2 -Standard - 70 - 0 - 40 -0 - 41 -1 - 50 -0 - 71 - 0 - 42 -1 - 3 -txt - 4 - - 0 -ENDTAB - 0 -TABLE - 2 -VIEW - 5 -6 -330 -0 -100 -AcDbSymbolTable - 70 - 0 - 0 -ENDTAB - 0 -TABLE - 2 -UCS - 5 -7 -330 -0 -100 -AcDbSymbolTable - 70 - 0 - 0 -ENDTAB - 0 -TABLE - 2 -APPID - 5 -9 -330 -0 -100 -AcDbSymbolTable - 70 - 1 - 0 -APPID - 5 -12 -330 -9 -100 -AcDbSymbolTableRecord -100 -AcDbRegAppTableRecord - 2 -ACAD - 70 - 0 - 0 -APPID - 5 -4B -330 -9 -100 -AcDbSymbolTableRecord -100 -AcDbRegAppTableRecord - 2 -LibreCad - 70 - 0 - 0 -ENDTAB - 0 -TABLE - 2 -DIMSTYLE - 5 -A -330 -0 -100 -AcDbSymbolTable - 70 - 1 -100 -AcDbDimStyleTable - 71 - 1 - 0 -DIMSTYLE -105 -4C -330 -A -100 -AcDbSymbolTableRecord -100 -AcDbDimStyleTableRecord - 2 -Standard - 70 - 0 - 40 -2.5 - 41 -2.5 - 42 -0.625 - 43 -0.38 - 44 -1.25 - 45 -0 - 46 -0 - 47 -0 - 48 -0 - 49 -1 -140 -2.5 -141 -0.09 -142 -0 -143 -25.4 -144 -1 -145 -0 -146 -1 -147 -0.625 -148 -0 - 71 - 0 - 72 - 0 - 73 - 0 - 74 - 1 - 75 - 0 - 76 - 0 - 77 - 0 - 78 - 8 - 79 - 0 -170 - 0 -171 - 2 -172 - 0 -173 - 0 -174 - 0 -175 - 0 -176 - 0 -177 - 0 -178 - 0 -179 - 0 -271 - 2 -272 - 4 -273 - 2 -274 - 2 -275 - 0 -276 - 0 -277 - 2 -278 - 44 -279 - 0 -280 - 0 -281 - 0 -282 - 0 -283 - 1 -284 - 0 -285 - 0 -286 - 0 -288 - 0 -289 - 3 -340 -STANDARD -341 - -371 - -2 -372 - -2 - 0 -ENDTAB - 0 -TABLE - 2 -BLOCK_RECORD - 5 -1 -330 -0 -100 -AcDbSymbolTable - 70 - 2 - 0 -BLOCK_RECORD - 5 -1F -330 -1 -100 -AcDbSymbolTableRecord -100 -AcDbBlockTableRecord - 2 -*Model_Space - 70 - 0 -280 - 1 -281 - 0 - 0 -BLOCK_RECORD - 5 -1E -330 -1 -100 -AcDbSymbolTableRecord -100 -AcDbBlockTableRecord - 2 -*Paper_Space - 70 - 0 -280 - 1 -281 - 0 - 0 -ENDTAB - 0 -ENDSEC - 0 -SECTION - 2 -BLOCKS - 0 -BLOCK - 5 -20 -330 -1F -100 -AcDbEntity - 8 -0 -100 -AcDbBlockBegin - 2 -*Model_Space - 70 - 0 - 10 -0 - 20 -0 - 30 -0 - 3 -*Model_Space - 1 - - 0 -ENDBLK - 5 -21 -330 -1F -100 -AcDbEntity - 8 -0 -100 -AcDbBlockEnd - 0 -BLOCK - 5 -1C -330 -1B -100 -AcDbEntity - 8 -0 -100 -AcDbBlockBegin - 2 -*Paper_Space - 70 - 0 - 10 -0 - 20 -0 - 30 -0 - 3 -*Paper_Space - 1 - - 0 -ENDBLK - 5 -1D -330 -1F -100 -AcDbEntity - 8 -0 -100 -AcDbBlockEnd - 0 -ENDSEC - 0 -SECTION - 2 -ENTITIES - 0 -LINE - 5 -4D -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbLine - 10 -60 - 20 -70 - 11 -130 - 21 -100 - 0 -LINE - 5 -4E -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbLine - 10 -130 - 20 -100 - 11 -220 - 21 -80 - 0 -LINE - 5 -4F -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbLine - 10 -220 - 20 -80 - 11 -130 - 21 -10 - 0 -LINE - 5 -50 -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbLine - 10 -130 - 20 -100 - 11 -220 - 21 -80 - 0 -LINE - 5 -51 -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbLine - 10 -220 - 20 -80 - 11 -130 - 21 -100 - 0 -LINE - 5 -52 -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbLine - 10 -130 - 20 -10 - 11 -210 - 21 -20 - 0 -LINE - 5 -53 -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbLine - 10 -210 - 20 -20 - 11 -230 - 21 -50 - 0 -LINE - 5 -54 -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbLine - 10 -230 - 20 -50 - 11 -210 - 21 -20 - 0 -LINE - 5 -55 -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbLine - 10 -210 - 20 -20 - 11 -130 - 21 -10 - 0 -LINE - 5 -56 -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbLine - 10 -130 - 20 -10 - 11 -210 - 21 -20 - 0 -ARC - 5 -57 -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbCircle - 10 -53.75 - 20 -82.5 - 40 -38.01726581436387 -100 -AcDbArc - 50 -46.33221985386964 - 51 -152.5924245621816 - 0 -ARC - 5 -58 -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbCircle - 10 -53.75 - 20 -82.5 - 40 -38.01726581436387 -100 -AcDbArc - 50 -46.33221985386964 - 51 -152.5924245621816 - 0 -POINT - 5 -59 -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbPoint - 10 -80 - 20 -60 - 0 -POINT - 5 -5A -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbPoint - 10 -70 - 20 -30 - 0 -POINT - 5 -5B -100 -AcDbEntity - 8 -0 - 6 -ByLayer - 62 - 256 -370 - -1 -100 -AcDbPoint - 10 -100 - 20 -50 - 0 -ENDSEC - 0 -SECTION - 2 -OBJECTS - 0 -DICTIONARY - 5 -C -330 -0 -100 -AcDbDictionary -281 - 1 - 3 -ACAD_GROUP -350 -D - 0 -DICTIONARY - 5 -D -330 -C -100 -AcDbDictionary -281 - 1 - 0 -ENDSEC - 0 -EOF diff --git a/test/path.cpp b/test/path.cpp new file mode 100644 index 0000000..de8a469 --- /dev/null +++ b/test/path.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include + +TEST(PathTest, ShouldEmitSignalsWhenSelected) +{ + model::Path path(geometry::Polyline(), "1", model::PathSettings(1, 1, 1, 1)); + + QSignalSpy spy(&path, &model::Path::selectedChanged); + + path.setSelected(true); + + ASSERT_EQ(spy.count(), 1); + EXPECT_TRUE(spy.takeFirst().at(0).toBool()); +} + +TEST(PathTest, ShouldEmitSignalsWhenDeselected) +{ + model::Path path(geometry::Polyline(), "1", model::PathSettings(1, 1, 1, 1)); + path.setSelected(true); + + QSignalSpy spy(&path, &model::Path::selectedChanged); + + path.setSelected(false); + + ASSERT_EQ(spy.count(), 1); + EXPECT_FALSE(spy.takeFirst().at(0).toBool()); +} + +TEST(PathTest, ShouldNoEmitSignalsWhenReselected) +{ + model::Path path(geometry::Polyline(), "1", model::PathSettings(1, 1, 1, 1)); + path.setSelected(true); + + QSignalSpy spy(&path, &model::Path::selectedChanged); + + path.setSelected(true); + + ASSERT_EQ(spy.count(), 0); +} diff --git a/test/task.cpp b/test/task.cpp new file mode 100644 index 0000000..67a6552 --- /dev/null +++ b/test/task.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include + +TEST(TaskTest, ShouldEmitSignalsWhenOnePathSelected) +{ + model::Path::UPtr path1 = std::make_unique(geometry::Polyline(), "1", model::PathSettings(1, 1, 1, 1)); + model::Path::UPtr path2 = std::make_unique(geometry::Polyline(), "2", model::PathSettings(2, 2, 2, 2)); + + model::Path::ListUPtr paths; + paths.push_back(std::move(path1)); + paths.push_back(std::move(path2)); + model::Layer::UPtr layer = std::make_unique("layer", std::move(paths)); + + model::Layer::ListUPtr layers; + layers.push_back(std::move(layer)); + model::Task task(std::move(layers)); + + QSignalSpy spy(&task, &model::Task::selectionChanged); + + task.pathAt(0).setSelected(true); + + ASSERT_EQ(spy.count(), 1); +} From 41310e562a19fda771b94502050000111f53e820 Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 13 Nov 2022 13:48:23 +0100 Subject: [PATCH 06/32] fix: selected path not showing properties --- src/model/task.cpp | 24 ++++++++++++++++++++---- src/model/task.h | 4 ++-- src/view/task/path.cpp | 10 +++++----- src/view/task/path.h | 2 +- test/task.cpp | 4 +++- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/model/task.cpp b/src/model/task.cpp index 868cd05..721f5c9 100644 --- a/src/model/task.cpp +++ b/src/model/task.cpp @@ -15,11 +15,18 @@ void Task::initPathsFromLayers() forEachPath([this](Path &path) { connect(&path, &Path::selectedChanged, this, [this, &path](bool selected){ emit pathSelectedChanged(path, selected); - emit selectionChanged(m_selectedPaths.size()); + emit selectionChanged(pathSelectionEmpty()); }); }); } +bool Task::pathSelectionEmpty() const +{ + return !std::any_of(m_paths.begin(), m_paths.end(), [](const Path *path) { + return path->selected(); + }); +} + Task::Task(Layer::ListUPtr &&layers) :m_layers(std::move(layers)) { @@ -79,12 +86,21 @@ void Task::cutterCompensationSelection(float scaledRadius, float minimumPolyline void Task::pocketSelection(float radius, float minimumPolylineLength, float minimumArcLength) { - if (m_selectedPaths.empty()) { + const Path::ListPtr::iterator it = m_paths.begin(); + const Path::ListPtr::iterator end = m_paths.end(); + + const auto isSelectedPred = [](const Path* path){ return path->selected(); }; + + const Path::ListPtr::iterator borderIt = std::find_if(it, end, isSelectedPred); + if (borderIt == end) { + // No selected path found return; } - Path *border = m_selectedPaths.front(); - const Path::ListCPtr islands(m_selectedPaths.begin() + 1, m_selectedPaths.end()); + Path::ListCPtr islands; + std::copy_if(borderIt + 1, end, std::back_inserter(islands), isSelectedPred); + + Path *border = *borderIt; border->pocket(islands, radius, minimumPolylineLength, minimumArcLength); } diff --git a/src/model/task.h b/src/model/task.h index b0e9cbe..b353063 100644 --- a/src/model/task.h +++ b/src/model/task.h @@ -19,9 +19,9 @@ class Task : public QObject, public common::Aggregable Layer::ListUPtr m_layers; Path::ListPtr m_stack; - Path::ListPtr m_selectedPaths; void initPathsFromLayers(); + bool pathSelectionEmpty() const; public: enum class MoveDirection @@ -92,7 +92,7 @@ class Task : public QObject, public common::Aggregable Q_SIGNALS: void pathSelectedChanged(Path &path, bool selected); - void selectionChanged(int size); + void selectionChanged(bool empty); }; template diff --git a/src/view/task/path.cpp b/src/view/task/path.cpp index d668ed7..440a030 100644 --- a/src/view/task/path.cpp +++ b/src/view/task/path.cpp @@ -28,9 +28,12 @@ Path::Path(model::Application &app) setupUi(this); } -void Path::selectionChanged(int size) +void Path::selectionChanged(bool empty) { - if (size > 0) { + if (empty) { + hide(); + } + else { show(); updateFieldValue(planeFeedRate, m_groupSettings->planeFeedRate()); @@ -38,9 +41,6 @@ void Path::selectionChanged(int size) updateFieldValue(intensity, m_groupSettings->intensity()); updateFieldValue(Ui::Path::depth, m_groupSettings->depth()); } - else { - hide(); - } } } diff --git a/src/view/task/path.h b/src/view/task/path.h index c4896a5..ddc7d27 100644 --- a/src/view/task/path.h +++ b/src/view/task/path.h @@ -16,7 +16,7 @@ class Path : public model::DocumentModelObserver, private Ui::Path private: std::unique_ptr m_groupSettings; - void selectionChanged(int size); + void selectionChanged(bool empty); template void connectOnFieldChanged(Field *field, std::function &&func) diff --git a/test/task.cpp b/test/task.cpp index 67a6552..f3750f0 100644 --- a/test/task.cpp +++ b/test/task.cpp @@ -19,7 +19,9 @@ TEST(TaskTest, ShouldEmitSignalsWhenOnePathSelected) QSignalSpy spy(&task, &model::Task::selectionChanged); - task.pathAt(0).setSelected(true); + model::Path &firstPath = task.pathAt(0); + firstPath.setSelected(true); ASSERT_EQ(spy.count(), 1); + EXPECT_FALSE(spy.takeFirst().at(0).toBool()); } From 086adb9d04dc740be95e2c9fee25d23225ab6d93 Mon Sep 17 00:00:00 2001 From: tristan Date: Sat, 22 Oct 2022 22:57:57 +0200 Subject: [PATCH 07/32] feat: add renderer mecanism --- CMakeLists.txt | 1 + src/exporter/CMakeLists.txt | 1 + src/exporter/gcode/CMakeLists.txt | 2 - src/exporter/gcode/exporter.cpp | 139 +---------------------- src/exporter/gcode/exporter.h | 6 - src/exporter/gcode/pathpostprocessor.cpp | 41 ------- src/exporter/gcode/pathpostprocessor.h | 35 ------ src/exporter/gcode/postprocessor.cpp | 34 +++++- src/exporter/gcode/postprocessor.h | 10 +- src/exporter/renderer/CMakeLists.txt | 11 ++ src/exporter/renderer/passesiterator.cpp | 40 +++++++ src/exporter/renderer/passesiterator.h | 25 ++++ src/exporter/renderer/renderer.cpp | 1 + src/exporter/renderer/renderer.h | 118 +++++++++++++++++++ src/model/application.h | 2 +- src/model/simulation.h | 20 ++++ 16 files changed, 260 insertions(+), 226 deletions(-) delete mode 100644 src/exporter/gcode/pathpostprocessor.cpp delete mode 100644 src/exporter/gcode/pathpostprocessor.h create mode 100644 src/exporter/renderer/CMakeLists.txt create mode 100644 src/exporter/renderer/passesiterator.cpp create mode 100644 src/exporter/renderer/passesiterator.h create mode 100644 src/exporter/renderer/renderer.cpp create mode 100644 src/exporter/renderer/renderer.h create mode 100644 src/model/simulation.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d4a8d3..9b45223 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ set(LINK_LIBRARIES importer-dxfplot exporter-gcode exporter-dxfplot + exporter-renderer geometry geometry-filter libdxfrw diff --git a/src/exporter/CMakeLists.txt b/src/exporter/CMakeLists.txt index 3886037..8f66adf 100644 --- a/src/exporter/CMakeLists.txt +++ b/src/exporter/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(dxfplot) add_subdirectory(gcode) +add_subdirectory(renderer) diff --git a/src/exporter/gcode/CMakeLists.txt b/src/exporter/gcode/CMakeLists.txt index bbcee21..343d2ed 100644 --- a/src/exporter/gcode/CMakeLists.txt +++ b/src/exporter/gcode/CMakeLists.txt @@ -1,11 +1,9 @@ set(SRC exporter.cpp postprocessor.cpp - pathpostprocessor.cpp exporter.h postprocessor.h - pathpostprocessor.h ) add_library(exporter-gcode ${SRC}) diff --git a/src/exporter/gcode/exporter.cpp b/src/exporter/gcode/exporter.cpp index 97e10bf..7d4e121 100644 --- a/src/exporter/gcode/exporter.cpp +++ b/src/exporter/gcode/exporter.cpp @@ -1,141 +1,10 @@ #include -#include +#include +#include namespace exporter::gcode { -void Exporter::convertToGCode(const model::Task &task, std::ostream &output) const -{ - PostProcessor processor(m_tool, m_profile.gcode(), output); - - // Retract tool before work piece - processor.retractDepth(); - - task.forEachPathInStack([this, &output](const model::Path &path){ - if (path.globallyVisible()) { - convertToGCode(path, output); - } - }); - - // Back to home - processor.fastPlaneMove(QVector2D(0.0f, 0.0f)); -} - -void Exporter::convertToGCode(const model::Path &path, std::ostream &output) const -{ - const model::PathSettings &settings = path.settings(); - const geometry::CuttingDirection cuttingDirection = path.cuttingDirection() | m_profile.cut().direction(); - PathPostProcessor processor(settings, m_tool, m_profile.gcode(), output); - - const geometry::Polyline::List polylines = path.finalPolylines(); - - // Depth to be cut - const float depth = settings.depth(); - - for (const geometry::Polyline &polyline : polylines) { - convertToGCode(processor, polyline, depth, cuttingDirection); - } -} - -// Return next polyline to convert -class PassesIterator -{ -private: - bool m_odd = true; - const bool m_closed; - const bool m_cuttingBackward; - const geometry::Polyline& m_polyline; - const geometry::Polyline m_polylineInverse; - - bool needPolylineInverse() const - { - return (!m_closed || m_cuttingBackward); - } - - const geometry::Polyline &polylineForward() const - { - return m_cuttingBackward ? m_polylineInverse : m_polyline; - } - - const geometry::Polyline &polylineBackward() const - { - return m_cuttingBackward ? m_polyline : m_polylineInverse; - } - -public: - explicit PassesIterator(const geometry::Polyline &polyline, geometry::CuttingDirection direction) - :m_closed(polyline.isClosed()), - m_cuttingBackward(direction == geometry::CuttingDirection::BACKWARD), - m_polyline(polyline), - m_polylineInverse(needPolylineInverse() ? m_polyline.inverse() : geometry::Polyline()) - { - } - - const geometry::Polyline &operator*() const - { - if (m_closed || m_odd) { - return polylineForward(); - } - return polylineBackward(); - } - - PassesIterator &operator++() - { - m_odd = !m_odd; - - return *this; - } -}; - -void Exporter::convertToGCode(PathPostProcessor &processor, const geometry::Polyline &polyline, float maxDepth, geometry::CuttingDirection cuttingDirection) const -{ - const float depthPerCut = m_tool.general().depthPerCut(); - - PassesIterator iterator(polyline, cuttingDirection); - - // Move to polyline beginning - processor.fastPlaneMove((*iterator).start()); - processor.preCut(); - - for (float depth = 0.0f; depth < maxDepth + depthPerCut; depth += depthPerCut, ++iterator) { - const float boundDepth = std::fminf(depth, maxDepth); - processor.depthLinearMove(-boundDepth); - - convertToGCode(processor, *iterator); - } - - // Retract tool for further operations - processor.retractDepth(); - processor.postCut(); -} - -void Exporter::convertToGCode(PathPostProcessor &processor, const geometry::Polyline &polyline) const -{ - polyline.forEachBulge([this, &processor](const geometry::Bulge &bulge){ convertToGCode(processor, bulge); }); -} - -void Exporter::convertToGCode(PathPostProcessor &processor, const geometry::Bulge &bulge) const -{ - if (bulge.isLine()) { - processor.planeLinearMove(bulge.end()); - } - else { - const geometry::Circle circle = bulge.toCircle(); - // Relative center to start - const QVector2D relativeCenter = circle.center() - bulge.start(); - switch (circle.orientation()) { - case geometry::Orientation::CW: - processor.cwArcMove(relativeCenter, bulge.end()); - break; - case geometry::Orientation::CCW: - processor.ccwArcMove(relativeCenter, bulge.end()); - break; - default: - break; - } - } -} - struct CommentLineStream { std::ostream &m_output; @@ -206,7 +75,9 @@ void Exporter::operator()(const model::Document &document, std::ostream &output) convertConfigNodeToComments(m_profile, output); } - convertToGCode(document.task(), output); + PostProcessor processor(m_profile.gcode(), output); + renderer::Renderer renderer(m_tool, m_profile, processor); + renderer.render(document); } diff --git a/src/exporter/gcode/exporter.h b/src/exporter/gcode/exporter.h index b1d537e..81b558b 100644 --- a/src/exporter/gcode/exporter.h +++ b/src/exporter/gcode/exporter.h @@ -25,12 +25,6 @@ class Exporter const config::Profiles::Profile &m_profile; const Options m_options; - void convertToGCode(const model::Task &task, std::ostream &output) const; - void convertToGCode(const model::Path &path, std::ostream &output) const; - void convertToGCode(PathPostProcessor &processor, const geometry::Polyline &polyline) const; - void convertToGCode(PathPostProcessor &processor, const geometry::Polyline &polyline, float maxDepth, geometry::CuttingDirection cuttingDirection) const; - void convertToGCode(PathPostProcessor &processor, const geometry::Bulge &bulge) const; - public: explicit Exporter(const config::Tools::Tool& tool, const config::Profiles::Profile& profile, Options options = None); ~Exporter() = default; diff --git a/src/exporter/gcode/pathpostprocessor.cpp b/src/exporter/gcode/pathpostprocessor.cpp deleted file mode 100644 index 5859217..0000000 --- a/src/exporter/gcode/pathpostprocessor.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include - -using namespace fmt::literals; - -namespace exporter::gcode -{ - -PathPostProcessor::PathPostProcessor(const model::PathSettings &settings, const config::Tools::Tool& tool, const config::Profiles::Profile::Gcode& gcode, std::ostream &stream) - :PostProcessor(tool, gcode, stream), - m_settings(settings) -{ -} - -void PathPostProcessor::preCut() -{ - printWithSettings(m_gcode.preCut()); -} - -void PathPostProcessor::planeLinearMove(const QVector2D &to) -{ - printWithSettings(m_gcode.planeLinearMove(), "X"_a=to.x(), "Y"_a=to.y(), "F"_a=m_settings.planeFeedRate()); -} - -void PathPostProcessor::depthLinearMove(float depth) -{ - printWithSettings(m_gcode.depthLinearMove(), "Z"_a=depth, "F"_a=m_settings.depthFeedRate()); -} - -void PathPostProcessor::cwArcMove(const QVector2D &relativeCenter, const QVector2D &to) -{ - printWithSettings(m_gcode.cwArcMove(), "X"_a=to.x(), "Y"_a=to.y(), - "I"_a=relativeCenter.x(), "J"_a=relativeCenter.y(), "F"_a=m_settings.planeFeedRate()); -} - -void PathPostProcessor::ccwArcMove(const QVector2D &relativeCenter, const QVector2D &to) -{ - printWithSettings(m_gcode.ccwArcMove(), "X"_a=to.x(), "Y"_a=to.y(), - "I"_a=relativeCenter.x(), "J"_a=relativeCenter.y(), "F"_a=m_settings.planeFeedRate()); -} - -} diff --git a/src/exporter/gcode/pathpostprocessor.h b/src/exporter/gcode/pathpostprocessor.h deleted file mode 100644 index edb3baa..0000000 --- a/src/exporter/gcode/pathpostprocessor.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include - -using namespace fmt::literals; - -namespace exporter::gcode -{ - -/** @brief Augmented post processor including path settings variables such as feed rate and intensity - */ -class PathPostProcessor : public PostProcessor -{ -private: - const model::PathSettings &m_settings; - - template - void printWithSettings(const std::string &format, Args&& ...args) - { - print(format, std::forward(args)..., - "S"_a=m_settings.intensity()); - } - -public: - explicit PathPostProcessor(const model::PathSettings &settings, const config::Tools::Tool& tool, const config::Profiles::Profile::Gcode& gcode, std::ostream &stream); - - void preCut(); - void planeLinearMove(const QVector2D &to); - void depthLinearMove(float depth); - void cwArcMove(const QVector2D &relativeCenter, const QVector2D &to); - void ccwArcMove(const QVector2D &relativeCenter, const QVector2D &to); -}; - -} - diff --git a/src/exporter/gcode/postprocessor.cpp b/src/exporter/gcode/postprocessor.cpp index 70e7c02..f98d5eb 100644 --- a/src/exporter/gcode/postprocessor.cpp +++ b/src/exporter/gcode/postprocessor.cpp @@ -5,26 +5,52 @@ using namespace fmt::literals; namespace exporter::gcode { -PostProcessor::PostProcessor(const config::Tools::Tool& tool, const config::Profiles::Profile::Gcode& gcode, std::ostream &stream) +PostProcessor::PostProcessor(const config::Profiles::Profile::Gcode& gcode, std::ostream &stream) :m_stream(stream), - m_tool(tool), m_gcode(gcode) { } +void PostProcessor::preCut(float intensity) +{ + print(m_gcode.preCut(), "S"_a=intensity); +} + void PostProcessor::postCut() { print(m_gcode.postCut()); } +void PostProcessor::planeLinearMove(const QVector2D &to, float feedRate, float intensity) +{ + print(m_gcode.planeLinearMove(), "X"_a=to.x(), "Y"_a=to.y(), "F"_a=feedRate, "S"_a=intensity); +} + +void PostProcessor::depthLinearMove(float depth, float feedRate, float intensity) +{ + print(m_gcode.depthLinearMove(), "Z"_a=depth, "F"_a=feedRate, "S"_a=intensity); +} + +void PostProcessor::cwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate, float intensity) +{ + print(m_gcode.cwArcMove(), "X"_a=to.x(), "Y"_a=to.y(), + "I"_a=relativeCenter.x(), "J"_a=relativeCenter.y(), "F"_a=feedRate, "S"_a=intensity); +} + +void PostProcessor::ccwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate, float intensity) +{ + print(m_gcode.ccwArcMove(), "X"_a=to.x(), "Y"_a=to.y(), + "I"_a=relativeCenter.x(), "J"_a=relativeCenter.y(), "F"_a=feedRate, "S"_a=intensity); +} + void PostProcessor::fastPlaneMove(const QVector2D &to) { print(m_gcode.planeFastMove(), "X"_a=to.x(), "Y"_a=to.y()); } -void PostProcessor::retractDepth() +void PostProcessor::retractDepth(float depth) { - print(m_gcode.depthFastMove(), "Z"_a=m_tool.general().retractDepth()); + print(m_gcode.depthFastMove(), "Z"_a=depth); } } diff --git a/src/exporter/gcode/postprocessor.h b/src/exporter/gcode/postprocessor.h index 5ce9680..e3f9e4f 100644 --- a/src/exporter/gcode/postprocessor.h +++ b/src/exporter/gcode/postprocessor.h @@ -16,7 +16,6 @@ class PostProcessor std::ostream &m_stream; protected: - const config::Tools::Tool& m_tool; const config::Profiles::Profile::Gcode& m_gcode; /** Print a command to stream with a format and a list of named arguments @@ -35,11 +34,16 @@ class PostProcessor } public: - explicit PostProcessor(const config::Tools::Tool& tool, const config::Profiles::Profile::Gcode& gcode, std::ostream &stream); + explicit PostProcessor(const config::Profiles::Profile::Gcode& gcode, std::ostream &stream); + void preCut(float intensity); void postCut(); + void planeLinearMove(const QVector2D &to, float feedRate, float intensity); + void depthLinearMove(float depth, float feedRate, float intensity); + void cwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate, float intensity); + void ccwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate, float intensity); void fastPlaneMove(const QVector2D &to); - void retractDepth(); + void retractDepth(float depth); }; } diff --git a/src/exporter/renderer/CMakeLists.txt b/src/exporter/renderer/CMakeLists.txt new file mode 100644 index 0000000..4faa8d5 --- /dev/null +++ b/src/exporter/renderer/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SRC + passesiterator.cpp + renderer.cpp + + passesiterator.h + renderer.h +) + +add_library(exporter-renderer ${SRC}) +add_dependencies(exporter-renderer generate-config) +add_coverage(exporter-renderer) diff --git a/src/exporter/renderer/passesiterator.cpp b/src/exporter/renderer/passesiterator.cpp new file mode 100644 index 0000000..f0ac1bb --- /dev/null +++ b/src/exporter/renderer/passesiterator.cpp @@ -0,0 +1,40 @@ +#include + +bool PassesIterator::needPolylineInverse() const +{ + return (!m_closed || m_cuttingBackward); +} + +const geometry::Polyline &PassesIterator::polylineForward() const +{ + return m_cuttingBackward ? m_polylineInverse : m_polyline; +} + +const geometry::Polyline &PassesIterator::polylineBackward() const +{ + return m_cuttingBackward ? m_polyline : m_polylineInverse; +} + +PassesIterator::PassesIterator(const geometry::Polyline &polyline, geometry::CuttingDirection direction) + :m_closed(polyline.isClosed()), + m_cuttingBackward(direction == geometry::CuttingDirection::BACKWARD), + m_polyline(polyline), + m_polylineInverse(needPolylineInverse() ? m_polyline.inverse() : geometry::Polyline()) +{ +} + +const geometry::Polyline &PassesIterator::operator*() const +{ + if (m_closed || m_odd) { + return polylineForward(); + } + return polylineBackward(); +} + +PassesIterator &PassesIterator::operator++() +{ + m_odd = !m_odd; + + return *this; +} + diff --git a/src/exporter/renderer/passesiterator.h b/src/exporter/renderer/passesiterator.h new file mode 100644 index 0000000..7374106 --- /dev/null +++ b/src/exporter/renderer/passesiterator.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +// Return next polyline to convert +class PassesIterator +{ +private: + bool m_odd = true; + const bool m_closed; + const bool m_cuttingBackward; + const geometry::Polyline& m_polyline; + const geometry::Polyline m_polylineInverse; + + bool needPolylineInverse() const; + const geometry::Polyline &polylineForward() const; + const geometry::Polyline &polylineBackward() const; + +public: + explicit PassesIterator(const geometry::Polyline &polyline, geometry::CuttingDirection direction); + + const geometry::Polyline &operator*() const; + PassesIterator &operator++(); +}; + diff --git a/src/exporter/renderer/renderer.cpp b/src/exporter/renderer/renderer.cpp new file mode 100644 index 0000000..b766102 --- /dev/null +++ b/src/exporter/renderer/renderer.cpp @@ -0,0 +1 @@ +#include diff --git a/src/exporter/renderer/renderer.h b/src/exporter/renderer/renderer.h new file mode 100644 index 0000000..329e4b7 --- /dev/null +++ b/src/exporter/renderer/renderer.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include + +namespace exporter::renderer +{ + +template +class Renderer +{ +private: + const config::Tools::Tool &m_tool; + const config::Profiles::Profile &m_profile; + const float m_depthPerCut; + const float m_depthToRetract; + Visitor &m_visitor; + + void render(const model::Task &task) const + { + // Retract tool before work piece + m_visitor.retractDepth(m_depthToRetract); + + task.forEachPathInStack([this](const model::Path &path){ + if (path.globallyVisible()) { + render(path); + } + }); + + // Back to home + m_visitor.fastPlaneMove(QVector2D(0.0f, 0.0f)); + } + + void render(const model::Path &path) const + { + const model::PathSettings &settings = path.settings(); + const geometry::CuttingDirection cuttingDirection = path.cuttingDirection() | m_profile.cut().direction(); + + const geometry::Polyline::List polylines = path.finalPolylines(); + + // Depth to be cut + + for (const geometry::Polyline &polyline : polylines) { + render(polyline, settings, cuttingDirection); + } + } + + void render(const geometry::Polyline &polyline, const model::PathSettings &settings, geometry::CuttingDirection cuttingDirection) const + { + const float maxDepth = settings.depth(); + const float intensity = settings.intensity(); + const float depthFeedRate = settings.depthFeedRate(); + + PassesIterator iterator(polyline, cuttingDirection); + + // Move to polyline beginning + m_visitor.fastPlaneMove((*iterator).start()); + m_visitor.preCut(intensity); + + for (float depth = 0.0f; depth < maxDepth + m_depthPerCut; depth += m_depthPerCut, ++iterator) { + const float boundDepth = std::fminf(depth, maxDepth); + m_visitor.depthLinearMove(-boundDepth, depthFeedRate, intensity); + + render(*iterator, settings); + } + + // Retract tool for further operations + m_visitor.retractDepth(m_depthToRetract); + m_visitor.postCut(); + } + + void render(const geometry::Polyline &polyline, const model::PathSettings &settings) const + { + polyline.forEachBulge([this, &settings](const geometry::Bulge &bulge){ render(bulge, settings); }); + } + + void render(const geometry::Bulge &bulge, const model::PathSettings &settings) const + { + const float intensity = settings.intensity(); + const float planeFeedRate = settings.planeFeedRate(); + + if (bulge.isLine()) { + m_visitor.planeLinearMove(bulge.end(), planeFeedRate, intensity); + } + else { + const geometry::Circle circle = bulge.toCircle(); + // Relative center to start + const QVector2D relativeCenter = circle.center() - bulge.start(); + switch (circle.orientation()) { + case geometry::Orientation::CW: + m_visitor.cwArcMove(relativeCenter, bulge.end(), planeFeedRate, intensity); + break; + case geometry::Orientation::CCW: + m_visitor.ccwArcMove(relativeCenter, bulge.end(), planeFeedRate, intensity); + break; + default: + break; + } + } + } + +public: + explicit Renderer(const config::Tools::Tool& tool, const config::Profiles::Profile& profile, Visitor& visitor) + :m_tool(tool), + m_profile(profile), + m_depthPerCut(m_tool.general().depthPerCut()), + m_depthToRetract(m_tool.general().retractDepth()), + m_visitor(visitor) + { + } + + void render(const model::Document &document) + { + render(document.task()); + } +}; + +} diff --git a/src/model/application.h b/src/model/application.h index 76bad08..c6ca88a 100644 --- a/src/model/application.h +++ b/src/model/application.h @@ -49,7 +49,7 @@ class Application : public QObject Task::UPtr createTaskFromDxfImporter(const importer::dxf::Importer& importer); template - bool saveToFile(Exporter &exporter, const QString &fileName) + bool saveToFile(Exporter &&exporter, const QString &fileName) { qInfo() << "Saving to " << fileName; std::ofstream output(fileName.toStdString()); diff --git a/src/model/simulation.h b/src/model/simulation.h new file mode 100644 index 0000000..23aa7ef --- /dev/null +++ b/src/model/simulation.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace model +{ + +class Document; + +class Simulation +{ +private: + +public: + explicit Simulation(Document &document); + + QVector3D position(float time); +}; + +} From 68fdf87ba6603ac858b91e5b8f30281db34bba80 Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 13 Nov 2022 12:46:11 +0100 Subject: [PATCH 08/32] temp --- README.md | 10 ++-- src/exporter/gcode/postprocessor.cpp | 81 +++++++++++++++++++++++++--- src/exporter/gcode/postprocessor.h | 23 +++++--- src/exporter/renderer/renderer.h | 46 +++------------- src/geometry/CMakeLists.txt | 2 + src/geometry/arc.h | 2 +- src/geometry/bulge.cpp | 5 ++ src/geometry/bulge.h | 2 + src/geometry/circle.h | 2 +- src/geometry/line.cpp | 22 ++++++++ src/geometry/line.h | 21 ++++++++ src/model/CMakeLists.txt | 2 + src/model/simulation.cpp | 44 +++++++++++++++ src/model/simulation.h | 48 ++++++++++++++++- 14 files changed, 247 insertions(+), 63 deletions(-) create mode 100644 src/geometry/line.cpp create mode 100644 src/geometry/line.h create mode 100644 src/model/simulation.cpp diff --git a/README.md b/README.md index e54490a..0eeb199 100644 --- a/README.md +++ b/README.md @@ -100,14 +100,14 @@ Simple set of GCode command is used per tool: | Description | Default Command | Available Variables | | - | - | - | -| Pre Cut | M4 S \{S:.3f} | S F | +| Pre Cut | M4 S \{S:.3f} | S | | Post Cut | M5 | | | Plane Fast Move | G0 X \{X:.3f} Y \{Y:.3f} | X Y | -| Plane Linear Move | G1 X \{X:.3f} Y \{Y:.3f} F \{F:.3f} | S F X Y | +| Plane Linear Move | G1 X \{X:.3f} Y \{Y:.3f} F \{F:.3f} | F X Y | | Depth Fast Move | G0 Z \{Z:.3f} | Z | -| Depth Linear Move | G1 Z \{Z:.3f} | S F Z | -| CW Arc Move | G2 X \{X:.3f} Y \{Y:.3f} I \{I:.3f} J \{J:.3f} F \{F:.3f} | S F X Y I J | -| CCW Arc Move | G3 X \{X:.3f} Y \{Y:.3f} I \{I:.3f} J \{J:.3f} F \{F:.3f} | S F X Y I J | +| Depth Linear Move | G1 Z \{Z:.3f} | F Z | +| CW Arc Move | G2 X \{X:.3f} Y \{Y:.3f} I \{I:.3f} J \{J:.3f} F \{F:.3f} | F X Y I J | +| CCW Arc Move | G3 X \{X:.3f} Y \{Y:.3f} I \{I:.3f} J \{J:.3f} F \{F:.3f} | F X Y I J | They can be customized from tool Settings panel `Configuration->Settings->Tools->ToolName->Gcode` or from dxfplotter/config.yml file in your applications configuration folder. diff --git a/src/exporter/gcode/postprocessor.cpp b/src/exporter/gcode/postprocessor.cpp index f98d5eb..c89e063 100644 --- a/src/exporter/gcode/postprocessor.cpp +++ b/src/exporter/gcode/postprocessor.cpp @@ -21,26 +21,26 @@ void PostProcessor::postCut() print(m_gcode.postCut()); } -void PostProcessor::planeLinearMove(const QVector2D &to, float feedRate, float intensity) +void PostProcessor::planeLinearMove(const QVector2D &to, float feedRate) { - print(m_gcode.planeLinearMove(), "X"_a=to.x(), "Y"_a=to.y(), "F"_a=feedRate, "S"_a=intensity); + print(m_gcode.planeLinearMove(), "X"_a=to.x(), "Y"_a=to.y(), "F"_a=feedRate); } -void PostProcessor::depthLinearMove(float depth, float feedRate, float intensity) +void PostProcessor::depthLinearMove(float depth, float feedRate) { - print(m_gcode.depthLinearMove(), "Z"_a=depth, "F"_a=feedRate, "S"_a=intensity); + print(m_gcode.depthLinearMove(), "Z"_a=depth, "F"_a=feedRate); } -void PostProcessor::cwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate, float intensity) +void PostProcessor::cwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate) { print(m_gcode.cwArcMove(), "X"_a=to.x(), "Y"_a=to.y(), - "I"_a=relativeCenter.x(), "J"_a=relativeCenter.y(), "F"_a=feedRate, "S"_a=intensity); + "I"_a=relativeCenter.x(), "J"_a=relativeCenter.y(), "F"_a=feedRate); } -void PostProcessor::ccwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate, float intensity) +void PostProcessor::ccwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate) { print(m_gcode.ccwArcMove(), "X"_a=to.x(), "Y"_a=to.y(), - "I"_a=relativeCenter.x(), "J"_a=relativeCenter.y(), "F"_a=feedRate, "S"_a=intensity); + "I"_a=relativeCenter.x(), "J"_a=relativeCenter.y(), "F"_a=feedRate); } void PostProcessor::fastPlaneMove(const QVector2D &to) @@ -53,4 +53,69 @@ void PostProcessor::retractDepth(float depth) print(m_gcode.depthFastMove(), "Z"_a=depth); } +void PostProcessor::processBulge(const geometry::Bulge &bulge, float planeFeedRate) +{ + if (bulge.isArc()) { + processArc(bulge, planeFeedRate); + } + else { + processLine(bulge, planeFeedRate); + } +} + +void PostProcessor::processLine(const geometry::Bulge &bulge, float planeFeedRate) +{ + planeLinearMove(bulge.end(), planeFeedRate); +} + +void PostProcessor::processArc(const geometry::Bulge &bulge, float planeFeedRate) +{ + const geometry::Circle circle = bulge.toCircle(); + // Relative center to start + const QVector2D relativeCenter = circle.center() - bulge.start(); + switch (circle.orientation()) { + case geometry::Orientation::CW: + cwArcMove(relativeCenter, bulge.end(), planeFeedRate); + break; + case geometry::Orientation::CCW: + ccwArcMove(relativeCenter, bulge.end(), planeFeedRate); + break; + default: + break; + } +} + +void PostProcessor::start(float depth) +{ + retractDepth(depth); +} + +void PostProcessor::end(const QVector2D& to) +{ + fastPlaneMove(to); +} + +void PostProcessor::startOperation(const QVector2D& to, float intensity) +{ + // Move to polyline beginning and start tooling + fastPlaneMove(to); + preCut(intensity); +} + +void PostProcessor::endOperation(float depth) +{ + // Retract tool for further operations + retractDepth(depth); + postCut(); +} + +void PostProcessor::processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, float depthFeedRate) +{ + depthLinearMove(depth, depthFeedRate); + + polyline.forEachBulge([this, planeFeedRate](const geometry::Bulge &bulge){ + processBulge(bulge, planeFeedRate); + }); +} + } diff --git a/src/exporter/gcode/postprocessor.h b/src/exporter/gcode/postprocessor.h index e3f9e4f..c415b32 100644 --- a/src/exporter/gcode/postprocessor.h +++ b/src/exporter/gcode/postprocessor.h @@ -33,17 +33,26 @@ class PostProcessor } } -public: - explicit PostProcessor(const config::Profiles::Profile::Gcode& gcode, std::ostream &stream); - void preCut(float intensity); void postCut(); - void planeLinearMove(const QVector2D &to, float feedRate, float intensity); - void depthLinearMove(float depth, float feedRate, float intensity); - void cwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate, float intensity); - void ccwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate, float intensity); + void planeLinearMove(const QVector2D &to, float feedRate); + void depthLinearMove(float depth, float feedRate); + void cwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate); + void ccwArcMove(const QVector2D &relativeCenter, const QVector2D &to, float feedRate); void fastPlaneMove(const QVector2D &to); void retractDepth(float depth); + void processBulge(const geometry::Bulge &bulge, float planeFeedRate); + void processLine(const geometry::Bulge &bulge, float planeFeedRate); + void processArc(const geometry::Bulge &bulge, float planeFeedRate); + +public: + explicit PostProcessor(const config::Profiles::Profile::Gcode& gcode, std::ostream &stream); + + void start(float depth); + void end(const QVector2D& to); + void startOperation(const QVector2D& to, float intensity); + void endOperation(float depth); + void processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, float depthFeedRate); }; } diff --git a/src/exporter/renderer/renderer.h b/src/exporter/renderer/renderer.h index 329e4b7..b02a5f5 100644 --- a/src/exporter/renderer/renderer.h +++ b/src/exporter/renderer/renderer.h @@ -19,7 +19,7 @@ class Renderer void render(const model::Task &task) const { // Retract tool before work piece - m_visitor.retractDepth(m_depthToRetract); + m_visitor.start(m_depthToRetract); task.forEachPathInStack([this](const model::Path &path){ if (path.globallyVisible()) { @@ -28,7 +28,7 @@ class Renderer }); // Back to home - m_visitor.fastPlaneMove(QVector2D(0.0f, 0.0f)); + m_visitor.end(QVector2D(0.0f, 0.0f)); } void render(const model::Path &path) const @@ -49,54 +49,20 @@ class Renderer { const float maxDepth = settings.depth(); const float intensity = settings.intensity(); + const float planeFeedRate = settings.planeFeedRate(); const float depthFeedRate = settings.depthFeedRate(); PassesIterator iterator(polyline, cuttingDirection); - // Move to polyline beginning - m_visitor.fastPlaneMove((*iterator).start()); - m_visitor.preCut(intensity); + m_visitor.startOperation((*iterator).start(), intensity); for (float depth = 0.0f; depth < maxDepth + m_depthPerCut; depth += m_depthPerCut, ++iterator) { const float boundDepth = std::fminf(depth, maxDepth); - m_visitor.depthLinearMove(-boundDepth, depthFeedRate, intensity); - render(*iterator, settings); + m_visitor.processPathAtDepth(*iterator, -boundDepth, planeFeedRate, depthFeedRate); } - // Retract tool for further operations - m_visitor.retractDepth(m_depthToRetract); - m_visitor.postCut(); - } - - void render(const geometry::Polyline &polyline, const model::PathSettings &settings) const - { - polyline.forEachBulge([this, &settings](const geometry::Bulge &bulge){ render(bulge, settings); }); - } - - void render(const geometry::Bulge &bulge, const model::PathSettings &settings) const - { - const float intensity = settings.intensity(); - const float planeFeedRate = settings.planeFeedRate(); - - if (bulge.isLine()) { - m_visitor.planeLinearMove(bulge.end(), planeFeedRate, intensity); - } - else { - const geometry::Circle circle = bulge.toCircle(); - // Relative center to start - const QVector2D relativeCenter = circle.center() - bulge.start(); - switch (circle.orientation()) { - case geometry::Orientation::CW: - m_visitor.cwArcMove(relativeCenter, bulge.end(), planeFeedRate, intensity); - break; - case geometry::Orientation::CCW: - m_visitor.ccwArcMove(relativeCenter, bulge.end(), planeFeedRate, intensity); - break; - default: - break; - } - } + m_visitor.endOperation(m_depthToRetract); } public: diff --git a/src/geometry/CMakeLists.txt b/src/geometry/CMakeLists.txt index efcdcb3..bafe5d0 100644 --- a/src/geometry/CMakeLists.txt +++ b/src/geometry/CMakeLists.txt @@ -7,6 +7,7 @@ set(SRC bulge.cpp circle.cpp cubicspline.cpp + line.cpp pocketer.cpp polyline.cpp quadraticspline.cpp @@ -19,6 +20,7 @@ set(SRC bulge.h circle.h cubicspline.h + line.h polyline.h quadraticspline.h spline.h diff --git a/src/geometry/arc.h b/src/geometry/arc.h index 4497872..fa68bae 100644 --- a/src/geometry/arc.h +++ b/src/geometry/arc.h @@ -27,4 +27,4 @@ class Arc : public Circle float spanAngle() const; }; -}; +} diff --git a/src/geometry/bulge.cpp b/src/geometry/bulge.cpp index 33da315..ad8cf05 100644 --- a/src/geometry/bulge.cpp +++ b/src/geometry/bulge.cpp @@ -218,6 +218,11 @@ Arc Bulge::toArc() const return Arc(circle, m_start, m_end, startAngle, endAngle); } +Line Bulge::toLine() const +{ + return Line(m_start, m_end); +} + inline QVector2D mapVector2D(const QVector2D &vect, const QTransform &matrix) { const QPointF point = vect.toPointF(); diff --git a/src/geometry/bulge.h b/src/geometry/bulge.h index 310d01c..991fc33 100644 --- a/src/geometry/bulge.h +++ b/src/geometry/bulge.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -70,6 +71,7 @@ class Bulge : public common::Aggregable Circle toCircle() const; Arc toArc() const; + Line toLine() const; void transform(const QTransform &matrix); diff --git a/src/geometry/circle.h b/src/geometry/circle.h index 4b160ec..2240566 100644 --- a/src/geometry/circle.h +++ b/src/geometry/circle.h @@ -20,4 +20,4 @@ class Circle Orientation orientation() const; }; -}; +} diff --git a/src/geometry/line.cpp b/src/geometry/line.cpp new file mode 100644 index 0000000..b4d258b --- /dev/null +++ b/src/geometry/line.cpp @@ -0,0 +1,22 @@ +#include + +namespace geometry +{ + +Line::Line(const QVector2D &start, const QVector2D &end) + :m_start(start), + m_end(end) +{ +} + +const QVector2D &Line::start() const +{ + return m_start; +} + +const QVector2D &Line::end() const +{ + return m_end; +} + +} diff --git a/src/geometry/line.h b/src/geometry/line.h new file mode 100644 index 0000000..d48262e --- /dev/null +++ b/src/geometry/line.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace geometry +{ + +class Line +{ +private: + QVector2D m_start; + QVector2D m_end; + +public: + explicit Line(const QVector2D &start, const QVector2D& end); + + const QVector2D &start() const; + const QVector2D &end() const; +}; + +} diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt index f7ee52e..340dc4a 100644 --- a/src/model/CMakeLists.txt +++ b/src/model/CMakeLists.txt @@ -7,6 +7,7 @@ set(SRC pathsettings.cpp pathgroupsettings.cpp renderable.cpp + simulation.cpp task.cpp application.h @@ -17,6 +18,7 @@ set(SRC pathsettings.h pathgroupsettings.h renderable.h + simulation.h task.h documentmodelobserver.h ) diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp new file mode 100644 index 0000000..08bb6bb --- /dev/null +++ b/src/model/simulation.cpp @@ -0,0 +1,44 @@ +#include +#include + +namespace model +{ + +class Simulation::RenderVisitor +{ +private: + EntityList m_entities; + +public: + void start(float depth) + { + } + + void end(const QVector2D& to) + { + } + + void startOperation(const QVector2D& to, float intensity) + { + } + + void endOperation(float depth) + { + } + + void processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, float depthFeedRate) + { + // TODO depth motion ? + polyline.forEachBulge([this, depth, planeFeedRate, depthFeedRate](const geometry::Bulge& bulge){ + convertBulgeToMotion() + }); + } +}; + +Simulation::Simulation(Document &document) +{ + +} + +} + diff --git a/src/model/simulation.h b/src/model/simulation.h index 23aa7ef..c5d6a07 100644 --- a/src/model/simulation.h +++ b/src/model/simulation.h @@ -1,6 +1,10 @@ #pragma once -#include +#include +#include + +#include +#include namespace model { @@ -10,11 +14,53 @@ class Document; class Simulation { private: + class RenderVisitor; + + class Traversable + { + protected: + float m_startTime; + float m_endTime; + float m_depth; + + public: + explicit Traversable(float startTime, float endTime, float depth); + }; + + class LineMotion : Traversable + { + private: + geometry::Line m_line; + + public: + explicit LineMotion(float startTime, float feedRate, const geometry::Line &line); + + QVector3D pointAtTime(float time) const; + }; + + class ArcMotion : Traversable + { + private: + geometry::Arc m_arc; + + public: + explicit ArcMotion(float startTime, float feedRate, const geometry::Arc &arc); + + QVector3D pointAtTime(float time) const; + }; + + using Motion = std::variant; + + using MotionList = std::vector; + MotionList m_motions; + + const Motion &findMotionAtTime(float time) const; public: explicit Simulation(Document &document); QVector3D position(float time); + float duration() const; }; } From ede47897f8603d8eb255a64d41ad584a924b1c6b Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 4 Dec 2022 22:51:43 +0100 Subject: [PATCH 09/32] chore: create motion for simulation data --- src/exporter/gcode/postprocessor.cpp | 10 ++-- src/exporter/gcode/postprocessor.h | 6 +-- src/exporter/renderer/renderer.h | 6 ++- src/geometry/line.cpp | 5 ++ src/geometry/line.h | 2 + src/model/simulation.cpp | 71 +++++++++++++++++++++++++--- src/model/simulation.h | 28 +++++++---- 7 files changed, 102 insertions(+), 26 deletions(-) diff --git a/src/exporter/gcode/postprocessor.cpp b/src/exporter/gcode/postprocessor.cpp index c89e063..2623267 100644 --- a/src/exporter/gcode/postprocessor.cpp +++ b/src/exporter/gcode/postprocessor.cpp @@ -85,12 +85,12 @@ void PostProcessor::processArc(const geometry::Bulge &bulge, float planeFeedRate } } -void PostProcessor::start(float depth) +void PostProcessor::start(const QVector2D& from, float safetyDepth) { - retractDepth(depth); + retractDepth(safetyDepth); } -void PostProcessor::end(const QVector2D& to) +void PostProcessor::end(const QVector2D& to, float safetyDepth) { fastPlaneMove(to); } @@ -102,10 +102,10 @@ void PostProcessor::startOperation(const QVector2D& to, float intensity) preCut(intensity); } -void PostProcessor::endOperation(float depth) +void PostProcessor::endOperation(float safetyDepth) { // Retract tool for further operations - retractDepth(depth); + retractDepth(safetyDepth); postCut(); } diff --git a/src/exporter/gcode/postprocessor.h b/src/exporter/gcode/postprocessor.h index c415b32..9bd0da0 100644 --- a/src/exporter/gcode/postprocessor.h +++ b/src/exporter/gcode/postprocessor.h @@ -48,10 +48,10 @@ class PostProcessor public: explicit PostProcessor(const config::Profiles::Profile::Gcode& gcode, std::ostream &stream); - void start(float depth); - void end(const QVector2D& to); + void start(const QVector2D& from, float safetyDepth); + void end(const QVector2D& to, float safetyDepth); void startOperation(const QVector2D& to, float intensity); - void endOperation(float depth); + void endOperation(float safetyDepth); void processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, float depthFeedRate); }; diff --git a/src/exporter/renderer/renderer.h b/src/exporter/renderer/renderer.h index b02a5f5..228906b 100644 --- a/src/exporter/renderer/renderer.h +++ b/src/exporter/renderer/renderer.h @@ -18,8 +18,10 @@ class Renderer void render(const model::Task &task) const { + const QVector2D home(0.0f, 0.0f); + // Retract tool before work piece - m_visitor.start(m_depthToRetract); + m_visitor.start(home, m_depthToRetract); task.forEachPathInStack([this](const model::Path &path){ if (path.globallyVisible()) { @@ -28,7 +30,7 @@ class Renderer }); // Back to home - m_visitor.end(QVector2D(0.0f, 0.0f)); + m_visitor.end(home, m_depthToRetract); } void render(const model::Path &path) const diff --git a/src/geometry/line.cpp b/src/geometry/line.cpp index b4d258b..83535f4 100644 --- a/src/geometry/line.cpp +++ b/src/geometry/line.cpp @@ -19,4 +19,9 @@ const QVector2D &Line::end() const return m_end; } +bool Line::lengthNonZero() const +{ + return (m_start != m_end); +} + } diff --git a/src/geometry/line.h b/src/geometry/line.h index d48262e..868814c 100644 --- a/src/geometry/line.h +++ b/src/geometry/line.h @@ -16,6 +16,8 @@ class Line const QVector2D &start() const; const QVector2D &end() const; + + bool lengthNonZero() const; }; } diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp index 08bb6bb..9ecd163 100644 --- a/src/model/simulation.cpp +++ b/src/model/simulation.cpp @@ -7,30 +7,87 @@ namespace model class Simulation::RenderVisitor { private: - EntityList m_entities; + const float m_fastMoveFeedRate; + + MotionList m_motions; + + QVector2D m_cursorInPlane; + float m_cursorDepth; + + void initCursor(const QVector2D& to, float depth) + { + m_cursorInPlane = to; + m_cursorDepth = depth; + } + + void linearMoveCursorInDepthTo(float toDepth, float feedRate) + { + m_motions.push_back(DepthMotion(m_cursorDepth, toDepth, feedRate)); + m_cursorDepth = toDepth; + } + + void linearMoveCursorInPlaneTo(const QVector2D& to, float feedRate) + { + const geometry::Line line(m_cursorInPlane, to); + if (line.lengthNonZero()) { + m_motions.push_back(PlaneLineMotion(m_cursorDepth, feedRate, line)); + m_cursorInPlane = to; + } + } + + void moveCursorInPlaneAlong(const geometry::Line &line, float feedRate) + { + if (line.lengthNonZero()) { + m_motions.push_back(PlaneLineMotion(m_cursorDepth, feedRate, line)); + m_cursorInPlane = line.end(); + } + } + + void moveCursorInPlaneAlong(const geometry::Arc &arc, float feedRate) + { + m_motions.push_back(PlaneArcMotion(m_cursorDepth, feedRate, arc)); + m_cursorInPlane = arc.end(); + } public: - void start(float depth) + explicit RenderVisitor(float fastMoveFeedRate) + :m_fastMoveFeedRate(fastMoveFeedRate) { } - void end(const QVector2D& to) + void start(const QVector2D& from, float safetyDepth) { + initCursor(from, 0.0f); + linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate); + } + + void end(const QVector2D& to, float safetyDepth) + { + linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate); + linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate); } void startOperation(const QVector2D& to, float intensity) { + linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate); } - void endOperation(float depth) + void endOperation(float safetyDepth) { + linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate); } void processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, float depthFeedRate) { - // TODO depth motion ? - polyline.forEachBulge([this, depth, planeFeedRate, depthFeedRate](const geometry::Bulge& bulge){ - convertBulgeToMotion() + linearMoveCursorInDepthTo(depth, depthFeedRate); + + polyline.forEachBulge([this, planeFeedRate](const geometry::Bulge& bulge){ + if (bulge.isArc()) { + moveCursorInPlaneAlong(bulge.toArc(), planeFeedRate); + } + else { + moveCursorInPlaneAlong(bulge.toLine(), planeFeedRate); + } }); } }; diff --git a/src/model/simulation.h b/src/model/simulation.h index c5d6a07..bde0fbc 100644 --- a/src/model/simulation.h +++ b/src/model/simulation.h @@ -19,37 +19,47 @@ class Simulation class Traversable { protected: - float m_startTime; - float m_endTime; - float m_depth; public: - explicit Traversable(float startTime, float endTime, float depth); + explicit Traversable(); }; - class LineMotion : Traversable + class PlaneLineMotion : Traversable { private: geometry::Line m_line; + float m_depth; public: - explicit LineMotion(float startTime, float feedRate, const geometry::Line &line); + explicit PlaneLineMotion(float depth, float feedRate, const geometry::Line &line); QVector3D pointAtTime(float time) const; }; - class ArcMotion : Traversable + class PlaneArcMotion : Traversable { private: geometry::Arc m_arc; + float m_depth; + + public: + explicit PlaneArcMotion(float depth, float feedRate, const geometry::Arc &arc); + + QVector3D pointAtTime(float time) const; + }; + + class DepthMotion : Traversable + { + private: + public: - explicit ArcMotion(float startTime, float feedRate, const geometry::Arc &arc); + explicit DepthMotion(float fromDepth, float toDepth, float feedRate); QVector3D pointAtTime(float time) const; }; - using Motion = std::variant; + using Motion = std::variant; using MotionList = std::vector; MotionList m_motions; From f68d71dd95f1645919fe86e4da4a796bf6a078b4 Mon Sep 17 00:00:00 2001 From: tristan Date: Thu, 15 Dec 2022 22:00:28 +0100 Subject: [PATCH 10/32] feat: convert task to list of motions --- src/geometry/arc.cpp | 5 ++ src/geometry/arc.h | 2 + src/geometry/line.cpp | 5 ++ src/geometry/line.h | 1 + src/model/application.cpp | 6 ++ src/model/application.h | 2 + src/model/simulation.cpp | 133 ++++++++++++++++++++++++++++++++++--- src/model/simulation.h | 67 ++++++++++++++++--- src/view/mainwindow.cpp | 7 ++ src/view/mainwindow.h | 1 + template/uic/mainwindow.ui | 11 +++ 11 files changed, 220 insertions(+), 20 deletions(-) diff --git a/src/geometry/arc.cpp b/src/geometry/arc.cpp index acbf444..4a64db6 100644 --- a/src/geometry/arc.cpp +++ b/src/geometry/arc.cpp @@ -49,4 +49,9 @@ float Arc::spanAngle() const return m_spanAngle; } +float Arc::length() const +{ + return std::abs(m_spanAngle) * radius(); +} + } diff --git a/src/geometry/arc.h b/src/geometry/arc.h index fa68bae..7fdeb22 100644 --- a/src/geometry/arc.h +++ b/src/geometry/arc.h @@ -25,6 +25,8 @@ class Arc : public Circle float startAngle() const; float endAngle() const; float spanAngle() const; + + float length() const; }; } diff --git a/src/geometry/line.cpp b/src/geometry/line.cpp index 83535f4..a2b9d26 100644 --- a/src/geometry/line.cpp +++ b/src/geometry/line.cpp @@ -24,4 +24,9 @@ bool Line::lengthNonZero() const return (m_start != m_end); } +float Line::length() const +{ + return m_start.distanceToPoint(m_end); +} + } diff --git a/src/geometry/line.h b/src/geometry/line.h index 868814c..1beb0e9 100644 --- a/src/geometry/line.h +++ b/src/geometry/line.h @@ -18,6 +18,7 @@ class Line const QVector2D &end() const; bool lengthNonZero() const; + float length() const; }; } diff --git a/src/model/application.cpp b/src/model/application.cpp index 48de608..ea7a296 100644 --- a/src/model/application.cpp +++ b/src/model/application.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -357,4 +358,9 @@ void Application::showHidden() task.showHidden(); } +void Application::createSimulation() +{ + Simulation simulation(*m_openedDocument, *m_defaultToolConfig, *m_defaultProfileConfig); +} + } diff --git a/src/model/application.h b/src/model/application.h index c6ca88a..b2d8ade 100644 --- a/src/model/application.h +++ b/src/model/application.h @@ -104,6 +104,8 @@ class Application : public QObject void hideSelection(); void showHidden(); + void createSimulation(); + Q_SIGNALS: void documentChanged(Document *newDocument); void titleChanged(QString title); diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp index 9ecd163..d96cb41 100644 --- a/src/model/simulation.cpp +++ b/src/model/simulation.cpp @@ -1,9 +1,100 @@ #include #include +#include + +#include // TODO namespace model { +float entityDuration(const geometry::Line &line, float feedRate) +{ + return line.length() / feedRate; +} + +float entityDuration(float start, float end, float feedRate) +{ + return std::abs(end - start) / feedRate; +} + +float entityDuration(const geometry::Arc &arc, float feedRate) +{ + return arc.length() / feedRate; +} + +Simulation::Traversable::Traversable(float startTime, float duration) + :m_startTime(startTime), + m_duration(duration) +{ +} + +float Simulation::Traversable::startTime() const +{ + return m_startTime; +} + +float Simulation::Traversable::endTime() const +{ + return m_startTime + m_duration; +} + +float Simulation::Traversable::duration() const +{ + return m_duration; +} + +Simulation::PlaneLineMotion::PlaneLineMotion(float depth, const geometry::Line &line, float feedRate, float startTime) + :Traversable(startTime, entityDuration(line, feedRate)), + m_line(line), + m_depth(depth) +{ +} + +float Simulation::PlaneLineMotion::endDepth() const +{ + return m_depth; +} + +const QVector2D& Simulation::PlaneLineMotion::endPlanePos() const +{ + return m_line.end(); +} + +Simulation::PlaneArcMotion::PlaneArcMotion(float depth, const geometry::Arc &arc, float feedRate, float startTime) + :Traversable(startTime, entityDuration(arc, feedRate)), + m_arc(arc), + m_depth(depth) +{ +} + +float Simulation::PlaneArcMotion::endDepth() const +{ + return m_depth; +} + +const QVector2D& Simulation::PlaneArcMotion::endPlanePos() const +{ + return m_arc.end(); +} + +Simulation::DepthMotion::DepthMotion(const QVector2D &planePos, float fromDepth, float toDepth, float feedRate, float startTime) + :Traversable(startTime, entityDuration(fromDepth, toDepth, feedRate)), + m_planePos(planePos), + m_fromDepth(fromDepth), + m_toDepth(toDepth) +{ +} + +float Simulation::DepthMotion::endDepth() const +{ + return m_toDepth; +} + +const QVector2D& Simulation::DepthMotion::endPlanePos() const +{ + return m_planePos; +} + class Simulation::RenderVisitor { private: @@ -13,40 +104,46 @@ class Simulation::RenderVisitor QVector2D m_cursorInPlane; float m_cursorDepth; + float m_cursorTime; void initCursor(const QVector2D& to, float depth) { m_cursorInPlane = to; m_cursorDepth = depth; + m_cursorTime = 0.0f; + } + + void addMotion(Motion&& motion) + { + m_cursorTime = std::visit([](auto &arg){ return arg.endTime(); }, motion); + m_cursorDepth = std::visit([](auto &arg){ return arg.endDepth(); }, motion); + m_cursorInPlane = std::visit([](auto &arg){ return arg.endPlanePos(); }, motion); + m_motions.push_back(std::move(motion)); } void linearMoveCursorInDepthTo(float toDepth, float feedRate) { - m_motions.push_back(DepthMotion(m_cursorDepth, toDepth, feedRate)); - m_cursorDepth = toDepth; + addMotion(DepthMotion(m_cursorInPlane, m_cursorDepth, toDepth, feedRate, m_cursorTime)); } void linearMoveCursorInPlaneTo(const QVector2D& to, float feedRate) { const geometry::Line line(m_cursorInPlane, to); if (line.lengthNonZero()) { - m_motions.push_back(PlaneLineMotion(m_cursorDepth, feedRate, line)); - m_cursorInPlane = to; + addMotion(PlaneLineMotion(m_cursorDepth, line, feedRate, m_cursorTime)); } } void moveCursorInPlaneAlong(const geometry::Line &line, float feedRate) { if (line.lengthNonZero()) { - m_motions.push_back(PlaneLineMotion(m_cursorDepth, feedRate, line)); - m_cursorInPlane = line.end(); + addMotion(PlaneLineMotion(m_cursorDepth, line, feedRate, m_cursorTime)); } } void moveCursorInPlaneAlong(const geometry::Arc &arc, float feedRate) { - m_motions.push_back(PlaneArcMotion(m_cursorDepth, feedRate, arc)); - m_cursorInPlane = arc.end(); + addMotion(PlaneArcMotion(m_cursorDepth, arc, feedRate, m_cursorTime)); } public: @@ -90,11 +187,27 @@ class Simulation::RenderVisitor } }); } + + MotionList &&motions() + { + return std::move(m_motions); + } }; -Simulation::Simulation(Document &document) + +Simulation::MotionList Simulation::renderDocumentToMotions(const Document &document, const config::Tools::Tool& tool, const config::Profiles::Profile& profile) const +{ + RenderVisitor visitor(42); // TODO fastfeedRate config + exporter::renderer::Renderer renderer(tool, profile, visitor); + + renderer.render(document); + + return visitor.motions(); +} + +Simulation::Simulation(const Document &document, const config::Tools::Tool& tool, const config::Profiles::Profile& profile) + :m_motions(renderDocumentToMotions(document, tool, profile)) { - } } diff --git a/src/model/simulation.h b/src/model/simulation.h index bde0fbc..4520d5b 100644 --- a/src/model/simulation.h +++ b/src/model/simulation.h @@ -6,12 +6,15 @@ #include #include +#include +#include + namespace model { class Document; -class Simulation +class Simulation : public common::Aggregable { private: class RenderVisitor; @@ -19,44 +22,77 @@ class Simulation class Traversable { protected: + float m_startTime; + float m_duration; public: - explicit Traversable(); + explicit Traversable(float startTime, float duration); + + float startTime() const; + float endTime() const; + float duration() const; }; - class PlaneLineMotion : Traversable + class PlaneLineMotion : public Traversable { private: geometry::Line m_line; float m_depth; public: - explicit PlaneLineMotion(float depth, float feedRate, const geometry::Line &line); + explicit PlaneLineMotion(float depth, const geometry::Line &line, float feedRate, float startTime); QVector3D pointAtTime(float time) const; + + float endDepth() const; + const QVector2D& endPlanePos() const; }; - class PlaneArcMotion : Traversable + class PlaneArcMotion : public Traversable { private: geometry::Arc m_arc; float m_depth; public: - explicit PlaneArcMotion(float depth, float feedRate, const geometry::Arc &arc); + explicit PlaneArcMotion(float depth, const geometry::Arc &arc, float feedRate, float startTime); QVector3D pointAtTime(float time) const; + + float endDepth() const; + const QVector2D& endPlanePos() const; }; - class DepthMotion : Traversable + class DepthMotion : public Traversable { private: - + QVector2D m_planePos; + float m_fromDepth; + float m_toDepth; public: - explicit DepthMotion(float fromDepth, float toDepth, float feedRate); + explicit DepthMotion(const QVector2D &planePos, float fromDepth, float toDepth, float feedRate, float startTime); QVector3D pointAtTime(float time) const; + + float endDepth() const; + const QVector2D& endPlanePos() const; + }; + + template + struct GeometryVisitorAdapter + { + Visitor &visitor; + + void operator()(const PlaneLineMotion& motion) + { + } + void operator()(const PlaneArcMotion& motion) + { + } + void operator()(const DepthMotion& motion) + { + } }; using Motion = std::variant; @@ -66,11 +102,22 @@ class Simulation const Motion &findMotionAtTime(float time) const; + MotionList renderDocumentToMotions(const Document &document, const config::Tools::Tool& tool, const config::Profiles::Profile& profil) const; + public: - explicit Simulation(Document &document); + explicit Simulation(const Document &document, const config::Tools::Tool& tool, const config::Profiles::Profile& profile); QVector3D position(float time); float duration() const; + + template + void visitGeometry(Visitor &&visitor) + { + GeometryVisitorAdapter adapter{visitor}; + std::for_each(m_motions.begin(), m_motions.end(), [&adapter](const Motion& motion){ + std::visit(adapter, motion); + }); + } }; } diff --git a/src/view/mainwindow.cpp b/src/view/mainwindow.cpp index 8f136aa..4f9b878 100644 --- a/src/view/mainwindow.cpp +++ b/src/view/mainwindow.cpp @@ -91,6 +91,7 @@ void MainWindow::setupMenuActions() connect(actionTransformSelection, &QAction::triggered, this, &MainWindow::transformSelection); connect(actionMirrorSelection, &QAction::triggered, this, &MainWindow::mirrorSelection); connect(actionSetSelectionOrigin, &QAction::triggered, this, &MainWindow::setSelectionOrigin); + connect(actionSimulate, &QAction::triggered, this, &MainWindow::simulate); } void MainWindow::setupOpenedDocumentActions() @@ -108,6 +109,7 @@ void MainWindow::setupOpenedDocumentActions() m_openedDocumentActions.addAction(actionTransformSelection); m_openedDocumentActions.addAction(actionMirrorSelection); m_openedDocumentActions.addAction(actionSetSelectionOrigin); + m_openedDocumentActions.addAction(actionSimulate); m_openedDocumentActions.setExclusive(true); } @@ -240,4 +242,9 @@ void MainWindow::displayError(const QString &message) messageBox.critical(this, "Error", message); } +void MainWindow::simulate() +{ + m_app.createSimulation(); +} + } diff --git a/src/view/mainwindow.h b/src/view/mainwindow.h index dbf5200..67af422 100644 --- a/src/view/mainwindow.h +++ b/src/view/mainwindow.h @@ -54,6 +54,7 @@ protected Q_SLOTS: void setSelectionOrigin(); void documentChanged(model::Document *newDocument); void displayError(const QString &message); + void simulate(); }; } diff --git a/template/uic/mainwindow.ui b/template/uic/mainwindow.ui index 23f164c..72762ea 100644 --- a/template/uic/mainwindow.ui +++ b/template/uic/mainwindow.ui @@ -61,6 +61,8 @@ + + @@ -96,6 +98,7 @@ + @@ -295,6 +298,14 @@ O + + + Simulate + + + Ctrl+R + + From 24a7780da7ee42e7e5b087a1495ede3688397751 Mon Sep 17 00:00:00 2001 From: tristan Date: Thu, 15 Dec 2022 22:41:05 +0100 Subject: [PATCH 11/32] chore: move arc to lines approximation in geometry::Arc class --- src/geometry/arc.h | 30 ++++++++++++++++++++++++++++++ src/geometry/utils.h | 1 + src/model/application.cpp | 2 +- src/model/simulation.cpp | 9 ++++++--- src/model/simulation.h | 29 +++-------------------------- src/view/view2d/bulgepainter.cpp | 32 +++----------------------------- test/CMakeLists.txt | 1 + 7 files changed, 45 insertions(+), 59 deletions(-) diff --git a/src/geometry/arc.h b/src/geometry/arc.h index 7fdeb22..f24026b 100644 --- a/src/geometry/arc.h +++ b/src/geometry/arc.h @@ -16,6 +16,14 @@ class Arc : public Circle float m_endAngle; float m_spanAngle; + template + void lineToArcPointVisit(float angle, Visitor &visitor) const + { + const QVector2D relativeNormalizedPoint(std::cos(angle), std::sin(angle)); + const QVector2D point = (center() + relativeNormalizedPoint * radius()); + visitor(point); + } + public: explicit Arc(const Circle &circle, const QVector2D &start, const QVector2D &end, float starAngle, float endAngle); @@ -27,6 +35,28 @@ class Arc : public Circle float spanAngle() const; float length() const; + + template + void approximateToLinesVisit(float maxError, Visitor &&visitor) const + { + visitor(m_start); + + // Calculate the angle step to not exceed allowed error (distance from line to arc). + const float angleStep = std::fmax(std::acos(1.0f - maxError) * 2.0f, maxError); + + if (orientation() == geometry::Orientation::CCW) { + for (float angle = m_startAngle + angleStep, end = m_endAngle; angle < end; angle += angleStep) { + lineToArcPointVisit(angle, visitor); + } + } + else { + for (float angle = m_startAngle - angleStep, end = m_endAngle; angle > end; angle -= angleStep) { + lineToArcPointVisit(angle, visitor); + } + } + + visitor(m_end); + } }; } diff --git a/src/geometry/utils.h b/src/geometry/utils.h index f74eb06..b9807fd 100644 --- a/src/geometry/utils.h +++ b/src/geometry/utils.h @@ -21,6 +21,7 @@ namespace geometry } using Point2DList = std::vector; + using Point3DList = std::vector; enum class Orientation { diff --git a/src/model/application.cpp b/src/model/application.cpp index ea7a296..d0f634d 100644 --- a/src/model/application.cpp +++ b/src/model/application.cpp @@ -360,7 +360,7 @@ void Application::showHidden() void Application::createSimulation() { - Simulation simulation(*m_openedDocument, *m_defaultToolConfig, *m_defaultProfileConfig); + Simulation simulation(*m_openedDocument); } } diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp index d96cb41..b8d2a0f 100644 --- a/src/model/simulation.cpp +++ b/src/model/simulation.cpp @@ -195,8 +195,11 @@ class Simulation::RenderVisitor }; -Simulation::MotionList Simulation::renderDocumentToMotions(const Document &document, const config::Tools::Tool& tool, const config::Profiles::Profile& profile) const +Simulation::MotionList Simulation::renderDocumentToMotions(const Document &document) const { + const config::Tools::Tool& tool = document.toolConfig(); + const config::Profiles::Profile& profile = document.profileConfig(); + RenderVisitor visitor(42); // TODO fastfeedRate config exporter::renderer::Renderer renderer(tool, profile, visitor); @@ -205,8 +208,8 @@ Simulation::MotionList Simulation::renderDocumentToMotions(const Document &docum return visitor.motions(); } -Simulation::Simulation(const Document &document, const config::Tools::Tool& tool, const config::Profiles::Profile& profile) - :m_motions(renderDocumentToMotions(document, tool, profile)) +Simulation::Simulation(const Document &document) + :m_motions(renderDocumentToMotions(document)) { } diff --git a/src/model/simulation.h b/src/model/simulation.h index 4520d5b..5bb074e 100644 --- a/src/model/simulation.h +++ b/src/model/simulation.h @@ -79,22 +79,6 @@ class Simulation : public common::Aggregable const QVector2D& endPlanePos() const; }; - template - struct GeometryVisitorAdapter - { - Visitor &visitor; - - void operator()(const PlaneLineMotion& motion) - { - } - void operator()(const PlaneArcMotion& motion) - { - } - void operator()(const DepthMotion& motion) - { - } - }; - using Motion = std::variant; using MotionList = std::vector; @@ -102,22 +86,15 @@ class Simulation : public common::Aggregable const Motion &findMotionAtTime(float time) const; - MotionList renderDocumentToMotions(const Document &document, const config::Tools::Tool& tool, const config::Profiles::Profile& profil) const; + MotionList renderDocumentToMotions(const Document &document) const; public: - explicit Simulation(const Document &document, const config::Tools::Tool& tool, const config::Profiles::Profile& profile); + explicit Simulation(const Document &document); QVector3D position(float time); float duration() const; - template - void visitGeometry(Visitor &&visitor) - { - GeometryVisitorAdapter adapter{visitor}; - std::for_each(m_motions.begin(), m_motions.end(), [&adapter](const Motion& motion){ - std::visit(adapter, motion); - }); - } + geometry::Point3DList approximatedPathToLines(float maxError) const; }; } diff --git a/src/view/view2d/bulgepainter.cpp b/src/view/view2d/bulgepainter.cpp index c629ff6..8558c29 100644 --- a/src/view/view2d/bulgepainter.cpp +++ b/src/view/view2d/bulgepainter.cpp @@ -4,13 +4,6 @@ namespace view::view2d { -void BulgePainter::lineToArcPoint(const QVector2D ¢er, float radius, float angle) -{ - const QVector2D relativeNormalizedPoint(std::cos(angle), std::sin(angle)); - const QPointF point = (center + relativeNormalizedPoint * radius).toPointF(); - m_painter.lineTo(point); -} - BulgePainter::BulgePainter(QPainterPath &painter) :m_painter(painter) { @@ -27,28 +20,9 @@ void BulgePainter::operator()(const geometry::Bulge &bulge) const float maxError = 0.0001; // TODO const - const float radius = arc.radius(); - const QVector2D ¢er = arc.center(); - - // Calculate the angle step to not exceed allowed error (distance from line to arc). - const float angleStep = std::fmax(std::acos(1.0f - maxError) * 2.0f, maxError); - - // Pass by starting point. - m_painter.lineTo(arc.start().toPointF()); - - if (arc.orientation() == geometry::Orientation::CCW) { - for (float angle = arc.startAngle() + angleStep, end = arc.endAngle(); angle < end; angle += angleStep) { - lineToArcPoint(center, radius, angle); - } - } - else { - for (float angle = arc.startAngle() - angleStep, end = arc.endAngle(); angle > end; angle -= angleStep) { - lineToArcPoint(center, radius, angle); - } - } - - // Pass by ending point. - m_painter.lineTo(arc.end().toPointF()); + arc.approximateToLinesVisit(maxError, [this](const QVector2D &point){ + m_painter.lineTo(point.toPointF()); + }); } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6f7b086..01c4373 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,6 +27,7 @@ set(SRC polyline.cpp polylineutils.cpp serializer.cpp + simulation.cpp task.cpp verticalspeed.cpp From d31cb665fc8d9634ecdca660359e1cb0fae1d323 Mon Sep 17 00:00:00 2001 From: tristan Date: Sat, 17 Dec 2022 12:57:38 +0100 Subject: [PATCH 12/32] feat: convert simulation to list of 3d points --- src/geometry/arc.h | 2 -- src/model/simulation.cpp | 15 ++++++++- src/model/simulation.h | 20 ++++++++++++ test/simulation.cpp | 67 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 test/simulation.cpp diff --git a/src/geometry/arc.h b/src/geometry/arc.h index f24026b..6634210 100644 --- a/src/geometry/arc.h +++ b/src/geometry/arc.h @@ -39,8 +39,6 @@ class Arc : public Circle template void approximateToLinesVisit(float maxError, Visitor &&visitor) const { - visitor(m_start); - // Calculate the angle step to not exceed allowed error (distance from line to arc). const float angleStep = std::fmax(std::acos(1.0f - maxError) * 2.0f, maxError); diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp index b8d2a0f..00fb0f4 100644 --- a/src/model/simulation.cpp +++ b/src/model/simulation.cpp @@ -160,7 +160,6 @@ class Simulation::RenderVisitor void end(const QVector2D& to, float safetyDepth) { - linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate); linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate); } @@ -213,5 +212,19 @@ Simulation::Simulation(const Document &document) { } +geometry::Point3DList Simulation::approximatedPathToLines(float maxError) const +{ + geometry::Point3DList points; + auto pushBackPoint = [&points](const QVector3D &point){ points.push_back(point); }; + + std::for_each(m_motions.begin(), m_motions.end(), [&pushBackPoint, maxError](const Motion& motionVariant){ + std::visit([&pushBackPoint, maxError](const auto &motion){ + motion.approximateToLinesVisit(maxError, pushBackPoint); + }, motionVariant); + }); + + return points; +} + } diff --git a/src/model/simulation.h b/src/model/simulation.h index 5bb074e..0ac356a 100644 --- a/src/model/simulation.h +++ b/src/model/simulation.h @@ -46,6 +46,12 @@ class Simulation : public common::Aggregable float endDepth() const; const QVector2D& endPlanePos() const; + + template + void approximateToLinesVisit(float maxError, Visitor &&visitor) const + { + visitor(QVector3D(m_line.end(), m_depth)); + } }; class PlaneArcMotion : public Traversable @@ -61,6 +67,14 @@ class Simulation : public common::Aggregable float endDepth() const; const QVector2D& endPlanePos() const; + + template + void approximateToLinesVisit(float maxError, Visitor &&visitor) const + { + m_arc.approximateToLinesVisit(maxError, [this, &visitor](const QVector2D &point){ + visitor(QVector3D(point, m_depth)); + }); + } }; class DepthMotion : public Traversable @@ -77,6 +91,12 @@ class Simulation : public common::Aggregable float endDepth() const; const QVector2D& endPlanePos() const; + + template + void approximateToLinesVisit(float maxError, Visitor &&visitor) const + { + visitor(QVector3D(m_planePos, m_toDepth)); + } }; using Motion = std::variant; diff --git a/test/simulation.cpp b/test/simulation.cpp new file mode 100644 index 0000000..f39f2de --- /dev/null +++ b/test/simulation.cpp @@ -0,0 +1,67 @@ +#include +#include +#include + +#include + +config::Tools::Tool tool1mmPass() +{ + config::Tools::Tool tool {"tool", YAML::Node()}; + tool.general().depthPerCut() = 1.0f; + + return tool; +} + +static const config::Tools::Tool tool(tool1mmPass()); +static const config::Profiles::Profile::Gcode gcode{"gcode", YAML::Node()}; +static const config::Profiles::Profile profile{"profile", YAML::Node()}; + +model::Document::UPtr documentFromPolylines(geometry::Polyline &&polyline, const model::PathSettings &settings) +{ + model::Path::UPtr path = std::make_unique(std::move(polyline), "", settings); + + model::Path::ListUPtr paths; + paths.push_back(std::move(path)); + + model::Layer::UPtr layer = std::make_unique("layer", std::move(paths)); + + model::Layer::ListUPtr layers; + layers.push_back(std::move(layer)); + model::Task::UPtr task = std::make_unique(std::move(layers)); + return std::make_unique(std::move(task), tool, profile); +} + +TEST(SimulationTest, shouldHasMultiLayerDepth) +{ + const int nbCut = 10; + const geometry::Bulge bulge(QVector2D(1, 0), QVector2D(1, 1), 0); + geometry::Polyline polyline({bulge}); + + const model::PathSettings settings{10, 10, 10, nbCut - 1}; + model::Document::UPtr document = documentFromPolylines(std::move(polyline), settings); + + model::Simulation simulation(*document); + const geometry::Point3DList points = simulation.approximatedPathToLines(0.001); + + const int exceptedNbPoints = 2 /* retract + start */ + 2 * nbCut /* cut */ + 2 /* retract and home */; + EXPECT_EQ(exceptedNbPoints, points.size()); +} + +TEST(SimulationTest, shouldNotContainsDuplicatePoints) +{ + const int nbCut = 10; + const geometry::Bulge bulge(QVector2D(1, 0), QVector2D(1, 1), 1); + const geometry::Bulge bulge2(QVector2D(1, 1), QVector2D(-1, 1), -0.5); + geometry::Polyline polyline({bulge}); + + const model::PathSettings settings{10, 10, 10, nbCut - 1}; + model::Document::UPtr document = documentFromPolylines(std::move(polyline), settings); + + model::Simulation simulation(*document); + const geometry::Point3DList points = simulation.approximatedPathToLines(0.001); + + for (int i = 0, size = points.size() - 1; i < size; ++i) { + EXPECT_NE(points[i], points[i + 1]); + } +} + From 668d17a24eac8566e692ab84368e630ee61d0d42 Mon Sep 17 00:00:00 2001 From: tristan Date: Thu, 22 Dec 2022 22:16:24 +0100 Subject: [PATCH 13/32] feat: render tool path in 3d --- CMakeLists.txt | 15 +++++- src/geometry/utils.h | 1 + src/main.cpp | 5 ++ src/model/application.cpp | 4 +- src/model/application.h | 3 +- src/model/simulation.h | 1 + src/view/CMakeLists.txt | 1 + src/view/mainwindow.cpp | 20 +++++--- src/view/mainwindow.h | 7 ++- src/view/view3d/CMakeLists.txt | 11 ++++ src/view/view3d/internal/CMakeLists.txt | 10 ++++ src/view/view3d/internal/toolpath.cpp | 68 +++++++++++++++++++++++++ src/view/view3d/internal/toolpath.h | 27 ++++++++++ src/view/view3d/internal/viewport.cpp | 31 +++++++++++ src/view/view3d/internal/viewport.h | 24 +++++++++ src/view/view3d/viewport.cpp | 51 +++++++++++++++++++ src/view/view3d/viewport.h | 40 +++++++++++++++ thirdparty/CMakeLists.txt | 1 - thirdparty/fmt | 1 - 19 files changed, 306 insertions(+), 15 deletions(-) create mode 100644 src/view/view3d/CMakeLists.txt create mode 100644 src/view/view3d/internal/CMakeLists.txt create mode 100644 src/view/view3d/internal/toolpath.cpp create mode 100644 src/view/view3d/internal/toolpath.h create mode 100644 src/view/view3d/internal/viewport.cpp create mode 100644 src/view/view3d/internal/viewport.h create mode 100644 src/view/view3d/viewport.cpp create mode 100644 src/view/view3d/viewport.h delete mode 160000 thirdparty/fmt diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b45223..042e09b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,14 @@ set(TEMPLATE_DIR ${PROJECT_SOURCE_DIR}/template) find_package(codecov) -find_package(PythonInterp) +find_package(PythonInterp REQUIRED) + +find_package(VTK COMPONENTS REQUIRED + CommonColor + CommonCore + GUISupportQt + RenderingCore +) find_package(Qt5 COMPONENTS REQUIRED Core @@ -52,7 +59,6 @@ set(INCLUDE_DIRS thirdparty thirdparty/cereal/include thirdparty/cavaliercontours/include - thirdparty/fmt/include thirdparty/nanoflann/include thirdparty/yaml-cpp/include template @@ -60,6 +66,7 @@ set(INCLUDE_DIRS ${CMAKE_BINARY_DIR}/template ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} + ${VTK_INCLUDE_DIRS} ) set(LINK_LIBRARIES @@ -69,6 +76,8 @@ set(LINK_LIBRARIES view-dialogs-settings view-task view-view2d + view-view3d + view-view3d-internal model config importer-dxf @@ -83,6 +92,7 @@ set(LINK_LIBRARIES Qt5::Widgets Qt5::Svg yaml-cpp + ${VTK_LIBRARIES} ) include_directories(${INCLUDE_DIRS}) @@ -98,6 +108,7 @@ endif() add_executable(dxfplotter src/main.cpp) target_link_libraries(dxfplotter ${LINK_LIBRARIES}) +vtk_module_autoinit(TARGETS dxfplotter MODULES ${VTK_LIBRARIES}) add_coverage(dxfplotter) install(TARGETS dxfplotter DESTINATION bin) diff --git a/src/geometry/utils.h b/src/geometry/utils.h index b9807fd..bf859df 100644 --- a/src/geometry/utils.h +++ b/src/geometry/utils.h @@ -10,6 +10,7 @@ #include #include +#include #include diff --git a/src/main.cpp b/src/main.cpp index 2cc9e17..567fde9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,9 @@ #include #include #include +#include + +#include void setDarkPalette(QApplication &qapp) { @@ -32,6 +35,8 @@ void setDarkPalette(QApplication &qapp) int main(int argc, char *argv[]) { Q_INIT_RESOURCE(resource); + // Needed to ensure appropriate OpenGL context is created for VTK rendering. + QSurfaceFormat::setDefaultFormat(QVTKOpenGLNativeWidget::defaultFormat()); QApplication qapp(argc, argv); qapp.setApplicationName("dxfplotter"); diff --git a/src/model/application.cpp b/src/model/application.cpp index d0f634d..0218b87 100644 --- a/src/model/application.cpp +++ b/src/model/application.cpp @@ -358,9 +358,9 @@ void Application::showHidden() task.showHidden(); } -void Application::createSimulation() +Simulation Application::createSimulation() { - Simulation simulation(*m_openedDocument); + return Simulation(*m_openedDocument); } } diff --git a/src/model/application.h b/src/model/application.h index b2d8ade..ef416e5 100644 --- a/src/model/application.h +++ b/src/model/application.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -104,7 +105,7 @@ class Application : public QObject void hideSelection(); void showHidden(); - void createSimulation(); + Simulation createSimulation(); Q_SIGNALS: void documentChanged(Document *newDocument); diff --git a/src/model/simulation.h b/src/model/simulation.h index 0ac356a..734ea05 100644 --- a/src/model/simulation.h +++ b/src/model/simulation.h @@ -109,6 +109,7 @@ class Simulation : public common::Aggregable MotionList renderDocumentToMotions(const Document &document) const; public: + Simulation() = default; explicit Simulation(const Document &document); QVector3D position(float time); diff --git a/src/view/CMakeLists.txt b/src/view/CMakeLists.txt index 8750224..46712dd 100644 --- a/src/view/CMakeLists.txt +++ b/src/view/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(dialogs) add_subdirectory(task) add_subdirectory(view2d) +add_subdirectory(view3d) set(SRC info.cpp diff --git a/src/view/mainwindow.cpp b/src/view/mainwindow.cpp index 4f9b878..37d6dd6 100644 --- a/src/view/mainwindow.cpp +++ b/src/view/mainwindow.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -35,16 +36,22 @@ QWidget *MainWindow::setupLeftPanel() QWidget *MainWindow::setupCenterPanel() { - view2d::Viewport *viewport = new view2d::Viewport(m_app); - Info *info = new Info(*viewport, m_app); + view2d::Viewport *viewport2d = new view2d::Viewport(m_app); + m_viewport3d = new view3d::Viewport(m_app); + Info *info = new Info(*viewport2d, m_app); - QWidget *container = new QWidget(this); - QVBoxLayout *layout = new QVBoxLayout(); + QSplitter *splitter = new QSplitter(Qt::Horizontal, this); + splitter->addWidget(viewport2d); + splitter->addWidget(m_viewport3d); + splitter->setStretchFactor(0, 1); + splitter->setStretchFactor(1, 0); - layout->addWidget(viewport); + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(splitter); layout->addWidget(info); layout->setStretch(0, 1); + QWidget *container = new QWidget(this); container->setLayout(layout); return container; @@ -244,7 +251,8 @@ void MainWindow::displayError(const QString &message) void MainWindow::simulate() { - m_app.createSimulation(); + model::Simulation simulation = m_app.createSimulation(); + m_viewport3d->setSimulation(std::move(simulation)); } } diff --git a/src/view/mainwindow.h b/src/view/mainwindow.h index 67af422..4dd1086 100644 --- a/src/view/mainwindow.h +++ b/src/view/mainwindow.h @@ -13,10 +13,9 @@ class QComboBox; namespace view { -namespace Task +namespace view3d { -class Task; class Viewport; } @@ -26,6 +25,8 @@ class MainWindow : public QMainWindow, private Ui::MainWindow private: model::Application &m_app; + view3d::Viewport *m_viewport3d; + QActionGroup m_openedDocumentActions; QWidget *setupLeftPanel(); @@ -54,6 +55,8 @@ protected Q_SLOTS: void setSelectionOrigin(); void documentChanged(model::Document *newDocument); void displayError(const QString &message); + +signals: void simulate(); }; diff --git a/src/view/view3d/CMakeLists.txt b/src/view/view3d/CMakeLists.txt new file mode 100644 index 0000000..d7ac5ca --- /dev/null +++ b/src/view/view3d/CMakeLists.txt @@ -0,0 +1,11 @@ +add_subdirectory(internal) + +set(SRC + viewport.cpp + + viewport.h +) + +add_library(view-view3d ${SRC}) +target_link_libraries(view-view3d PRIVATE ${VTK_LIBRARIES}) +add_dependencies(view-view3d generate-config uic) diff --git a/src/view/view3d/internal/CMakeLists.txt b/src/view/view3d/internal/CMakeLists.txt new file mode 100644 index 0000000..d100111 --- /dev/null +++ b/src/view/view3d/internal/CMakeLists.txt @@ -0,0 +1,10 @@ +set(SRC + toolpath.cpp + viewport.cpp + + toolpath.h + viewport.h +) + +add_library(view-view3d-internal ${SRC}) +target_link_libraries(view-view3d-internal PRIVATE ${VTK_LIBRARIES}) diff --git a/src/view/view3d/internal/toolpath.cpp b/src/view/view3d/internal/toolpath.cpp new file mode 100644 index 0000000..04d82b8 --- /dev/null +++ b/src/view/view3d/internal/toolpath.cpp @@ -0,0 +1,68 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include // TODO + +namespace view::view3d::internal +{ + +static vtkNew colors; + +void ToolPath::createPolylineFromPoints(const geometry::Point3DList& points) +{ + const int nbPoints = points.size(); + + vtkNew vertices; + vertices->SetNumberOfPoints(nbPoints); + + vtkNew polyline; + vtkIdList *ids = polyline->GetPointIds(); + ids->SetNumberOfIds(nbPoints); + + for (int i = 0; i < nbPoints; ++i) { + const QVector3D& point = points[i]; + vertices->SetPoint(i, point.x(), point.y(), point.z()); + + ids->SetId(i, i); + } + + vertices->GetBounds(m_boundingBox); + + vtkNew cells; + cells->InsertNextCell(polyline); + + vtkNew polyData; + polyData->SetPoints(vertices); + polyData->SetLines(cells); + + vtkNew mapper; + mapper->SetInputData(polyData); + + m_actor->SetMapper(mapper); + m_actor->GetProperty()->SetColor(colors->GetColor3d("Tomato").GetData()); +} + +ToolPath::ToolPath(const geometry::Point3DList& points) +{ + createPolylineFromPoints(points); +} + +vtkActor *ToolPath::actor() +{ + return m_actor; +} + +const double (&ToolPath::boundingBox() const)[6] +{ + return m_boundingBox; +} + + +} diff --git a/src/view/view3d/internal/toolpath.h b/src/view/view3d/internal/toolpath.h new file mode 100644 index 0000000..3415a57 --- /dev/null +++ b/src/view/view3d/internal/toolpath.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include + +namespace view::view3d::internal +{ + +class ToolPath +{ +private: + vtkNew m_actor; + double m_boundingBox[6]; + + void createPolylineFromPoints(const geometry::Point3DList &points); + +public: + ToolPath() = default; + explicit ToolPath(const geometry::Point3DList &points); + + vtkActor *actor(); + const double (&boundingBox() const)[6]; +}; + +} diff --git a/src/view/view3d/internal/viewport.cpp b/src/view/view3d/internal/viewport.cpp new file mode 100644 index 0000000..f81b201 --- /dev/null +++ b/src/view/view3d/internal/viewport.cpp @@ -0,0 +1,31 @@ +#include + +#include + +namespace view::view3d::internal +{ + +Viewport::Viewport() +{ + vtkNew colors; + m_renderer->SetBackground(0.0, 0.0, 0.0); +} + +void Viewport::addActor(vtkActor* actor) +{ + m_renderer->AddActor(actor); +} + +void Viewport::resetCamera(const double bounds[6]) +{ + m_renderer->ResetCamera(bounds); +} + +vtkRenderer *Viewport::renderer() const +{ + return m_renderer; +} + + +} + diff --git a/src/view/view3d/internal/viewport.h b/src/view/view3d/internal/viewport.h new file mode 100644 index 0000000..6769419 --- /dev/null +++ b/src/view/view3d/internal/viewport.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +class vtkActor; + +namespace view::view3d::internal +{ + +class Viewport +{ +private: + vtkNew m_renderer; + +public: + explicit Viewport(); + + void addActor(vtkActor *actor); + void resetCamera(const double bounds[6]); + vtkRenderer *renderer() const; +}; + +} diff --git a/src/view/view3d/viewport.cpp b/src/view/view3d/viewport.cpp new file mode 100644 index 0000000..ba0a3b4 --- /dev/null +++ b/src/view/view3d/viewport.cpp @@ -0,0 +1,51 @@ +#include + +#include +#include + +#include +#include + +#include + +namespace view::view3d +{ + +void Viewport::documentChanged() +{ +} + +void Viewport::resizeEvent(QResizeEvent *event) +{ + m_vtkWidget->resize(event->size()); +} + +Viewport::Viewport(model::Application& app) + :DocumentModelObserver(app), + m_vtkWidget(new QVTKOpenGLNativeWidget(this)) +{ + vtkNew renderWindow; + m_vtkWidget->setRenderWindow(renderWindow); + + m_vtkWidget->show(); +} + +void Viewport::setSimulation(model::Simulation && simulation) +{ + vtkRenderWindow *window = m_vtkWidget->renderWindow(); + if (m_viewport) { + window->RemoveRenderer(m_viewport->renderer()); + } + + m_simulation = std::move(simulation); + m_path = std::make_unique(m_simulation.approximatedPathToLines(0.01)); // TODO maxError + + m_viewport = std::make_unique(); + m_viewport->addActor(m_path->actor()); + m_viewport->resetCamera(m_path->boundingBox()); + + window->AddRenderer(m_viewport->renderer()); + window->Render(); +} + +} diff --git a/src/view/view3d/viewport.h b/src/view/view3d/viewport.h new file mode 100644 index 0000000..0644ef0 --- /dev/null +++ b/src/view/view3d/viewport.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include + +#include + +class QVTKOpenGLNativeWidget; + +namespace view::view3d +{ + +namespace internal +{ + +class ToolPath; +class Viewport; + +} + +class Viewport : public model::DocumentModelObserver +{ +private: + model::Simulation m_simulation; + QVTKOpenGLNativeWidget *m_vtkWidget; + std::unique_ptr m_path; + std::unique_ptr m_viewport; + +protected: + void documentChanged() override; + void resizeEvent(QResizeEvent *event) override; + +public: + explicit Viewport(model::Application &app); + + void setSimulation(model::Simulation&& simulation); +}; + +} diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index c4dea1d..76ca75d 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -1,6 +1,5 @@ add_subdirectory(cavaliercontours) add_subdirectory(cereal) -add_subdirectory(fmt) add_subdirectory(libdxfrw) add_subdirectory(nanoflann) add_subdirectory(yaml-cpp) diff --git a/thirdparty/fmt b/thirdparty/fmt deleted file mode 160000 index d141cdb..0000000 --- a/thirdparty/fmt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d141cdbeb0fb422a3fb7173b285fd38e0d1772dc From 16e52d39d969f8bf4cff04c3000644f55aaa95e4 Mon Sep 17 00:00:00 2001 From: tristan Date: Sat, 24 Dec 2022 17:23:12 +0100 Subject: [PATCH 14/32] feat: draw tool and add test for position at time in simulation --- CMakeLists.txt | 1 + src/geometry/utils.h | 6 ++ src/model/simulation.cpp | 130 +++++++++++++++++++----- src/model/simulation.h | 50 ++++++--- src/view/view3d/internal/CMakeLists.txt | 2 + src/view/view3d/internal/tool.cpp | 28 +++++ src/view/view3d/internal/tool.h | 20 ++++ src/view/view3d/internal/toolpath.cpp | 45 +++++--- src/view/view3d/internal/toolpath.h | 6 +- src/view/view3d/internal/viewport.cpp | 7 +- src/view/view3d/viewport.cpp | 9 +- src/view/view3d/viewport.h | 4 +- test/simulation.cpp | 33 +++++- 13 files changed, 272 insertions(+), 69 deletions(-) create mode 100644 src/view/view3d/internal/tool.cpp create mode 100644 src/view/view3d/internal/tool.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 042e09b..1717b6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ find_package(VTK COMPONENTS REQUIRED CommonCore GUISupportQt RenderingCore + RenderingAnnotation ) find_package(Qt5 COMPONENTS REQUIRED diff --git a/src/geometry/utils.h b/src/geometry/utils.h index bf859df..a1db268 100644 --- a/src/geometry/utils.h +++ b/src/geometry/utils.h @@ -124,6 +124,12 @@ namespace geometry { return (EnsureEndGreater(start, end) - start); } + + template + constexpr Vector Lerp(const Vector& startPoint, const Vector& endPoint, float factor) + { + return startPoint * (1.0f - factor) + endPoint * factor; + } } namespace common::enumerate diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp index 00fb0f4..49f6505 100644 --- a/src/model/simulation.cpp +++ b/src/model/simulation.cpp @@ -7,6 +7,12 @@ namespace model { +Simulation::ToolPathPoint3D::ToolPathPoint3D(const QVector3D& position, model::Simulation::MoveType moveType) + :position(position), + moveType(moveType) +{ +} + float entityDuration(const geometry::Line &line, float feedRate) { return line.length() / feedRate; @@ -22,12 +28,18 @@ float entityDuration(const geometry::Arc &arc, float feedRate) return arc.length() / feedRate; } -Simulation::Traversable::Traversable(float startTime, float duration) +Simulation::Traversable::Traversable(float startTime, float duration, MoveType moveType) :m_startTime(startTime), - m_duration(duration) + m_duration(duration), + m_moveType(moveType) { } +float Simulation::Traversable::timeFactor(float time) const +{ + return (time - m_startTime) / m_duration; +} + float Simulation::Traversable::startTime() const { return m_startTime; @@ -43,13 +55,19 @@ float Simulation::Traversable::duration() const return m_duration; } -Simulation::PlaneLineMotion::PlaneLineMotion(float depth, const geometry::Line &line, float feedRate, float startTime) - :Traversable(startTime, entityDuration(line, feedRate)), +Simulation::PlaneLineMotion::PlaneLineMotion(float depth, const geometry::Line &line, float feedRate, float startTime, MoveType moveType) + :Traversable(startTime, entityDuration(line, feedRate), moveType), m_line(line), m_depth(depth) { } +Simulation::ToolPathPoint3D Simulation::PlaneLineMotion::pointAtTime(float time) const +{ + const QVector2D planePos = geometry::Lerp(m_line.start(), m_line.end(), timeFactor(time)); + return ToolPathPoint3D(QVector3D(planePos, m_depth), m_moveType); +} + float Simulation::PlaneLineMotion::endDepth() const { return m_depth; @@ -60,13 +78,19 @@ const QVector2D& Simulation::PlaneLineMotion::endPlanePos() const return m_line.end(); } -Simulation::PlaneArcMotion::PlaneArcMotion(float depth, const geometry::Arc &arc, float feedRate, float startTime) - :Traversable(startTime, entityDuration(arc, feedRate)), +Simulation::PlaneArcMotion::PlaneArcMotion(float depth, const geometry::Arc &arc, float feedRate, float startTime, MoveType moveType) + :Traversable(startTime, entityDuration(arc, feedRate), moveType), m_arc(arc), m_depth(depth) { } +Simulation::ToolPathPoint3D Simulation::PlaneArcMotion::pointAtTime(float time) const +{ + const QVector2D planePos = geometry::Lerp(m_arc.start(), m_arc.end(), timeFactor(time)); + return ToolPathPoint3D(QVector3D(planePos, m_depth), m_moveType); +} + float Simulation::PlaneArcMotion::endDepth() const { return m_depth; @@ -77,14 +101,20 @@ const QVector2D& Simulation::PlaneArcMotion::endPlanePos() const return m_arc.end(); } -Simulation::DepthMotion::DepthMotion(const QVector2D &planePos, float fromDepth, float toDepth, float feedRate, float startTime) - :Traversable(startTime, entityDuration(fromDepth, toDepth, feedRate)), +Simulation::DepthMotion::DepthMotion(const QVector2D &planePos, float fromDepth, float toDepth, float feedRate, float startTime, MoveType moveType) + :Traversable(startTime, entityDuration(fromDepth, toDepth, feedRate), moveType), m_planePos(planePos), m_fromDepth(fromDepth), m_toDepth(toDepth) { } +Simulation::ToolPathPoint3D Simulation::DepthMotion::pointAtTime(float time) const +{ + const float depth = geometry::Lerp(m_fromDepth, m_toDepth, timeFactor(time)); + return ToolPathPoint3D(QVector3D(m_planePos, depth), m_moveType); +} + float Simulation::DepthMotion::endDepth() const { return m_toDepth; @@ -121,29 +151,29 @@ class Simulation::RenderVisitor m_motions.push_back(std::move(motion)); } - void linearMoveCursorInDepthTo(float toDepth, float feedRate) + void linearMoveCursorInDepthTo(float toDepth, float feedRate, MoveType moveType) { - addMotion(DepthMotion(m_cursorInPlane, m_cursorDepth, toDepth, feedRate, m_cursorTime)); + addMotion(DepthMotion(m_cursorInPlane, m_cursorDepth, toDepth, feedRate, m_cursorTime, moveType)); } - void linearMoveCursorInPlaneTo(const QVector2D& to, float feedRate) + void linearMoveCursorInPlaneTo(const QVector2D& to, float feedRate, MoveType moveType) { const geometry::Line line(m_cursorInPlane, to); if (line.lengthNonZero()) { - addMotion(PlaneLineMotion(m_cursorDepth, line, feedRate, m_cursorTime)); + addMotion(PlaneLineMotion(m_cursorDepth, line, feedRate, m_cursorTime, moveType)); } } - void moveCursorInPlaneAlong(const geometry::Line &line, float feedRate) + void moveCursorInPlaneAlong(const geometry::Line &line, float feedRate, MoveType moveType) { if (line.lengthNonZero()) { - addMotion(PlaneLineMotion(m_cursorDepth, line, feedRate, m_cursorTime)); + addMotion(PlaneLineMotion(m_cursorDepth, line, feedRate, m_cursorTime, moveType)); } } - void moveCursorInPlaneAlong(const geometry::Arc &arc, float feedRate) + void moveCursorInPlaneAlong(const geometry::Arc &arc, float feedRate, MoveType moveType) { - addMotion(PlaneArcMotion(m_cursorDepth, arc, feedRate, m_cursorTime)); + addMotion(PlaneArcMotion(m_cursorDepth, arc, feedRate, m_cursorTime, moveType)); } public: @@ -155,34 +185,34 @@ class Simulation::RenderVisitor void start(const QVector2D& from, float safetyDepth) { initCursor(from, 0.0f); - linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate); + linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate, MoveType::FastWithoutCut); } void end(const QVector2D& to, float safetyDepth) { - linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate); + linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate, MoveType::FastWithoutCut); } void startOperation(const QVector2D& to, float intensity) { - linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate); + linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate, MoveType::FastWithoutCut); } void endOperation(float safetyDepth) { - linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate); + linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate, MoveType::FastWithoutCut); } void processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, float depthFeedRate) { - linearMoveCursorInDepthTo(depth, depthFeedRate); + linearMoveCursorInDepthTo(depth, depthFeedRate, MoveType::NormalWithCut); polyline.forEachBulge([this, planeFeedRate](const geometry::Bulge& bulge){ if (bulge.isArc()) { - moveCursorInPlaneAlong(bulge.toArc(), planeFeedRate); + moveCursorInPlaneAlong(bulge.toArc(), planeFeedRate, MoveType::NormalWithCut); } else { - moveCursorInPlaneAlong(bulge.toLine(), planeFeedRate); + moveCursorInPlaneAlong(bulge.toLine(), planeFeedRate, MoveType::NormalWithCut); } }); } @@ -193,8 +223,18 @@ class Simulation::RenderVisitor } }; +const Simulation::Motion &Simulation::findMotionAtTime(float time) const +{ + MotionList::const_iterator motionIt = std::lower_bound(m_motions.begin(), m_motions.end(), + time, [](const Motion& motionVariant, float time){ + const float endTime = std::visit([](auto &motion){ return motion.endTime(); }, motionVariant); + return endTime < time; + }); + + return *motionIt; +} -Simulation::MotionList Simulation::renderDocumentToMotions(const Document &document) const +Simulation::MotionList Simulation::renderDocumentToMotions(const Document &document) { const config::Tools::Tool& tool = document.toolConfig(); const config::Profiles::Profile& profile = document.profileConfig(); @@ -207,15 +247,49 @@ Simulation::MotionList Simulation::renderDocumentToMotions(const Document &docum return visitor.motions(); } +float Simulation::totalDurationOfMotions(const MotionList& motions) +{ + const Motion &lastMotion = motions.back(); + return std::visit([](const auto &motion){ + return motion.endTime(); + }, lastMotion); +} + + Simulation::Simulation(const Document &document) - :m_motions(renderDocumentToMotions(document)) + :m_motions(renderDocumentToMotions(document)), + m_duration(totalDurationOfMotions(m_motions)), + m_toolRadius(document.toolConfig().general().radius()) +{ +} + +Simulation::ToolPathPoint3D Simulation::position(float time) const { + const Motion& motion = findMotionAtTime(time); + return std::visit([time](auto &arg){ + return arg.pointAtTime(time); + }, motion); } -geometry::Point3DList Simulation::approximatedPathToLines(float maxError) const +float Simulation::duration() const { - geometry::Point3DList points; - auto pushBackPoint = [&points](const QVector3D &point){ points.push_back(point); }; + return m_duration; +} + +float Simulation::toolRadius() const +{ + return m_toolRadius; +} + +Simulation::ToolPathPoint3D::List Simulation::approximatedToolPathToLines(float maxError) const +{ + ToolPathPoint3D::List points; + auto pushBackPoint = [&points](const QVector3D &point, MoveType moveType){ + points.emplace_back(point, moveType); + }; + + const QVector3D home(0.0f, 0.0f, 0.0f); + pushBackPoint(home, MoveType::FastWithoutCut); std::for_each(m_motions.begin(), m_motions.end(), [&pushBackPoint, maxError](const Motion& motionVariant){ std::visit([&pushBackPoint, maxError](const auto &motion){ diff --git a/src/model/simulation.h b/src/model/simulation.h index 734ea05..5c36b30 100644 --- a/src/model/simulation.h +++ b/src/model/simulation.h @@ -16,6 +16,22 @@ class Document; class Simulation : public common::Aggregable { +public: + enum class MoveType + { + NormalWithCut = 0, + FastWithoutCut + }; + + struct ToolPathPoint3D : common::Aggregable + { + QVector3D position; + MoveType moveType; + + ToolPathPoint3D() = default; + explicit ToolPathPoint3D(const QVector3D& position, MoveType moveType); + }; + private: class RenderVisitor; @@ -24,9 +40,12 @@ class Simulation : public common::Aggregable protected: float m_startTime; float m_duration; + MoveType m_moveType; + + float timeFactor(float time) const; public: - explicit Traversable(float startTime, float duration); + explicit Traversable(float startTime, float duration, MoveType moveType); float startTime() const; float endTime() const; @@ -40,9 +59,9 @@ class Simulation : public common::Aggregable float m_depth; public: - explicit PlaneLineMotion(float depth, const geometry::Line &line, float feedRate, float startTime); + explicit PlaneLineMotion(float depth, const geometry::Line &line, float feedRate, float startTime, MoveType moveType); - QVector3D pointAtTime(float time) const; + ToolPathPoint3D pointAtTime(float time) const; float endDepth() const; const QVector2D& endPlanePos() const; @@ -50,7 +69,7 @@ class Simulation : public common::Aggregable template void approximateToLinesVisit(float maxError, Visitor &&visitor) const { - visitor(QVector3D(m_line.end(), m_depth)); + visitor(QVector3D(m_line.end(), m_depth), m_moveType); } }; @@ -61,9 +80,9 @@ class Simulation : public common::Aggregable float m_depth; public: - explicit PlaneArcMotion(float depth, const geometry::Arc &arc, float feedRate, float startTime); + explicit PlaneArcMotion(float depth, const geometry::Arc &arc, float feedRate, float startTime, MoveType moveType); - QVector3D pointAtTime(float time) const; + ToolPathPoint3D pointAtTime(float time) const; float endDepth() const; const QVector2D& endPlanePos() const; @@ -72,7 +91,7 @@ class Simulation : public common::Aggregable void approximateToLinesVisit(float maxError, Visitor &&visitor) const { m_arc.approximateToLinesVisit(maxError, [this, &visitor](const QVector2D &point){ - visitor(QVector3D(point, m_depth)); + visitor(QVector3D(point, m_depth), m_moveType); }); } }; @@ -85,9 +104,9 @@ class Simulation : public common::Aggregable float m_toDepth; public: - explicit DepthMotion(const QVector2D &planePos, float fromDepth, float toDepth, float feedRate, float startTime); + explicit DepthMotion(const QVector2D &planePos, float fromDepth, float toDepth, float feedRate, float startTime, MoveType moveType); - QVector3D pointAtTime(float time) const; + ToolPathPoint3D pointAtTime(float time) const; float endDepth() const; const QVector2D& endPlanePos() const; @@ -95,7 +114,7 @@ class Simulation : public common::Aggregable template void approximateToLinesVisit(float maxError, Visitor &&visitor) const { - visitor(QVector3D(m_planePos, m_toDepth)); + visitor(QVector3D(m_planePos, m_toDepth), m_moveType); } }; @@ -103,19 +122,24 @@ class Simulation : public common::Aggregable using MotionList = std::vector; MotionList m_motions; + float m_duration; + float m_toolRadius; const Motion &findMotionAtTime(float time) const; - MotionList renderDocumentToMotions(const Document &document) const; + static MotionList renderDocumentToMotions(const Document &document); + static float totalDurationOfMotions(const MotionList& motions); public: Simulation() = default; explicit Simulation(const Document &document); - QVector3D position(float time); + ToolPathPoint3D position(float time) const; float duration() const; - geometry::Point3DList approximatedPathToLines(float maxError) const; + float toolRadius() const; + + ToolPathPoint3D::List approximatedToolPathToLines(float maxError) const; }; } diff --git a/src/view/view3d/internal/CMakeLists.txt b/src/view/view3d/internal/CMakeLists.txt index d100111..a58acea 100644 --- a/src/view/view3d/internal/CMakeLists.txt +++ b/src/view/view3d/internal/CMakeLists.txt @@ -1,9 +1,11 @@ set(SRC toolpath.cpp viewport.cpp + tool.cpp toolpath.h viewport.h + tool.h ) add_library(view-view3d-internal ${SRC}) diff --git a/src/view/view3d/internal/tool.cpp b/src/view/view3d/internal/tool.cpp new file mode 100644 index 0000000..93c3331 --- /dev/null +++ b/src/view/view3d/internal/tool.cpp @@ -0,0 +1,28 @@ +#include + +#include +#include + +namespace view::view3d::internal +{ + +Tool::Tool(float radius, float height) +{ + vtkNew cylinderSource; + cylinderSource->SetCenter(0.0, height / 2.0, 0.0); + cylinderSource->SetRadius(radius); + cylinderSource->SetHeight(height); + cylinderSource->SetResolution(64); + + vtkNew mapper; + mapper->SetInputConnection(cylinderSource->GetOutputPort()); + m_actor->SetMapper(mapper); + m_actor->RotateX(90); +} + +vtkActor *Tool::actor() +{ + return m_actor; +} + +} diff --git a/src/view/view3d/internal/tool.h b/src/view/view3d/internal/tool.h new file mode 100644 index 0000000..9239a2a --- /dev/null +++ b/src/view/view3d/internal/tool.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace view::view3d::internal +{ + +class Tool +{ +private: + vtkNew m_actor; + +public: + explicit Tool(float radius, float height); + + vtkActor *actor(); +}; + +} diff --git a/src/view/view3d/internal/toolpath.cpp b/src/view/view3d/internal/toolpath.cpp index 04d82b8..f896dd8 100644 --- a/src/view/view3d/internal/toolpath.cpp +++ b/src/view/view3d/internal/toolpath.cpp @@ -3,8 +3,9 @@ #include #include #include -#include +#include #include +#include #include #include @@ -13,43 +14,57 @@ namespace view::view3d::internal { -static vtkNew colors; +static vtkNew namedColors; -void ToolPath::createPolylineFromPoints(const geometry::Point3DList& points) +void ToolPath::createPolylineFromPoints(const model::Simulation::ToolPathPoint3D::List& points) { const int nbPoints = points.size(); + const int nbLines = nbPoints - 1; vtkNew vertices; vertices->SetNumberOfPoints(nbPoints); - vtkNew polyline; - vtkIdList *ids = polyline->GetPointIds(); - ids->SetNumberOfIds(nbPoints); - for (int i = 0; i < nbPoints; ++i) { - const QVector3D& point = points[i]; - vertices->SetPoint(i, point.x(), point.y(), point.z()); - - ids->SetId(i, i); + const model::Simulation::ToolPathPoint3D &point = points[i]; + const QVector3D& position = point.position; + vertices->SetPoint(i, position.x(), position.y(), position.z()); } - vertices->GetBounds(m_boundingBox); + vtkNew colors; + colors->SetNumberOfComponents(3); + colors->SetNumberOfTuples(nbLines); + + static const vtkColor3ub colorsByMoveType[] = { + namedColors->GetColor3ub("OrangeRed"), // MoveType::NormalWithCut + namedColors->GetColor3ub("SlateBlue") // MoveType::FastWithoutCut + }; vtkNew cells; - cells->InsertNextCell(polyline); + for (int i = 0; i < nbLines; ++i) { + const model::Simulation::ToolPathPoint3D &secondPoint = points[i + 1]; + + colors->SetTypedTuple(i, colorsByMoveType[static_cast(secondPoint.moveType)].GetData()); + + vtkNew line; + line->GetPointIds()->SetId(0, i); + line->GetPointIds()->SetId(1, i + 1); + cells->InsertNextCell(line); + } + + vertices->GetBounds(m_boundingBox); vtkNew polyData; polyData->SetPoints(vertices); polyData->SetLines(cells); + polyData->GetCellData()->SetScalars(colors); vtkNew mapper; mapper->SetInputData(polyData); m_actor->SetMapper(mapper); - m_actor->GetProperty()->SetColor(colors->GetColor3d("Tomato").GetData()); } -ToolPath::ToolPath(const geometry::Point3DList& points) +ToolPath::ToolPath(const model::Simulation::ToolPathPoint3D::List& points) { createPolylineFromPoints(points); } diff --git a/src/view/view3d/internal/toolpath.h b/src/view/view3d/internal/toolpath.h index 3415a57..38748c7 100644 --- a/src/view/view3d/internal/toolpath.h +++ b/src/view/view3d/internal/toolpath.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -14,11 +14,11 @@ class ToolPath vtkNew m_actor; double m_boundingBox[6]; - void createPolylineFromPoints(const geometry::Point3DList &points); + void createPolylineFromPoints(const model::Simulation::ToolPathPoint3D::List &points); public: ToolPath() = default; - explicit ToolPath(const geometry::Point3DList &points); + explicit ToolPath(const model::Simulation::ToolPathPoint3D::List &points); vtkActor *actor(); const double (&boundingBox() const)[6]; diff --git a/src/view/view3d/internal/viewport.cpp b/src/view/view3d/internal/viewport.cpp index f81b201..831c42a 100644 --- a/src/view/view3d/internal/viewport.cpp +++ b/src/view/view3d/internal/viewport.cpp @@ -1,14 +1,17 @@ #include -#include +#include namespace view::view3d::internal { Viewport::Viewport() { - vtkNew colors; m_renderer->SetBackground(0.0, 0.0, 0.0); + + vtkNew axes; + axes->AxisLabelsOff(); + m_renderer->AddActor(axes); } void Viewport::addActor(vtkActor* actor) diff --git a/src/view/view3d/viewport.cpp b/src/view/view3d/viewport.cpp index ba0a3b4..874495e 100644 --- a/src/view/view3d/viewport.cpp +++ b/src/view/view3d/viewport.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -38,11 +39,13 @@ void Viewport::setSimulation(model::Simulation && simulation) } m_simulation = std::move(simulation); - m_path = std::make_unique(m_simulation.approximatedPathToLines(0.01)); // TODO maxError + m_tool = std::make_unique(m_simulation.toolRadius(), 1.0f); // TODO conf + m_toolPath = std::make_unique(m_simulation.approximatedToolPathToLines(0.01)); // TODO maxError m_viewport = std::make_unique(); - m_viewport->addActor(m_path->actor()); - m_viewport->resetCamera(m_path->boundingBox()); + m_viewport->addActor(m_tool->actor()); + m_viewport->addActor(m_toolPath->actor()); + m_viewport->resetCamera(m_toolPath->boundingBox()); window->AddRenderer(m_viewport->renderer()); window->Render(); diff --git a/src/view/view3d/viewport.h b/src/view/view3d/viewport.h index 0644ef0..db15d0d 100644 --- a/src/view/view3d/viewport.h +++ b/src/view/view3d/viewport.h @@ -14,6 +14,7 @@ namespace view::view3d namespace internal { +class Tool; class ToolPath; class Viewport; @@ -24,7 +25,8 @@ class Viewport : public model::DocumentModelObserver private: model::Simulation m_simulation; QVTKOpenGLNativeWidget *m_vtkWidget; - std::unique_ptr m_path; + std::unique_ptr m_tool; + std::unique_ptr m_toolPath; std::unique_ptr m_viewport; protected: diff --git a/test/simulation.cpp b/test/simulation.cpp index f39f2de..890b659 100644 --- a/test/simulation.cpp +++ b/test/simulation.cpp @@ -41,9 +41,9 @@ TEST(SimulationTest, shouldHasMultiLayerDepth) model::Document::UPtr document = documentFromPolylines(std::move(polyline), settings); model::Simulation simulation(*document); - const geometry::Point3DList points = simulation.approximatedPathToLines(0.001); + const model::Simulation::ToolPathPoint3D::List points = simulation.approximatedToolPathToLines(0.001); - const int exceptedNbPoints = 2 /* retract + start */ + 2 * nbCut /* cut */ + 2 /* retract and home */; + const int exceptedNbPoints = 1 /* home */ + 2 /* retract + start */ + 2 * nbCut /* cut */ + 2 /* retract and home */; EXPECT_EQ(exceptedNbPoints, points.size()); } @@ -58,10 +58,35 @@ TEST(SimulationTest, shouldNotContainsDuplicatePoints) model::Document::UPtr document = documentFromPolylines(std::move(polyline), settings); model::Simulation simulation(*document); - const geometry::Point3DList points = simulation.approximatedPathToLines(0.001); + const model::Simulation::ToolPathPoint3D::List points = simulation.approximatedToolPathToLines(0.001); for (int i = 0, size = points.size() - 1; i < size; ++i) { - EXPECT_NE(points[i], points[i + 1]); + EXPECT_NE(points[i].position, points[i + 1].position); } } +#define EXPECT_POINT3D_EQ(a, b) \ + EXPECT_FLOAT_EQ(a.x(), b.x()); \ + EXPECT_FLOAT_EQ(a.y(), b.y()); \ + EXPECT_FLOAT_EQ(a.z(), b.z()); + +TEST(SimulationTest, shouldMatchPositionAtStartAndEndTime) +{ + const geometry::Bulge bulge(QVector2D(0, 0), QVector2D(1, 0), 0); + geometry::Polyline polyline({bulge}); + + const model::PathSettings settings{10, 10, 10, 1}; + model::Document::UPtr document = documentFromPolylines(std::move(polyline), settings); + + model::Simulation simulation(*document); + + const model::Simulation::ToolPathPoint3D::List points = simulation.approximatedToolPathToLines(0.001); + const float duration = simulation.duration(); + + const model::Simulation::ToolPathPoint3D firstToolPoint = simulation.position(0.0f); + EXPECT_POINT3D_EQ(firstToolPoint.position, points.front().position); + + const model::Simulation::ToolPathPoint3D lastToolPoint = simulation.position(duration); + EXPECT_POINT3D_EQ(lastToolPoint.position, points.back().position); +} + From 50e2117372538c98a5c1bfed84564d415abc99ad Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 25 Dec 2022 10:53:40 +0100 Subject: [PATCH 15/32] feat: move tool to given position at time --- CMakeLists.txt | 4 +- src/model/simulation.cpp | 2 +- src/model/simulation.h | 2 +- src/view/CMakeLists.txt | 2 +- src/view/mainwindow.cpp | 8 +-- src/view/mainwindow.h | 6 +- src/view/simulation/CMakeLists.txt | 11 ++++ src/view/simulation/internal/CMakeLists.txt | 12 ++++ src/view/simulation/internal/tool.cpp | 46 +++++++++++++ .../{view3d => simulation}/internal/tool.h | 6 +- .../internal/toolpath.cpp | 2 +- .../internal/toolpath.h | 2 +- src/view/simulation/internal/viewport.cpp | 40 +++++++++++ src/view/simulation/internal/viewport.h | 23 +++++++ src/view/simulation/simulation.cpp | 42 ++++++++++++ src/view/simulation/simulation.h | 39 +++++++++++ src/view/view3d/CMakeLists.txt | 11 ---- src/view/view3d/internal/CMakeLists.txt | 12 ---- src/view/view3d/internal/tool.cpp | 28 -------- src/view/view3d/internal/viewport.cpp | 34 ---------- src/view/view3d/internal/viewport.h | 24 ------- src/view/view3d/viewport.cpp | 54 --------------- src/view/view3d/viewport.h | 42 ------------ template/uic/CMakeLists.txt | 3 +- template/uic/simulation/CMakeLists.txt | 5 ++ template/uic/simulation/simulation.ui | 66 +++++++++++++++++++ test/simulation.cpp | 4 +- 27 files changed, 307 insertions(+), 223 deletions(-) create mode 100644 src/view/simulation/CMakeLists.txt create mode 100644 src/view/simulation/internal/CMakeLists.txt create mode 100644 src/view/simulation/internal/tool.cpp rename src/view/{view3d => simulation}/internal/tool.h (57%) rename src/view/{view3d => simulation}/internal/toolpath.cpp (98%) rename src/view/{view3d => simulation}/internal/toolpath.h (92%) create mode 100644 src/view/simulation/internal/viewport.cpp create mode 100644 src/view/simulation/internal/viewport.h create mode 100644 src/view/simulation/simulation.cpp create mode 100644 src/view/simulation/simulation.h delete mode 100644 src/view/view3d/CMakeLists.txt delete mode 100644 src/view/view3d/internal/CMakeLists.txt delete mode 100644 src/view/view3d/internal/tool.cpp delete mode 100644 src/view/view3d/internal/viewport.cpp delete mode 100644 src/view/view3d/internal/viewport.h delete mode 100644 src/view/view3d/viewport.cpp delete mode 100644 src/view/view3d/viewport.h create mode 100644 template/uic/simulation/CMakeLists.txt create mode 100644 template/uic/simulation/simulation.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index 1717b6b..4ea7560 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,8 +77,8 @@ set(LINK_LIBRARIES view-dialogs-settings view-task view-view2d - view-view3d - view-view3d-internal + view-simulation + view-simulation-internal model config importer-dxf diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp index 49f6505..a089567 100644 --- a/src/model/simulation.cpp +++ b/src/model/simulation.cpp @@ -263,7 +263,7 @@ Simulation::Simulation(const Document &document) { } -Simulation::ToolPathPoint3D Simulation::position(float time) const +Simulation::ToolPathPoint3D Simulation::toolPositionAtTime(float time) const { const Motion& motion = findMotionAtTime(time); return std::visit([time](auto &arg){ diff --git a/src/model/simulation.h b/src/model/simulation.h index 5c36b30..63c2d19 100644 --- a/src/model/simulation.h +++ b/src/model/simulation.h @@ -134,7 +134,7 @@ class Simulation : public common::Aggregable Simulation() = default; explicit Simulation(const Document &document); - ToolPathPoint3D position(float time) const; + ToolPathPoint3D toolPositionAtTime(float time) const; float duration() const; float toolRadius() const; diff --git a/src/view/CMakeLists.txt b/src/view/CMakeLists.txt index 46712dd..33184ab 100644 --- a/src/view/CMakeLists.txt +++ b/src/view/CMakeLists.txt @@ -1,7 +1,7 @@ add_subdirectory(dialogs) add_subdirectory(task) add_subdirectory(view2d) -add_subdirectory(view3d) +add_subdirectory(simulation) set(SRC info.cpp diff --git a/src/view/mainwindow.cpp b/src/view/mainwindow.cpp index 37d6dd6..b6e105f 100644 --- a/src/view/mainwindow.cpp +++ b/src/view/mainwindow.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include @@ -37,12 +37,12 @@ QWidget *MainWindow::setupLeftPanel() QWidget *MainWindow::setupCenterPanel() { view2d::Viewport *viewport2d = new view2d::Viewport(m_app); - m_viewport3d = new view3d::Viewport(m_app); + m_simulation = new simulation::Simulation(m_app); Info *info = new Info(*viewport2d, m_app); QSplitter *splitter = new QSplitter(Qt::Horizontal, this); splitter->addWidget(viewport2d); - splitter->addWidget(m_viewport3d); + splitter->addWidget(m_simulation); splitter->setStretchFactor(0, 1); splitter->setStretchFactor(1, 0); @@ -252,7 +252,7 @@ void MainWindow::displayError(const QString &message) void MainWindow::simulate() { model::Simulation simulation = m_app.createSimulation(); - m_viewport3d->setSimulation(std::move(simulation)); + m_simulation->setSimulation(std::move(simulation)); } } diff --git a/src/view/mainwindow.h b/src/view/mainwindow.h index 4dd1086..f3a85b3 100644 --- a/src/view/mainwindow.h +++ b/src/view/mainwindow.h @@ -13,10 +13,10 @@ class QComboBox; namespace view { -namespace view3d +namespace simulation { -class Viewport; +class Simulation; } @@ -25,7 +25,7 @@ class MainWindow : public QMainWindow, private Ui::MainWindow private: model::Application &m_app; - view3d::Viewport *m_viewport3d; + simulation::Simulation *m_simulation; QActionGroup m_openedDocumentActions; diff --git a/src/view/simulation/CMakeLists.txt b/src/view/simulation/CMakeLists.txt new file mode 100644 index 0000000..8624cd6 --- /dev/null +++ b/src/view/simulation/CMakeLists.txt @@ -0,0 +1,11 @@ +add_subdirectory(internal) + +set(SRC + simulation.cpp + + simulation.h +) + +add_library(view-simulation ${SRC}) +target_link_libraries(view-simulation PRIVATE ${VTK_LIBRARIES}) +add_dependencies(view-simulation generate-config uic) diff --git a/src/view/simulation/internal/CMakeLists.txt b/src/view/simulation/internal/CMakeLists.txt new file mode 100644 index 0000000..f90556d --- /dev/null +++ b/src/view/simulation/internal/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SRC + toolpath.cpp + viewport.cpp + tool.cpp + + toolpath.h + viewport.h + tool.h +) + +add_library(view-simulation-internal ${SRC}) +target_link_libraries(view-simulation-internal PRIVATE ${VTK_LIBRARIES}) diff --git a/src/view/simulation/internal/tool.cpp b/src/view/simulation/internal/tool.cpp new file mode 100644 index 0000000..5b9495e --- /dev/null +++ b/src/view/simulation/internal/tool.cpp @@ -0,0 +1,46 @@ +#include + +#include +#include +#include +#include +#include + +namespace view::simulation::internal +{ + +Tool::Tool(float radius, float height) +{ + vtkNew cylinderSource; + cylinderSource->SetCenter(0.0, height / 2.0, 0.0); + cylinderSource->SetRadius(radius); + cylinderSource->SetHeight(height); + cylinderSource->SetResolution(64); + + vtkNew mapper; + mapper->SetInputConnection(cylinderSource->GetOutputPort()); + m_actor->SetMapper(mapper); + m_actor->RotateX(90); +} + +vtkActor *Tool::actor() +{ + return m_actor; +} + +void Tool::setPosition(const model::Simulation::ToolPathPoint3D& position) +{ + const QVector3D& pos = position.position; + m_actor->SetPosition(pos.x(), pos.y(), pos.z()); + + static vtkNew namedColors; + + static vtkColor3d colorsByMoveType[] = { + namedColors->GetColor3d("AliceBlue"), // MoveType::NormalWithCut + namedColors->GetColor3d("DimGray") // MoveType::FastWithoutCut + }; + + m_actor->GetProperty()->SetColor(colorsByMoveType[static_cast(position.moveType)].GetData()); +} + +} diff --git a/src/view/view3d/internal/tool.h b/src/view/simulation/internal/tool.h similarity index 57% rename from src/view/view3d/internal/tool.h rename to src/view/simulation/internal/tool.h index 9239a2a..0b6c015 100644 --- a/src/view/view3d/internal/tool.h +++ b/src/view/simulation/internal/tool.h @@ -3,7 +3,9 @@ #include #include -namespace view::view3d::internal +#include + +namespace view::simulation::internal { class Tool @@ -15,6 +17,8 @@ class Tool explicit Tool(float radius, float height); vtkActor *actor(); + + void setPosition(const model::Simulation::ToolPathPoint3D& position); }; } diff --git a/src/view/view3d/internal/toolpath.cpp b/src/view/simulation/internal/toolpath.cpp similarity index 98% rename from src/view/view3d/internal/toolpath.cpp rename to src/view/simulation/internal/toolpath.cpp index f896dd8..70b5a35 100644 --- a/src/view/view3d/internal/toolpath.cpp +++ b/src/view/simulation/internal/toolpath.cpp @@ -11,7 +11,7 @@ #include // TODO -namespace view::view3d::internal +namespace view::simulation::internal { static vtkNew namedColors; diff --git a/src/view/view3d/internal/toolpath.h b/src/view/simulation/internal/toolpath.h similarity index 92% rename from src/view/view3d/internal/toolpath.h rename to src/view/simulation/internal/toolpath.h index 38748c7..fadf65c 100644 --- a/src/view/view3d/internal/toolpath.h +++ b/src/view/simulation/internal/toolpath.h @@ -5,7 +5,7 @@ #include #include -namespace view::view3d::internal +namespace view::simulation::internal { class ToolPath diff --git a/src/view/simulation/internal/viewport.cpp b/src/view/simulation/internal/viewport.cpp new file mode 100644 index 0000000..c5abcfa --- /dev/null +++ b/src/view/simulation/internal/viewport.cpp @@ -0,0 +1,40 @@ +#include + +#include +#include +#include +#include + +#include + +namespace view::simulation::internal +{ + +Viewport::Viewport(const model::Simulation& simulation) + :m_tool(simulation.toolRadius(), 1.0f), + m_toolPath(simulation.approximatedToolPathToLines(0.01)) +{ + vtkNew renderer; + renderer->SetBackground(0.0, 0.0, 0.0); + + renderer->AddActor(m_tool.actor()); + renderer->AddActor(m_toolPath.actor()); + + vtkNew axes; + axes->AxisLabelsOff(); + renderer->AddActor(axes); + + vtkNew renderWindow; + renderWindow->AddRenderer(renderer); + renderer->ResetCamera(m_toolPath.boundingBox()); + setRenderWindow(renderWindow); +} + +void Viewport::setToolPosition(const model::Simulation::ToolPathPoint3D& position) +{ + m_tool.setPosition(position); + renderWindow()->Render(); +} + +} + diff --git a/src/view/simulation/internal/viewport.h b/src/view/simulation/internal/viewport.h new file mode 100644 index 0000000..d79c894 --- /dev/null +++ b/src/view/simulation/internal/viewport.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include +#include + +namespace view::simulation::internal +{ + +class Viewport : public QVTKOpenGLNativeWidget +{ +private: + Tool m_tool; + ToolPath m_toolPath; + +public: + explicit Viewport(const model::Simulation& simulation); + + void setToolPosition(const model::Simulation::ToolPathPoint3D& position); +}; + +} diff --git a/src/view/simulation/simulation.cpp b/src/view/simulation/simulation.cpp new file mode 100644 index 0000000..a9214ba --- /dev/null +++ b/src/view/simulation/simulation.cpp @@ -0,0 +1,42 @@ +#include +#include + +namespace view::simulation +{ + +void Simulation::documentChanged() +{ +} + +void Simulation::moveToolAtTime(int ms) +{ + const float seconds = float(ms) / 1e3; + timeLabel->setText(QString("%1").arg(seconds, 0, 'f', 3)); + + const model::Simulation::ToolPathPoint3D toolPosition = m_simulation.toolPositionAtTime(seconds); + + m_viewport->setToolPosition(toolPosition); +} + +Simulation::Simulation(model::Application& app) + :DocumentModelObserver(app) +{ + setupUi(this); + + connect(timeSlider, &QSlider::valueChanged, this, &Simulation::moveToolAtTime); +} + +Simulation::~Simulation() = default; + +void Simulation::setSimulation(model::Simulation && simulation) +{ + m_simulation = std::move(simulation); + + m_viewport = std::make_unique(m_simulation); + container->addWidget(m_viewport.get()); + + const float secondDuration = m_simulation.duration(); + timeSlider->setMaximum(secondDuration * 1e3); +} + +} diff --git a/src/view/simulation/simulation.h b/src/view/simulation/simulation.h new file mode 100644 index 0000000..ccc38b5 --- /dev/null +++ b/src/view/simulation/simulation.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "uic/simulation/ui_simulation.h" + +#include + +namespace view::simulation +{ + +namespace internal +{ + +class Viewport; + +} + +class Simulation : private Ui::Simulation, public model::DocumentModelObserver +{ +private: + model::Simulation m_simulation; + std::unique_ptr m_viewport; + +protected: + void documentChanged() override; + +protected slots: + void moveToolAtTime(int ms); + +public: + explicit Simulation(model::Application &app); + ~Simulation(); + + void setSimulation(model::Simulation&& simulation); +}; + +} diff --git a/src/view/view3d/CMakeLists.txt b/src/view/view3d/CMakeLists.txt deleted file mode 100644 index d7ac5ca..0000000 --- a/src/view/view3d/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_subdirectory(internal) - -set(SRC - viewport.cpp - - viewport.h -) - -add_library(view-view3d ${SRC}) -target_link_libraries(view-view3d PRIVATE ${VTK_LIBRARIES}) -add_dependencies(view-view3d generate-config uic) diff --git a/src/view/view3d/internal/CMakeLists.txt b/src/view/view3d/internal/CMakeLists.txt deleted file mode 100644 index a58acea..0000000 --- a/src/view/view3d/internal/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -set(SRC - toolpath.cpp - viewport.cpp - tool.cpp - - toolpath.h - viewport.h - tool.h -) - -add_library(view-view3d-internal ${SRC}) -target_link_libraries(view-view3d-internal PRIVATE ${VTK_LIBRARIES}) diff --git a/src/view/view3d/internal/tool.cpp b/src/view/view3d/internal/tool.cpp deleted file mode 100644 index 93c3331..0000000 --- a/src/view/view3d/internal/tool.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include - -#include -#include - -namespace view::view3d::internal -{ - -Tool::Tool(float radius, float height) -{ - vtkNew cylinderSource; - cylinderSource->SetCenter(0.0, height / 2.0, 0.0); - cylinderSource->SetRadius(radius); - cylinderSource->SetHeight(height); - cylinderSource->SetResolution(64); - - vtkNew mapper; - mapper->SetInputConnection(cylinderSource->GetOutputPort()); - m_actor->SetMapper(mapper); - m_actor->RotateX(90); -} - -vtkActor *Tool::actor() -{ - return m_actor; -} - -} diff --git a/src/view/view3d/internal/viewport.cpp b/src/view/view3d/internal/viewport.cpp deleted file mode 100644 index 831c42a..0000000 --- a/src/view/view3d/internal/viewport.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include - -#include - -namespace view::view3d::internal -{ - -Viewport::Viewport() -{ - m_renderer->SetBackground(0.0, 0.0, 0.0); - - vtkNew axes; - axes->AxisLabelsOff(); - m_renderer->AddActor(axes); -} - -void Viewport::addActor(vtkActor* actor) -{ - m_renderer->AddActor(actor); -} - -void Viewport::resetCamera(const double bounds[6]) -{ - m_renderer->ResetCamera(bounds); -} - -vtkRenderer *Viewport::renderer() const -{ - return m_renderer; -} - - -} - diff --git a/src/view/view3d/internal/viewport.h b/src/view/view3d/internal/viewport.h deleted file mode 100644 index 6769419..0000000 --- a/src/view/view3d/internal/viewport.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include - -class vtkActor; - -namespace view::view3d::internal -{ - -class Viewport -{ -private: - vtkNew m_renderer; - -public: - explicit Viewport(); - - void addActor(vtkActor *actor); - void resetCamera(const double bounds[6]); - vtkRenderer *renderer() const; -}; - -} diff --git a/src/view/view3d/viewport.cpp b/src/view/view3d/viewport.cpp deleted file mode 100644 index 874495e..0000000 --- a/src/view/view3d/viewport.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include - -#include -#include -#include - -#include -#include - -#include - -namespace view::view3d -{ - -void Viewport::documentChanged() -{ -} - -void Viewport::resizeEvent(QResizeEvent *event) -{ - m_vtkWidget->resize(event->size()); -} - -Viewport::Viewport(model::Application& app) - :DocumentModelObserver(app), - m_vtkWidget(new QVTKOpenGLNativeWidget(this)) -{ - vtkNew renderWindow; - m_vtkWidget->setRenderWindow(renderWindow); - - m_vtkWidget->show(); -} - -void Viewport::setSimulation(model::Simulation && simulation) -{ - vtkRenderWindow *window = m_vtkWidget->renderWindow(); - if (m_viewport) { - window->RemoveRenderer(m_viewport->renderer()); - } - - m_simulation = std::move(simulation); - m_tool = std::make_unique(m_simulation.toolRadius(), 1.0f); // TODO conf - m_toolPath = std::make_unique(m_simulation.approximatedToolPathToLines(0.01)); // TODO maxError - - m_viewport = std::make_unique(); - m_viewport->addActor(m_tool->actor()); - m_viewport->addActor(m_toolPath->actor()); - m_viewport->resetCamera(m_toolPath->boundingBox()); - - window->AddRenderer(m_viewport->renderer()); - window->Render(); -} - -} diff --git a/src/view/view3d/viewport.h b/src/view/view3d/viewport.h deleted file mode 100644 index db15d0d..0000000 --- a/src/view/view3d/viewport.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include - -#include - -#include - -class QVTKOpenGLNativeWidget; - -namespace view::view3d -{ - -namespace internal -{ - -class Tool; -class ToolPath; -class Viewport; - -} - -class Viewport : public model::DocumentModelObserver -{ -private: - model::Simulation m_simulation; - QVTKOpenGLNativeWidget *m_vtkWidget; - std::unique_ptr m_tool; - std::unique_ptr m_toolPath; - std::unique_ptr m_viewport; - -protected: - void documentChanged() override; - void resizeEvent(QResizeEvent *event) override; - -public: - explicit Viewport(model::Application &app); - - void setSimulation(model::Simulation&& simulation); -}; - -} diff --git a/template/uic/CMakeLists.txt b/template/uic/CMakeLists.txt index 015a3a8..3b5343f 100644 --- a/template/uic/CMakeLists.txt +++ b/template/uic/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(dialogs) +add_subdirectory(simulation) qt5_wrap_ui(UIC_HEADERS info.ui @@ -9,5 +10,5 @@ qt5_wrap_ui(UIC_HEADERS ) add_custom_target(uic DEPENDS ${UIC_HEADERS}) -add_dependencies(uic uic-dialogs) +add_dependencies(uic uic-dialogs uic-simulation) diff --git a/template/uic/simulation/CMakeLists.txt b/template/uic/simulation/CMakeLists.txt new file mode 100644 index 0000000..51d2061 --- /dev/null +++ b/template/uic/simulation/CMakeLists.txt @@ -0,0 +1,5 @@ +qt5_wrap_ui(UIC_HEADERS + simulation.ui +) + +add_custom_target(uic-simulation DEPENDS ${UIC_HEADERS}) diff --git a/template/uic/simulation/simulation.ui b/template/uic/simulation/simulation.ui new file mode 100644 index 0000000..10936ba --- /dev/null +++ b/template/uic/simulation/simulation.ui @@ -0,0 +1,66 @@ + + + Simulation + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + + + 0 + + + + + 0.000 + + + + + + + 0 + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + + + + diff --git a/test/simulation.cpp b/test/simulation.cpp index 890b659..1796c1f 100644 --- a/test/simulation.cpp +++ b/test/simulation.cpp @@ -83,10 +83,10 @@ TEST(SimulationTest, shouldMatchPositionAtStartAndEndTime) const model::Simulation::ToolPathPoint3D::List points = simulation.approximatedToolPathToLines(0.001); const float duration = simulation.duration(); - const model::Simulation::ToolPathPoint3D firstToolPoint = simulation.position(0.0f); + const model::Simulation::ToolPathPoint3D firstToolPoint = simulation.toolPositionAtTime(0.0f); EXPECT_POINT3D_EQ(firstToolPoint.position, points.front().position); - const model::Simulation::ToolPathPoint3D lastToolPoint = simulation.position(duration); + const model::Simulation::ToolPathPoint3D lastToolPoint = simulation.toolPositionAtTime(duration); EXPECT_POINT3D_EQ(lastToolPoint.position, points.back().position); } From 22c95d4b7f6e1e8495bdde8ecf0bf3a482a8a9fc Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 25 Dec 2022 11:20:52 +0100 Subject: [PATCH 16/32] feat: interpolate arc in simulation --- src/geometry/arc.cpp | 7 +++++++ src/geometry/arc.h | 14 ++++---------- src/model/simulation.cpp | 3 ++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/geometry/arc.cpp b/src/geometry/arc.cpp index 4a64db6..3f94635 100644 --- a/src/geometry/arc.cpp +++ b/src/geometry/arc.cpp @@ -54,4 +54,11 @@ float Arc::length() const return std::abs(m_spanAngle) * radius(); } +QVector2D Arc::pointAtAngle(float angle) const +{ + const QVector2D relativeNormalizedPoint(std::cos(angle), std::sin(angle)); + const QVector2D point = (center() + relativeNormalizedPoint * radius()); + return point; +} + } diff --git a/src/geometry/arc.h b/src/geometry/arc.h index 6634210..4a94a3b 100644 --- a/src/geometry/arc.h +++ b/src/geometry/arc.h @@ -16,14 +16,6 @@ class Arc : public Circle float m_endAngle; float m_spanAngle; - template - void lineToArcPointVisit(float angle, Visitor &visitor) const - { - const QVector2D relativeNormalizedPoint(std::cos(angle), std::sin(angle)); - const QVector2D point = (center() + relativeNormalizedPoint * radius()); - visitor(point); - } - public: explicit Arc(const Circle &circle, const QVector2D &start, const QVector2D &end, float starAngle, float endAngle); @@ -36,6 +28,8 @@ class Arc : public Circle float length() const; + QVector2D pointAtAngle(float angle) const; + template void approximateToLinesVisit(float maxError, Visitor &&visitor) const { @@ -44,12 +38,12 @@ class Arc : public Circle if (orientation() == geometry::Orientation::CCW) { for (float angle = m_startAngle + angleStep, end = m_endAngle; angle < end; angle += angleStep) { - lineToArcPointVisit(angle, visitor); + visitor(pointAtAngle(angle)); } } else { for (float angle = m_startAngle - angleStep, end = m_endAngle; angle > end; angle -= angleStep) { - lineToArcPointVisit(angle, visitor); + visitor(pointAtAngle(angle)); } } diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp index a089567..302521d 100644 --- a/src/model/simulation.cpp +++ b/src/model/simulation.cpp @@ -87,7 +87,8 @@ Simulation::PlaneArcMotion::PlaneArcMotion(float depth, const geometry::Arc &arc Simulation::ToolPathPoint3D Simulation::PlaneArcMotion::pointAtTime(float time) const { - const QVector2D planePos = geometry::Lerp(m_arc.start(), m_arc.end(), timeFactor(time)); + const float angle = geometry::Lerp(m_arc.startAngle(), m_arc.endAngle(), timeFactor(time)); + const QVector2D planePos = m_arc.pointAtAngle(angle); return ToolPathPoint3D(QVector3D(planePos, m_depth), m_moveType); } From 345202bbf3712b1ac5c1f931f691f6ed34997a01 Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 25 Dec 2022 15:01:50 +0100 Subject: [PATCH 17/32] chore: setup timer to aniamtion time slider --- CMakeLists.txt | 1 + src/view/simulation/simulation.cpp | 6 ++++++ src/view/simulation/simulation.h | 2 ++ template/uic/info.ui | 4 ++-- template/uic/simulation/simulation.ui | 9 ++++++++- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ea7560..ce8742c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ set(INCLUDE_DIRS thirdparty/cereal/include thirdparty/cavaliercontours/include thirdparty/nanoflann/include + thirdparty/units/include thirdparty/yaml-cpp/include template ${CMAKE_BINARY_DIR}/src diff --git a/src/view/simulation/simulation.cpp b/src/view/simulation/simulation.cpp index a9214ba..ad12c6c 100644 --- a/src/view/simulation/simulation.cpp +++ b/src/view/simulation/simulation.cpp @@ -24,6 +24,12 @@ Simulation::Simulation(model::Application& app) setupUi(this); connect(timeSlider, &QSlider::valueChanged, this, &Simulation::moveToolAtTime); + + m_timer.setInterval(30); + m_timer.callOnTimeout([this](){ + timeSlider->setSliderPosition(timeSlider->sliderPosition() + 30); + }); + m_timer.start(); } Simulation::~Simulation() = default; diff --git a/src/view/simulation/simulation.h b/src/view/simulation/simulation.h index ccc38b5..d1fc591 100644 --- a/src/view/simulation/simulation.h +++ b/src/view/simulation/simulation.h @@ -6,6 +6,7 @@ #include "uic/simulation/ui_simulation.h" #include +#include namespace view::simulation { @@ -22,6 +23,7 @@ class Simulation : private Ui::Simulation, public model::DocumentModelObserver m_viewport; + QTimer m_timer; protected: void documentChanged() override; diff --git a/template/uic/info.ui b/template/uic/info.ui index 86c9eb8..cffb76f 100644 --- a/template/uic/info.ui +++ b/template/uic/info.ui @@ -6,8 +6,8 @@ 0 0 - 568 - 76 + 563 + 64 diff --git a/template/uic/simulation/simulation.ui b/template/uic/simulation/simulation.ui index 10936ba..418d477 100644 --- a/template/uic/simulation/simulation.ui +++ b/template/uic/simulation/simulation.ui @@ -30,13 +30,20 @@ - + 0 0 + + + + P + + + From 783b96cece1e68e0a0dae3e09382ac94b2e52139 Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 25 Dec 2022 22:39:17 +0100 Subject: [PATCH 18/32] chore: add icons for simulate and start stop buttons --- resource/icons/playback-pause.svg | 8 ++++++++ resource/icons/playback-start.svg | 8 ++++++++ resource/icons/simulate.svg | 13 +++++++++++++ resource/resource.qrc | 3 +++ src/model/simulation.cpp | 17 +++++++++++------ src/view/simulation/simulation.cpp | 26 +++++++++++++++++++++----- src/view/simulation/simulation.h | 1 + template/uic/mainwindow.ui | 4 ++++ template/uic/simulation/simulation.ui | 10 ++++++++-- 9 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 resource/icons/playback-pause.svg create mode 100644 resource/icons/playback-start.svg create mode 100644 resource/icons/simulate.svg diff --git a/resource/icons/playback-pause.svg b/resource/icons/playback-pause.svg new file mode 100644 index 0000000..d6a4dd1 --- /dev/null +++ b/resource/icons/playback-pause.svg @@ -0,0 +1,8 @@ + + + + diff --git a/resource/icons/playback-start.svg b/resource/icons/playback-start.svg new file mode 100644 index 0000000..25c5fab --- /dev/null +++ b/resource/icons/playback-start.svg @@ -0,0 +1,8 @@ + + + + diff --git a/resource/icons/simulate.svg b/resource/icons/simulate.svg new file mode 100644 index 0000000..1fa36b5 --- /dev/null +++ b/resource/icons/simulate.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resource/resource.qrc b/resource/resource.qrc index 9b6db91..3d4bac2 100644 --- a/resource/resource.qrc +++ b/resource/resource.qrc @@ -21,10 +21,13 @@ icons/list-remove.svg icons/path-inset.svg icons/path-outset.svg + icons/playback-pause.svg + icons/playback-start.svg icons/pocket-shape.svg icons/resizecol.svg icons/set-origin.svg icons/show-path-outline.svg + icons/simulate.svg icons/transform-rotate.svg icons/zoom-in.svg diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp index 302521d..f2b776f 100644 --- a/src/model/simulation.cpp +++ b/src/model/simulation.cpp @@ -13,19 +13,24 @@ Simulation::ToolPathPoint3D::ToolPathPoint3D(const QVector3D& position, model::S { } -float entityDuration(const geometry::Line &line, float feedRate) +float mmPerMinTommPerSec(float mmPerMin) { - return line.length() / feedRate; + return mmPerMin / 60.0f; } -float entityDuration(float start, float end, float feedRate) +float entityDuration(const geometry::Line &line, float feedRateMmPerMin) { - return std::abs(end - start) / feedRate; + return line.length() / mmPerMinTommPerSec(feedRateMmPerMin); } -float entityDuration(const geometry::Arc &arc, float feedRate) +float entityDuration(float start, float end, float feedRateMmPerMin) { - return arc.length() / feedRate; + return std::abs(end - start) / mmPerMinTommPerSec(feedRateMmPerMin); +} + +float entityDuration(const geometry::Arc &arc, float feedRateMmPerMin) +{ + return arc.length() / mmPerMinTommPerSec(feedRateMmPerMin); } Simulation::Traversable::Traversable(float startTime, float duration, MoveType moveType) diff --git a/src/view/simulation/simulation.cpp b/src/view/simulation/simulation.cpp index ad12c6c..481f95f 100644 --- a/src/view/simulation/simulation.cpp +++ b/src/view/simulation/simulation.cpp @@ -1,6 +1,8 @@ #include #include +#include + namespace view::simulation { @@ -10,26 +12,40 @@ void Simulation::documentChanged() void Simulation::moveToolAtTime(int ms) { - const float seconds = float(ms) / 1e3; - timeLabel->setText(QString("%1").arg(seconds, 0, 'f', 3)); + const QString timeText = QTime::fromMSecsSinceStartOfDay(ms).toString("hh:mm:ss:zzz"); + timeLabel->setText(timeText); + const float seconds = float(ms) / 1e3; const model::Simulation::ToolPathPoint3D toolPosition = m_simulation.toolPositionAtTime(seconds); m_viewport->setToolPosition(toolPosition); } +void Simulation::startStopToolAnimation() +{ + if (m_timer.isActive()) { + m_timer.stop(); + startStopButton->setIcon(QIcon(":/icons/playback-start.svg")); + } + else { + m_timer.start(); + startStopButton->setIcon(QIcon(":/icons/playback-pause.svg")); + } +} + Simulation::Simulation(model::Application& app) :DocumentModelObserver(app) { setupUi(this); connect(timeSlider, &QSlider::valueChanged, this, &Simulation::moveToolAtTime); + connect(startStopButton, &QPushButton::clicked, this, &Simulation::startStopToolAnimation); - m_timer.setInterval(30); + static const int timerIntervalMs = 30; + m_timer.setInterval(timerIntervalMs); m_timer.callOnTimeout([this](){ - timeSlider->setSliderPosition(timeSlider->sliderPosition() + 30); + timeSlider->setSliderPosition(timeSlider->sliderPosition() + timerIntervalMs); }); - m_timer.start(); } Simulation::~Simulation() = default; diff --git a/src/view/simulation/simulation.h b/src/view/simulation/simulation.h index d1fc591..a2500e7 100644 --- a/src/view/simulation/simulation.h +++ b/src/view/simulation/simulation.h @@ -30,6 +30,7 @@ class Simulation : private Ui::Simulation, public model::DocumentModelObserver + + + :/icons/simulate.svg:/icons/simulate.svg + Simulate diff --git a/template/uic/simulation/simulation.ui b/template/uic/simulation/simulation.ui index 418d477..f4827a6 100644 --- a/template/uic/simulation/simulation.ui +++ b/template/uic/simulation/simulation.ui @@ -40,7 +40,11 @@ - P + + + + + :/icons/playback-start.svg:/icons/playback-start.svg @@ -68,6 +72,8 @@ - + + + From f4c394e6e661f550bf7359f17263206f6912fcd2 Mon Sep 17 00:00:00 2001 From: tristan Date: Fri, 30 Dec 2022 18:33:29 +0100 Subject: [PATCH 19/32] chore: simplify simulation visibility state --- src/view/mainwindow.cpp | 5 ++++- src/view/simulation/simulation.cpp | 7 +------ src/view/simulation/simulation.h | 8 ++------ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/view/mainwindow.cpp b/src/view/mainwindow.cpp index b6e105f..80846d0 100644 --- a/src/view/mainwindow.cpp +++ b/src/view/mainwindow.cpp @@ -37,7 +37,7 @@ QWidget *MainWindow::setupLeftPanel() QWidget *MainWindow::setupCenterPanel() { view2d::Viewport *viewport2d = new view2d::Viewport(m_app); - m_simulation = new simulation::Simulation(m_app); + m_simulation = new simulation::Simulation(); Info *info = new Info(*viewport2d, m_app); QSplitter *splitter = new QSplitter(Qt::Horizontal, this); @@ -241,6 +241,8 @@ void MainWindow::setSelectionOrigin() void MainWindow::documentChanged(model::Document *newDocument) { setDocumentToolsEnabled((newDocument != nullptr)); + + m_simulation->hide(); } void MainWindow::displayError(const QString &message) @@ -253,6 +255,7 @@ void MainWindow::simulate() { model::Simulation simulation = m_app.createSimulation(); m_simulation->setSimulation(std::move(simulation)); + m_simulation->show(); } } diff --git a/src/view/simulation/simulation.cpp b/src/view/simulation/simulation.cpp index 481f95f..9a42f64 100644 --- a/src/view/simulation/simulation.cpp +++ b/src/view/simulation/simulation.cpp @@ -6,10 +6,6 @@ namespace view::simulation { -void Simulation::documentChanged() -{ -} - void Simulation::moveToolAtTime(int ms) { const QString timeText = QTime::fromMSecsSinceStartOfDay(ms).toString("hh:mm:ss:zzz"); @@ -33,8 +29,7 @@ void Simulation::startStopToolAnimation() } } -Simulation::Simulation(model::Application& app) - :DocumentModelObserver(app) +Simulation::Simulation() { setupUi(this); diff --git a/src/view/simulation/simulation.h b/src/view/simulation/simulation.h index a2500e7..c89a0f8 100644 --- a/src/view/simulation/simulation.h +++ b/src/view/simulation/simulation.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include "uic/simulation/ui_simulation.h" @@ -18,22 +17,19 @@ class Viewport; } -class Simulation : private Ui::Simulation, public model::DocumentModelObserver +class Simulation : private Ui::Simulation, public QWidget { private: model::Simulation m_simulation; std::unique_ptr m_viewport; QTimer m_timer; -protected: - void documentChanged() override; - protected slots: void moveToolAtTime(int ms); void startStopToolAnimation(); public: - explicit Simulation(model::Application &app); + explicit Simulation(); ~Simulation(); void setSimulation(model::Simulation&& simulation); From ae679418fc5eb4a5530c620ba98479f059ec895a Mon Sep 17 00:00:00 2001 From: tristan Date: Sat, 31 Dec 2022 21:13:02 +0100 Subject: [PATCH 20/32] feat: Add config param for simulation fast move feed rate --- src/model/application.cpp | 3 ++- src/model/simulation.cpp | 8 ++++---- src/model/simulation.h | 4 ++-- template/config.xml | 3 +++ test/simulation.cpp | 12 ++++++------ 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/model/application.cpp b/src/model/application.cpp index 0218b87..990a916 100644 --- a/src/model/application.cpp +++ b/src/model/application.cpp @@ -360,7 +360,8 @@ void Application::showHidden() Simulation Application::createSimulation() { - return Simulation(*m_openedDocument); + const float fastMoveFeedRate = m_config.root().simulation().fastMoveFeedRate(); + return Simulation(*m_openedDocument, fastMoveFeedRate); } } diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp index f2b776f..b2f0164 100644 --- a/src/model/simulation.cpp +++ b/src/model/simulation.cpp @@ -240,12 +240,12 @@ const Simulation::Motion &Simulation::findMotionAtTime(float time) const return *motionIt; } -Simulation::MotionList Simulation::renderDocumentToMotions(const Document &document) +Simulation::MotionList Simulation::renderDocumentToMotions(const Document &document, float fastMoveFeedRate) { const config::Tools::Tool& tool = document.toolConfig(); const config::Profiles::Profile& profile = document.profileConfig(); - RenderVisitor visitor(42); // TODO fastfeedRate config + RenderVisitor visitor(fastMoveFeedRate); exporter::renderer::Renderer renderer(tool, profile, visitor); renderer.render(document); @@ -262,8 +262,8 @@ float Simulation::totalDurationOfMotions(const MotionList& motions) } -Simulation::Simulation(const Document &document) - :m_motions(renderDocumentToMotions(document)), +Simulation::Simulation(const Document &document, float fastMoveFeedRate) + :m_motions(renderDocumentToMotions(document, fastMoveFeedRate)), m_duration(totalDurationOfMotions(m_motions)), m_toolRadius(document.toolConfig().general().radius()) { diff --git a/src/model/simulation.h b/src/model/simulation.h index 63c2d19..9a8d398 100644 --- a/src/model/simulation.h +++ b/src/model/simulation.h @@ -127,12 +127,12 @@ class Simulation : public common::Aggregable const Motion &findMotionAtTime(float time) const; - static MotionList renderDocumentToMotions(const Document &document); + static MotionList renderDocumentToMotions(const Document &document, float fastMoveFeedRate); static float totalDurationOfMotions(const MotionList& motions); public: Simulation() = default; - explicit Simulation(const Document &document); + explicit Simulation(const Document &document, float fastMoveFeedRate); ToolPathPoint3D toolPositionAtTime(float time) const; float duration() const; diff --git a/template/config.xml b/template/config.xml index f0c2fff..6173e73 100644 --- a/template/config.xml +++ b/template/config.xml @@ -41,4 +41,7 @@ + + + diff --git a/test/simulation.cpp b/test/simulation.cpp index 1796c1f..aa67658 100644 --- a/test/simulation.cpp +++ b/test/simulation.cpp @@ -40,7 +40,7 @@ TEST(SimulationTest, shouldHasMultiLayerDepth) const model::PathSettings settings{10, 10, 10, nbCut - 1}; model::Document::UPtr document = documentFromPolylines(std::move(polyline), settings); - model::Simulation simulation(*document); + model::Simulation simulation(*document, 100.0f); const model::Simulation::ToolPathPoint3D::List points = simulation.approximatedToolPathToLines(0.001); const int exceptedNbPoints = 1 /* home */ + 2 /* retract + start */ + 2 * nbCut /* cut */ + 2 /* retract and home */; @@ -57,7 +57,7 @@ TEST(SimulationTest, shouldNotContainsDuplicatePoints) const model::PathSettings settings{10, 10, 10, nbCut - 1}; model::Document::UPtr document = documentFromPolylines(std::move(polyline), settings); - model::Simulation simulation(*document); + model::Simulation simulation(*document, 100.0f); const model::Simulation::ToolPathPoint3D::List points = simulation.approximatedToolPathToLines(0.001); for (int i = 0, size = points.size() - 1; i < size; ++i) { @@ -66,9 +66,9 @@ TEST(SimulationTest, shouldNotContainsDuplicatePoints) } #define EXPECT_POINT3D_EQ(a, b) \ - EXPECT_FLOAT_EQ(a.x(), b.x()); \ - EXPECT_FLOAT_EQ(a.y(), b.y()); \ - EXPECT_FLOAT_EQ(a.z(), b.z()); + EXPECT_NEAR(a.x(), b.x(), 1e-5); \ + EXPECT_NEAR(a.y(), b.y(), 1e-5); \ + EXPECT_NEAR(a.z(), b.z(), 1e-5); TEST(SimulationTest, shouldMatchPositionAtStartAndEndTime) { @@ -78,7 +78,7 @@ TEST(SimulationTest, shouldMatchPositionAtStartAndEndTime) const model::PathSettings settings{10, 10, 10, 1}; model::Document::UPtr document = documentFromPolylines(std::move(polyline), settings); - model::Simulation simulation(*document); + model::Simulation simulation(*document, 100.0f); const model::Simulation::ToolPathPoint3D::List points = simulation.approximatedToolPathToLines(0.001); const float duration = simulation.duration(); From faafdee8356de23ee6e954f091b06f990d5c3927 Mon Sep 17 00:00:00 2001 From: tristan Date: Mon, 6 Feb 2023 20:27:37 +0100 Subject: [PATCH 21/32] chore: Replace vtk with Qt3D --- CMakeLists.txt | 18 ++-- src/main.cpp | 4 - src/view/simulation/CMakeLists.txt | 1 - src/view/simulation/internal/CMakeLists.txt | 7 +- src/view/simulation/internal/scene.cpp | 20 ++++ src/view/simulation/internal/scene.h | 21 ++++ src/view/simulation/internal/tool.cpp | 47 ++++----- src/view/simulation/internal/tool.h | 14 +-- src/view/simulation/internal/toolpath.cpp | 108 ++++++++++++-------- src/view/simulation/internal/toolpath.h | 18 ++-- src/view/simulation/internal/viewport.cpp | 46 ++++----- src/view/simulation/internal/viewport.h | 17 ++- src/view/simulation/simulation.cpp | 11 +- src/view/simulation/simulation.h | 4 + thirdparty/CMakeLists.txt | 1 + thirdparty/cavaliercontours | 2 +- thirdparty/fmt | 1 + 17 files changed, 198 insertions(+), 142 deletions(-) create mode 100644 src/view/simulation/internal/scene.cpp create mode 100644 src/view/simulation/internal/scene.h create mode 160000 thirdparty/fmt diff --git a/CMakeLists.txt b/CMakeLists.txt index ce8742c..62fd227 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,19 +38,13 @@ find_package(codecov) find_package(PythonInterp REQUIRED) -find_package(VTK COMPONENTS REQUIRED - CommonColor - CommonCore - GUISupportQt - RenderingCore - RenderingAnnotation -) - find_package(Qt5 COMPONENTS REQUIRED Core Widgets Gui Svg + 3DCore + 3DExtras ) #qt_standard_project_setup() @@ -60,6 +54,7 @@ set(INCLUDE_DIRS thirdparty thirdparty/cereal/include thirdparty/cavaliercontours/include + thirdparty/fmt/include thirdparty/nanoflann/include thirdparty/units/include thirdparty/yaml-cpp/include @@ -68,7 +63,8 @@ set(INCLUDE_DIRS ${CMAKE_BINARY_DIR}/template ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} - ${VTK_INCLUDE_DIRS} + ${Qt53DCore_INCLUDE_DIRS} + ${Qt53DExtras_INCLUDE_DIRS} ) set(LINK_LIBRARIES @@ -93,8 +89,9 @@ set(LINK_LIBRARIES fmt::fmt Qt5::Widgets Qt5::Svg + Qt5::3DCore + Qt5::3DExtras yaml-cpp - ${VTK_LIBRARIES} ) include_directories(${INCLUDE_DIRS}) @@ -110,7 +107,6 @@ endif() add_executable(dxfplotter src/main.cpp) target_link_libraries(dxfplotter ${LINK_LIBRARIES}) -vtk_module_autoinit(TARGETS dxfplotter MODULES ${VTK_LIBRARIES}) add_coverage(dxfplotter) install(TARGETS dxfplotter DESTINATION bin) diff --git a/src/main.cpp b/src/main.cpp index 567fde9..b72ce46 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,8 +9,6 @@ #include #include -#include - void setDarkPalette(QApplication &qapp) { QPalette palette; @@ -35,8 +33,6 @@ void setDarkPalette(QApplication &qapp) int main(int argc, char *argv[]) { Q_INIT_RESOURCE(resource); - // Needed to ensure appropriate OpenGL context is created for VTK rendering. - QSurfaceFormat::setDefaultFormat(QVTKOpenGLNativeWidget::defaultFormat()); QApplication qapp(argc, argv); qapp.setApplicationName("dxfplotter"); diff --git a/src/view/simulation/CMakeLists.txt b/src/view/simulation/CMakeLists.txt index 8624cd6..9ee677f 100644 --- a/src/view/simulation/CMakeLists.txt +++ b/src/view/simulation/CMakeLists.txt @@ -7,5 +7,4 @@ set(SRC ) add_library(view-simulation ${SRC}) -target_link_libraries(view-simulation PRIVATE ${VTK_LIBRARIES}) add_dependencies(view-simulation generate-config uic) diff --git a/src/view/simulation/internal/CMakeLists.txt b/src/view/simulation/internal/CMakeLists.txt index f90556d..7637aa4 100644 --- a/src/view/simulation/internal/CMakeLists.txt +++ b/src/view/simulation/internal/CMakeLists.txt @@ -1,12 +1,13 @@ set(SRC + scene.cpp toolpath.cpp - viewport.cpp tool.cpp + viewport.cpp + scene.h toolpath.h - viewport.h tool.h + viewport.h ) add_library(view-simulation-internal ${SRC}) -target_link_libraries(view-simulation-internal PRIVATE ${VTK_LIBRARIES}) diff --git a/src/view/simulation/internal/scene.cpp b/src/view/simulation/internal/scene.cpp new file mode 100644 index 0000000..e961b92 --- /dev/null +++ b/src/view/simulation/internal/scene.cpp @@ -0,0 +1,20 @@ +#include + +#include + +namespace view::simulation::internal +{ + +Scene::Scene(const model::Simulation& simulation) + :m_tool(this, simulation.toolRadius(), 1.0f), + m_toolPath(this, simulation.approximatedToolPathToLines(0.01)) +{ +} + +void Scene::setToolPosition(const model::Simulation::ToolPathPoint3D& position) +{ + m_tool.setPosition(position); +} + +} + diff --git a/src/view/simulation/internal/scene.h b/src/view/simulation/internal/scene.h new file mode 100644 index 0000000..4d12de8 --- /dev/null +++ b/src/view/simulation/internal/scene.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace view::simulation::internal +{ + +class Scene : public Qt3DCore::QEntity +{ +private: + Tool m_tool; + ToolPath m_toolPath; + +public: + explicit Scene(const model::Simulation& simulation); + + void setToolPosition(const model::Simulation::ToolPathPoint3D& position); +}; + +} diff --git a/src/view/simulation/internal/tool.cpp b/src/view/simulation/internal/tool.cpp index 5b9495e..b299d07 100644 --- a/src/view/simulation/internal/tool.cpp +++ b/src/view/simulation/internal/tool.cpp @@ -1,46 +1,37 @@ #include -#include -#include -#include -#include -#include +#include namespace view::simulation::internal { -Tool::Tool(float radius, float height) +Tool::Tool(Qt3DCore::QEntity *parent, float radius, float height) + :Qt3DCore::QEntity(parent), + m_transform(new Qt3DCore::QTransform(this)), + m_material(new Qt3DExtras::QGoochMaterial(this)) { - vtkNew cylinderSource; - cylinderSource->SetCenter(0.0, height / 2.0, 0.0); - cylinderSource->SetRadius(radius); - cylinderSource->SetHeight(height); - cylinderSource->SetResolution(64); - - vtkNew mapper; - mapper->SetInputConnection(cylinderSource->GetOutputPort()); - m_actor->SetMapper(mapper); - m_actor->RotateX(90); -} + Qt3DExtras::QCylinderMesh *mesh = new Qt3DExtras::QCylinderMesh(this); + mesh->setRadius(radius); + mesh->setLength(height); -vtkActor *Tool::actor() -{ - return m_actor; + m_transform->setRotationX(90); + + addComponent(m_transform); + addComponent(mesh); + addComponent(m_material); } void Tool::setPosition(const model::Simulation::ToolPathPoint3D& position) { - const QVector3D& pos = position.position; - m_actor->SetPosition(pos.x(), pos.y(), pos.z()); - - static vtkNew namedColors; + m_transform->setTranslation(position.position); - static vtkColor3d colorsByMoveType[] = { - namedColors->GetColor3d("AliceBlue"), // MoveType::NormalWithCut - namedColors->GetColor3d("DimGray") // MoveType::FastWithoutCut + static const QColor colorsByMoveType[] = { + {240, 248, 255}, + {105, 105, 105} }; - m_actor->GetProperty()->SetColor(colorsByMoveType[static_cast(position.moveType)].GetData()); + const QColor &colorForCurrentMoveType = colorsByMoveType[static_cast(position.moveType)]; + m_material->setDiffuse(colorForCurrentMoveType); } } diff --git a/src/view/simulation/internal/tool.h b/src/view/simulation/internal/tool.h index 0b6c015..9f3a868 100644 --- a/src/view/simulation/internal/tool.h +++ b/src/view/simulation/internal/tool.h @@ -1,22 +1,22 @@ #pragma once -#include -#include +#include +#include +#include #include namespace view::simulation::internal { -class Tool +class Tool : public Qt3DCore::QEntity { private: - vtkNew m_actor; + Qt3DCore::QTransform *m_transform; + Qt3DExtras::QGoochMaterial *m_material; public: - explicit Tool(float radius, float height); - - vtkActor *actor(); + explicit Tool(Qt3DCore::QEntity *parent, float radius, float height); void setPosition(const model::Simulation::ToolPathPoint3D& position); }; diff --git a/src/view/simulation/internal/toolpath.cpp b/src/view/simulation/internal/toolpath.cpp index 70b5a35..3e9a4e7 100644 --- a/src/view/simulation/internal/toolpath.cpp +++ b/src/view/simulation/internal/toolpath.cpp @@ -1,78 +1,100 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include + +#include + +#include +#include #include // TODO namespace view::simulation::internal { -static vtkNew namedColors; +struct PackedVector3D +{ + float x; + float y; + float z; + + PackedVector3D() = default; + + explicit PackedVector3D(const QVector3D& other) + :x(other.x()), + y(other.y()), + z(other.z()) + { + } +}; void ToolPath::createPolylineFromPoints(const model::Simulation::ToolPathPoint3D::List& points) { + static const uint32_t colorsByMoveType[] = { + (uint32_t)QColor(255, 69, 0).value(), + (uint32_t)QColor(72, 61, 139).value() + }; + const int nbPoints = points.size(); - const int nbLines = nbPoints - 1; - vtkNew vertices; - vertices->SetNumberOfPoints(nbPoints); + m_packedPoints = std::make_unique(nbPoints); + m_colors = std::make_unique(nbPoints); + m_indices = std::make_unique(nbPoints); for (int i = 0; i < nbPoints; ++i) { const model::Simulation::ToolPathPoint3D &point = points[i]; - const QVector3D& position = point.position; - vertices->SetPoint(i, position.x(), position.y(), position.z()); + m_packedPoints[i] = PackedVector3D(point.position); + m_colors[i] = colorsByMoveType[static_cast(point.moveType)]; + m_indices[i] = i; } - vtkNew colors; - colors->SetNumberOfComponents(3); - colors->SetNumberOfTuples(nbLines); + Qt3DRender::QGeometry *geometry = new Qt3DRender::QGeometry(this); - static const vtkColor3ub colorsByMoveType[] = { - namedColors->GetColor3ub("OrangeRed"), // MoveType::NormalWithCut - namedColors->GetColor3ub("SlateBlue") // MoveType::FastWithoutCut - }; + const QByteArray vertexData = QByteArray::fromRawData((const char *)m_packedPoints.get(), sizeof(PackedVector3D) * nbPoints); + Qt3DRender::QBuffer *vertexBuffer = new Qt3DRender::QBuffer(geometry); + vertexBuffer->setData(vertexData); - vtkNew cells; - for (int i = 0; i < nbLines; ++i) { - const model::Simulation::ToolPathPoint3D &secondPoint = points[i + 1]; + const QByteArray colorData = QByteArray::fromRawData((const char *)m_colors.get(), sizeof(uint32_t) * nbPoints); + Qt3DRender::QBuffer *colorBuffer = new Qt3DRender::QBuffer(geometry); + colorBuffer->setData(colorData); - colors->SetTypedTuple(i, colorsByMoveType[static_cast(secondPoint.moveType)].GetData()); + const QByteArray indexData = QByteArray::fromRawData((const char *)m_indices.get(), sizeof(uint32_t) * nbPoints); + Qt3DRender::QBuffer *indexBuffer = new Qt3DRender::QBuffer(geometry); + indexBuffer->setData(indexData); - vtkNew line; - line->GetPointIds()->SetId(0, i); - line->GetPointIds()->SetId(1, i + 1); - cells->InsertNextCell(line); - } + Qt3DRender::QAttribute *vertexAttribute = new Qt3DRender::QAttribute(vertexBuffer, Qt3DRender::QAttribute::defaultPositionAttributeName(), Qt3DRender::QAttribute::Float, 3, nbPoints); + vertexAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); + + Qt3DRender::QAttribute *colorAttribute = new Qt3DRender::QAttribute(colorBuffer, Qt3DRender::QAttribute::defaultColorAttributeName(), Qt3DRender::QAttribute::UnsignedByte, 4, nbPoints); + colorAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); - vertices->GetBounds(m_boundingBox); + Qt3DRender::QAttribute *indexAttribute = new Qt3DRender::QAttribute(indexBuffer, Qt3DRender::QAttribute::UnsignedInt, 3, nbPoints); + indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute); - vtkNew polyData; - polyData->SetPoints(vertices); - polyData->SetLines(cells); - polyData->GetCellData()->SetScalars(colors); + geometry->addAttribute(vertexAttribute); + geometry->addAttribute(colorAttribute); + geometry->addAttribute(indexAttribute); - vtkNew mapper; - mapper->SetInputData(polyData); + Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer(this); + renderer->setGeometry(geometry); + renderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::LineStrip); - m_actor->SetMapper(mapper); + Qt3DExtras::QPerVertexColorMaterial *material = new Qt3DExtras::QPerVertexColorMaterial(this); + + addComponent(material); + addComponent(renderer); } -ToolPath::ToolPath(const model::Simulation::ToolPathPoint3D::List& points) +ToolPath::ToolPath(Qt3DCore::QEntity *parent, const model::Simulation::ToolPathPoint3D::List& points) + :Qt3DCore::QEntity(parent) { createPolylineFromPoints(points); } -vtkActor *ToolPath::actor() -{ - return m_actor; -} +ToolPath::~ToolPath() = default; const double (&ToolPath::boundingBox() const)[6] { diff --git a/src/view/simulation/internal/toolpath.h b/src/view/simulation/internal/toolpath.h index fadf65c..5c5c359 100644 --- a/src/view/simulation/internal/toolpath.h +++ b/src/view/simulation/internal/toolpath.h @@ -1,26 +1,30 @@ #pragma once -#include +#include -#include -#include +#include namespace view::simulation::internal { -class ToolPath +struct PackedVector3D; + +class ToolPath : public Qt3DCore::QEntity { private: - vtkNew m_actor; double m_boundingBox[6]; + std::unique_ptr m_packedPoints; + std::unique_ptr m_colors; + std::unique_ptr m_indices; + void createPolylineFromPoints(const model::Simulation::ToolPathPoint3D::List &points); public: ToolPath() = default; - explicit ToolPath(const model::Simulation::ToolPathPoint3D::List &points); + explicit ToolPath(Qt3DCore::QEntity *parent, const model::Simulation::ToolPathPoint3D::List &points); + ~ToolPath(); - vtkActor *actor(); const double (&boundingBox() const)[6]; }; diff --git a/src/view/simulation/internal/viewport.cpp b/src/view/simulation/internal/viewport.cpp index c5abcfa..f0cb426 100644 --- a/src/view/simulation/internal/viewport.cpp +++ b/src/view/simulation/internal/viewport.cpp @@ -1,40 +1,36 @@ #include +#include -#include -#include -#include -#include +#include +#include +#include -#include +#include namespace view::simulation::internal { -Viewport::Viewport(const model::Simulation& simulation) - :m_tool(simulation.toolRadius(), 1.0f), - m_toolPath(simulation.approximatedToolPathToLines(0.01)) +Viewport::Viewport() { - vtkNew renderer; - renderer->SetBackground(0.0, 0.0, 0.0); - - renderer->AddActor(m_tool.actor()); - renderer->AddActor(m_toolPath.actor()); - - vtkNew axes; - axes->AxisLabelsOff(); - renderer->AddActor(axes); - - vtkNew renderWindow; - renderWindow->AddRenderer(renderer); - renderer->ResetCamera(m_toolPath.boundingBox()); - setRenderWindow(renderWindow); + defaultFrameGraph()->setClearColor(QColor(0, 0, 0)); } -void Viewport::setToolPosition(const model::Simulation::ToolPathPoint3D& position) +QWidget *Viewport::container() { - m_tool.setPosition(position); - renderWindow()->Render(); + return QWidget::createWindowContainer(this); } +void Viewport::setScene(Scene *scene) +{ + setRootEntity(scene); + + camera()->viewAll(); + + Qt3DExtras::QOrbitCameraController *camController = new Qt3DExtras::QOrbitCameraController(scene); + camController->setLinearSpeed( 200.0f ); + camController->setLookSpeed( 200.0f ); + camController->setCamera(camera()); + } +} diff --git a/src/view/simulation/internal/viewport.h b/src/view/simulation/internal/viewport.h index d79c894..5a49d0e 100644 --- a/src/view/simulation/internal/viewport.h +++ b/src/view/simulation/internal/viewport.h @@ -1,23 +1,22 @@ #pragma once -#include - -#include -#include +#include namespace view::simulation::internal { -class Viewport : public QVTKOpenGLNativeWidget +class Scene; + +class Viewport : public Qt3DExtras::Qt3DWindow { private: - Tool m_tool; - ToolPath m_toolPath; public: - explicit Viewport(const model::Simulation& simulation); + explicit Viewport(); + + QWidget *container(); - void setToolPosition(const model::Simulation::ToolPathPoint3D& position); + void setScene(Scene *scene); }; } diff --git a/src/view/simulation/simulation.cpp b/src/view/simulation/simulation.cpp index 9a42f64..8771280 100644 --- a/src/view/simulation/simulation.cpp +++ b/src/view/simulation/simulation.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -14,7 +15,7 @@ void Simulation::moveToolAtTime(int ms) const float seconds = float(ms) / 1e3; const model::Simulation::ToolPathPoint3D toolPosition = m_simulation.toolPositionAtTime(seconds); - m_viewport->setToolPosition(toolPosition); + m_scene->setToolPosition(toolPosition); } void Simulation::startStopToolAnimation() @@ -30,12 +31,15 @@ void Simulation::startStopToolAnimation() } Simulation::Simulation() + :m_viewport(new internal::Viewport()) { setupUi(this); connect(timeSlider, &QSlider::valueChanged, this, &Simulation::moveToolAtTime); connect(startStopButton, &QPushButton::clicked, this, &Simulation::startStopToolAnimation); + container->addWidget(m_viewport->container()); + static const int timerIntervalMs = 30; m_timer.setInterval(timerIntervalMs); m_timer.callOnTimeout([this](){ @@ -49,8 +53,9 @@ void Simulation::setSimulation(model::Simulation && simulation) { m_simulation = std::move(simulation); - m_viewport = std::make_unique(m_simulation); - container->addWidget(m_viewport.get()); + internal::Scene *newScene = new internal::Scene(m_simulation); + m_viewport->setScene(newScene); + m_scene.reset(newScene); const float secondDuration = m_simulation.duration(); timeSlider->setMaximum(secondDuration * 1e3); diff --git a/src/view/simulation/simulation.h b/src/view/simulation/simulation.h index c89a0f8..516b69e 100644 --- a/src/view/simulation/simulation.h +++ b/src/view/simulation/simulation.h @@ -13,6 +13,7 @@ namespace view::simulation namespace internal { +class Scene; class Viewport; } @@ -21,7 +22,10 @@ class Simulation : private Ui::Simulation, public QWidget { private: model::Simulation m_simulation; + std::unique_ptr m_viewport; + std::unique_ptr m_scene; + QTimer m_timer; protected slots: diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 76ca75d..c4dea1d 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(cavaliercontours) add_subdirectory(cereal) +add_subdirectory(fmt) add_subdirectory(libdxfrw) add_subdirectory(nanoflann) add_subdirectory(yaml-cpp) diff --git a/thirdparty/cavaliercontours b/thirdparty/cavaliercontours index 1fa520c..7a35376 160000 --- a/thirdparty/cavaliercontours +++ b/thirdparty/cavaliercontours @@ -1 +1 @@ -Subproject commit 1fa520c8601264f9488084d493cbf3bf31f9e2bb +Subproject commit 7a35376eb4c2d5f917d3e0564ea630c94137255e diff --git a/thirdparty/fmt b/thirdparty/fmt new file mode 160000 index 0000000..d141cdb --- /dev/null +++ b/thirdparty/fmt @@ -0,0 +1 @@ +Subproject commit d141cdbeb0fb422a3fb7173b285fd38e0d1772dc From f977388ca60cf0cefaaab9f1452e5b5e00204907 Mon Sep 17 00:00:00 2001 From: tristan Date: Tue, 7 Feb 2023 22:57:48 +0100 Subject: [PATCH 22/32] fix: install qt3d during CI --- .github/workflows/deploy-linux-appimage.yml | 2 +- .github/workflows/sonarcloud.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-linux-appimage.yml b/.github/workflows/deploy-linux-appimage.yml index a91f65f..13324ef 100644 --- a/.github/workflows/deploy-linux-appimage.yml +++ b/.github/workflows/deploy-linux-appimage.yml @@ -16,7 +16,7 @@ jobs: - name: Install package run: | sudo apt-get update - sudo apt-get -y install qtbase5-dev libqt5svg5-dev freeglut3-dev + sudo apt-get -y install qtbase5-dev qt3d5-dev libqt5svg5-dev freeglut3-dev - name: Build and test run: ci/buildappimage.sh - name: Create Release diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 3b0bb5a..e0a0bff 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -15,7 +15,7 @@ jobs: - name: Install package run: | sudo apt-get update - sudo apt-get -y install qtbase5-dev libqt5svg5-dev freeglut3-dev lcov + sudo apt-get -y install qtbase5-dev qt3d5-dev libqt5svg5-dev freeglut3-dev lcov - name: Install build wrapper run: | wget http://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip From eb34a28ec1d34c9838c7a2ad366a8e68e9d09e75 Mon Sep 17 00:00:00 2001 From: tristan Date: Tue, 7 Feb 2023 23:00:36 +0100 Subject: [PATCH 23/32] fix: remove compile dependency to generated config.h from simulation.h --- src/model/simulation.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/model/simulation.h b/src/model/simulation.h index 9a8d398..d41fa3b 100644 --- a/src/model/simulation.h +++ b/src/model/simulation.h @@ -6,7 +6,6 @@ #include #include -#include #include namespace model From d85d66ce08c9490fdb06bc6e531c91be05b46825 Mon Sep 17 00:00:00 2001 From: tristan Date: Sat, 11 Mar 2023 17:00:50 +0100 Subject: [PATCH 24/32] feat: embbed gcode command to test the bounding rect of the project. --- src/exporter/gcode/CMakeLists.txt | 2 + src/exporter/gcode/exporter.cpp | 49 +++++++++++++++++- src/exporter/gcode/metadata.cpp | 74 ++++++++++++++++++++++++++++ src/exporter/gcode/metadata.h | 38 ++++++++++++++ src/exporter/gcode/postprocessor.cpp | 33 ------------- src/exporter/gcode/postprocessor.h | 8 +-- src/model/task.cpp | 20 ++++++++ src/model/task.h | 1 + 8 files changed, 183 insertions(+), 42 deletions(-) create mode 100644 src/exporter/gcode/metadata.cpp create mode 100644 src/exporter/gcode/metadata.h diff --git a/src/exporter/gcode/CMakeLists.txt b/src/exporter/gcode/CMakeLists.txt index 343d2ed..2a9e23b 100644 --- a/src/exporter/gcode/CMakeLists.txt +++ b/src/exporter/gcode/CMakeLists.txt @@ -1,8 +1,10 @@ set(SRC exporter.cpp + metadata.cpp postprocessor.cpp exporter.h + metadata.h postprocessor.h ) diff --git a/src/exporter/gcode/exporter.cpp b/src/exporter/gcode/exporter.cpp index 7d4e121..d472f64 100644 --- a/src/exporter/gcode/exporter.cpp +++ b/src/exporter/gcode/exporter.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -61,6 +62,46 @@ void convertConfigNodeToComments(const Group &group, std::ostream &output) visitor(group); } +class RendererVisitor : private PostProcessor +{ +public: + using PostProcessor::PostProcessor; + + void start(const QVector2D& from, float safetyDepth) + { + retractDepth(safetyDepth); + } + + void end(const QVector2D& to, float safetyDepth) + { + fastPlaneMove(to); + } + + void startOperation(const QVector2D& to, float intensity) + { + // Move to polyline beginning and start tooling + fastPlaneMove(to); + preCut(intensity); + } + + void endOperation(float safetyDepth) + { + // Retract tool for further operations + retractDepth(safetyDepth); + postCut(); + } + + void processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, float depthFeedRate) + { + depthLinearMove(depth, depthFeedRate); + + polyline.forEachBulge([this, planeFeedRate](const geometry::Bulge &bulge){ + processBulge(bulge, planeFeedRate); + }); + } +}; + + Exporter::Exporter(const config::Tools::Tool& tool, const config::Profiles::Profile& profile, Options options) :m_tool(tool), m_profile(profile), @@ -75,8 +116,12 @@ void Exporter::operator()(const model::Document &document, std::ostream &output) convertConfigNodeToComments(m_profile, output); } - PostProcessor processor(m_profile.gcode(), output); - renderer::Renderer renderer(m_tool, m_profile, processor); + const config::Profiles::Profile::Gcode& gcode = m_profile.gcode(); + const Metadata metadata(document, gcode, m_tool.general().retractDepth()); + output << metadata.toComment(); + + RendererVisitor visitor(gcode, output); + renderer::Renderer renderer(m_tool, m_profile, visitor); renderer.render(document); } diff --git a/src/exporter/gcode/metadata.cpp b/src/exporter/gcode/metadata.cpp new file mode 100644 index 0000000..73cd775 --- /dev/null +++ b/src/exporter/gcode/metadata.cpp @@ -0,0 +1,74 @@ +#include +#include + +#include + +#include +#include +#include + +#include + +namespace exporter::gcode +{ + +std::string Metadata::fastPlaneMoveGCode(const QVector2D &to) const +{ + std::ostringstream output; + PostProcessor postprocessor(m_gcode, output); + postprocessor.fastPlaneMove(to); + return output.str(); +} + +std::string Metadata::fastDepthMoveGCode(float to) const +{ + std::ostringstream output; + PostProcessor postprocessor(m_gcode, output); + postprocessor.retractDepth(to); + return output.str(); +} + +QJsonArray Metadata::boundingRectGcodes() const +{ + std::initializer_list gcodes{ + fastDepthMoveGCode(m_retractDepth), + fastPlaneMoveGCode(m_boundingRect.bottomLeft()), + fastPlaneMoveGCode(m_boundingRect.bottomRight()), + fastPlaneMoveGCode(m_boundingRect.topRight()), + fastPlaneMoveGCode(m_boundingRect.topLeft()), + fastPlaneMoveGCode(QVector2D(0.0f, 0.0f)), + fastDepthMoveGCode(0.0f) + }; + + QJsonArray jsonGcodes; + for (const std::string &gcode : gcodes) { + jsonGcodes.push_back(QString::fromStdString(gcode)); + } + + return jsonGcodes; +} + +QJsonDocument Metadata::toJson() const +{ + QJsonObject root; + root["bounding_rect_gcodes"] = boundingRectGcodes(); + + QJsonDocument document(root); + return document; +} + +Metadata::Metadata(const model::Document& document, const config::Profiles::Profile::Gcode& gcode, float retractDepth) + :m_boundingRect(document.task().visibleBoundingRect()), + m_gcode(gcode), + m_retractDepth((retractDepth)) +{ +} + +std::string Metadata::toComment() const +{ + const QJsonDocument& jsonDocument = toJson(); + + return "; metadata_json = " + jsonDocument.toJson(QJsonDocument::Compact).toStdString() + "\n"; +} + +} diff --git a/src/exporter/gcode/metadata.h b/src/exporter/gcode/metadata.h new file mode 100644 index 0000000..10ff84a --- /dev/null +++ b/src/exporter/gcode/metadata.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include +#include + +namespace model +{ + +class Document; + +} + +class QJsonObject; + +namespace exporter::gcode +{ + +class Metadata +{ +private: + const geometry::Rect m_boundingRect; + const config::Profiles::Profile::Gcode& m_gcode; + const float m_retractDepth; + + std::string fastPlaneMoveGCode(const QVector2D &to) const; + std::string fastDepthMoveGCode(float to) const; + QJsonArray boundingRectGcodes() const; + QJsonDocument toJson() const; + +public: + explicit Metadata(const model::Document& document, const config::Profiles::Profile::Gcode& gcode, float retractDepth); + + std::string toComment() const; +}; + +} diff --git a/src/exporter/gcode/postprocessor.cpp b/src/exporter/gcode/postprocessor.cpp index 2623267..73c38e0 100644 --- a/src/exporter/gcode/postprocessor.cpp +++ b/src/exporter/gcode/postprocessor.cpp @@ -85,37 +85,4 @@ void PostProcessor::processArc(const geometry::Bulge &bulge, float planeFeedRate } } -void PostProcessor::start(const QVector2D& from, float safetyDepth) -{ - retractDepth(safetyDepth); -} - -void PostProcessor::end(const QVector2D& to, float safetyDepth) -{ - fastPlaneMove(to); -} - -void PostProcessor::startOperation(const QVector2D& to, float intensity) -{ - // Move to polyline beginning and start tooling - fastPlaneMove(to); - preCut(intensity); -} - -void PostProcessor::endOperation(float safetyDepth) -{ - // Retract tool for further operations - retractDepth(safetyDepth); - postCut(); -} - -void PostProcessor::processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, float depthFeedRate) -{ - depthLinearMove(depth, depthFeedRate); - - polyline.forEachBulge([this, planeFeedRate](const geometry::Bulge &bulge){ - processBulge(bulge, planeFeedRate); - }); -} - } diff --git a/src/exporter/gcode/postprocessor.h b/src/exporter/gcode/postprocessor.h index 9bd0da0..800f81d 100644 --- a/src/exporter/gcode/postprocessor.h +++ b/src/exporter/gcode/postprocessor.h @@ -33,6 +33,7 @@ class PostProcessor } } +public: void preCut(float intensity); void postCut(); void planeLinearMove(const QVector2D &to, float feedRate); @@ -45,14 +46,7 @@ class PostProcessor void processLine(const geometry::Bulge &bulge, float planeFeedRate); void processArc(const geometry::Bulge &bulge, float planeFeedRate); -public: explicit PostProcessor(const config::Profiles::Profile::Gcode& gcode, std::ostream &stream); - - void start(const QVector2D& from, float safetyDepth); - void end(const QVector2D& to, float safetyDepth); - void startOperation(const QVector2D& to, float intensity); - void endOperation(float safetyDepth); - void processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, float depthFeedRate); }; } diff --git a/src/model/task.cpp b/src/model/task.cpp index 721f5c9..b2ea3d1 100644 --- a/src/model/task.cpp +++ b/src/model/task.cpp @@ -145,6 +145,26 @@ geometry::Rect Task::selectionBoundingRect() const return boundingRect; } +geometry::Rect Task::visibleBoundingRect() const +{ + bool isFirstPath = true; + geometry::Rect boundingRect; + + forEachPathInStack([&isFirstPath, &boundingRect](const model::Path &path){ + if (path.globallyVisible()) { + const geometry::Rect pathBoundingRect = path.boundingRect(); + if (isFirstPath) { + boundingRect = pathBoundingRect; + isFirstPath = false; + } + else { + boundingRect |= pathBoundingRect; + } + } + }); + + return boundingRect; +} int Task::layerCount() const { diff --git a/src/model/task.h b/src/model/task.h index b353063..770ef3d 100644 --- a/src/model/task.h +++ b/src/model/task.h @@ -83,6 +83,7 @@ class Task : public QObject, public common::Aggregable void showHidden(); geometry::Rect selectionBoundingRect() const; + geometry::Rect visibleBoundingRect() const; int layerCount() const; const Layer &layerAt(int index) const; From 73e1ff1826ac58531d9b1a084b6b317f20fb1d4d Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 12 Mar 2023 11:38:24 +0100 Subject: [PATCH 25/32] fix: boundign rect gcode missing final corner --- src/exporter/gcode/metadata.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/exporter/gcode/metadata.cpp b/src/exporter/gcode/metadata.cpp index 73cd775..d329745 100644 --- a/src/exporter/gcode/metadata.cpp +++ b/src/exporter/gcode/metadata.cpp @@ -36,6 +36,7 @@ QJsonArray Metadata::boundingRectGcodes() const fastPlaneMoveGCode(m_boundingRect.bottomRight()), fastPlaneMoveGCode(m_boundingRect.topRight()), fastPlaneMoveGCode(m_boundingRect.topLeft()), + fastPlaneMoveGCode(m_boundingRect.bottomLeft()), fastPlaneMoveGCode(QVector2D(0.0f, 0.0f)), fastDepthMoveGCode(0.0f) }; From 96d80b457ce046e402b35054e1b65b1c965ad585 Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 16 Apr 2023 13:36:51 +0200 Subject: [PATCH 26/32] fix: ignore zero polylines offsetted paths --- src/model/path.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/model/path.cpp b/src/model/path.cpp index 57aeb32..89b5d53 100644 --- a/src/model/path.cpp +++ b/src/model/path.cpp @@ -81,7 +81,10 @@ void Path::offset(float margin, float minimumPolylineLength, float minimumArcLen const OffsettedPath::Direction direction = (margin > 0.0f) ? OffsettedPath::Direction::LEFT : OffsettedPath::Direction::RIGHT; - m_offsettedPath = std::make_unique(cleaner.polylines(), direction); + geometry::Polyline::List cleanedPolylines = cleaner.polylines(); + if (!cleanedPolylines.empty()) { + m_offsettedPath = std::make_unique(std::move(cleanedPolylines), direction); + } emit offsettedPathChanged(); } From d36ec7e7a66086183f6501d64a13390fb2d96c18 Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 16 Apr 2023 14:10:09 +0200 Subject: [PATCH 27/32] fix: camera translation and slow motion --- src/view/simulation/internal/tool.cpp | 6 ++- src/view/simulation/internal/tool.h | 1 + src/view/simulation/internal/viewport.cpp | 60 +++++++++++++++++++++-- src/view/simulation/internal/viewport.h | 11 +++++ src/view/simulation/simulation.cpp | 2 + template/uic/simulation/simulation.ui | 27 +++++++++- 6 files changed, 99 insertions(+), 8 deletions(-) diff --git a/src/view/simulation/internal/tool.cpp b/src/view/simulation/internal/tool.cpp index b299d07..77e5061 100644 --- a/src/view/simulation/internal/tool.cpp +++ b/src/view/simulation/internal/tool.cpp @@ -8,13 +8,15 @@ namespace view::simulation::internal Tool::Tool(Qt3DCore::QEntity *parent, float radius, float height) :Qt3DCore::QEntity(parent), m_transform(new Qt3DCore::QTransform(this)), - m_material(new Qt3DExtras::QGoochMaterial(this)) + m_material(new Qt3DExtras::QGoochMaterial(this)), + m_halfHeight(0.0f, 0.0f, height / 2.0f) { Qt3DExtras::QCylinderMesh *mesh = new Qt3DExtras::QCylinderMesh(this); mesh->setRadius(radius); mesh->setLength(height); m_transform->setRotationX(90); + m_transform->setTranslation(m_halfHeight); addComponent(m_transform); addComponent(mesh); @@ -23,7 +25,7 @@ Tool::Tool(Qt3DCore::QEntity *parent, float radius, float height) void Tool::setPosition(const model::Simulation::ToolPathPoint3D& position) { - m_transform->setTranslation(position.position); + m_transform->setTranslation(position.position + m_halfHeight); static const QColor colorsByMoveType[] = { {240, 248, 255}, diff --git a/src/view/simulation/internal/tool.h b/src/view/simulation/internal/tool.h index 9f3a868..2e0b8cb 100644 --- a/src/view/simulation/internal/tool.h +++ b/src/view/simulation/internal/tool.h @@ -14,6 +14,7 @@ class Tool : public Qt3DCore::QEntity private: Qt3DCore::QTransform *m_transform; Qt3DExtras::QGoochMaterial *m_material; + const QVector3D m_halfHeight; public: explicit Tool(Qt3DCore::QEntity *parent, float radius, float height); diff --git a/src/view/simulation/internal/viewport.cpp b/src/view/simulation/internal/viewport.cpp index f0cb426..c99b17a 100644 --- a/src/view/simulation/internal/viewport.cpp +++ b/src/view/simulation/internal/viewport.cpp @@ -6,10 +6,57 @@ #include #include +#include +#include namespace view::simulation::internal { +void Viewport::mousePressEvent(QMouseEvent *e) +{ + if (e->buttons() & Qt::MiddleButton) { + m_lastMousePos = e->pos(); + } +} + +void Viewport::mouseMoveEvent(QMouseEvent *e) +{ + if (e->buttons() & Qt::MiddleButton) { + const QPoint delta = computeMouseDelta(e->pos()); + + const bool shiftCenter = e->modifiers() & Qt::ShiftModifier; + if (shiftCenter) { + const bool slowMotion = e->modifiers() & Qt::ControlModifier; + const float factor = slowMotion ? 0.001f : 0.01f; + const QVector3D translation(-delta.x() * factor, delta.y() * factor, 0.0f); + camera()->translate(translation); + } + else { + camera()->panAboutViewCenter(-delta.x()); + camera()->tiltAboutViewCenter(delta.y()); + } + } +} + +void Viewport::wheelEvent(QWheelEvent *e) +{ + const QPoint numPixels = e->pixelDelta(); + + const bool slowMotion = e->modifiers() & Qt::ControlModifier; + const float factor = slowMotion ? 0.001f : 0.01f; + + const QVector3D translation(0.0f, 0.0f, numPixels.y() * factor); + camera()->translate(translation, Qt3DRender::QCamera::DontTranslateViewCenter); +} + +QPoint Viewport::computeMouseDelta(const QPoint &pos) +{ + const QPoint delta = pos - m_lastMousePos; + m_lastMousePos = pos; + + return delta; +} + Viewport::Viewport() { defaultFrameGraph()->setClearColor(QColor(0, 0, 0)); @@ -24,13 +71,16 @@ void Viewport::setScene(Scene *scene) { setRootEntity(scene); + camera()->lens()->setPerspectiveProjection(45.0f, 16.0f/9.0f, 0.1f, 1000.0f); + camera()->setPosition(QVector3D(0, 0, 40.0f)); camera()->viewAll(); + camera()->setViewCenter(QVector3D(0.0f, 0.0f, 0.0f)); + qInfo() << camera()->viewCenter(); +} - Qt3DExtras::QOrbitCameraController *camController = new Qt3DExtras::QOrbitCameraController(scene); - camController->setLinearSpeed( 200.0f ); - camController->setLookSpeed( 200.0f ); - camController->setCamera(camera()); - +bool Viewport::event(QEvent *e) +{ + return Qt3DWindow::event(e); } } diff --git a/src/view/simulation/internal/viewport.h b/src/view/simulation/internal/viewport.h index 5a49d0e..11665bf 100644 --- a/src/view/simulation/internal/viewport.h +++ b/src/view/simulation/internal/viewport.h @@ -2,6 +2,9 @@ #include +class QMouseEvent; +class QWheelEvent; + namespace view::simulation::internal { @@ -10,6 +13,12 @@ class Scene; class Viewport : public Qt3DExtras::Qt3DWindow { private: + void mousePressEvent(QMouseEvent *e) final; + void mouseMoveEvent(QMouseEvent *e) final; + void wheelEvent(QWheelEvent *e) final; + + QPoint m_lastMousePos; + QPoint computeMouseDelta(const QPoint& pos); public: explicit Viewport(); @@ -17,6 +26,8 @@ class Viewport : public Qt3DExtras::Qt3DWindow QWidget *container(); void setScene(Scene *scene); + + bool event(QEvent *e) override; }; } diff --git a/src/view/simulation/simulation.cpp b/src/view/simulation/simulation.cpp index 8771280..ca34c2f 100644 --- a/src/view/simulation/simulation.cpp +++ b/src/view/simulation/simulation.cpp @@ -37,6 +37,8 @@ Simulation::Simulation() connect(timeSlider, &QSlider::valueChanged, this, &Simulation::moveToolAtTime); connect(startStopButton, &QPushButton::clicked, this, &Simulation::startStopToolAnimation); + + addAction(actionPauseResume); container->addWidget(m_viewport->container()); diff --git a/template/uic/simulation/simulation.ui b/template/uic/simulation/simulation.ui index f4827a6..47b178c 100644 --- a/template/uic/simulation/simulation.ui +++ b/template/uic/simulation/simulation.ui @@ -71,9 +71,34 @@ + + + PauseResume + + + Space + + - + + + actionPauseResume + triggered() + startStopButton + click() + + + -1 + -1 + + + 16 + 282 + + + + From 93bf0d646b7b53ceed10e81dfeb687ebf507e821 Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 16 Apr 2023 14:10:27 +0200 Subject: [PATCH 28/32] chore: update README with simulation --- README.md | 16 ++++++++++++++++ doc/simulation.gif | Bin 0 -> 517546 bytes src/view/simulation/internal/viewport.cpp | 2 -- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 doc/simulation.gif diff --git a/README.md b/README.md index 0eeb199..9ca5737 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,16 @@ It targets only laser and router CNC following every lines and arcs from DXF fil * Pocket generation with islands capabilities * Multi passes with depth * Scale and translate paths +* 3D path simulation of the file

+

+ +

+ ## Installation Currently only linux distributions are supported, windows will come in futur. @@ -125,6 +130,17 @@ Variables provided in formatting are available with {#:nf} where # is one of the Properties `S` and `F` are exposed in path settings in UI. +## 3D simulation view + +The following controls help you to navigate into the view: + +| Keys | Action | +| Middle click | Rotate around view center| +| Middle click + Shift | Translate view center | +| Wheel | Zoom | + +Ctrl key can be pressed to slow down motion. + ## Author 👤 **Tristan Porteries** diff --git a/doc/simulation.gif b/doc/simulation.gif new file mode 100644 index 0000000000000000000000000000000000000000..7947557872271c3dac2dfb0b80ce17e0bfc4b8bb GIT binary patch literal 517546 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
4Ya`LgTCl{J`Ie(Pdx0u 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