diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml
new file mode 100644
index 0000000..0050891
--- /dev/null
+++ b/.github/workflows/build-wasm.yml
@@ -0,0 +1,38 @@
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - "**"
+env:
+ QT_VERSION: '6.3.0'
+
+jobs:
+ build-wasm:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ with:
+ submodules: true
+ - name: Install package
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install freeglut3-dev cmake python3-jinja2
+ - name: Install Qt desktop
+ uses: jurplel/install-qt-action@v3
+ with:
+ aqtversion: '==3.1.*'
+ version: ${{ env.QT_VERSION }}
+ host: 'linux'
+ target: 'desktop'
+ arch: 'gcc_64'
+ - name: Install Qt wasm
+ uses: jurplel/install-qt-action@v3
+ with:
+ aqtversion: '==3.1.*'
+ version: ${{ env.QT_VERSION }}
+ host: 'linux'
+ target: 'desktop'
+ arch: 'wasm_32'
+ - name: Build and scan
+ run: ci/buildwasm.sh
diff --git a/.github/workflows/deploy-linux-appimage.yml b/.github/workflows/deploy-linux-appimage.yml
index 13324ef..8d4e549 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 qt3d5-dev libqt5svg5-dev freeglut3-dev
+ sudo apt-get -y install qt6-base-dev qt6-3d-dev libqt6svg6-dev freeglut3-dev cmake python3-jinja2
- name: Build and test
run: ci/buildappimage.sh
- name: Create Release
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index e0a0bff..0eff272 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 qt3d5-dev libqt5svg5-dev freeglut3-dev lcov
+ sudo apt-get -y install qt6-base-dev qt6-3d-dev libqt6svg6-dev freeglut3-dev lcov cmake python3-jinja2
- name: Install build wrapper
run: |
wget http://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 62fd227..1ba69f8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -34,21 +34,34 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(TEMPLATE_DIR ${PROJECT_SOURCE_DIR}/template)
+option(BUILD_WASM "Build to WebAssembly" OFF)
+
find_package(codecov)
find_package(PythonInterp REQUIRED)
-find_package(Qt5 COMPONENTS REQUIRED
+if (BUILD_WASM)
+ message(STATUS "Building to WebAssembly (Qt6_DIR ${Qt6_DIR} QT_HOST_PATH ${QT_HOST_PATH})")
+
+ add_compile_definitions(BUILD_WASM)
+
+ set(WITH_3D OFF)
+
+ # set(QT_HOST_PATH "/home/tristan/Compilation/wasm-test/6.5.0/gcc_64/")
+ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
+ list(APPEND CMAKE_PREFIX_PATH ${Qt6_DIR})
+ include(${Qt6_DIR}/lib/cmake/Qt6/QtPublicWasmToolchainHelpers.cmake)
+else()
+ set(WITH_3D ON)
+endif()
+
+find_package(Qt6 COMPONENTS REQUIRED
Core
Widgets
Gui
Svg
- 3DCore
- 3DExtras
)
-#qt_standard_project_setup()
-
set(INCLUDE_DIRS
src
thirdparty
@@ -61,10 +74,8 @@ set(INCLUDE_DIRS
template
${CMAKE_BINARY_DIR}/src
${CMAKE_BINARY_DIR}/template
- ${Qt5Widgets_INCLUDE_DIRS}
- ${Qt5Gui_INCLUDE_DIRS}
- ${Qt53DCore_INCLUDE_DIRS}
- ${Qt53DExtras_INCLUDE_DIRS}
+ ${Qt6Widgets_INCLUDE_DIRS}
+ ${Qt6Gui_INCLUDE_DIRS}
)
set(LINK_LIBRARIES
@@ -74,8 +85,6 @@ set(LINK_LIBRARIES
view-dialogs-settings
view-task
view-view2d
- view-simulation
- view-simulation-internal
model
config
importer-dxf
@@ -87,13 +96,34 @@ set(LINK_LIBRARIES
geometry-filter
libdxfrw
fmt::fmt
- Qt5::Widgets
- Qt5::Svg
- Qt5::3DCore
- Qt5::3DExtras
+ Qt::Core
+ Qt::Gui
+ Qt::Svg
+ Qt::Widgets
yaml-cpp
)
+if (WITH_3D)
+ add_compile_definitions(WITH_3D)
+
+ find_package(Qt6 COMPONENTS REQUIRED
+ 3DCore
+ 3DExtras
+ )
+
+ list(APPEND INCLUDE_DIRS
+ ${Qt63DCore_INCLUDE_DIRS}
+ ${Qt63DExtras_INCLUDE_DIRS}
+ )
+
+ list(APPEND LINK_LIBRARIES
+ view-simulation
+ view-simulation-internal
+ Qt::3DCore
+ Qt::3DExtras
+ )
+endif()
+
include_directories(${INCLUDE_DIRS})
add_subdirectory(template)
@@ -105,8 +135,13 @@ if (BUILD_TESTING)
add_subdirectory(test)
endif()
-add_executable(dxfplotter src/main.cpp)
-target_link_libraries(dxfplotter ${LINK_LIBRARIES})
+qt_add_executable(dxfplotter src/main.cpp)
+target_link_libraries(dxfplotter PRIVATE ${LINK_LIBRARIES})
+
+if (BUILD_WASM)
+ target_link_options(dxfplotter PRIVATE -lidbfs.js)
+endif()
+
add_coverage(dxfplotter)
install(TARGETS dxfplotter DESTINATION bin)
diff --git a/ci/buildappimage.sh b/ci/buildappimage.sh
index 2a7e075..1f9b6a1 100755
--- a/ci/buildappimage.sh
+++ b/ci/buildappimage.sh
@@ -35,7 +35,7 @@ pushd "$BUILD_DIR"
# configure build files with CMake
# we need to explicitly set the install prefix, as CMake's default is /usr/local for some reason...
-cmake "$REPO_ROOT" -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_TESTING=OFF
+cmake "$REPO_ROOT" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_TESTING=OFF
# build project and install files into AppDir
make -j$(nproc)
diff --git a/ci/buildwasm.sh b/ci/buildwasm.sh
new file mode 100755
index 0000000..ad7e5b4
--- /dev/null
+++ b/ci/buildwasm.sh
@@ -0,0 +1,39 @@
+#! /bin/bash
+
+set -x
+set -e
+
+# building in temporary directory to keep system clean
+# use RAM disk if possible (as in: not building on CI system like Travis, and RAM disk is available)
+if [ "$CI" == "" ] && [ -d /dev/shm ]; then
+ TEMP_BASE=/dev/shm
+else
+ TEMP_BASE=/tmp
+fi
+
+BUILD_DIR=$(mktemp -d -p "$TEMP_BASE" analyse-sonarcloud-XXXXXX)
+
+# make sure to clean up build dir, even if errors occur
+cleanup () {
+ if [ -d "$BUILD_DIR" ]; then
+ rm -rf "$BUILD_DIR"
+ fi
+}
+trap cleanup EXIT
+
+# store repo root as variable
+REPO_ROOT=$(readlink -f $(dirname $(dirname $0)))
+OLD_CWD=$(readlink -f .)
+
+# switch to build dir
+pushd "$BUILD_DIR"
+
+QT6_DIR="${REPO_ROOT}/${QT_VERSION}/"
+QT6_DIR_DESKTOP="${QT6_DIR}/gcc_64/"
+QT6_DIR_WASM="${QT6_DIR}/wasm_32/"
+
+# configure build files with CMake
+cmake "$REPO_ROOT" -DCMAKE_BUILD_TYPE=Release -DBUILD_WASM=ON -DQt6_DIR=${QT6_DIR_WASM} -DQT_HOST_PATH=${QT6_DIR_DESKTOP}
+cmake --build .
+
+
diff --git a/ci/deploywindows.sh b/ci/deploywindows.sh
old mode 100644
new mode 100755
diff --git a/resource/CMakeLists.txt b/resource/CMakeLists.txt
index 70b1618..c4eb371 100644
--- a/resource/CMakeLists.txt
+++ b/resource/CMakeLists.txt
@@ -1,4 +1,4 @@
-qt5_add_resources(SOURCES resource.qrc)
+qt_add_resources(SOURCES resource.qrc)
add_library(resource ${SOURCES})
diff --git a/resource/icons/layer-bottom.svg b/resource/icons/layer-bottom.svg
new file mode 100644
index 0000000..709d1cc
--- /dev/null
+++ b/resource/icons/layer-bottom.svg
@@ -0,0 +1,22 @@
+
diff --git a/resource/icons/layer-top.svg b/resource/icons/layer-top.svg
new file mode 100644
index 0000000..bb9952d
--- /dev/null
+++ b/resource/icons/layer-top.svg
@@ -0,0 +1,22 @@
+
diff --git a/resource/resource.qrc b/resource/resource.qrc
index 3d4bac2..1c0939e 100644
--- a/resource/resource.qrc
+++ b/resource/resource.qrc
@@ -13,8 +13,10 @@
icons/edit-copy.svg
icons/go-down.svg
icons/go-up.svg
+ icons/layer-bottom.svg
icons/layer-lower.svg
icons/layer-raise.svg
+ icons/layer-top.svg
icons/layer-visible-off.svg
icons/layer-visible-on.svg
icons/list-add.svg
diff --git a/src/common/copy.h b/src/common/copy.h
new file mode 100644
index 0000000..3c8dd67
--- /dev/null
+++ b/src/common/copy.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include
+#include
+#include
+
+namespace common
+{
+
+template >
+std::vector deepcopy(const std::vector &other)
+{
+ std::vector duplicated(other.size());
+ std::transform(other.begin(), other.end(), duplicated.begin(), [](const ItemUPtr &item){
+ return std::make_unique- (*item);
+ });
+
+ return duplicated;
+}
+
+}
diff --git a/src/config/config.cpp b/src/config/config.cpp
index 35ca952..22b6b63 100644
--- a/src/config/config.cpp
+++ b/src/config/config.cpp
@@ -1,21 +1,27 @@
+#include "yaml-cpp/exceptions.h"
#include
#include
#include
+#ifdef BUILD_WASM
+# include
+#endif
+
namespace config
{
Config::Config(const std::string &filePath)
:m_filePath(filePath)
{
- try {
- m_yamlRoot = YAML::LoadFile(filePath);
+ /*try {
+ //m_yamlRoot = YAML::LoadFile(filePath);
+ throw YAML::BadFile(filePath);
}
catch (const YAML::BadFile&) {
qInfo() << "Initializing configuration from defaults";
- }
+ }*/
// Instantiation of root group
m_root = Root(m_yamlRoot);
diff --git a/src/exporter/gcode/metadata.h b/src/exporter/gcode/metadata.h
index 10ff84a..6bc90a4 100644
--- a/src/exporter/gcode/metadata.h
+++ b/src/exporter/gcode/metadata.h
@@ -12,7 +12,8 @@ class Document;
}
-class QJsonObject;
+class QJsonArray;
+class QJsonDocument;
namespace exporter::gcode
{
diff --git a/src/geometry/filter/CMakeLists.txt b/src/geometry/filter/CMakeLists.txt
index 34aaa5b..be943ae 100644
--- a/src/geometry/filter/CMakeLists.txt
+++ b/src/geometry/filter/CMakeLists.txt
@@ -2,12 +2,10 @@ set(SRC
assembler.cpp
cleaner.cpp
removeexactduplicate.cpp
- sorter.cpp
assembler.h
cleaner.h
removeexactduplicate.h
- sorter.h
)
add_library(geometry-filter ${SRC})
diff --git a/src/geometry/filter/sorter.cpp b/src/geometry/filter/sorter.cpp
deleted file mode 100644
index 545e27f..0000000
--- a/src/geometry/filter/sorter.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#include
-
-namespace geometry::filter
-{
-
-Sorter::Sorter(Polyline::List &&polylines)
- :m_polylines(polylines.size())
-{
- struct PolylineLength
- {
- Polyline *polyline;
- float length;
-
- PolylineLength() = default;
-
- explicit PolylineLength(Polyline &polyline)
- :polyline(&polyline),
- length(polyline.length())
- {
- }
-
- bool operator<(const PolylineLength& other) const
- {
- return length < other.length;
- }
- };
-
- std::vector polylinesLength(polylines.size());
- std::transform(polylines.begin(), polylines.end(), polylinesLength.begin(),
- [](Polyline& polyline){ return PolylineLength(polyline); });
-
- std::sort(polylinesLength.begin(), polylinesLength.end());
-
- std::transform(polylinesLength.begin(), polylinesLength.end(), m_polylines.begin(),
- [](PolylineLength& polylineLength){ return std::move(*polylineLength.polyline); });
-}
-
-Polyline::List &&Sorter::polylines()
-{
- return std::move(m_polylines);
-}
-
-}
diff --git a/src/geometry/filter/sorter.h b/src/geometry/filter/sorter.h
deleted file mode 100644
index 9f1253b..0000000
--- a/src/geometry/filter/sorter.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-
-#include
-
-namespace geometry::filter
-{
-
-/** @brief Sort polyline by length.
- */
-class Sorter
-{
-private:
- Polyline::List m_polylines;
-
-public:
- explicit Sorter(Polyline::List &&polylines);
-
- Polyline::List &&polylines();
-};
-
-}
diff --git a/src/importer/dxf/importer.cpp b/src/importer/dxf/importer.cpp
index c2ee24d..838493b 100644
--- a/src/importer/dxf/importer.cpp
+++ b/src/importer/dxf/importer.cpp
@@ -16,16 +16,18 @@ void Importer::addLayer(const DRW_Layer &layer)
}
}
-Importer::Importer(const std::string& filename, float splineToArcPrecision, float minimumSplineLength, float minimumArcLength)
+Importer::Importer(float splineToArcPrecision, float minimumSplineLength, float minimumArcLength)
:m_entityImporterSettings({splineToArcPrecision, minimumSplineLength, minimumArcLength}),
m_ignoreEntities(false)
+{
+}
+
+bool Importer::import(std::istream& stream)
{
Interface interface(*this);
- dxfRW rw(filename.c_str());
- if (!rw.read(&interface, false)) {
- throw common::FileCouldNotOpenException();
- }
+ dxfRW rw("");
+ return rw.read(stream, &interface, false);
}
Layer::List Importer::layers() const
diff --git a/src/importer/dxf/importer.h b/src/importer/dxf/importer.h
index e73f466..daa5622 100644
--- a/src/importer/dxf/importer.h
+++ b/src/importer/dxf/importer.h
@@ -25,7 +25,9 @@ class Importer
void addLayer(const DRW_Layer &layer);
public:
- explicit Importer(const std::string &filename, float splineToArcPrecision, float minimumSplineLength, float minimumArcLength);
+ explicit Importer(float splineToArcPrecision, float minimumSplineLength, float minimumArcLength);
+
+ bool import(std::istream &stream);
Layer::List layers() const;
diff --git a/src/main.cpp b/src/main.cpp
index b72ce46..e241947 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -9,6 +9,21 @@
#include
#include
+#include
+
+#include
+
+#ifdef BUILD_WASM
+
+Q_IMPORT_PLUGIN(QWasmIntegrationPlugin)
+Q_IMPORT_PLUGIN(QSvgIconPlugin)
+Q_IMPORT_PLUGIN(QGifPlugin)
+Q_IMPORT_PLUGIN(QICOPlugin)
+Q_IMPORT_PLUGIN(QJpegPlugin)
+Q_IMPORT_PLUGIN(QSvgPlugin)
+
+#endif
+
void setDarkPalette(QApplication &qapp)
{
QPalette palette;
@@ -32,6 +47,8 @@ void setDarkPalette(QApplication &qapp)
int main(int argc, char *argv[])
{
+ qputenv("QT3D_RENDERER", "opengl");
+
Q_INIT_RESOURCE(resource);
QApplication qapp(argc, argv);
@@ -71,5 +88,7 @@ int main(int argc, char *argv[])
const QString fileName = parser.positionalArguments().value(0, "");
app.loadFileFromCmd(fileName);
- qapp.exec();
+ window.show();
+
+ return qapp.exec();
}
diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt
index 340dc4a..4fd7a2e 100644
--- a/src/model/CMakeLists.txt
+++ b/src/model/CMakeLists.txt
@@ -1,6 +1,7 @@
set(SRC
application.cpp
document.cpp
+ documenthistory.cpp
layer.cpp
offsettedpath.cpp
path.cpp
@@ -12,6 +13,7 @@ set(SRC
application.h
document.h
+ documenthistory.h
layer.h
path.h
offsettedpath.h
diff --git a/src/model/application.cpp b/src/model/application.cpp
index 3daf544..e29ba1a 100644
--- a/src/model/application.cpp
+++ b/src/model/application.cpp
@@ -4,7 +4,6 @@
#include
#include
#include
-#include
#include
#include
@@ -20,26 +19,78 @@
#include
#include
+#ifdef BUILD_WASM
+# include
+#endif
+
namespace model
{
static const QString configFileName = "config.yml";
+static QDir configFileDir()
+{
+ return QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
+}
+
+static void ensureDirExists(const QDir &directory)
+{
+ directory.mkpath(".");
+
+#ifdef BUILD_WASM
+ const char *dirPath = directory.absolutePath().toUtf8().constData();
+
+ EM_ASM({
+ let directory = UTF8ToString($0);
+ console.log("Setup config directory to: " + directory);
+
+ //let analyse = FS.analyzePath(directory);
+ //if (!analyse.exists) {
+ // Make a directory other than '/'
+ // FS.mkdir(directory);
+ //}
+
+ // Then mount with IDBFS type
+ FS.mount(IDBFS, {}, directory);
+
+ // Then sync
+ FS.syncfs(true, function (err) {
+ // Error
+ });
+ }, dirPath);
+#endif
+}
+
/** Retrieves application config file path
* @return config file path
*/
static std::string configFilePath()
{
- const QDir dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
+ const QDir directory = configFileDir();
// Ensure the path exists
- dir.mkpath(".");
+ ensureDirExists(directory);
- const QString path = dir.filePath(configFileName);
+ const QString path = directory.filePath(configFileName);
return path.toStdString();
}
+void Application::setOpenedDocument(Document::UPtr &&document)
+{
+ m_openedDocument = std::move(document);
+ m_documentHistory = std::make_unique(*m_openedDocument);
+
+ emit newDocumentOpened(m_openedDocument.get());
+}
+
+void Application::setRestoredDocument(const Document &documentVersion)
+{
+ m_openedDocument = std::make_unique(documentVersion);
+ emit documentRestoredFromHistory(m_openedDocument.get());
+}
+
+
QString Application::baseName(const QString& fileName)
{
const QFileInfo fileInfo(fileName);
@@ -100,10 +151,6 @@ geometry::Polyline::List Application::postProcessImportedPolylines(geometry::Pol
// Remove small bulges
geometry::filter::Cleaner cleaner(assembler.polylines(), dxf.minimumPolylineLength(), dxf.minimumArcLength());
- if (dxf.sortPathByLength()) {
- geometry::filter::Sorter sorter(cleaner.polylines());
- return sorter.polylines();
- }
return cleaner.polylines();
}
@@ -120,7 +167,30 @@ Task::UPtr Application::createTaskFromDxfImporter(const importer::dxf::Importer&
layers.emplace_back(std::make_unique(layerName, std::move(children)));
}
- return std::make_unique(std::move(layers));
+ Task::UPtr task = std::make_unique(std::move(layers));
+
+ const config::Import::Dxf &dxf = m_config.root().import().dxf();
+ if (dxf.sortPathByLength()) {
+ task->sortPathsByLength();
+ }
+
+ return task;
+}
+
+std::optional Application::findFileType(const QString &fileName) const
+{
+ const QMimeDatabase db;
+ const QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension);
+ const QString mineName = mime.name();
+
+ if (mineName == "image/vnd.dxf") {
+ return SupportFileType::Dxf;
+ }
+ else if (mineName == "application/octet-stream") {
+ return SupportFileType::Dxfplot;
+ }
+
+ return std::nullopt;
}
Application::Application()
@@ -215,25 +285,61 @@ void Application::loadFileFromCmd(const QString &fileName)
bool Application::loadFile(const QString &fileName)
{
- qInfo() << "Opening " << fileName;
+ std::optional fileTypeOpt = findFileType(fileName);
+ if (!fileTypeOpt) {
+ qCritical() << "Unsupported file type: " << fileName;
+ return false;
+ }
- const QMimeDatabase db;
- const QMimeType mime = db.mimeTypeForFile(fileName);
- const QString mineName = mime.name();
+ QFile file(fileName);
+ file.open(QIODeviceBase::ReadOnly);
+ const QByteArray fileContent(file.readAll());
+ file.close();
- if (mineName == "image/vnd.dxf") {
- if (!loadFromDxf(fileName)) {
- return false;
- }
- }
- else if (mineName == "text/plain") {
- loadFromDxfplot(fileName);
+ if (fileContent.isEmpty()) {
+ qCritical() << "Failed opening " << fileName;
+ return false;
}
- else {
- qCritical() << "Invalid file type: " << fileName;
+
+ return loadFile(fileName, *fileTypeOpt, fileContent);
+}
+
+bool Application::loadFile(const QString &fileName, const QByteArray &fileContent)
+{
+ std::optional fileTypeOpt = findFileType(fileName);
+ if (!fileTypeOpt) {
+ qCritical() << "Unsupported file type: " << fileName;
return false;
}
+ return loadFile(fileName, *fileTypeOpt, fileContent);
+}
+
+bool Application::loadFile(const QString &fileName, SupportFileType fileType, const QByteArray &fileContent)
+{
+ qInfo() << "Opening " << fileName;
+
+ std::istringstream streamFileContent(fileContent.toStdString());
+
+ switch (fileType) {
+ case SupportFileType::Dxf:
+ {
+ if (!loadFromDxf(streamFileContent)) {
+ return false;
+ }
+ break;
+ }
+ case SupportFileType::Dxfplot:
+ {
+ if (!loadFromDxfplot(fileName)) {
+ return false;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
m_lastHandledFileBaseName = baseName(fileName);
resetLastSavedFileNames();
@@ -245,21 +351,17 @@ bool Application::loadFile(const QString &fileName)
return true;
}
-bool Application::loadFromDxf(const QString &fileName)
+bool Application::loadFromDxf(std::istream &fileContent)
{
const config::Import::Dxf &dxf = m_config.root().import().dxf();
- try {
- importer::dxf::Importer importer(fileName.toStdString(), dxf.splineToArcPrecision(), dxf.minimumSplineLength(), dxf.minimumArcLength());
-
- m_openedDocument = std::make_unique(createTaskFromDxfImporter(importer), *m_defaultToolConfig, *m_defaultProfileConfig);
- }
- catch (const common::FileCouldNotOpenException&) {
- qCritical() << "File not found:" << fileName;
+ importer::dxf::Importer importer(dxf.splineToArcPrecision(), dxf.minimumSplineLength(), dxf.minimumArcLength());
+ if (!importer.import(fileContent)) {
+ qCritical() << "Failed to import dxf file";
return false;
}
- emit documentChanged(m_openedDocument.get());
+ setOpenedDocument(std::make_unique(createTaskFromDxfImporter(importer), *m_defaultToolConfig, *m_defaultProfileConfig));
return true;
}
@@ -269,26 +371,24 @@ bool Application::loadFromDxfplot(const QString &fileName)
try {
importer::dxfplot::Importer importer(m_config.root().tools(), m_config.root().profiles());
- m_openedDocument = importer(fileName.toStdString());
+ setOpenedDocument(importer(fileName.toStdString()));
}
catch (const common::FileCouldNotOpenException&) {
return false;
}
- emit documentChanged(m_openedDocument.get());
-
return true;
}
+constexpr exporter::gcode::Exporter::Options gcodeExportOptions = static_cast(
+ exporter::gcode::Exporter::ExportConfig |
+ exporter::gcode::Exporter::ExportMetadata
+);
+
bool Application::saveToGcode(const QString &fileName)
{
try {
- const exporter::gcode::Exporter::Options options = static_cast(
- exporter::gcode::Exporter::ExportConfig |
- exporter::gcode::Exporter::ExportMetadata
- );
-
- exporter::gcode::Exporter exporter(m_openedDocument->toolConfig(), m_openedDocument->profileConfig(),options);
+ exporter::gcode::Exporter exporter(m_openedDocument->toolConfig(), m_openedDocument->profileConfig(), gcodeExportOptions);
const bool saved = saveToFile(exporter, fileName);
if (saved) {
m_lastSavedGcodeFileName = fileName;
@@ -302,6 +402,13 @@ bool Application::saveToGcode(const QString &fileName)
return false;
}
+QByteArray Application::saveToGcode()
+{
+ exporter::gcode::Exporter exporter(m_openedDocument->toolConfig(), m_openedDocument->profileConfig(), gcodeExportOptions);
+
+ return saveToBuffer(exporter);
+}
+
bool Application::saveToDxfplot(const QString &fileName)
{
exporter::dxfplot::Exporter exporter;
@@ -314,20 +421,33 @@ bool Application::saveToDxfplot(const QString &fileName)
return saved;
}
+QByteArray Application::saveToDxfplot()
+{
+ exporter::dxfplot::Exporter exporter;
+
+ return saveToBuffer(exporter);
+}
+
void Application::leftCutterCompensation()
{
cutterCompensation(1.0f);
+
+ takeDocumentSnapshot();
}
void Application::rightCutterCompensation()
{
cutterCompensation(-1.0f);
+
+ takeDocumentSnapshot();
}
void Application::resetCutterCompensation()
{
Task &task = m_openedDocument->task();
task.resetCutterCompensationSelection();
+
+ takeDocumentSnapshot();
}
void Application::pocketSelection()
@@ -337,6 +457,8 @@ void Application::pocketSelection()
Task &task = m_openedDocument->task();
task.pocketSelection(radius, dxf.minimumPolylineLength(), dxf.minimumArcLength());
+
+ takeDocumentSnapshot();
}
geometry::Rect Application::selectionBoundingRect() const
@@ -349,18 +471,24 @@ void Application::transformSelection(const QTransform& matrix)
{
Task &task = m_openedDocument->task();
task.transformSelection(matrix);
+
+ takeDocumentSnapshot();
}
void Application::hideSelection()
{
Task &task = m_openedDocument->task();
task.hideSelection();
+
+ takeDocumentSnapshot();
}
void Application::showHidden()
{
Task &task = m_openedDocument->task();
task.showHidden();
+
+ takeDocumentSnapshot();
}
Simulation Application::createSimulation()
@@ -369,4 +497,19 @@ Simulation Application::createSimulation()
return Simulation(*m_openedDocument, fastMoveFeedRate);
}
+void Application::takeDocumentSnapshot()
+{
+ m_documentHistory->takeSnapshot(*m_openedDocument);
+}
+
+void Application::undoDocumentChanges()
+{
+ setRestoredDocument(m_documentHistory->undo());
+}
+
+void Application::redoDocumentChanges()
+{
+ setRestoredDocument(m_documentHistory->redo());
+}
+
}
diff --git a/src/model/application.cpp.orig b/src/model/application.cpp.orig
new file mode 100644
index 0000000..906b613
--- /dev/null
+++ b/src/model/application.cpp.orig
@@ -0,0 +1,516 @@
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef BUILD_WASM
+# include
+#endif
+
+namespace model
+{
+
+static const QString configFileName = "config.yml";
+
+static QDir configFileDir()
+{
+ return QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
+}
+
+static void ensureDirExists(const QDir &directory)
+{
+ directory.mkpath(".");
+
+#ifdef BUILD_WASM
+ const char *dirPath = directory.absolutePath().toUtf8().constData();
+
+ EM_ASM({
+ let directory = UTF8ToString($0);
+ console.log("Setup config directory to: " + directory);
+
+ //let analyse = FS.analyzePath(directory);
+ //if (!analyse.exists) {
+ // Make a directory other than '/'
+ // FS.mkdir(directory);
+ //}
+
+ // Then mount with IDBFS type
+ FS.mount(IDBFS, {}, directory);
+
+ // Then sync
+ FS.syncfs(true, function (err) {
+ // Error
+ });
+ }, dirPath);
+#endif
+}
+
+/** Retrieves application config file path
+ * @return config file path
+ */
+static std::string configFilePath()
+{
+ const QDir directory = configFileDir();
+
+ // Ensure the path exists
+ ensureDirExists(directory);
+
+ const QString path = directory.filePath(configFileName);
+
+ return path.toStdString();
+}
+
+void Application::setOpenedDocument(Document::UPtr &&document)
+{
+ m_openedDocument = std::move(document);
+ m_documentHistory = std::make_unique(*m_openedDocument);
+
+ emit newDocumentOpened(m_openedDocument.get());
+}
+
+void Application::setRestoredDocument(const Document &documentVersion)
+{
+ m_openedDocument = std::make_unique(documentVersion);
+ emit documentRestoredFromHistory(m_openedDocument.get());
+}
+
+
+QString Application::baseName(const QString& fileName)
+{
+ const QFileInfo fileInfo(fileName);
+ return fileInfo.absoluteDir().filePath(fileInfo.baseName());
+}
+
+void Application::resetLastSavedFileNames()
+{
+ m_lastSavedDxfplotFileName.clear();
+ m_lastSavedGcodeFileName.clear();
+}
+
+PathSettings Application::defaultPathSettings() const
+{
+ const config::Profiles::Profile::DefaultPath &defaultPath = m_defaultProfileConfig->defaultPath();
+ return PathSettings(defaultPath.planeFeedRate(), defaultPath.depthFeedRate(), defaultPath.intensity(), defaultPath.depth());
+}
+
+const config::Tools::Tool *Application::findTool(const std::string &name) const
+{
+ const config::Tools &tools = m_config.root().tools();
+ if (tools.has(name)) {
+ return &tools[name];
+ }
+
+ return nullptr;
+}
+
+const config::Profiles::Profile *Application::findProfile(const std::string &name) const
+{
+ const config::Profiles &profiles = m_config.root().profiles();
+ if (profiles.has(name)) {
+ return &profiles[name];
+ }
+
+ return nullptr;
+}
+
+void Application::cutterCompensation(float scale)
+{
+ const config::Import::Dxf &dxf = m_config.root().import().dxf();
+
+ const float radius = m_openedDocument->toolConfig().general().radius();
+ const float scaledRadius = radius * scale;
+
+ Task &task = m_openedDocument->task();
+ task.cutterCompensationSelection(scaledRadius, dxf.minimumPolylineLength(), dxf.minimumArcLength());
+}
+
+geometry::Polyline::List Application::postProcessImportedPolylines(geometry::Polyline::List &&rawPolylines) const
+{
+ const config::Import::Dxf &dxf = m_config.root().import().dxf();
+
+ geometry::filter::RemoveExactDuplicate removeExactDuplicate(std::move(rawPolylines));
+
+ // Merge polylines to create longest contours
+ geometry::filter::Assembler assembler(removeExactDuplicate.polylines(), dxf.assembleTolerance());
+ // Remove small bulges
+ geometry::filter::Cleaner cleaner(assembler.polylines(), dxf.minimumPolylineLength(), dxf.minimumArcLength());
+
+ return cleaner.polylines();
+}
+
+Task::UPtr Application::createTaskFromDxfImporter(const importer::dxf::Importer& importer)
+{
+ Layer::ListUPtr layers;
+ for (importer::dxf::Layer &importerLayer : importer.layers()) {
+ const std::string &layerName = importerLayer.name();
+ geometry::Polyline::List polylines = postProcessImportedPolylines(importerLayer.polylines());
+
+ // Create paths from merged and cleaned polylines of one layer
+ Path::ListUPtr children = Path::FromPolylines(std::move(polylines), defaultPathSettings(), layerName);
+
+ layers.emplace_back(std::make_unique(layerName, std::move(children)));
+ }
+
+ Task::UPtr task = std::make_unique(std::move(layers));
+
+ const config::Import::Dxf &dxf = m_config.root().import().dxf();
+ if (dxf.sortPathByLength()) {
+ task->sortPathsByLength();
+ }
+
+ return task;
+}
+
+std::optional Application::findFileType(const QString &fileName) const
+{
+ const QMimeDatabase db;
+ const QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension);
+ const QString mineName = mime.name();
+
+ if (mineName == "image/vnd.dxf") {
+ return SupportFileType::Dxf;
+ }
+ else if (mineName == "application/octet-stream") {
+ return SupportFileType::Dxfplot;
+ }
+
+ return std::nullopt;
+}
+
+Application::Application()
+ :m_config(configFilePath()),
+ // Default select first tool
+ m_defaultToolConfig(&m_config.root().tools().first()),
+ // Default select first profile
+ m_defaultProfileConfig(&m_config.root().profiles().first())
+{
+}
+
+config::Config &Application::config()
+{
+ return m_config;
+}
+
+void Application::setConfig(config::Config &&config)
+{
+ m_config = std::move(config);
+ emit configChanged(m_config);
+}
+
+bool Application::selectTool(const QString &toolName)
+{
+ const std::string name = toolName.toStdString();
+ const config::Tools::Tool *tool = findTool(name);
+
+ if (tool) {
+ if (m_openedDocument) {
+ m_openedDocument->setToolConfig(*tool);
+ }
+ m_defaultToolConfig = tool;
+
+ return true;
+ }
+
+ return false;
+}
+
+void Application::defaultToolFromCmd(const QString &toolName)
+{
+ if (!selectTool(toolName)) {
+ qCritical() << "Invalid tool name " << toolName;
+ }
+}
+
+bool Application::selectProfile(const QString &profileName)
+{
+ const std::string name = profileName.toStdString();
+ const config::Profiles::Profile *profile = findProfile(name);
+
+ if (profile) {
+ if (m_openedDocument) {
+ m_openedDocument->setProfileConfig(*profile);
+ }
+ m_defaultProfileConfig = profile;
+
+ return true;
+ }
+
+ return false;
+}
+
+void Application::defaultProfileFromCmd(const QString &profileName)
+{
+ if (!selectProfile(profileName)) {
+ qCritical() << "Invalid profile name " << profileName;
+ }
+}
+
+const QString &Application::lastHandledFileBaseName() const
+{
+ return m_lastHandledFileBaseName;
+}
+
+const QString &Application::lastSavedDxfplotFileName() const
+{
+ return m_lastSavedDxfplotFileName;
+}
+
+const QString &Application::lastSavedGcodeFileName() const
+{
+ return m_lastSavedGcodeFileName;
+}
+
+void Application::loadFileFromCmd(const QString &fileName)
+{
+ if (!fileName.isEmpty()) {
+ loadFile(fileName);
+ }
+}
+
+bool Application::loadFile(const QString &fileName)
+{
+ std::optional fileTypeOpt = findFileType(fileName);
+ if (!fileTypeOpt) {
+ qCritical() << "Unsupported file type: " << fileName;
+ return false;
+ }
+
+ QFile file(fileName);
+ file.open(QIODeviceBase::ReadOnly);
+ const QByteArray fileContent(file.readAll());
+ file.close();
+
+ if (fileContent.isEmpty()) {
+ qCritical() << "Failed opening " << fileName;
+ return false;
+ }
+
+ return loadFile(fileName, *fileTypeOpt, fileContent);
+}
+
+bool Application::loadFile(const QString &fileName, const QByteArray &fileContent)
+{
+ std::optional fileTypeOpt = findFileType(fileName);
+ if (!fileTypeOpt) {
+ qCritical() << "Unsupported file type: " << fileName;
+ return false;
+ }
+
+ return loadFile(fileName, *fileTypeOpt, fileContent);
+}
+
+bool Application::loadFile(const QString &fileName, SupportFileType fileType, const QByteArray &fileContent)
+{
+ qInfo() << "Opening " << fileName;
+
+ std::istringstream streamFileContent(fileContent.toStdString());
+
+ switch (fileType) {
+ case SupportFileType::Dxf:
+ {
+ if (!loadFromDxf(streamFileContent)) {
+ return false;
+ }
+ break;
+ }
+ case SupportFileType::Dxfplot:
+ {
+ if (!loadFromDxfplot(fileName)) {
+ return false;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ m_lastHandledFileBaseName = baseName(fileName);
+ resetLastSavedFileNames();
+
+ // Update window title based on file name.
+ const QFileInfo fileInfo(fileName);
+ const QString title = fileInfo.fileName();
+ emit titleChanged(title);
+
+ return true;
+}
+
+bool Application::loadFromDxf(std::istream &fileContent)
+{
+ const config::Import::Dxf &dxf = m_config.root().import().dxf();
+
+<<<<<<< HEAD
+ try {
+ importer::dxf::Importer importer(fileName.toStdString(), dxf.splineToArcPrecision(), dxf.minimumSplineLength(), dxf.minimumArcLength());
+
+ setOpenedDocument(std::make_unique(createTaskFromDxfImporter(importer), *m_defaultToolConfig, *m_defaultProfileConfig));
+ }
+ catch (const common::FileCouldNotOpenException&) {
+ qCritical() << "File not found:" << fileName;
+ return false;
+ }
+
+=======
+ importer::dxf::Importer importer(dxf.splineToArcPrecision(), dxf.minimumSplineLength(), dxf.minimumArcLength());
+ if (!importer.import(fileContent)) {
+ qCritical() << "Failed to import dxf file";
+ return false;
+ }
+
+ m_openedDocument = std::make_unique(createTaskFromDxfImporter(importer), *m_defaultToolConfig, *m_defaultProfileConfig);
+
+ emit documentChanged(m_openedDocument.get());
+
+>>>>>>> d6f2ed7 (chore: Use qt async open file function to open dxf & dxfplot)
+ return true;
+}
+
+bool Application::loadFromDxfplot(const QString &fileName)
+{
+ try {
+ importer::dxfplot::Importer importer(m_config.root().tools(), m_config.root().profiles());
+
+ setOpenedDocument(importer(fileName.toStdString()));
+ }
+ catch (const common::FileCouldNotOpenException&) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Application::saveToGcode(const QString &fileName)
+{
+ try {
+ const exporter::gcode::Exporter::Options options = static_cast(
+ exporter::gcode::Exporter::ExportConfig |
+ exporter::gcode::Exporter::ExportMetadata
+ );
+
+ exporter::gcode::Exporter exporter(m_openedDocument->toolConfig(), m_openedDocument->profileConfig(),options);
+ const bool saved = saveToFile(exporter, fileName);
+ if (saved) {
+ m_lastSavedGcodeFileName = fileName;
+ }
+ return saved;
+ }
+ catch (const std::exception &exception) {
+ emit errorRaised(exception.what());
+ }
+
+ return false;
+}
+
+bool Application::saveToDxfplot(const QString &fileName)
+{
+ exporter::dxfplot::Exporter exporter;
+
+ const bool saved = saveToFile(exporter, fileName);
+ if (saved) {
+ m_lastSavedDxfplotFileName = fileName;
+ }
+
+ return saved;
+}
+
+void Application::leftCutterCompensation()
+{
+ cutterCompensation(1.0f);
+
+ takeDocumentSnapshot();
+}
+
+void Application::rightCutterCompensation()
+{
+ cutterCompensation(-1.0f);
+
+ takeDocumentSnapshot();
+}
+
+void Application::resetCutterCompensation()
+{
+ Task &task = m_openedDocument->task();
+ task.resetCutterCompensationSelection();
+
+ takeDocumentSnapshot();
+}
+
+void Application::pocketSelection()
+{
+ const config::Import::Dxf &dxf = m_config.root().import().dxf();
+ const float radius = m_openedDocument->toolConfig().general().radius();
+
+ Task &task = m_openedDocument->task();
+ task.pocketSelection(radius, dxf.minimumPolylineLength(), dxf.minimumArcLength());
+
+ takeDocumentSnapshot();
+}
+
+geometry::Rect Application::selectionBoundingRect() const
+{
+ Task &task = m_openedDocument->task();
+ return task.selectionBoundingRect();
+}
+
+void Application::transformSelection(const QTransform& matrix)
+{
+ Task &task = m_openedDocument->task();
+ task.transformSelection(matrix);
+
+ takeDocumentSnapshot();
+}
+
+void Application::hideSelection()
+{
+ Task &task = m_openedDocument->task();
+ task.hideSelection();
+
+ takeDocumentSnapshot();
+}
+
+void Application::showHidden()
+{
+ Task &task = m_openedDocument->task();
+ task.showHidden();
+
+ takeDocumentSnapshot();
+}
+
+Simulation Application::createSimulation()
+{
+ const float fastMoveFeedRate = m_config.root().simulation().fastMoveFeedRate();
+ return Simulation(*m_openedDocument, fastMoveFeedRate);
+}
+
+void Application::takeDocumentSnapshot()
+{
+ m_documentHistory->takeSnapshot(*m_openedDocument);
+}
+
+void Application::undoDocumentChanges()
+{
+ setRestoredDocument(m_documentHistory->undo());
+}
+
+void Application::redoDocumentChanges()
+{
+ setRestoredDocument(m_documentHistory->redo());
+}
+
+}
diff --git a/src/model/application.h b/src/model/application.h
index ef416e5..22757da 100644
--- a/src/model/application.h
+++ b/src/model/application.h
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
#include
#include
@@ -35,6 +36,10 @@ class Application : public QObject
QString m_lastSavedDxfplotFileName;
Document::UPtr m_openedDocument;
+ DocumentHistory::UPtr m_documentHistory;
+
+ void setOpenedDocument(Document::UPtr &&document);
+ void setRestoredDocument(const Document &documentVersion);
static QString baseName(const QString& fileName);
void resetLastSavedFileNames();
@@ -64,6 +69,23 @@ class Application : public QObject
return false;
}
+ template
+ QByteArray saveToBuffer(Exporter &&exporter)
+ {
+ std::stringstream outputStream;
+ exporter(*m_openedDocument, outputStream);
+ const std::string output = outputStream.str();
+
+ return QByteArray(output.data(), output.size());
+ }
+
+ enum class SupportFileType {
+ Dxf,
+ Dxfplot
+ };
+
+ std::optional findFileType(const QString &fileName) const;
+
public:
struct FileExtension {
inline static const QString Gcode = ".ngc";
@@ -88,11 +110,15 @@ class Application : public QObject
const QString &lastSavedGcodeFileName() const;
void loadFileFromCmd(const QString &fileName);
bool loadFile(const QString &fileName);
- bool loadFromDxf(const QString &fileName);
+ bool loadFile(const QString &fileName, const QByteArray &fileContent);
+ bool loadFile(const QString &fileName, SupportFileType fileType, const QByteArray &fileContent);
+ bool loadFromDxf(std::istream &fileContent);
bool loadFromDxfplot(const QString &fileName);
bool saveToGcode(const QString &fileName);
+ QByteArray saveToGcode();
bool saveToDxfplot(const QString &fileName);
+ QByteArray saveToDxfplot();
void leftCutterCompensation();
void rightCutterCompensation();
@@ -107,8 +133,13 @@ class Application : public QObject
Simulation createSimulation();
+ void takeDocumentSnapshot();
+ void undoDocumentChanges();
+ void redoDocumentChanges();
+
Q_SIGNALS:
- void documentChanged(Document *newDocument);
+ void newDocumentOpened(Document *newDocument);
+ void documentRestoredFromHistory(Document *newDocument);
void titleChanged(QString title);
void configChanged(config::Config &config);
void errorRaised(const QString& message) const;
diff --git a/src/model/document.cpp b/src/model/document.cpp
index bf457ab..92ded7e 100644
--- a/src/model/document.cpp
+++ b/src/model/document.cpp
@@ -8,7 +8,20 @@ Document::Document(Task::UPtr&& task, const config::Tools::Tool &toolConfig, con
m_toolConfig(&toolConfig),
m_profileConfig(&profileConfig)
{
+}
+Document::Document(const Document &other)
+ :m_task(std::make_unique(other.task())),
+ m_toolConfig(&other.toolConfig()),
+ m_profileConfig(&other.profileConfig())
+{
+}
+
+Document &Document::operator=(const Document &other)
+{
+ m_task = std::make_unique(other.task());
+ m_toolConfig = &other.toolConfig();
+ m_profileConfig = &other.profileConfig();
}
Task &Document::task()
@@ -34,13 +47,11 @@ const config::Profiles::Profile &Document::profileConfig() const
void Document::setToolConfig(const config::Tools::Tool &tool)
{
m_toolConfig = &tool;
- emit toolConfigChanged(tool);
}
void Document::setProfileConfig(const config::Profiles::Profile &profile)
{
m_profileConfig = &profile;
- emit profileConfigChanged(profile);
}
}
diff --git a/src/model/document.h b/src/model/document.h
index fe40fc1..0cb182f 100644
--- a/src/model/document.h
+++ b/src/model/document.h
@@ -6,10 +6,8 @@
namespace model
{
-class Document : public QObject, public common::Aggregable
+class Document : public common::Aggregable
{
- Q_OBJECT;
-
private:
Task::UPtr m_task;
const config::Tools::Tool *m_toolConfig;
@@ -18,6 +16,10 @@ class Document : public QObject, public common::Aggregable
public:
explicit Document(Task::UPtr&& task, const config::Tools::Tool &toolConfig, const config::Profiles::Profile &profileConfig);
Document() = default;
+ Document(const Document &other);
+
+ Document &operator=(Document &&other) = default;
+ Document &operator=(const Document &other);
Task& task();
const Task& task() const;
@@ -26,10 +28,6 @@ class Document : public QObject, public common::Aggregable
const config::Profiles::Profile &profileConfig() const;
void setToolConfig(const config::Tools::Tool &tool);
void setProfileConfig(const config::Profiles::Profile &profile);
-
-Q_SIGNALS:
- void toolConfigChanged(const config::Tools::Tool &tool);
- void profileConfigChanged(const config::Profiles::Profile &profile);
};
}
diff --git a/src/model/documenthistory.cpp b/src/model/documenthistory.cpp
new file mode 100644
index 0000000..9954724
--- /dev/null
+++ b/src/model/documenthistory.cpp
@@ -0,0 +1,55 @@
+#include
+
+#include
+
+namespace model
+{
+
+bool DocumentHistory::isCurrentDocumentLastOfHistory() const
+{
+ return m_currentDocumentIt == (m_documentHistory.end() - 1);
+}
+
+bool DocumentHistory::isCurrentDocumentFirstOfHistory() const
+{
+ return m_currentDocumentIt == m_documentHistory.begin();
+}
+
+DocumentHistory::DocumentHistory(const Document& initialDocument)
+ :m_currentDocumentIt(m_documentHistory.insert(m_documentHistory.end(), initialDocument))
+{
+}
+
+void DocumentHistory::takeSnapshot(const Document& currentDocument)
+{
+ if (!isCurrentDocumentLastOfHistory()) {
+ m_documentHistory.erase(m_currentDocumentIt + 1, m_documentHistory.end());
+ }
+
+ constexpr int MaximumSnapshots = 100;
+ if (m_documentHistory.size() == MaximumSnapshots) {
+ m_documentHistory.erase(m_documentHistory.begin());
+ }
+
+ m_currentDocumentIt = m_documentHistory.insert(m_documentHistory.end(), currentDocument);
+}
+
+const Document &DocumentHistory::undo()
+{
+ if (!isCurrentDocumentFirstOfHistory()) {
+ --m_currentDocumentIt;
+ }
+
+ return *m_currentDocumentIt;
+}
+
+const Document &DocumentHistory::redo()
+{
+ if (!isCurrentDocumentLastOfHistory()) {
+ ++m_currentDocumentIt;
+ }
+
+ return *m_currentDocumentIt;
+}
+
+}
diff --git a/src/model/documenthistory.h b/src/model/documenthistory.h
new file mode 100644
index 0000000..7931029
--- /dev/null
+++ b/src/model/documenthistory.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include
+
+namespace model
+{
+
+class Document;
+
+class DocumentHistory : public common::Aggregable
+{
+private:
+ Document::List m_documentHistory;
+ Document::List::iterator m_currentDocumentIt;
+
+ bool isCurrentDocumentLastOfHistory() const;
+ bool isCurrentDocumentFirstOfHistory() const;
+
+public:
+ explicit DocumentHistory(const Document& initialDocument);
+
+ void takeSnapshot(const Document& currentDocument);
+ const Document &undo();
+ const Document &redo();
+};
+
+}
diff --git a/src/model/documentmodelobserver.h b/src/model/documentmodelobserver.h
index 4287b82..74e48a7 100644
--- a/src/model/documentmodelobserver.h
+++ b/src/model/documentmodelobserver.h
@@ -26,6 +26,16 @@ class DocumentModelObserver : public QtBaseObject
*/
virtual void documentChanged() = 0;
+ /// Notify the document was changed after an undo or redo operation
+ virtual void documentRestoredFromHistory()
+ {
+ }
+
+ /// Notify the document was changed after an opening operation
+ virtual void newDocumentOpened()
+ {
+ }
+
protected:
Document *document() const
{
@@ -46,11 +56,24 @@ private Q_SLOTS:
documentChanged();
}
+ void internalDocumentRestoredFromHistory(Document *newDocument)
+ {
+ internalDocumentChanged(newDocument);
+ documentRestoredFromHistory();
+ }
+
+ void internalNewDocumentOpened(Document *newDocument)
+ {
+ internalDocumentChanged(newDocument);
+ newDocumentOpened();
+ }
+
public:
explicit DocumentModelObserver(Application &app)
:m_document(nullptr)
{
- QObject::connect(&app, &Application::documentChanged, this, &DocumentModelObserver::internalDocumentChanged);
+ QObject::connect(&app, &Application::documentRestoredFromHistory, this, &DocumentModelObserver::internalDocumentRestoredFromHistory);
+ QObject::connect(&app, &Application::newDocumentOpened, this, &DocumentModelObserver::internalNewDocumentOpened);
}
};
diff --git a/src/model/layer.cpp b/src/model/layer.cpp
index 88082a0..0a8531c 100644
--- a/src/model/layer.cpp
+++ b/src/model/layer.cpp
@@ -1,5 +1,7 @@
#include
+#include
+
namespace model
{
@@ -17,6 +19,15 @@ Layer::Layer(const std::string &name, Path::ListUPtr &&children)
assignSelfToChildren();
}
+Layer::Layer(const Layer& other)
+ :Renderable(other),
+ m_children(common::deepcopy(other.m_children))
+{
+ for (Path::UPtr &child : m_children) {
+ child->setLayer(*this);
+ }
+}
+
int Layer::childrenCount() const
{
return m_children.size();
diff --git a/src/model/layer.h b/src/model/layer.h
index 1e7c5e5..1cd2ef7 100644
--- a/src/model/layer.h
+++ b/src/model/layer.h
@@ -21,6 +21,7 @@ class Layer : public Renderable, public common::Aggregable
public:
explicit Layer(const std::string &name, Path::ListUPtr &&children);
explicit Layer() = default;
+ explicit Layer(const Layer& other);;
int childrenCount() const;
Path& childrenAt(int index);
diff --git a/src/model/offsettedpath.cpp b/src/model/offsettedpath.cpp
index 25294e7..d2465f3 100644
--- a/src/model/offsettedpath.cpp
+++ b/src/model/offsettedpath.cpp
@@ -9,6 +9,13 @@ OffsettedPath::OffsettedPath(geometry::Polyline::List &&offsettedPolylines, Dire
{
}
+OffsettedPath::OffsettedPath(const OffsettedPath& other)
+ :QObject(),
+ m_polylines(other.m_polylines),
+ m_direction(other.m_direction)
+{
+}
+
const geometry::Polyline::List &OffsettedPath::polylines() const
{
return m_polylines;
diff --git a/src/model/offsettedpath.h b/src/model/offsettedpath.h
index 8c623e6..55e015d 100644
--- a/src/model/offsettedpath.h
+++ b/src/model/offsettedpath.h
@@ -33,6 +33,7 @@ class OffsettedPath : public QObject
public:
explicit OffsettedPath(geometry::Polyline::List &&offsettedPolylines, Direction direction);
+ explicit OffsettedPath(const OffsettedPath& other);
explicit OffsettedPath() = default;
const geometry::Polyline::List &polylines() const;
diff --git a/src/model/path.cpp b/src/model/path.cpp
index 89b5d53..526688d 100644
--- a/src/model/path.cpp
+++ b/src/model/path.cpp
@@ -27,6 +27,19 @@ Path::Path(geometry::Polyline &&basePolyline, const std::string &name, const Pat
connect(this, &Path::visibilityChanged, this, &Path::updateGlobalVisibility);
}
+Path::Path(const Path& other)
+ :Renderable(other),
+ m_basePolyline(other.m_basePolyline),
+ m_settings(other.m_settings),
+ m_globallyVisible(other.m_globallyVisible)
+{
+ connect(this, &Path::visibilityChanged, this, &Path::updateGlobalVisibility);
+
+ if (other.m_offsettedPath) {
+ m_offsettedPath = std::make_unique(*other.m_offsettedPath);
+ }
+}
+
Path::ListUPtr Path::FromPolylines(geometry::Polyline::List &&polylines, const PathSettings &settings, const std::string &layerName)
{
const int size = polylines.size();
diff --git a/src/model/path.h b/src/model/path.h
index cbb2d85..41e0dce 100644
--- a/src/model/path.h
+++ b/src/model/path.h
@@ -34,6 +34,7 @@ class Path : public Renderable, public common::Aggregable
public:
explicit Path(geometry::Polyline &&basePolyline, const std::string &name, const PathSettings& settings);
+ explicit Path(const Path& other);
explicit Path() = default;
static ListUPtr FromPolylines(geometry::Polyline::List &&polylines, const PathSettings &settings, const std::string &layerName);
diff --git a/src/model/renderable.cpp b/src/model/renderable.cpp
index 9c7935f..06d6d6e 100644
--- a/src/model/renderable.cpp
+++ b/src/model/renderable.cpp
@@ -10,6 +10,14 @@ Renderable::Renderable(const std::string &name)
{
}
+Renderable::Renderable(const Renderable &other)
+ :QObject(),
+ m_name(other.name()),
+ m_selected(false),
+ m_visible(other.visible())
+{
+}
+
const std::string &Renderable::name() const
{
return m_name;
diff --git a/src/model/renderable.h b/src/model/renderable.h
index 3fb5f08..0cfb557 100644
--- a/src/model/renderable.h
+++ b/src/model/renderable.h
@@ -30,6 +30,7 @@ class Renderable : public QObject
public:
explicit Renderable(const std::string &name);
explicit Renderable() = default;
+ explicit Renderable(const Renderable &other);
const std::string &name() const;
diff --git a/src/model/task.cpp b/src/model/task.cpp
index b2ea3d1..f163b7e 100644
--- a/src/model/task.cpp
+++ b/src/model/task.cpp
@@ -1,6 +1,7 @@
#include
#include
+#include
namespace model
{
@@ -35,6 +36,24 @@ Task::Task(Layer::ListUPtr &&layers)
m_stack = m_paths;
}
+Task::Task(const Task &other)
+ :QObject(),
+ m_layers(common::deepcopy(other.m_layers)),
+ m_stack(other.m_stack.size())
+{
+ initPathsFromLayers();
+
+ // Remap pointers of path on stack
+ std::unordered_map pathRemapping;
+ for (Path::ListPtr::const_iterator ito = other.m_paths.begin(), it = m_paths.begin(), end = m_paths.end(); it != end; ++it, ++ito) {
+ pathRemapping.insert({*ito, *it});
+ }
+
+ std::transform(other.m_stack.begin(), other.m_stack.end(), m_stack.begin(), [&pathRemapping](Path *path){
+ return pathRemapping.find(path)->second;
+ });
+}
+
int Task::pathCount() const
{
return m_paths.size();
@@ -72,6 +91,58 @@ void Task::movePath(int index, MoveDirection direction)
}
}
+void Task::movePathToTip(int index, MoveTip tip)
+{
+ assert(0 <= index && index < pathCount());
+
+ Path *path = m_stack[index];
+ m_stack.erase(m_stack.begin() + index);
+
+ switch (tip) {
+ case MoveTip::Bottom:
+ {
+ m_stack.push_back(path);
+ break;
+ }
+ case MoveTip::Top:
+ {
+ m_stack.insert(m_stack.begin(), path);
+ break;
+ }
+ }
+}
+
+void Task::sortPathsByLength()
+{
+ struct PathLength
+ {
+ Path *path;
+ float length;
+
+ PathLength() = default;
+
+ explicit PathLength(Path *path)
+ :path(path),
+ length(path->basePolyline().length())
+ {
+ }
+
+ bool operator<(const PathLength& other) const
+ {
+ return length < other.length;
+ }
+ };
+
+ std::vector pathsLength(m_paths.size());
+ std::transform(m_paths.begin(), m_paths.end(), pathsLength.begin(),
+ [](Path *path){ return PathLength(path); });
+
+ std::sort(pathsLength.begin(), pathsLength.end());
+
+ std::transform(pathsLength.begin(), pathsLength.end(), m_stack.begin(),
+ [](PathLength& pathLength){ return pathLength.path; });
+}
+
void Task::resetCutterCompensationSelection()
{
forEachSelectedPath([](model::Path &path){ path.resetOffset(); });
diff --git a/src/model/task.h b/src/model/task.h
index 770ef3d..eb65b21 100644
--- a/src/model/task.h
+++ b/src/model/task.h
@@ -30,8 +30,15 @@ class Task : public QObject, public common::Aggregable
DOWN = 1
};
+ enum class MoveTip
+ {
+ Top,
+ Bottom
+ };
+
explicit Task() = default;
explicit Task(Layer::ListUPtr &&layers);
+ explicit Task(const Task &other);
int pathCount() const;
const Path &pathAt(int index) const;
@@ -39,6 +46,9 @@ class Task : public QObject, public common::Aggregable
int pathIndexFor(const Path &path) const;
void movePath(int index, MoveDirection direction);
+ void movePathToTip(int index, MoveTip tip);
+
+ void sortPathsByLength();
template
void forEachPathInStack(Functor &&functor) const
diff --git a/src/view/CMakeLists.txt b/src/view/CMakeLists.txt
index 33184ab..387b579 100644
--- a/src/view/CMakeLists.txt
+++ b/src/view/CMakeLists.txt
@@ -1,7 +1,10 @@
add_subdirectory(dialogs)
add_subdirectory(task)
add_subdirectory(view2d)
-add_subdirectory(simulation)
+
+if (WITH_3D)
+ add_subdirectory(simulation)
+endif()
set(SRC
info.cpp
diff --git a/src/view/dialogs/settings/settings.cpp b/src/view/dialogs/settings/settings.cpp
index a6da204..6dd22a3 100644
--- a/src/view/dialogs/settings/settings.cpp
+++ b/src/view/dialogs/settings/settings.cpp
@@ -62,13 +62,11 @@ void Settings::currentChanged(const QModelIndex ¤t, const QModelIndex &)
NodeVisitor visitor;
m_model->visit(current, visitor);
- // Replace old center widget
+ // Replace old properties widget
QWidget *newWidget = visitor.newWidget();
- QLayoutItem *item = gridLayout->replaceWidget(center, newWidget);
- center = newWidget;
-
- // Delete old center widget
- delete item->widget();
+ QWidget *oldWidget = center->findChild(QString(), Qt::FindDirectChildrenOnly);
+ center->layout()->replaceWidget(oldWidget, newWidget);
+ delete oldWidget;
const bool isList = m_model->isList(current);
addButton->disconnect();
diff --git a/src/view/mainwindow.cpp b/src/view/mainwindow.cpp
index 80846d0..85535eb 100644
--- a/src/view/mainwindow.cpp
+++ b/src/view/mainwindow.cpp
@@ -4,7 +4,9 @@
#include
#include
#include
-#include
+#ifdef WITH_3D
+# include
+#endif
#include
#include
#include
@@ -37,12 +39,16 @@ QWidget *MainWindow::setupLeftPanel()
QWidget *MainWindow::setupCenterPanel()
{
view2d::Viewport *viewport2d = new view2d::Viewport(m_app);
- m_simulation = new simulation::Simulation();
Info *info = new Info(*viewport2d, m_app);
QSplitter *splitter = new QSplitter(Qt::Horizontal, this);
splitter->addWidget(viewport2d);
+
+#ifdef WITH_3D
+ m_simulation = new simulation::Simulation();
splitter->addWidget(m_simulation);
+#endif
+
splitter->setStretchFactor(0, 1);
splitter->setStretchFactor(1, 0);
@@ -82,10 +88,15 @@ void MainWindow::setupMenuActions()
{
// File actions
connect(actionOpenFile, &QAction::triggered, this, &MainWindow::openFile);
+#ifdef BUILD_WASM
+ connect(actionSaveFile, &QAction::triggered, this, &MainWindow::downloadFile);
+ connect(actionExportFile, &QAction::triggered, this, &MainWindow::downloadExportFile);
+#else
connect(actionSaveFile, &QAction::triggered, this, &MainWindow::saveFile);
connect(actionSaveAsFile, &QAction::triggered, this, &MainWindow::saveAsFile);
connect(actionExportFile, &QAction::triggered, this, &MainWindow::exportFile);
connect(actionExportAsFile, &QAction::triggered, this, &MainWindow::exportAsFile);
+#endif
connect(actionOpenSettings, &QAction::triggered, this, &MainWindow::openSettings);
// Edit actions
@@ -99,13 +110,20 @@ void MainWindow::setupMenuActions()
connect(actionMirrorSelection, &QAction::triggered, this, &MainWindow::mirrorSelection);
connect(actionSetSelectionOrigin, &QAction::triggered, this, &MainWindow::setSelectionOrigin);
connect(actionSimulate, &QAction::triggered, this, &MainWindow::simulate);
+ connect(actionUndo, &QAction::triggered, this, &MainWindow::undo);
+ connect(actionRedo, &QAction::triggered, this, &MainWindow::redo);
}
void MainWindow::setupOpenedDocumentActions()
{
+#ifndef BUILD_WASM
+ m_openedDocumentActions.addAction(actionSaveFile);
m_openedDocumentActions.addAction(actionExportFile);
+#else
+ actionSaveAsFile->setVisible(false);
+ actionExportAsFile->setVisible(false);
+#endif
m_openedDocumentActions.addAction(actionExportAsFile);
- m_openedDocumentActions.addAction(actionSaveFile);
m_openedDocumentActions.addAction(actionSaveAsFile);
m_openedDocumentActions.addAction(actionLeftCutterCompensation);
m_openedDocumentActions.addAction(actionRightCutterCompensation);
@@ -117,6 +135,8 @@ void MainWindow::setupOpenedDocumentActions()
m_openedDocumentActions.addAction(actionMirrorSelection);
m_openedDocumentActions.addAction(actionSetSelectionOrigin);
m_openedDocumentActions.addAction(actionSimulate);
+ m_openedDocumentActions.addAction(actionUndo);
+ m_openedDocumentActions.addAction(actionRedo);
m_openedDocumentActions.setExclusive(true);
}
@@ -144,16 +164,31 @@ MainWindow::MainWindow(model::Application &app)
setDocumentToolsEnabled(false);
connect(&m_app, &model::Application::titleChanged, this, &MainWindow::setWindowTitle);
- connect(&m_app, &model::Application::documentChanged, this, &MainWindow::documentChanged);
+ connect(&m_app, &model::Application::newDocumentOpened, this, &MainWindow::newDocumentOpened);
connect(&m_app, &model::Application::errorRaised, this, &MainWindow::displayError);
}
void MainWindow::openFile()
{
- const QString fileName = QFileDialog::getOpenFileName(this);
- if (!fileName.isEmpty() && !m_app.loadFile(fileName)) {
- QMessageBox::critical(this, "Error", "Invalid file type " + fileName);
- }
+ auto onFileContentReady = [this](const QString &fileName, const QByteArray &fileContent){
+ if (!fileName.isEmpty() && !m_app.loadFile(fileName, fileContent)) {
+ QMessageBox::critical(this, "Error", "Invalid file type " + fileName);
+ }
+ };
+
+ QFileDialog::getOpenFileContent("Text files (*.dxf *.dxfplot)", onFileContentReady);
+}
+
+void MainWindow::downloadFile()
+{
+ const QString fileName = defaultFileName(model::Application::FileExtension::Dxfplot);
+ QFileDialog::saveFileContent(m_app.saveToDxfplot(), fileName);
+}
+
+void MainWindow::downloadExportFile()
+{
+ const QString fileName = defaultFileName(model::Application::FileExtension::Gcode);
+ QFileDialog::saveFileContent(m_app.saveToGcode(), fileName);
}
void MainWindow::saveFile()
@@ -216,6 +251,7 @@ void MainWindow::transformSelection()
{
dialogs::Transform transform;
if (transform.exec() == QDialog::Accepted) {
+ m_app.takeDocumentSnapshot();
m_app.transformSelection(transform.matrix());
}
}
@@ -224,6 +260,7 @@ void MainWindow::mirrorSelection()
{
dialogs::Mirror mirror;
if (mirror.exec() == QDialog::Accepted) {
+ m_app.takeDocumentSnapshot();
m_app.transformSelection(mirror.matrix());
}
}
@@ -234,15 +271,18 @@ void MainWindow::setSelectionOrigin()
dialogs::SetOrigin setOrigin(selectionBoundingRect);
if (setOrigin.exec() == QDialog::Accepted) {
+ m_app.takeDocumentSnapshot();
m_app.transformSelection(setOrigin.matrix()); // TODO common function
}
}
-void MainWindow::documentChanged(model::Document *newDocument)
+void MainWindow::newDocumentOpened(model::Document *newDocument)
{
setDocumentToolsEnabled((newDocument != nullptr));
+#ifdef WITH_3D
m_simulation->hide();
+#endif
}
void MainWindow::displayError(const QString &message)
@@ -253,9 +293,22 @@ void MainWindow::displayError(const QString &message)
void MainWindow::simulate()
{
+#ifdef WITH_3D
model::Simulation simulation = m_app.createSimulation();
m_simulation->setSimulation(std::move(simulation));
m_simulation->show();
+#endif
+}
+
+void MainWindow::undo()
+{
+ m_app.undoDocumentChanges();
}
+void MainWindow::redo()
+{
+ m_app.redoDocumentChanges();
+}
+
+
}
diff --git a/src/view/mainwindow.h b/src/view/mainwindow.h
index f3a85b3..3a9eec6 100644
--- a/src/view/mainwindow.h
+++ b/src/view/mainwindow.h
@@ -25,7 +25,9 @@ class MainWindow : public QMainWindow, private Ui::MainWindow
private:
model::Application &m_app;
+#ifdef WITH_3D
simulation::Simulation *m_simulation;
+#endif
QActionGroup m_openedDocumentActions;
@@ -49,15 +51,17 @@ protected Q_SLOTS:
void saveAsFile();
void exportFile();
void exportAsFile();
+ void downloadFile();
+ void downloadExportFile();
void openSettings();
void transformSelection();
void mirrorSelection();
void setSelectionOrigin();
- void documentChanged(model::Document *newDocument);
+ void newDocumentOpened(model::Document *newDocument);
void displayError(const QString &message);
-
-signals:
void simulate();
+ void undo();
+ void redo();
};
}
diff --git a/src/view/profile.cpp b/src/view/profile.cpp
index 18750be..2546baa 100644
--- a/src/view/profile.cpp
+++ b/src/view/profile.cpp
@@ -14,9 +14,7 @@ void Profile::updateAllComboBoxesItems()
Profile::Profile(model::Application& app)
:DocumentModelObserver(app),
- m_app(app),
- m_outsideToolChangeBlocked(false),
- m_outsideProfileChangeBlocked(false)
+ m_app(app)
{
setupUi(this);
@@ -29,9 +27,6 @@ Profile::Profile(model::Application& app)
void Profile::documentChanged()
{
- connect(document(), &model::Document::toolConfigChanged, this, &Profile::toolConfigChanged);
- connect(document(), &model::Document::profileConfigChanged, this, &Profile::profileConfigChanged);
-
toolComboBox->setCurrentText(QString::fromStdString(document()->toolConfig().name()));
profileComboBox->setCurrentText(QString::fromStdString(document()->profileConfig().name())); // TODO updateTextFromProfileConfig
}
@@ -41,36 +36,14 @@ void Profile::configChanged([[maybe_unused]] const config::Config &config)
updateAllComboBoxesItems();
}
-void Profile::toolConfigChanged(const config::Tools::Tool& tool)
-{
- if (!m_outsideToolChangeBlocked) {
- toolComboBox->setCurrentText(QString::fromStdString(tool.name()));
- }
-}
-
void Profile::currentToolTextChanged(const QString& toolName)
{
- m_outsideToolChangeBlocked = true;
-
m_app.selectTool(toolName);
-
- m_outsideToolChangeBlocked = false;
-}
-
-void Profile::profileConfigChanged(const config::Profiles::Profile& profile)
-{
- if (!m_outsideProfileChangeBlocked) {
- profileComboBox->setCurrentText(QString::fromStdString(profile.name()));
- }
}
void Profile::currentProfileTextChanged(const QString& profileName)
{
- m_outsideProfileChangeBlocked = true;
-
m_app.selectProfile(profileName);
-
- m_outsideProfileChangeBlocked = false;
}
}
diff --git a/src/view/profile.h b/src/view/profile.h
index 8df40e3..eb8d35a 100644
--- a/src/view/profile.h
+++ b/src/view/profile.h
@@ -13,9 +13,6 @@ class Profile : public model::DocumentModelObserver, public Ui::Profile
private:
model::Application &m_app;
- bool m_outsideToolChangeBlocked;
- bool m_outsideProfileChangeBlocked;
-
template
void updateComboBoxItems(const ConfigList &list, QComboBox *comboBox)
{
@@ -43,9 +40,7 @@ class Profile : public model::DocumentModelObserver, public Ui::Profile
public Q_SLOTS:
void configChanged(const config::Config &config);
- void toolConfigChanged(const config::Tools::Tool &tool);
void currentToolTextChanged(const QString &toolName);
- void profileConfigChanged(const config::Profiles::Profile &profile);
void currentProfileTextChanged(const QString &profileName);
};
diff --git a/src/view/simulation/internal/toolpath.cpp b/src/view/simulation/internal/toolpath.cpp
index 3e9a4e7..4c99938 100644
--- a/src/view/simulation/internal/toolpath.cpp
+++ b/src/view/simulation/internal/toolpath.cpp
@@ -1,9 +1,9 @@
#include
-#include
-#include
-#include
-#include
+#include
+#include
+#include
+#include
#include
@@ -51,28 +51,28 @@ void ToolPath::createPolylineFromPoints(const model::Simulation::ToolPathPoint3D
m_indices[i] = i;
}
- Qt3DRender::QGeometry *geometry = new Qt3DRender::QGeometry(this);
+ Qt3DCore::QGeometry *geometry = new Qt3DCore::QGeometry(this);
const QByteArray vertexData = QByteArray::fromRawData((const char *)m_packedPoints.get(), sizeof(PackedVector3D) * nbPoints);
- Qt3DRender::QBuffer *vertexBuffer = new Qt3DRender::QBuffer(geometry);
+ Qt3DCore::QBuffer *vertexBuffer = new Qt3DCore::QBuffer(geometry);
vertexBuffer->setData(vertexData);
const QByteArray colorData = QByteArray::fromRawData((const char *)m_colors.get(), sizeof(uint32_t) * nbPoints);
- Qt3DRender::QBuffer *colorBuffer = new Qt3DRender::QBuffer(geometry);
+ Qt3DCore::QBuffer *colorBuffer = new Qt3DCore::QBuffer(geometry);
colorBuffer->setData(colorData);
const QByteArray indexData = QByteArray::fromRawData((const char *)m_indices.get(), sizeof(uint32_t) * nbPoints);
- Qt3DRender::QBuffer *indexBuffer = new Qt3DRender::QBuffer(geometry);
+ Qt3DCore::QBuffer *indexBuffer = new Qt3DCore::QBuffer(geometry);
indexBuffer->setData(indexData);
- Qt3DRender::QAttribute *vertexAttribute = new Qt3DRender::QAttribute(vertexBuffer, Qt3DRender::QAttribute::defaultPositionAttributeName(), Qt3DRender::QAttribute::Float, 3, nbPoints);
- vertexAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
+ Qt3DCore::QAttribute *vertexAttribute = new Qt3DCore::QAttribute(vertexBuffer, Qt3DCore::QAttribute::defaultPositionAttributeName(), Qt3DCore::QAttribute::Float, 3, nbPoints);
+ vertexAttribute->setAttributeType(Qt3DCore::QAttribute::VertexAttribute);
- Qt3DRender::QAttribute *colorAttribute = new Qt3DRender::QAttribute(colorBuffer, Qt3DRender::QAttribute::defaultColorAttributeName(), Qt3DRender::QAttribute::UnsignedByte, 4, nbPoints);
- colorAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
+ Qt3DCore::QAttribute *colorAttribute = new Qt3DCore::QAttribute(colorBuffer, Qt3DCore::QAttribute::defaultColorAttributeName(), Qt3DCore::QAttribute::UnsignedByte, 4, nbPoints);
+ colorAttribute->setAttributeType(Qt3DCore::QAttribute::VertexAttribute);
- Qt3DRender::QAttribute *indexAttribute = new Qt3DRender::QAttribute(indexBuffer, Qt3DRender::QAttribute::UnsignedInt, 3, nbPoints);
- indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute);
+ Qt3DCore::QAttribute *indexAttribute = new Qt3DCore::QAttribute(indexBuffer, Qt3DCore::QAttribute::UnsignedInt, 3, nbPoints);
+ indexAttribute->setAttributeType(Qt3DCore::QAttribute::IndexAttribute);
geometry->addAttribute(vertexAttribute);
geometry->addAttribute(colorAttribute);
diff --git a/src/view/task/layertreemodel.cpp b/src/view/task/layertreemodel.cpp
index 61eef27..4badfe4 100644
--- a/src/view/task/layertreemodel.cpp
+++ b/src/view/task/layertreemodel.cpp
@@ -7,7 +7,8 @@ namespace view::task
LayerTreeModel::LayerTreeModel(model::Task &task, QObject *parent)
:QAbstractItemModel(parent),
- m_task(task)
+ m_task(task),
+ m_ignoreSelectionChanged(false)
{
}
@@ -117,21 +118,40 @@ void LayerTreeModel::itemClicked(const QModelIndex& index)
model::Renderable *item = static_cast(index.internalPointer());
item->toggleVisible();
+ emit documentVisibilityChanged();
+
emit dataChanged(index, index);
}
}
}
+void LayerTreeModel::clearSelection(QItemSelectionModel *selectionModel)
+{
+ m_ignoreSelectionChanged = true;
+
+ selectionModel->clear();
+
+ m_ignoreSelectionChanged = false;
+}
+
void LayerTreeModel::updateItemSelection(const model::Path &path, QItemSelectionModel::SelectionFlag flag, QItemSelectionModel *selectionModel)
{
+ m_ignoreSelectionChanged = true;
+
const std::pair indices = m_task.layerAndPathIndexFor(path);
const QModelIndex parentIndex = index(indices.first, 0);
const QModelIndex childIndex = index(indices.second, 0, parentIndex);
selectionModel->select(childIndex, flag);
+
+ m_ignoreSelectionChanged = false;
}
void LayerTreeModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
+ if (m_ignoreSelectionChanged) {
+ return;
+ }
+
for (const QModelIndex &index : selected.indexes()) {
model::Renderable &renderable = *static_cast(index.internalPointer());
renderable.setSelected(true);
diff --git a/src/view/task/layertreemodel.h b/src/view/task/layertreemodel.h
index d8863c7..d215c81 100644
--- a/src/view/task/layertreemodel.h
+++ b/src/view/task/layertreemodel.h
@@ -16,6 +16,7 @@ class LayerTreeModel: public QAbstractItemModel
private:
model::Task &m_task;
+ bool m_ignoreSelectionChanged;
public:
explicit LayerTreeModel(model::Task &task, QObject *parent);
@@ -28,8 +29,12 @@ class LayerTreeModel: public QAbstractItemModel
Qt::ItemFlags flags(const QModelIndex &index) const override;
void itemClicked(const QModelIndex &index);
+ void clearSelection(QItemSelectionModel *selectionModel);
void updateItemSelection(const model::Path &path, QItemSelectionModel::SelectionFlag flag, QItemSelectionModel *selectionModel);
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
+
+signals:
+ void documentVisibilityChanged();
};
}
diff --git a/src/view/task/path.cpp b/src/view/task/path.cpp
index 440a030..14d5acc 100644
--- a/src/view/task/path.cpp
+++ b/src/view/task/path.cpp
@@ -7,7 +7,7 @@ void Path::setupModel()
{
m_groupSettings.reset(new model::PathGroupSettings(task()));
- hide();
+ stackedWidget->setCurrentWidget(pageNoSelection);
connect(&task(), &model::Task::selectionChanged, this, &Path::selectionChanged);
@@ -23,7 +23,8 @@ void Path::documentChanged()
}
Path::Path(model::Application &app)
- :DocumentModelObserver(app)
+ :DocumentModelObserver(app),
+ m_app(app)
{
setupUi(this);
}
@@ -31,15 +32,15 @@ Path::Path(model::Application &app)
void Path::selectionChanged(bool empty)
{
if (empty) {
- hide();
+ stackedWidget->setCurrentWidget(pageNoSelection);
}
else {
- show();
-
updateFieldValue(planeFeedRate, m_groupSettings->planeFeedRate());
updateFieldValue(depthFeedRate, m_groupSettings->depthFeedRate());
updateFieldValue(intensity, m_groupSettings->intensity());
updateFieldValue(Ui::Path::depth, m_groupSettings->depth());
+
+ stackedWidget->setCurrentWidget(pagePathSelected);
}
}
diff --git a/src/view/task/path.h b/src/view/task/path.h
index ddc7d27..bfb35f3 100644
--- a/src/view/task/path.h
+++ b/src/view/task/path.h
@@ -14,6 +14,8 @@ namespace view::task
class Path : public model::DocumentModelObserver, private Ui::Path
{
private:
+ model::Application &m_app;
+
std::unique_ptr m_groupSettings;
void selectionChanged(bool empty);
@@ -21,7 +23,13 @@ class Path : public model::DocumentModelObserver, private Ui::Path
template
void connectOnFieldChanged(Field *field, std::function &&func)
{
- connect(field, static_cast(&Field::valueChanged), this, func);
+ disconnect(field, static_cast(&Field::valueChanged), nullptr, nullptr);
+
+ connect(field, static_cast(&Field::valueChanged), [this, func](ValueType value){
+ func(value);
+
+ m_app.takeDocumentSnapshot();
+ });
}
template
diff --git a/src/view/task/pathlistmodel.cpp b/src/view/task/pathlistmodel.cpp
index f9ca39e..18e0e37 100644
--- a/src/view/task/pathlistmodel.cpp
+++ b/src/view/task/pathlistmodel.cpp
@@ -8,7 +8,8 @@ namespace view::task
PathListModel::PathListModel(model::Task &task, QObject *parent)
:QAbstractListModel(parent),
- m_task(task)
+ m_task(task),
+ m_ignoreSelectionChanged(false)
{
}
@@ -74,7 +75,7 @@ Qt::ItemFlags PathListModel::flags(const QModelIndex &index) const
return (path.layer().visible()) ? Qt::ItemIsEnabled : Qt::NoItemFlags;
}
-QModelIndex PathListModel::movePath(const QModelIndex &index, model::Task::MoveDirection direction)
+QModelIndex PathListModel::movePathToDirection(const QModelIndex &index, model::Task::MoveDirection direction)
{
const int row = index.row();
const int newRow = row + direction;
@@ -96,6 +97,23 @@ QModelIndex PathListModel::movePath(const QModelIndex &index, model::Task::MoveD
return index;
}
+QModelIndex PathListModel::movePathToTip(const QModelIndex &index, model::Task::MoveTip tip)
+{
+ const int row = index.row();
+ const int newRow = (tip == model::Task::MoveTip::Top) ? 0 : (rowCount(index) - 1);
+
+ if (index.isValid()) {
+ m_task.movePathToTip(row, tip);
+
+ const QModelIndex newIndex = this->index(newRow);
+ emit dataChanged(index, newIndex);
+
+ return newIndex;
+ }
+
+ return index;
+}
+
void PathListModel::itemClicked(const QModelIndex& index)
{
if ((index.flags() & Qt::ItemIsEnabled) == 0) {
@@ -113,14 +131,31 @@ void PathListModel::itemClicked(const QModelIndex& index)
}
}
+void PathListModel::clearSelection(QItemSelectionModel *selectionModel)
+{
+ m_ignoreSelectionChanged = true;
+
+ selectionModel->clear();
+
+ m_ignoreSelectionChanged = false;
+}
+
void PathListModel::updateItemSelection(const model::Path &path, QItemSelectionModel::SelectionFlag flag, QItemSelectionModel *selectionModel)
{
+ m_ignoreSelectionChanged = true;
+
const int row = m_task.pathIndexFor(path);
selectionModel->select(index(row, 0), flag);
+
+ m_ignoreSelectionChanged = false;
}
void PathListModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
+ if (m_ignoreSelectionChanged) {
+ return;
+ }
+
for (const QModelIndex &index : selected.indexes()) {
model::Path &path = m_task.pathAt(index.row());
path.setSelected(true);
diff --git a/src/view/task/pathlistmodel.h b/src/view/task/pathlistmodel.h
index 8652e4a..88f155c 100644
--- a/src/view/task/pathlistmodel.h
+++ b/src/view/task/pathlistmodel.h
@@ -16,6 +16,7 @@ class PathListModel : public QAbstractListModel
private:
model::Task &m_task;
+ bool m_ignoreSelectionChanged;
public:
explicit PathListModel(model::Task &task, QObject *parent);
@@ -25,11 +26,16 @@ class PathListModel : public QAbstractListModel
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
- QModelIndex movePath(const QModelIndex &index, model::Task::MoveDirection direction);
+ QModelIndex movePathToDirection(const QModelIndex &index, model::Task::MoveDirection direction);
+ QModelIndex movePathToTip(const QModelIndex &index, model::Task::MoveTip tip);
void itemClicked(const QModelIndex &index);
+ void clearSelection(QItemSelectionModel *selectionModel);
void updateItemSelection(const model::Path &path, QItemSelectionModel::SelectionFlag flag, QItemSelectionModel *selectionModel);
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
+
+signals:
+ void documentVisibilityChanged();
};
}
diff --git a/src/view/task/task.cpp b/src/view/task/task.cpp
index 6ad375b..3067af9 100644
--- a/src/view/task/task.cpp
+++ b/src/view/task/task.cpp
@@ -10,7 +10,8 @@ namespace view::task
{
Task::Task(model::Application &app)
- :DocumentModelObserver(app)
+ :DocumentModelObserver(app),
+ m_app(app)
{
setupUi(this);
}
@@ -31,8 +32,10 @@ void Task::setupController()
setupTreeViewController(m_pathListModel, pathsTreeView);
setupTreeViewController(m_layerTreeModel, layersTreeView);
- connect(moveUp, &QPushButton::pressed, [this](){ moveCurrentPath(model::Task::MoveDirection::UP); });
- connect(moveDown, &QPushButton::pressed, [this](){ moveCurrentPath(model::Task::MoveDirection::DOWN); });
+ connect(moveUp, &QPushButton::pressed, [this](){ moveCurrentPathToDirection(model::Task::MoveDirection::UP); });
+ connect(moveDown, &QPushButton::pressed, [this](){ moveCurrentPathToDirection(model::Task::MoveDirection::DOWN); });
+ connect(moveTop, &QPushButton::pressed, [this](){ moveCurrentPathToTip(model::Task::MoveTip::Top); });
+ connect(moveBottom, &QPushButton::pressed, [this](){ moveCurrentPathToTip(model::Task::MoveTip::Bottom); });
}
void Task::updateItemSelection(const model::Path &path, QItemSelectionModel::SelectionFlag flag)
@@ -53,12 +56,39 @@ void Task::pathSelectedChanged(model::Path &path, bool selected)
selected ? QItemSelectionModel::Select : QItemSelectionModel::Deselect);
}
-void Task::moveCurrentPath(model::Task::MoveDirection direction)
+void Task::moveCurrentPathToDirection(model::Task::MoveDirection direction)
{
- QItemSelectionModel *selectionModel = pathsTreeView->selectionModel();
- const QModelIndex currentSelectedIndex = selectionModel->currentIndex();
- const QModelIndex newSelectedIndex = m_pathListModel->movePath(currentSelectedIndex, direction);
- selectionModel->setCurrentIndex(newSelectedIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current);
+ moveCurrentPath([this, direction](const QModelIndex& index){
+ m_pathListModel->movePathToDirection(index, direction);
+ });
+}
+
+void Task::documentVisibilityChanged()
+{
+ m_app.takeDocumentSnapshot();
+}
+
+void Task::moveCurrentPathToTip(model::Task::MoveTip tip)
+{
+ moveCurrentPath([this, tip](const QModelIndex& index){
+ m_pathListModel->movePathToTip(index, tip);
+ });
+}
+
+void Task::rebuildSelectionFromTask()
+{
+ QItemSelectionModel *pathsTreeSelectionModel = pathsTreeView->selectionModel();
+ QItemSelectionModel *layersTreeSelectionModel = layersTreeView->selectionModel();
+
+ m_pathListModel->clearSelection(pathsTreeSelectionModel);
+ m_layerTreeModel->clearSelection(layersTreeSelectionModel);
+
+ task().forEachSelectedPath([this, pathsTreeSelectionModel, layersTreeSelectionModel](const model::Path& path){
+ constexpr QItemSelectionModel::SelectionFlag flag = QItemSelectionModel::Select;
+
+ m_pathListModel->updateItemSelection(path, flag, pathsTreeSelectionModel);
+ m_layerTreeModel->updateItemSelection(path, flag, layersTreeSelectionModel);
+ });
}
}
diff --git a/src/view/task/task.h b/src/view/task/task.h
index ba35ea2..a532270 100644
--- a/src/view/task/task.h
+++ b/src/view/task/task.h
@@ -17,6 +17,8 @@ class LayerTreeModel;
class Task : public model::DocumentModelObserver, private Ui::Task
{
private:
+ model::Application &m_app;
+
std::unique_ptr m_pathListModel;
std::unique_ptr m_layerTreeModel;
@@ -42,6 +44,8 @@ class Task : public model::DocumentModelObserver, private Ui::Task
connect(selectionModel, &QItemSelectionModel::selectionChanged, model.get(), &Model::selectionChanged);
connect(treeView, &QTreeView::clicked, model.get(), &Model::itemClicked);
+
+ connect(model.get(), &Model::documentVisibilityChanged, this, &Task::documentVisibilityChanged);
}
void setupModel();
@@ -49,6 +53,26 @@ class Task : public model::DocumentModelObserver, private Ui::Task
void updateItemSelection(const model::Path &path, QItemSelectionModel::SelectionFlag flag);
+ void moveCurrentPathToDirection(model::Task::MoveDirection direction);
+ void moveCurrentPathToTip(model::Task::MoveTip tip);
+
+ template
+ void moveCurrentPath(Func &&movement)
+ {
+ QItemSelectionModel *selectionModel = pathsTreeView->selectionModel();
+
+ const QModelIndexList selectedItems = selectionModel->selectedIndexes();
+ for (const QModelIndex& selectedIndex : selectedItems) {
+ movement(selectedIndex);
+ }
+
+ rebuildSelectionFromTask();
+
+ m_app.takeDocumentSnapshot();
+ }
+
+ void rebuildSelectionFromTask();
+
public:
explicit Task(model::Application &app);
@@ -56,9 +80,8 @@ class Task : public model::DocumentModelObserver, private Ui::Task
void documentChanged();
protected Q_SLOTS:
- void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
void pathSelectedChanged(model::Path &path, bool selected);
- void moveCurrentPath(model::Task::MoveDirection direction);
+ void documentVisibilityChanged();
};
}
diff --git a/src/view/view2d/viewport.cpp b/src/view/view2d/viewport.cpp
index 2c41469..cb63a11 100644
--- a/src/view/view2d/viewport.cpp
+++ b/src/view/view2d/viewport.cpp
@@ -250,7 +250,11 @@ class BackgroundPainter
void Viewport::documentChanged()
{
setupModel();
- fitItemsInView();
+}
+
+void Viewport::newDocumentOpened()
+{
+ fitItemsInView(); // TODO delay after UI update
}
void Viewport::wheelEvent(QWheelEvent *event)
diff --git a/src/view/view2d/viewport.h b/src/view/view2d/viewport.h
index e67e807..51c6f18 100644
--- a/src/view/view2d/viewport.h
+++ b/src/view/view2d/viewport.h
@@ -42,6 +42,7 @@ class Viewport : public model::DocumentModelObserver
protected:
void documentChanged() override;
+ void newDocumentOpened() override;
void wheelEvent(QWheelEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
diff --git a/template/uic/CMakeLists.txt b/template/uic/CMakeLists.txt
index 3b5343f..620b9f2 100644
--- a/template/uic/CMakeLists.txt
+++ b/template/uic/CMakeLists.txt
@@ -1,7 +1,7 @@
add_subdirectory(dialogs)
add_subdirectory(simulation)
-qt5_wrap_ui(UIC_HEADERS
+qt_wrap_ui(UIC_HEADERS
info.ui
mainwindow.ui
path.ui
diff --git a/template/uic/dialogs/CMakeLists.txt b/template/uic/dialogs/CMakeLists.txt
index 8237e21..3e6171a 100644
--- a/template/uic/dialogs/CMakeLists.txt
+++ b/template/uic/dialogs/CMakeLists.txt
@@ -1,6 +1,6 @@
add_subdirectory(settings)
-qt5_wrap_ui(UIC_HEADERS
+qt_wrap_ui(UIC_HEADERS
transform.ui
mirror.ui
setorigin.ui
diff --git a/template/uic/dialogs/settings/CMakeLists.txt b/template/uic/dialogs/settings/CMakeLists.txt
index 90acc95..cd3a43c 100644
--- a/template/uic/dialogs/settings/CMakeLists.txt
+++ b/template/uic/dialogs/settings/CMakeLists.txt
@@ -1,4 +1,4 @@
-qt5_wrap_ui(UIC_HEADERS
+qt_wrap_ui(UIC_HEADERS
group.ui
list.ui
settings.ui
diff --git a/template/uic/dialogs/settings/list.ui b/template/uic/dialogs/settings/list.ui
index ba306c3..f8dd218 100644
--- a/template/uic/dialogs/settings/list.ui
+++ b/template/uic/dialogs/settings/list.ui
@@ -14,7 +14,7 @@
GroupBox
- Qt::RightToLeft
+ Qt::LeftToRight
diff --git a/template/uic/dialogs/settings/settings.ui b/template/uic/dialogs/settings/settings.ui
index 5879977..c49ffda 100644
--- a/template/uic/dialogs/settings/settings.ui
+++ b/template/uic/dialogs/settings/settings.ui
@@ -7,21 +7,15 @@
0
0
1000
- 298
+ 498
-
+
0
0
-
-
- 1000
- 0
-
-
Settings
@@ -31,62 +25,110 @@
0
-
+
QLayout::SetDefaultConstraint
-
-
-
-
- false
-
-
-
+
-
+
+
+ 0
-
-
- :/icons/list-remove.svg:/icons/list-remove.svg
-
-
+ -
+
+
+ true
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ false
+
+
+
+
+
+
+ :/icons/list-add.svg:/icons/list-add.svg
+
+
+
+ -
+
+
+ false
+
+
+
+
+
+
+ :/icons/list-remove.svg:/icons/list-remove.svg
+
+
+
+ -
+
+
+ false
+
+
+
+
+
+
+ :/icons/edit-copy.svg:/icons/edit-copy.svg
+
+
+
+
+
+
- -
-
-
+
-
+
+
true
-
-
- -
-
-
- false
-
-
-
-
-
-
- :/icons/list-add.svg:/icons/list-add.svg
-
-
-
- -
-
-
-
-
- -
-
-
- false
-
-
-
-
-
-
- :/icons/edit-copy.svg:/icons/edit-copy.svg
-
+
+
+
+ 0
+ 0
+ 723
+ 426
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+
+
+
diff --git a/template/uic/mainwindow.ui b/template/uic/mainwindow.ui
index 98ffe7b..dfe9ad4 100644
--- a/template/uic/mainwindow.ui
+++ b/template/uic/mainwindow.ui
@@ -63,6 +63,9 @@
+
+
+
@@ -310,6 +313,22 @@
Ctrl+R
+
+
+ Redo
+
+
+ Ctrl+Y
+
+
+
+
+ Undo
+
+
+ Ctrl+Z
+
+
diff --git a/template/uic/path.ui b/template/uic/path.ui
index 07d23c6..a796c43 100644
--- a/template/uic/path.ui
+++ b/template/uic/path.ui
@@ -7,7 +7,7 @@
0
0
230
- 168
+ 176
@@ -15,76 +15,117 @@
-
-
-
- QLayout::SetDefaultConstraint
+
+
+ 0
-
- QFormLayout::AllNonFixedFieldsGrow
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
-
-
-
-
-
- Plane Feed Rate
-
-
-
- -
-
-
- 9999.989999999999782
-
-
-
- -
-
-
- Intensity
-
-
-
- -
-
-
- 9999.989999999999782
-
-
-
- -
-
-
- Depth
+
+
+
+ 0
-
-
- -
-
-
- 9999.989999999999782
+
+ 0
-
- 0.100000000000000
+
+ 0
-
-
- -
-
-
- Depth Feed Rate
+
+ 0
-
-
- -
-
-
- 9999.989999999999782
+
+ 0
-
-
-
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
-
+
+
+ Plane Feed Rate
+
+
+
+ -
+
+
+ false
+
+
+ 9999.989999999999782
+
+
+
+ -
+
+
+ Intensity
+
+
+
+ -
+
+
+ false
+
+
+ 9999.989999999999782
+
+
+
+ -
+
+
+ Depth
+
+
+
+ -
+
+
+ false
+
+
+ 9999.989999999999782
+
+
+ 0.100000000000000
+
+
+
+ -
+
+
+ Depth Feed Rate
+
+
+
+ -
+
+
+ false
+
+
+ 9999.989999999999782
+
+
+
+
+
+
+
+
+
+
+
diff --git a/template/uic/simulation/CMakeLists.txt b/template/uic/simulation/CMakeLists.txt
index 51d2061..61ba327 100644
--- a/template/uic/simulation/CMakeLists.txt
+++ b/template/uic/simulation/CMakeLists.txt
@@ -1,4 +1,4 @@
-qt5_wrap_ui(UIC_HEADERS
+qt_wrap_ui(UIC_HEADERS
simulation.ui
)
diff --git a/template/uic/simulation/simulation.ui b/template/uic/simulation/simulation.ui
index 0007ab7..f0a7810 100644
--- a/template/uic/simulation/simulation.ui
+++ b/template/uic/simulation/simulation.ui
@@ -67,6 +67,12 @@
0
+
+ 100
+
+
+ 1000
+
Qt::Horizontal
diff --git a/template/uic/task.ui b/template/uic/task.ui
index 55e9a31..b92ab9a 100644
--- a/template/uic/task.ui
+++ b/template/uic/task.ui
@@ -92,6 +92,28 @@
+ -
+
+
+
+
+
+
+ :/icons/layer-top.svg:/icons/layer-top.svg
+
+
+
+ -
+
+
+
+
+
+
+ :/icons/layer-bottom.svg:/icons/layer-bottom.svg
+
+
+
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 01c4373..25a1139 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -9,7 +9,7 @@ FetchContent_MakeAvailable(googletest)
include(GoogleTest)
-find_package(Qt5 COMPONENTS REQUIRED
+find_package(Qt6 COMPONENTS REQUIRED
Test
)
@@ -37,8 +37,8 @@ set(SRC
add_executable(dxfplotter-test ${SRC} main.cpp)
-target_include_directories(dxfplotter-test PRIVATE ${Qt5Test_INCLUDE_DIRS})
-target_link_libraries(dxfplotter-test ${LINK_LIBRARIES} Qt5::Test gtest_main)
+target_include_directories(dxfplotter-test PRIVATE ${QtTest_INCLUDE_DIRS})
+target_link_libraries(dxfplotter-test ${LINK_LIBRARIES} Qt::Test gtest_main)
add_coverage(dxfplotter-test)
diff --git a/thirdparty/libdxfrw/intern/dxfreader.h b/thirdparty/libdxfrw/intern/dxfreader.h
index e4bba4b..b0a8652 100644
--- a/thirdparty/libdxfrw/intern/dxfreader.h
+++ b/thirdparty/libdxfrw/intern/dxfreader.h
@@ -27,7 +27,7 @@ class dxfReader {
};
enum TYPE type;
public:
- dxfReader(std::ifstream *stream){
+ dxfReader(std::istream *stream){
filestr = stream;
type = INVALID;
}
@@ -60,7 +60,7 @@ class dxfReader {
virtual bool readBool() = 0;
protected:
- std::ifstream *filestr;
+ std::istream *filestr;
std::string strData;
double doubleData;
signed int intData; //32 bits integer
@@ -73,7 +73,7 @@ class dxfReader {
class dxfReaderBinary : public dxfReader {
public:
- dxfReaderBinary(std::ifstream *stream):dxfReader(stream){skip = false; }
+ dxfReaderBinary(std::istream *stream):dxfReader(stream){skip = false; }
virtual ~dxfReaderBinary() {}
virtual bool readCode(int *code);
virtual bool readString(std::string *text);
@@ -88,7 +88,7 @@ class dxfReaderBinary : public dxfReader {
class dxfReaderAscii : public dxfReader {
public:
- dxfReaderAscii(std::ifstream *stream):dxfReader(stream){skip = true; }
+ dxfReaderAscii(std::istream *stream):dxfReader(stream){skip = true; }
virtual ~dxfReaderAscii(){}
virtual bool readCode(int *code);
virtual bool readString(std::string *text);
diff --git a/thirdparty/libdxfrw/libdxfrw.cpp b/thirdparty/libdxfrw/libdxfrw.cpp
index b40a17f..01c8f3d 100644
--- a/thirdparty/libdxfrw/libdxfrw.cpp
+++ b/thirdparty/libdxfrw/libdxfrw.cpp
@@ -62,49 +62,53 @@ void dxfRW::setDebug(DRW::DebugLevel lvl){
}
}
-bool dxfRW::read(DRW_Interface *interface_, bool ext){
- drw_assert(fileName.empty() == false);
+bool dxfRW::read(std::istream& stream, DRW_Interface *interface_, bool ext){
applyExt = ext;
- std::ifstream filestr;
- if (nullptr == interface_) {
- return setError(DRW::BAD_UNKNOWN);
- }
- DRW_DBG("dxfRW::read 1def\n");
- filestr.open (fileName.c_str(), std::ios_base::in | std::ios::binary);
- if (!filestr.is_open()
- || !filestr.good()) {
- return setError(DRW::BAD_OPEN);
- }
char line[22];
char line2[22] = "AutoCAD Binary DXF\r\n";
line2[20] = (char)26;
line2[21] = '\0';
- filestr.read (line, 22);
- filestr.close();
+ stream.read (line, 22);
+ stream.seekg(0);
iface = interface_;
DRW_DBG("dxfRW::read 2\n");
if (strcmp(line, line2) == 0) {
- filestr.open (fileName.c_str(), std::ios_base::in | std::ios::binary);
binFile = true;
- //skip sentinel
- filestr.seekg (22, std::ios::beg);
- reader = new dxfReaderBinary(&filestr);
+ reader = new dxfReaderBinary(&stream);
DRW_DBG("dxfRW::read binary file\n");
} else {
binFile = false;
- filestr.open (fileName.c_str(), std::ios_base::in);
- reader = new dxfReaderAscii(&filestr);
+ reader = new dxfReaderAscii(&stream);
}
bool isOk {processDxf()};
- filestr.close();
version = (DRW::Version) reader->getVersion();
delete reader;
reader = nullptr;
return isOk;
}
+
+bool dxfRW::read(DRW_Interface *interface_, bool ext){
+ drw_assert(fileName.empty() == false);
+ std::ifstream filestr;
+ if (nullptr == interface_) {
+ return setError(DRW::BAD_UNKNOWN);
+ }
+ DRW_DBG("dxfRW::read 1def\n");
+ filestr.open (fileName.c_str(), std::ios_base::in | std::ios::binary);
+ if (!filestr.is_open()
+ || !filestr.good()) {
+ return setError(DRW::BAD_OPEN);
+ }
+
+ const bool isOk = read(filestr, interface_, ext);
+ filestr.close();
+
+ return isOk;
+}
+
bool dxfRW::write(DRW_Interface *interface_, DRW::Version ver, bool bin){
bool isOk = false;
std::ofstream filestr;
diff --git a/thirdparty/libdxfrw/libdxfrw.h b/thirdparty/libdxfrw/libdxfrw.h
index 7deee2f..c52e0b5 100644
--- a/thirdparty/libdxfrw/libdxfrw.h
+++ b/thirdparty/libdxfrw/libdxfrw.h
@@ -39,6 +39,7 @@ class dxfRW {
* @return true for success
*/
bool read(DRW_Interface *interface_, bool ext);
+ bool read(std::istream &stream, DRW_Interface *interface_, bool ext);
void setBinary(bool b) {binFile = b;}
bool write(DRW_Interface *interface_, DRW::Version ver, bool bin);
diff --git a/wasm/Dockerfile b/wasm/Dockerfile
new file mode 100644
index 0000000..b6636e3
--- /dev/null
+++ b/wasm/Dockerfile
@@ -0,0 +1,4 @@
+FROM stateoftheartio/qt6:6.3-wasm-aqt
+
+RUN sudo apt-get update && sudo apt-get install -y python3-pip
+RUN python3 -m pip install jinja2