diff --git a/CMakeLists.txt b/CMakeLists.txt index 89c24f2..6958a3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,9 @@ elseif (CMAKE_MAJOR_VERSION STREQUAL "3" AND CMAKE_MINOR_VERSION STREQUAL "27") endif() # Allow cmake std module for stdlibc++ or msvc -if (CMAKE_VERSION VERSION_GREATER_EQUAL "4.3.0") +if (CMAKE_VERSION VERSION_GREATER_EQUAL "4.4.0") + set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "f35a9ac6-8463-4d38-8eec-5d6008153e7d") +elseif (CMAKE_VERSION VERSION_GREATER_EQUAL "4.3.0") set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "451f2fe2-a8a2-47c3-bc32-94786d8fc91b") elseif (CMAKE_VERSION VERSION_GREATER_EQUAL "4.0.3") set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444") @@ -62,7 +64,7 @@ else() add_compile_options(-fPIC -Wall -Wextra -DNDEBUG) endif() -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(TEMPLATE_DIR ${PROJECT_SOURCE_DIR}/template) @@ -115,6 +117,7 @@ set(LINK_LIBRARIES view-simulation view-simulation-internal model + model-application config importer-dxf importer-dxfplot @@ -123,6 +126,8 @@ set(LINK_LIBRARIES exporter-renderer geometry geometry-filter + common + serializer libdxfrw fmt::fmt Qt6::Widgets @@ -134,7 +139,10 @@ set(LINK_LIBRARIES include_directories(${INCLUDE_DIRS}) add_subdirectory(template) +# Third party sources are plain headers and sources, scanning them for modules only costs build time. +set(CMAKE_CXX_SCAN_FOR_MODULES OFF) add_subdirectory(thirdparty EXCLUDE_FROM_ALL) +set(CMAKE_CXX_SCAN_FOR_MODULES ${CMAKE_CXX_MODULE_DYNDEP}) include(cmake/modules.cmake) @@ -147,7 +155,7 @@ if (BUILD_TESTING) endif() add_executable(dxfplotter src/main.cpp) -target_link_libraries(dxfplotter ${LINK_LIBRARIES} model_module) +target_link_libraries(dxfplotter ${LINK_LIBRARIES}) add_coverage(dxfplotter) install(TARGETS dxfplotter DESTINATION bin) diff --git a/ci/docker/ubuntu/Dockerfile b/ci/docker/ubuntu/Dockerfile index 41e0224..916ef23 100644 --- a/ci/docker/ubuntu/Dockerfile +++ b/ci/docker/ubuntu/Dockerfile @@ -1,5 +1,14 @@ FROM ubuntu:noble +RUN apt update && apt install -y ca-certificates gnupg wget + +RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | \ + gpg --dearmor - | \ + tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null + +RUN echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ noble main' | \ + tee /etc/apt/sources.list.d/kitware.list >/dev/null + RUN apt update && apt install -y libglib2.0-bin libdbus-1-3 libgtk-3-0 \ libxcb* libxkbcommon* \ freeglut3-dev lcov \ diff --git a/cmake/modules.cmake b/cmake/modules.cmake index a1bd130..d13eee1 100644 --- a/cmake/modules.cmake +++ b/cmake/modules.cmake @@ -14,4 +14,13 @@ function (add_module_library lib_name) # BASE_DIRS is configured to allow files coming from src and build directory (for instance compile generated sources located in build/). target_sources(${lib_name} PUBLIC FILE_SET cxx_modules TYPE CXX_MODULES FILES ${sources} BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + + target_compile_features(${lib_name} PUBLIC cxx_std_23) + + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options(${lib_name} PRIVATE + -Wno-reserved-module-identifier + -Wno-module-file-config-mismatch + ) + endif() endfunction() diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index e69de29..66b339f 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -0,0 +1,10 @@ +set(SRC + aggregable.cpp + copy.cpp + enumerate.cpp + exception.cpp + function.cpp +) + +add_module_library(common ${SRC}) +add_coverage(common) diff --git a/src/common/aggregable.h b/src/common/aggregable.cpp similarity index 77% rename from src/common/aggregable.h rename to src/common/aggregable.cpp index 938c2db..a314259 100644 --- a/src/common/aggregable.h +++ b/src/common/aggregable.cpp @@ -1,11 +1,14 @@ -#pragma once +module; -#include #include -#include +#include #include +#include +#include + +export module common.aggregable; -namespace common +export namespace common { template @@ -19,9 +22,9 @@ class Aggregable using ListCPtr = std::vector; using ListUPtr = std::vector; - template + template using Array = std::array; - template + template using ArrayPtr = std::array; using SetPtr = std::set; diff --git a/src/common/copy.h b/src/common/copy.cpp similarity index 87% rename from src/common/copy.h rename to src/common/copy.cpp index 89e4411..2342a23 100644 --- a/src/common/copy.h +++ b/src/common/copy.cpp @@ -1,10 +1,12 @@ -#pragma once +module; -#include -#include #include +#include +#include + +export module common.copy; -namespace common +export namespace common { template> diff --git a/src/common/enum.h b/src/common/enum.h deleted file mode 100644 index f1035e8..0000000 --- a/src/common/enum.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -namespace common::enumerate -{ - -template -inline std::initializer_list All(); - -template -inline std::string toString(const EnumType& value); - -template -inline EnumType fromString(const std::string& value); - -} diff --git a/src/common/enumerate.cpp b/src/common/enumerate.cpp new file mode 100644 index 0000000..8c051b7 --- /dev/null +++ b/src/common/enumerate.cpp @@ -0,0 +1,21 @@ +module; + +#include +#include + +// Named common.enumerate and not common.enum: 'enum' is a keyword and cannot be a module name component. +export module common.enumerate; + +export namespace common::enumerate +{ + +template +std::initializer_list All(); + +template +std::string toString(const EnumType& value); + +template +EnumType fromString(const std::string& value); + +} diff --git a/src/common/exception.h b/src/common/exception.cpp similarity index 87% rename from src/common/exception.h rename to src/common/exception.cpp index aa73657..4ab500e 100644 --- a/src/common/exception.h +++ b/src/common/exception.cpp @@ -1,9 +1,14 @@ -#pragma once +module; +#include #include +#include #include +#include -namespace common +export module common.exception; + +export namespace common { class FileCouldNotOpenException : public std::exception diff --git a/src/common/function.h b/src/common/function.cpp similarity index 88% rename from src/common/function.h rename to src/common/function.cpp index c1768ff..4051010 100644 --- a/src/common/function.h +++ b/src/common/function.cpp @@ -1,6 +1,6 @@ -#pragma once +export module common.function; -namespace common +export namespace common { template diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt index 0f0dba6..6067b4b 100644 --- a/src/config/CMakeLists.txt +++ b/src/config/CMakeLists.txt @@ -1,24 +1,21 @@ -set_property(SOURCE config.h PROPERTY SKIP_AUTOGEN ON) - -set(SRC - config.cpp +set(MODULE_SRC node.cpp - - config.h - group.h - list.h - node.h - property.h + group.cpp + list.cpp + utils.cpp + property.cpp + ${CMAKE_CURRENT_BINARY_DIR}/configgen.cpp ) -add_library(config ${SRC}) +# config.cpp is a module implementation unit of config.config, not an interface unit. +add_module_library(config ${MODULE_SRC} NON_MODULE_SOURCES config.cpp) add_dependencies(config generate-config) -target_link_libraries(config PUBLIC yaml-cpp::yaml-cpp) +target_link_libraries(config PUBLIC yaml-cpp::yaml-cpp common geometry) add_custom_command( - OUTPUT config.h - COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/config_gen.py ${TEMPLATE_DIR}/config.xml ${CMAKE_CURRENT_SOURCE_DIR} config.h.in config.h - DEPENDS config.h.in ${TEMPLATE_DIR}/config.xml config_gen.py + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/configgen.cpp + COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/config_gen.py ${TEMPLATE_DIR}/config.xml ${CMAKE_CURRENT_SOURCE_DIR} configgen.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/configgen.cpp + DEPENDS configgen.cpp.in ${TEMPLATE_DIR}/config.xml config_gen.py ) -add_custom_target(generate-config DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/config.h) +add_custom_target(generate-config DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/configgen.cpp) diff --git a/src/config/config.cpp b/src/config/config.cpp index 7c736d1..637add6 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -1,8 +1,12 @@ -#include +module; + +#include + +#include #include -#include +module config.config; namespace config { diff --git a/src/config/config.h.in b/src/config/configgen.cpp.in similarity index 92% rename from src/config/config.h.in rename to src/config/configgen.cpp.in index a87e799..be4e7af 100644 --- a/src/config/config.h.in +++ b/src/config/configgen.cpp.in @@ -1,12 +1,19 @@ -#pragma once +module; +#include #include -#include -#include -#include +#include -namespace config +export module config.config; + +import config.group; +import geometry.utils; +import config.list; +import config.node; +import config.property; + +export namespace config { namespace internal diff --git a/src/config/group.h b/src/config/group.cpp similarity index 87% rename from src/config/group.h rename to src/config/group.cpp index fd61e24..2713c2f 100644 --- a/src/config/group.h +++ b/src/config/group.cpp @@ -1,10 +1,14 @@ -#pragma once +module; +#include #include +#include -#include +export module config.group; -namespace config +import config.node; + +export namespace config { /** @brief A configuration group. diff --git a/src/config/list.h b/src/config/list.cpp similarity index 94% rename from src/config/list.h rename to src/config/list.cpp index 7ecc7b0..2ca168a 100644 --- a/src/config/list.h +++ b/src/config/list.cpp @@ -1,11 +1,15 @@ -#pragma once +module; -#include #include +#include +#include +#include + +export module config.list; -#include +import config.node; -namespace config +export namespace config { /** @brief A configuration node list. diff --git a/src/config/node.cpp b/src/config/node.cpp index 3e3bb95..a108561 100644 --- a/src/config/node.cpp +++ b/src/config/node.cpp @@ -1,23 +1,32 @@ -#include +module; + #include +#include +#include -namespace config -{ +export module config.node; -Node::Node(const std::string& name, const std::string& description) - : m_name(name) - , m_description(description) +export namespace config { -} -const std::string& Node::name() const +class Node { - return m_name; -} +private: + std::string m_name; + std::string m_description; -const std::string& Node::description() const -{ - return m_description; -} +public: + explicit Node(const std::string& name, const std::string& description) + : m_name(name) + , m_description(description) + { + } + + Node() = default; + + const std::string& name() const { return m_name; } + + const std::string& description() const { return m_description; } +}; } diff --git a/src/config/node.h b/src/config/node.h deleted file mode 100644 index 6c0c292..0000000 --- a/src/config/node.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -#include - -namespace config -{ - -class Node -{ -private: - std::string m_name; - std::string m_description; - -public: - explicit Node(const std::string& name, const std::string& description); - Node() = default; - - const std::string& name() const; - const std::string& description() const; -}; - -} diff --git a/src/config/property.h b/src/config/property.cpp similarity index 80% rename from src/config/property.h rename to src/config/property.cpp index a5a7240..d27f2af 100644 --- a/src/config/property.h +++ b/src/config/property.cpp @@ -1,10 +1,14 @@ -#pragma once +module; -#include +#include +#include -#include +export module config.property; -namespace config +import config.node; +import config.utils; + +export namespace config { template diff --git a/src/config/utils.h b/src/config/utils.cpp similarity index 73% rename from src/config/utils.h rename to src/config/utils.cpp index 9f6a416..099f7b0 100644 --- a/src/config/utils.h +++ b/src/config/utils.cpp @@ -1,10 +1,18 @@ -#pragma once +module; +#include +#include #include -#include +export module config.utils; -namespace config +import common.enumerate; +import geometry.utils; + +import common.enumerate; +import geometry.utils; + +export namespace config { template diff --git a/src/exporter/dxfplot/CMakeLists.txt b/src/exporter/dxfplot/CMakeLists.txt index a2c8b75..39362a1 100644 --- a/src/exporter/dxfplot/CMakeLists.txt +++ b/src/exporter/dxfplot/CMakeLists.txt @@ -1,9 +1,7 @@ set(SRC exporter.cpp - - exporter.h ) -add_library(exporter-dxfplot ${SRC}) -target_link_libraries(exporter-dxfplot PRIVATE config) +add_module_library(exporter-dxfplot ${SRC}) +target_link_libraries(exporter-dxfplot PUBLIC model PRIVATE config geometry serializer) add_coverage(exporter-dxfplot) diff --git a/src/exporter/dxfplot/exporter.cpp b/src/exporter/dxfplot/exporter.cpp index d8f0ce0..44a1143 100644 --- a/src/exporter/dxfplot/exporter.cpp +++ b/src/exporter/dxfplot/exporter.cpp @@ -1,23 +1,38 @@ -#include +module; -#include +#include -#include +#include +#include -namespace exporter::dxfplot -{ +export module exporter.dxfplot.exporter; + +import model.document; +import serializer.task; -void Exporter::operator()(const model::Document& document, std::ostream& output) const +export namespace exporter::dxfplot { - Archive archive(output); - save(archive, document); -} -void Exporter::save(Archive& archive, const model::Document& document) const +class Exporter { - archive(cereal::make_nvp("task", document.task())); - archive(cereal::make_nvp("profile_name", document.profileConfig().name())); - archive(cereal::make_nvp("tool_name", document.toolConfig().name())); -} +public: + explicit Exporter() = default; + + void operator()(const model::Document& document, std::ostream& output) const + { + Archive archive(output); + save(archive, document); + } + +private: + using Archive = cereal::JSONOutputArchive; + + void save(Archive& archive, const model::Document& document) const + { + archive(cereal::make_nvp("task", document.task())); + archive(cereal::make_nvp("profile_name", document.profileConfig().name())); + archive(cereal::make_nvp("tool_name", document.toolConfig().name())); + } +}; } diff --git a/src/exporter/dxfplot/exporter.h b/src/exporter/dxfplot/exporter.h deleted file mode 100644 index af3ab37..0000000 --- a/src/exporter/dxfplot/exporter.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include - -#include - -#include - -namespace exporter::dxfplot -{ - -class Exporter -{ -public: - explicit Exporter() = default; - - void operator()(const model::Document& document, std::ostream& output) const; - -private: - using Archive = cereal::JSONOutputArchive; - - void save(Archive& archive, const model::Document& document) const; -}; - -} diff --git a/src/exporter/gcode/CMakeLists.txt b/src/exporter/gcode/CMakeLists.txt index 0dfedb1..d60f314 100644 --- a/src/exporter/gcode/CMakeLists.txt +++ b/src/exporter/gcode/CMakeLists.txt @@ -1,13 +1,9 @@ set(SRC - exporter.cpp metadata.cpp + exporter.cpp postprocessor.cpp - - exporter.h - metadata.h - postprocessor.h ) -add_library(exporter-gcode ${SRC}) -target_link_libraries(exporter-gcode PRIVATE config) +add_module_library(exporter-gcode ${SRC} NON_MODULE_SOURCES exporterimpl.cpp) +target_link_libraries(exporter-gcode PUBLIC common geometry model PRIVATE config exporter-renderer) add_coverage(exporter-gcode) diff --git a/src/exporter/gcode/exporter.cpp b/src/exporter/gcode/exporter.cpp index 4f0e42b..b928df7 100644 --- a/src/exporter/gcode/exporter.cpp +++ b/src/exporter/gcode/exporter.cpp @@ -1,118 +1,38 @@ -#include -#include -#include -#include +module; -namespace exporter::gcode -{ - -struct CommentLineStream -{ - std::ostream& m_output; - - explicit CommentLineStream(std::ostream& output, const std::string& prefix) - : m_output(output) - { - m_output << "; " << prefix; - } +#include +#include - CommentLineStream(CommentLineStream&) = delete; +export module exporter.gcode.exporter; - ~CommentLineStream() { m_output << "\n"; } +import config.config; +import config.property; +import config.utils; +import model.document; +import geometry.bulge; +import geometry.polyline; - template - CommentLineStream& operator<<(const Value& value) - { - m_output << value; - return *this; - } -}; - -struct ConfigToCommentVisitor +export namespace exporter::gcode { - std::ostream& m_output; - const std::string m_prefix; - - CommentLineStream commentLineStream() const { return CommentLineStream(m_output, m_prefix); } - template - void operator()(const config::Property& property) const - { - commentLineStream() << property.name() << " = " << config::toSerializable((ValueType)property); - } +class PathPostProcessor; - template - void operator()(const Node& node) const - { - commentLineStream() << node.name(); - node.visitChildren(ConfigToCommentVisitor { m_output, m_prefix + " " }); - } -}; - -template -void convertConfigNodeToComments(const Group& group, std::ostream& output) -{ - const ConfigToCommentVisitor visitor { output, "" }; - visitor(group); -} - -class RendererVisitor : private PostProcessor +class Exporter { public: - using PostProcessor::PostProcessor; - - void start(const QVector2D& from, float safetyDepth) { retractDepth(safetyDepth); } + enum Options { None = 0, ExportConfig = (1 << 0), ExportMetadata = (1 << 1) }; - void end(const QVector2D& to, float safetyDepth) { fastPlaneMove(to); } +private: + const config::Tools::Tool& m_tool; + const config::Profiles::Profile& m_profile; + const Options m_options; - 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); +public: + explicit Exporter(const config::Tools::Tool& tool, const config::Profiles::Profile& profile, + Options options = None); + ~Exporter() = default; - polyline.forEachBulge([this, planeFeedRate](const geometry::Bulge& bulge) { - processBulge(bulge, planeFeedRate); - }); - } + void operator()(const model::Document& document, std::ostream& output) const; }; -Exporter::Exporter(const config::Tools::Tool& tool, const config::Profiles::Profile& profile, Options options) - : m_tool(tool) - , m_profile(profile) - , m_options(options) -{ -} - -void Exporter::operator()(const model::Document& document, std::ostream& output) const -{ - if (m_options & ExportConfig) { - convertConfigNodeToComments(m_tool, output); - convertConfigNodeToComments(m_profile, output); - } - - const config::Profiles::Profile::Gcode& gcode = m_profile.gcode(); - - if (m_options & ExportMetadata) { - 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/exporter.h b/src/exporter/gcode/exporter.h deleted file mode 100644 index 53ae0d3..0000000 --- a/src/exporter/gcode/exporter.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include - -#include - -#include - -namespace exporter::gcode -{ - -class PathPostProcessor; - -class Exporter -{ -public: - enum Options { None = 0, ExportConfig = (1 << 0), ExportMetadata = (1 << 1) }; - -private: - const config::Tools::Tool& m_tool; - const config::Profiles::Profile& m_profile; - const Options m_options; - -public: - explicit Exporter(const config::Tools::Tool& tool, const config::Profiles::Profile& profile, - Options options = None); - ~Exporter() = default; - - void operator()(const model::Document& document, std::ostream& output) const; -}; - -} diff --git a/src/exporter/gcode/exporterimpl.cpp b/src/exporter/gcode/exporterimpl.cpp new file mode 100644 index 0000000..fde5eb3 --- /dev/null +++ b/src/exporter/gcode/exporterimpl.cpp @@ -0,0 +1,135 @@ +module; + +#include +#include + +#include + +module exporter.gcode.exporter; + +import config.config; +import config.property; +import config.utils; +import exporter.gcode.metadata; +import exporter.gcode.postprocessor; +import geometry.bulge; +import geometry.polyline; +import geometry.utils; +import model.document; +import model.path; +import model.pathsettings; +import model.task; +import exporter.renderer.renderer; +namespace exporter::gcode +{ + +struct CommentLineStream +{ + std::ostream& m_output; + + explicit CommentLineStream(std::ostream& output, const std::string& prefix) + : m_output(output) + { + m_output << "; " << prefix; + } + + CommentLineStream(CommentLineStream&) = delete; + + ~CommentLineStream() { m_output << "\n"; } + + template + CommentLineStream& operator<<(const Value& value) + { + m_output << value; + return *this; + } +}; + +struct ConfigToCommentVisitor +{ + std::ostream& m_output; + const std::string m_prefix; + + CommentLineStream commentLineStream() const { return CommentLineStream(m_output, m_prefix); } + + template + void operator()(const config::Property& property) const + { + commentLineStream() << property.name() << " = " << config::toSerializable((ValueType)property); + } + + template + void operator()(const Node& node) const + { + commentLineStream() << node.name(); + node.visitChildren(ConfigToCommentVisitor { m_output, m_prefix + " " }); + } +}; + +template +void convertConfigNodeToComments(const Group& group, std::ostream& output) +{ + const ConfigToCommentVisitor visitor { 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) + , m_options(options) +{ +} + +void Exporter::operator()(const model::Document& document, std::ostream& output) const +{ + if (m_options & ExportConfig) { + convertConfigNodeToComments(m_tool, output); + convertConfigNodeToComments(m_profile, output); + } + + const config::Profiles::Profile::Gcode& gcode = m_profile.gcode(); + + if (m_options & ExportMetadata) { + 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 index b16144d..6b9799c 100644 --- a/src/exporter/gcode/metadata.cpp +++ b/src/exporter/gcode/metadata.cpp @@ -1,73 +1,88 @@ -#include -#include - -#include +module; +#include +#include +#include +#include #include #include -#include -#include +export module exporter.gcode.metadata; -namespace exporter::gcode -{ +import config.config; +import geometry.rect; +import exporter.gcode.postprocessor; +import model.document; -std::string Metadata::fastPlaneMoveGCode(const QVector2D& to) const +export namespace exporter::gcode { - std::ostringstream output; - PostProcessor postprocessor(m_gcode, output); - postprocessor.fastPlaneMove(to); - return output.str(); -} -std::string Metadata::fastDepthMoveGCode(float to) const +class Metadata { - std::ostringstream output; - PostProcessor postprocessor(m_gcode, output); - postprocessor.retractDepth(to); - return output.str(); -} +private: + const geometry::Rect m_boundingRect; + const config::Profiles::Profile::Gcode& m_gcode; + const float m_retractDepth; -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(m_boundingRect.bottomLeft()), - fastPlaneMoveGCode(QVector2D(0.0f, 0.0f)), - fastDepthMoveGCode(0.0f) }; - - QJsonArray jsonGcodes; - for (const std::string& gcode : gcodes) { - jsonGcodes.push_back(QString::fromStdString(gcode)); + std::string fastPlaneMoveGCode(const QVector2D& to) const + { + std::ostringstream output; + PostProcessor postprocessor(m_gcode, output); + postprocessor.fastPlaneMove(to); + return output.str(); } - return jsonGcodes; -} + std::string fastDepthMoveGCode(float to) const + { + std::ostringstream output; + PostProcessor postprocessor(m_gcode, output); + postprocessor.retractDepth(to); + return output.str(); + } -QJsonDocument Metadata::toJson() const -{ - QJsonObject root; - root["bounding_rect_gcodes"] = boundingRectGcodes(); + QJsonArray 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(m_boundingRect.bottomLeft()), + fastPlaneMoveGCode(QVector2D(0.0f, 0.0f)), + fastDepthMoveGCode(0.0f) }; - QJsonDocument document(root); - return document; -} + QJsonArray jsonGcodes; + for (const std::string& gcode : gcodes) { + jsonGcodes.push_back(QString::fromStdString(gcode)); + } -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)) -{ -} + return jsonGcodes; + } -std::string Metadata::toComment() const -{ - const QJsonDocument& jsonDocument = toJson(); + QJsonDocument toJson() const + { + QJsonObject root; + root["bounding_rect_gcodes"] = boundingRectGcodes(); - return "; metadata_json = " + jsonDocument.toJson(QJsonDocument::Compact).toStdString() + "\n"; -} + QJsonDocument document(root); + return document; + } + +public: + explicit 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 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 deleted file mode 100644 index 682ff42..0000000 --- a/src/exporter/gcode/metadata.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace model -{ - -class Document; - -} - -class QJsonObject; -class QJsonArray; -class QJsonDocument; - -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 8684e1d..64afc3b 100644 --- a/src/exporter/gcode/postprocessor.cpp +++ b/src/exporter/gcode/postprocessor.cpp @@ -1,87 +1,113 @@ -#include +module; + +#include + +#include +#include +#include + +export module exporter.gcode.postprocessor; + +import common.exception; +import exporter.gcode.exporter; +import geometry.bulge; +import geometry.circle; +import geometry.line; +import geometry.utils; +import model.path; +import config.config; using namespace fmt::literals; -namespace exporter::gcode +export namespace exporter::gcode { -PostProcessor::PostProcessor(const config::Profiles::Profile::Gcode& gcode, std::ostream& stream) - : m_stream(stream) - , m_gcode(gcode) +class PostProcessor { -} +private: + std::ostream& m_stream; -void PostProcessor::preCut(float intensity) -{ - print(m_gcode.preCut(), "S"_a = intensity); -} +protected: + const config::Profiles::Profile::Gcode& m_gcode; -void PostProcessor::postCut() -{ - print(m_gcode.postCut()); -} + /** Print a command to stream with a format and a list of named arguments + * @param format A fmt valid format string. + * @param args List of named arguments + */ + template + void print(const std::string& format, Args&&... args) + { + try { + m_stream << fmt::format(fmt::runtime(format), std::forward(args)...) << "\n"; + } catch (const fmt::format_error& exception) { + throw common::GCodeFormatException(format, exception.what(), fmt::to_string(args.name)...); + } + } -void PostProcessor::planeLinearMove(const QVector2D& to, float feedRate) -{ - print(m_gcode.planeLinearMove(), "X"_a = to.x(), "Y"_a = to.y(), "F"_a = feedRate); -} +public: + void preCut(float intensity) { print(m_gcode.preCut(), "S"_a = intensity); } -void PostProcessor::depthLinearMove(float depth, float feedRate) -{ - print(m_gcode.depthLinearMove(), "Z"_a = depth, "F"_a = feedRate); -} + void postCut() { print(m_gcode.postCut()); } -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); -} + void planeLinearMove(const QVector2D& to, float feedRate) + { + print(m_gcode.planeLinearMove(), "X"_a = to.x(), "Y"_a = to.y(), "F"_a = feedRate); + } -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); -} + void depthLinearMove(float depth, float feedRate) + { + print(m_gcode.depthLinearMove(), "Z"_a = depth, "F"_a = feedRate); + } -void PostProcessor::fastPlaneMove(const QVector2D& to) -{ - print(m_gcode.planeFastMove(), "X"_a = to.x(), "Y"_a = to.y()); -} + void 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); + } -void PostProcessor::retractDepth(float depth) -{ - print(m_gcode.depthFastMove(), "Z"_a = depth); -} + void 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); + } -void PostProcessor::processBulge(const geometry::Bulge& bulge, float planeFeedRate) -{ - if (bulge.isArc()) { - processArc(bulge, planeFeedRate); - } else { - processLine(bulge, planeFeedRate); + void fastPlaneMove(const QVector2D& to) { print(m_gcode.planeFastMove(), "X"_a = to.x(), "Y"_a = to.y()); } + + void retractDepth(float depth) { print(m_gcode.depthFastMove(), "Z"_a = depth); } + + void 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 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 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; + } } -} + + explicit PostProcessor(const config::Profiles::Profile::Gcode& gcode, std::ostream& stream) + : m_stream(stream) + , m_gcode(gcode) + { + } +}; } diff --git a/src/exporter/gcode/postprocessor.h b/src/exporter/gcode/postprocessor.h deleted file mode 100644 index ed4f037..0000000 --- a/src/exporter/gcode/postprocessor.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include - -namespace exporter::gcode -{ - -class PostProcessor -{ -private: - std::ostream& m_stream; - -protected: - const config::Profiles::Profile::Gcode& m_gcode; - - /** Print a command to stream with a format and a list of named arguments - * @param format A fmt valid format string. - * @param args List of named arguments - */ - template - void print(const std::string& format, Args&&... args) - { - try { - m_stream << fmt::format(fmt::runtime(format), std::forward(args)...) << "\n"; - } catch (const fmt::format_error& exception) { - throw common::GCodeFormatException(format, exception.what(), fmt::to_string(args.name)...); - } - } - -public: - void preCut(float intensity); - void postCut(); - 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); - - explicit PostProcessor(const config::Profiles::Profile::Gcode& gcode, std::ostream& stream); -}; - -} diff --git a/src/exporter/renderer/CMakeLists.txt b/src/exporter/renderer/CMakeLists.txt index 682fde9..f3738f0 100644 --- a/src/exporter/renderer/CMakeLists.txt +++ b/src/exporter/renderer/CMakeLists.txt @@ -1,11 +1,8 @@ set(SRC passesiterator.cpp renderer.cpp - - passesiterator.h - renderer.h ) -add_library(exporter-renderer ${SRC}) -target_link_libraries(exporter-renderer PRIVATE config) +add_module_library(exporter-renderer ${SRC}) +target_link_libraries(exporter-renderer PUBLIC geometry model PRIVATE config) add_coverage(exporter-renderer) diff --git a/src/exporter/renderer/passesiterator.cpp b/src/exporter/renderer/passesiterator.cpp index ab953f8..b00ecac 100644 --- a/src/exporter/renderer/passesiterator.cpp +++ b/src/exporter/renderer/passesiterator.cpp @@ -1,4 +1,32 @@ -#include +module; + +#include + +export module exporter.renderer.passesiterator; + +import geometry.polyline; +import geometry.utils; + +// Return next polyline to convert +export 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++(); +}; bool PassesIterator::needPolylineInverse() const { diff --git a/src/exporter/renderer/passesiterator.h b/src/exporter/renderer/passesiterator.h deleted file mode 100644 index 37bb63e..0000000 --- a/src/exporter/renderer/passesiterator.h +++ /dev/null @@ -1,24 +0,0 @@ -#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 index b766102..1655915 100644 --- a/src/exporter/renderer/renderer.cpp +++ b/src/exporter/renderer/renderer.cpp @@ -1 +1,103 @@ -#include +module; + +#include + +export module exporter.renderer.renderer; + +import config.config; +import exporter.renderer.passesiterator; +import geometry.utils; +import model.document; +import geometry.polyline; +import model.path; +import model.pathsettings; +import model.task; + +export namespace exporter::renderer +{ + +template +class Renderer +{ +private: + const config::Tools::Tool& m_tool; + const config::Profiles::Profile& m_profile; + const bool m_laser; + const float m_depthPerCut; + const float m_maximumStartDepth; + const float m_depthToRetract; + Visitor& m_visitor; + + void render(const model::Task& task) const + { + const QVector2D home(0.0f, 0.0f); + + // Retract tool before work piece + m_visitor.start(home, m_depthToRetract); + + task.forEachPathInStack([this](const model::Path& path) { + if (path.globallyVisible()) { + render(path); + } + }); + + // Back to home + m_visitor.end(home, m_depthToRetract); + } + + 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 intensity = settings.intensity(); + const float planeFeedRate = settings.planeFeedRate(); + const float depthFeedRate = settings.depthFeedRate(); + + PassesIterator iterator(polyline, cuttingDirection); + + m_visitor.startOperation((*iterator).start(), intensity); + + if (m_laser) { + m_visitor.processPathAtDepth(*iterator, 0.0f, planeFeedRate, depthFeedRate); + } else { + const float maxDepth = settings.depth(); + const float startDepth = std::min(m_maximumStartDepth, maxDepth); + for (float depth = startDepth; depth < maxDepth + m_depthPerCut; depth += m_depthPerCut, ++iterator) { + const float boundDepth = std::fminf(depth, maxDepth); + + m_visitor.processPathAtDepth(*iterator, -boundDepth, planeFeedRate, depthFeedRate); + } + } + + m_visitor.endOperation(m_depthToRetract); + } + +public: + explicit Renderer(const config::Tools::Tool& tool, const config::Profiles::Profile& profile, Visitor& visitor) + : m_tool(tool) + , m_profile(profile) + , m_laser(m_tool.general().laser()) + , m_depthPerCut(m_tool.general().depthPerCut()) + , m_maximumStartDepth(m_profile.cut().passAtZeroDepth() ? 0.0f : m_depthPerCut) + , m_depthToRetract(m_laser ? 0.0f : m_tool.general().retractDepth()) + , m_visitor(visitor) + { + } + + void render(const model::Document& document) { render(document.task()); } +}; + +} diff --git a/src/exporter/renderer/renderer.h b/src/exporter/renderer/renderer.h deleted file mode 100644 index 199839e..0000000 --- a/src/exporter/renderer/renderer.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -#include -#include - -namespace exporter::renderer -{ - -template -class Renderer -{ -private: - const config::Tools::Tool& m_tool; - const config::Profiles::Profile& m_profile; - const bool m_laser; - const float m_depthPerCut; - const float m_maximumStartDepth; - const float m_depthToRetract; - Visitor& m_visitor; - - void render(const model::Task& task) const - { - const QVector2D home(0.0f, 0.0f); - - // Retract tool before work piece - m_visitor.start(home, m_depthToRetract); - - task.forEachPathInStack([this](const model::Path& path) { - if (path.globallyVisible()) { - render(path); - } - }); - - // Back to home - m_visitor.end(home, m_depthToRetract); - } - - 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 intensity = settings.intensity(); - const float planeFeedRate = settings.planeFeedRate(); - const float depthFeedRate = settings.depthFeedRate(); - - PassesIterator iterator(polyline, cuttingDirection); - - m_visitor.startOperation((*iterator).start(), intensity); - - if (m_laser) { - m_visitor.processPathAtDepth(*iterator, 0.0f, planeFeedRate, depthFeedRate); - } else { - const float maxDepth = settings.depth(); - const float startDepth = std::min(m_maximumStartDepth, maxDepth); - for (float depth = startDepth; depth < maxDepth + m_depthPerCut; depth += m_depthPerCut, ++iterator) { - const float boundDepth = std::fminf(depth, maxDepth); - - m_visitor.processPathAtDepth(*iterator, -boundDepth, planeFeedRate, depthFeedRate); - } - } - - m_visitor.endOperation(m_depthToRetract); - } - -public: - explicit Renderer(const config::Tools::Tool& tool, const config::Profiles::Profile& profile, Visitor& visitor) - : m_tool(tool) - , m_profile(profile) - , m_laser(m_tool.general().laser()) - , m_depthPerCut(m_tool.general().depthPerCut()) - , m_maximumStartDepth(m_profile.cut().passAtZeroDepth() ? 0.0f : m_depthPerCut) - , m_depthToRetract(m_laser ? 0.0f : m_tool.general().retractDepth()) - , m_visitor(visitor) - { - } - - void render(const model::Document& document) { render(document.task()); } -}; - -} diff --git a/src/geometry/CMakeLists.txt b/src/geometry/CMakeLists.txt index 85026be..3a73ecf 100644 --- a/src/geometry/CMakeLists.txt +++ b/src/geometry/CMakeLists.txt @@ -1,46 +1,32 @@ add_subdirectory(filter) set(SRC - arc.cpp - bezier.cpp - biarc.cpp - bulge.cpp + utils.cpp circle.cpp - cubicspline.cpp + arc.cpp line.cpp - pocketer.cpp - polyline.cpp - quadraticspline.cpp rect.cpp + bulge.cpp + polyline.cpp + biarc.cpp + bezier.cpp spline.cpp - - arc.h - bezier.h - biarc.h - bulge.h - circle.h - cubicspline.h - line.h - polyline.h - quadraticspline.h - spline.h - rect.h - utils.h + cubicspline.cpp + quadraticspline.cpp + pocketer.cpp ) if (WITH_ORTOOLS) list(APPEND SRC orderoptimizer.cpp - - orderoptimizer.h ) endif() -add_library(geometry ${SRC}) +add_module_library(geometry ${SRC} NON_MODULE_SOURCES cavcutils.h) +target_link_libraries(geometry PUBLIC common) if (WITH_ORTOOLS) target_link_libraries(geometry PUBLIC ortools::ortools) target_compile_definitions(geometry PUBLIC WITH_ORTOOLS) endif() - add_coverage(geometry) diff --git a/src/geometry/arc.cpp b/src/geometry/arc.cpp index 1ae7afa..713c8dc 100644 --- a/src/geometry/arc.cpp +++ b/src/geometry/arc.cpp @@ -1,62 +1,84 @@ -#include -#include +module; -namespace geometry +#include +#include +#include + +export module geometry.arc; + +import geometry.circle; +import geometry.utils; + +export namespace geometry { -Arc::Arc(const Circle& circle, const QVector2D& start, const QVector2D& end, float starAngle, float endAngle) - : Circle(circle) - , m_start(start) - , m_end(end) - , m_startAngle(starAngle) - , m_endAngle(endAngle) +/** Arcs are considered as angle clamped circles + */ +class Arc : public Circle { - if (orientation() == Orientation::CCW) { - m_endAngle = EnsureEndGreater(m_startAngle, m_endAngle); - assert(m_startAngle <= m_endAngle); - } else { - m_startAngle = EnsureEndGreater(m_endAngle, m_startAngle); - assert(m_endAngle <= m_startAngle); +private: + QVector2D m_start; + QVector2D m_end; + float m_startAngle; + float m_endAngle; + float m_spanAngle; + +public: + explicit Arc(const Circle& circle, const QVector2D& start, const QVector2D& end, float starAngle, float endAngle) + : Circle(circle) + , m_start(start) + , m_end(end) + , m_startAngle(starAngle) + , m_endAngle(endAngle) + { + if (orientation() == Orientation::CCW) { + m_endAngle = EnsureEndGreater(m_startAngle, m_endAngle); + assert(m_startAngle <= m_endAngle); + } else { + m_startAngle = EnsureEndGreater(m_endAngle, m_startAngle); + assert(m_endAngle <= m_startAngle); + } + + m_spanAngle = m_endAngle - m_startAngle; } - m_spanAngle = m_endAngle - m_startAngle; -} + const QVector2D& start() const { return m_start; } -const QVector2D& Arc::start() const -{ - return m_start; -} + const QVector2D& end() const { return m_end; } -const QVector2D& Arc::end() const -{ - return m_end; -} + float startAngle() const { return m_startAngle; } -float Arc::startAngle() const -{ - return m_startAngle; -} + float endAngle() const { return m_endAngle; } -float Arc::endAngle() const -{ - return m_endAngle; -} + float spanAngle() const { return m_spanAngle; } -float Arc::spanAngle() const -{ - return m_spanAngle; -} + float length() const { return std::abs(m_spanAngle) * radius(); } -float Arc::length() const -{ - return std::abs(m_spanAngle) * radius(); -} + QVector2D pointAtAngle(float angle) const + { + const QVector2D relativeNormalizedPoint(std::cos(angle), std::sin(angle)); + const QVector2D point = (center() + relativeNormalizedPoint * radius()); + return point; + } -QVector2D Arc::pointAtAngle(float angle) const -{ - const QVector2D relativeNormalizedPoint(std::cos(angle), std::sin(angle)); - const QVector2D point = (center() + relativeNormalizedPoint * radius()); - return point; -} + template + void approximateToLinesVisit(float maxError, Visitor&& visitor) const + { + // 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) { + visitor(pointAtAngle(angle)); + } + } else { + for (float angle = m_startAngle - angleStep, end = m_endAngle; angle > end; angle -= angleStep) { + visitor(pointAtAngle(angle)); + } + } + + visitor(m_end); + } +}; } diff --git a/src/geometry/arc.h b/src/geometry/arc.h deleted file mode 100644 index d8adf92..0000000 --- a/src/geometry/arc.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include - -namespace geometry -{ - -/** Arcs are considered as angle clamped circles - */ -class Arc : public Circle -{ -private: - QVector2D m_start; - QVector2D m_end; - float m_startAngle; - float m_endAngle; - float m_spanAngle; - -public: - explicit Arc(const Circle& circle, const QVector2D& start, const QVector2D& end, float starAngle, float endAngle); - - const QVector2D& start() const; - const QVector2D& end() const; - float startAngle() const; - float endAngle() const; - float spanAngle() const; - - float length() const; - - QVector2D pointAtAngle(float angle) const; - - template - void approximateToLinesVisit(float maxError, Visitor&& visitor) const - { - // 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) { - visitor(pointAtAngle(angle)); - } - } else { - for (float angle = m_startAngle - angleStep, end = m_endAngle; angle > end; angle -= angleStep) { - visitor(pointAtAngle(angle)); - } - } - - visitor(m_end); - } -}; - -} diff --git a/src/geometry/bezier.cpp b/src/geometry/bezier.cpp index 668254b..727b49d 100644 --- a/src/geometry/bezier.cpp +++ b/src/geometry/bezier.cpp @@ -1,254 +1,265 @@ -#include -#include +module; -namespace geometry -{ +#include +#include +#include +#include +#include + +export module geometry.bezier; + +import common.aggregable; +import geometry.biarc; +import geometry.bulge; +import geometry.polyline; +import geometry.utils; -bool Bezier::isRealInflexionPoint(const Bezier::Complex& point) +export namespace geometry { - return (point.real() > 0.0f && point.real() < 1.0f && point.imag() == 0.0f); // TODO epsilon -} -Bezier::InflexionPoints Bezier::inflexions() const +class Bezier : public common::Aggregable { - const QVector2D A = m_control1 - m_point1; - const QVector2D B = m_control2 - m_control1 - A; - const QVector2D C = m_point2 - m_control2 - A - 2.0f * B; - - const Complex a(B.x() * C.y() - B.y() * C.x(), 0.0f); - const Complex b(A.x() * C.y() - A.y() * C.x(), 0.0f); - const Complex c(A.x() * B.y() - A.y() * B.x(), 0.0f); - - const Complex d = sqrt(b * b - 4.0f * a * c); - const Complex e = 2.0f * a; - - Complex t1; - Complex t2; - - /* If control points are opposed and parrallel, e is zero and - * inflexion point is at half the curve. - */ - if (std::norm(e) == 0.0f) { // TODO epsilon - t1 = Complex(0.5f, 0.0f); - t2 = Complex(0.0f, 0.0f); - } else { - t1 = (-b + d) / e; - t2 = (-b - d) / e; +private: + using Complex = std::complex; + using InflexionPoints = std::array; + + QVector2D m_point1; + QVector2D m_point2; + QVector2D m_control1; + QVector2D m_control2; + + static bool isRealInflexionPoint(const Bezier::Complex& point) + { + return (point.real() > 0.0f && point.real() < 1.0f && point.imag() == 0.0f); // TODO epsilon } - return { t1, t2 }; -} + Bezier::InflexionPoints inflexions() const + { + const QVector2D A = m_control1 - m_point1; + const QVector2D B = m_control2 - m_control1 - A; + const QVector2D C = m_point2 - m_control2 - A - 2.0f * B; -QVector2D Bezier::derivativeAt(float t) const -{ - const float s = 1.0 - t; - const float s2 = s * s; - const float t2 = t * t; + const Complex a(B.x() * C.y() - B.y() * C.x(), 0.0f); + const Complex b(A.x() * C.y() - A.y() * C.x(), 0.0f); + const Complex c(A.x() * B.y() - A.y() * B.x(), 0.0f); - return (-3.0f * m_point1 * s2 + 3.0f * m_control1 * (s2 - 2.0f * t * s) + 3.0f * m_control2 * (2.0f * t * s - t2) - + 3.0f * m_point2 * t2); -} + const Complex d = sqrt(b * b - 4.0f * a * c); + const Complex e = 2.0f * a; -QVector2D Bezier::findNearestPointWithTangent(const QVector2D& point, const QVector2D& tangent, float maxError) const -{ - float tn = 0.5f; - QVector2D Q_tn = at(tn); + Complex t1; + Complex t2; + + /* If control points are opposed and parrallel, e is zero and + * inflexion point is at half the curve. + */ + if (std::norm(e) == 0.0f) { // TODO epsilon + t1 = Complex(0.5f, 0.0f); + t2 = Complex(0.0f, 0.0f); + } else { + t1 = (-b + d) / e; + t2 = (-b - d) / e; + } + + return { t1, t2 }; + } + + QVector2D derivativeAt(float t) const + { + const float s = 1.0 - t; + const float s2 = s * s; + const float t2 = t * t; + + return (-3.0f * m_point1 * s2 + 3.0f * m_control1 * (s2 - 2.0f * t * s) + + 3.0f * m_control2 * (2.0f * t * s - t2) + 3.0f * m_point2 * t2); + } + + QVector2D findNearestPointWithTangent(const QVector2D& point, const QVector2D& tangent, float maxError) const + { + float tn = 0.5f; + QVector2D Q_tn = at(tn); - const float dpt = QVector2D::dotProduct(point, tangent); - float fn = QVector2D::dotProduct(Q_tn, tangent) - dpt; + const float dpt = QVector2D::dotProduct(point, tangent); + float fn = QVector2D::dotProduct(Q_tn, tangent) - dpt; - while (std::abs(fn) > maxError) { - const QVector2D d_Q_tn = derivativeAt(tn); - const float d_fn = QVector2D::dotProduct(d_Q_tn, tangent); + while (std::abs(fn) > maxError) { + const QVector2D d_Q_tn = derivativeAt(tn); + const float d_fn = QVector2D::dotProduct(d_Q_tn, tangent); - tn = tn - fn / d_fn; + tn = tn - fn / d_fn; - // Stop when out of range - if (tn < 0.0f || tn > 1.0f) { - break; + // Stop when out of range + if (tn < 0.0f || tn > 1.0f) { + break; + } + + Q_tn = at(tn); + fn = QVector2D::dotProduct(Q_tn, tangent) - dpt; } - Q_tn = at(tn); - fn = QVector2D::dotProduct(Q_tn, tangent) - dpt; + return Q_tn; } - return Q_tn; -} +public: + explicit Bezier(const QVector2D& p1, const QVector2D& c1, const QVector2D& c2, const QVector2D& p2) + : m_point1(p1) + , m_point2(p2) + , m_control1(c1) + , m_control2(c2) + { + } -Bezier::Bezier(const QVector2D& p1, const QVector2D& c1, const QVector2D& c2, const QVector2D& p2) - : m_point1(p1) - , m_point2(p2) - , m_control1(c1) - , m_control2(c2) -{ -} + Bezier() = default; -const QVector2D& Bezier::point1() const -{ - return m_point1; -} + const QVector2D& point1() const { return m_point1; } -const QVector2D& Bezier::point2() const -{ - return m_point2; -} + const QVector2D& point2() const { return m_point2; } -const QVector2D& Bezier::control1() const -{ - return m_control1; -} + const QVector2D& control1() const { return m_control1; } -const QVector2D& Bezier::control2() const -{ - return m_control2; -} + const QVector2D& control2() const { return m_control2; } -QVector2D Bezier::at(float t) const -{ - assert(0.0f <= t && t <= 1.0f); + QVector2D at(float t) const + { + assert(0.0f <= t && t <= 1.0f); - const float ot = 1.0f - t; + const float ot = 1.0f - t; - return (ot * ot * ot) * m_point1 + (3.0f * (ot * ot) * t) * m_control1 + (3.0f * ot * (t * t)) * m_control2 - + (t * t * t) * m_point2; -} + return (ot * ot * ot) * m_point1 + (3.0f * (ot * ot) * t) * m_control1 + (3.0f * ot * (t * t)) * m_control2 + + (t * t * t) * m_point2; + } -float Bezier::approximateLength() const -{ - const float chord = (m_point2 - m_point1).length(); - const float controlNet - = (m_control1 - m_point1).length() + (m_control2 - m_control1).length() + (m_point2 - m_control2).length(); + float approximateLength() const + { + const float chord = (m_point2 - m_point1).length(); + const float controlNet + = (m_control1 - m_point1).length() + (m_control2 - m_control1).length() + (m_point2 - m_control2).length(); - return (chord + controlNet) / 2.0f; -} + return (chord + controlNet) / 2.0f; + } -Bezier::Pair Bezier::split(float t) const -{ - assert(0.0f < t && t < 1.0f); + Bezier::Pair split(float t) const + { + assert(0.0f < t && t < 1.0f); - const QVector2D p0 = m_point1 + t * (m_control1 - m_point1); - const QVector2D p1 = m_control1 + t * (m_control2 - m_control1); - const QVector2D p2 = m_control2 + t * (m_point2 - m_control2); + const QVector2D p0 = m_point1 + t * (m_control1 - m_point1); + const QVector2D p1 = m_control1 + t * (m_control2 - m_control1); + const QVector2D p2 = m_control2 + t * (m_point2 - m_control2); - const QVector2D p01 = p0 + t * (p1 - p0); - const QVector2D p12 = p1 + t * (p2 - p1); + const QVector2D p01 = p0 + t * (p1 - p0); + const QVector2D p12 = p1 + t * (p2 - p1); - const QVector2D dp = p01 + t * (p12 - p01); + const QVector2D dp = p01 + t * (p12 - p01); - const Bezier b1(m_point1, p0, p01, dp); - const Bezier b2(dp, p12, p2, m_point2); + const Bezier b1(m_point1, p0, p01, dp); + const Bezier b2(dp, p12, p2, m_point2); - return { b1, b2 }; -} + return { b1, b2 }; + } -Bezier::Pair Bezier::splitHalf() const -{ - const QVector2D qc = (m_control1 + m_control2) / 4.0f; + Bezier::Pair splitHalf() const + { + const QVector2D qc = (m_control1 + m_control2) / 4.0f; - const QVector2D r2 = (m_point1 + m_control1) / 2.0f; - const QVector2D r3 = r2 / 2.0f + qc; + const QVector2D r2 = (m_point1 + m_control1) / 2.0f; + const QVector2D r3 = r2 / 2.0f + qc; - const QVector2D s3 = (m_control2 + m_point2) / 2.0f; - const QVector2D s2 = s3 / 2.0f + qc; - const QVector2D dp = (r3 + s2) / 2.0f; + const QVector2D s3 = (m_control2 + m_point2) / 2.0f; + const QVector2D s2 = s3 / 2.0f + qc; + const QVector2D dp = (r3 + s2) / 2.0f; - const Bezier b1(m_point1, r2, r3, dp); - const Bezier b2(dp, s2, s3, m_point2); + const Bezier b1(m_point1, r2, r3, dp); + const Bezier b2(dp, s2, s3, m_point2); - return { b1, b2 }; -} + return { b1, b2 }; + } -Bezier::List Bezier::splitToConvex() const -{ - const InflexionPoints inflex = inflexions(); - const bool isReal1 = isRealInflexionPoint(inflex[0]); - const bool isReal2 = isRealInflexionPoint(inflex[1]); + /// Split bezier keeping only convex shape. + Bezier::List splitToConvex() const + { + const InflexionPoints inflex = inflexions(); + const bool isReal1 = isRealInflexionPoint(inflex[0]); + const bool isReal2 = isRealInflexionPoint(inflex[1]); - // Split at first point - if (isReal1 && !isReal2) { - const Pair splitted = split(inflex[0].real()); + // Split at first point + if (isReal1 && !isReal2) { + const Pair splitted = split(inflex[0].real()); - return { splitted[0], splitted[1] }; - } - // Split at second point - if (!isReal1 && isReal2) { - const Pair splitted = split(inflex[1].real()); + return { splitted[0], splitted[1] }; + } + // Split at second point + if (!isReal1 && isReal2) { + const Pair splitted = split(inflex[1].real()); - return { splitted[0], splitted[1] }; - } - // Split at both point - if (isReal1 && isReal2) { - float t1 = inflex[0].real(); - float t2 = inflex[1].real(); + return { splitted[0], splitted[1] }; + } + // Split at both point + if (isReal1 && isReal2) { + float t1 = inflex[0].real(); + float t2 = inflex[1].real(); - const Pair splitted1 = split(t1); + const Pair splitted1 = split(t1); - if (std::abs(t1 - t2) < 0.01) { - return { splitted1[0], splitted1[1] }; - } else { - // Order splitting points - if (t1 > t2) { - std::swap(t1, t2); - } + if (std::abs(t1 - t2) < 0.01) { + return { splitted1[0], splitted1[1] }; + } else { + // Order splitting points + if (t1 > t2) { + std::swap(t1, t2); + } - assert(t1 < t2); + assert(t1 < t2); - // Make t2 relative to second bezier. - t2 = (1.0f - t1) * t2; + // Make t2 relative to second bezier. + t2 = (1.0f - t1) * t2; - const Pair splitted2 = splitted1[1].split(t2); + const Pair splitted2 = splitted1[1].split(t2); - return { splitted1[0], splitted2[0], splitted2[1] }; + return { splitted1[0], splitted2[0], splitted2[1] }; + } } - } - - // No split - return { *this }; -} -std::optional Bezier::toBiarc() const -{ - // First find V, second vertex of triangle. - const std::optional optIntersection - = ForwardLineIntersection(m_point1, m_control1, m_point2, m_control2); - - /* If the intersection is not forward (from direction P1 -> C1) or the tangents are parrallels, - * no biars can be computed. - */ - if (!optIntersection) { - return std::nullopt; + // No split + return { *this }; } - const QVector2D v = *optIntersection; + std::optional toBiarc() const + { + // First find V, second vertex of triangle. + const std::optional optIntersection + = ForwardLineIntersection(m_point1, m_control1, m_point2, m_control2); + + /* If the intersection is not forward (from direction P1 -> C1) or the tangents are parrallels, + * no biars can be computed. + */ + if (!optIntersection) { + return std::nullopt; + } - // Find (G) incenter of triangle P1 V P2 - const QVector2D incenter = TriangleIncenter(m_point1, v, m_point2); + const QVector2D v = *optIntersection; - // Create biarc passing by P1 G P2 and with tangent at P1 and P2 - Biarc biarc(m_point1, incenter, m_point2, (m_control1 - m_point1), (m_control2 - m_point2)); + // Find (G) incenter of triangle P1 V P2 + const QVector2D incenter = TriangleIncenter(m_point1, v, m_point2); - return std::make_optional(biarc); -} + // Create biarc passing by P1 G P2 and with tangent at P1 and P2 + Biarc biarc(m_point1, incenter, m_point2, (m_control1 - m_point1), (m_control2 - m_point2)); -Polyline Bezier::toLine() const -{ - return Polyline({ Bulge { m_point1, m_point2, 0.0f } }); -} + return std::make_optional(biarc); + } -bool Bezier::isPoint() const -{ - return (m_point1 == m_point2); -} + Polyline toLine() const { return Polyline({ Bulge { m_point1, m_point2, 0.0f } }); } -float Bezier::maxError(const Biarc& biarc) const -{ - const QVector2D& middle = biarc.middle(); - const QVector2D tangent = biarc.tangentAtMiddle(); + bool isPoint() const { return (m_point1 == m_point2); } - // Find nearest point on curve to biarc middle with same tangent. - const QVector2D nearest = findNearestPointWithTangent(middle, tangent, 0.001); // TODO const + float maxError(const Biarc& biarc) const + { + const QVector2D& middle = biarc.middle(); + const QVector2D tangent = biarc.tangentAtMiddle(); - return (middle - nearest).lengthSquared(); -} + // Find nearest point on curve to biarc middle with same tangent. + const QVector2D nearest = findNearestPointWithTangent(middle, tangent, 0.001); // TODO const + + return (middle - nearest).lengthSquared(); + } +}; } diff --git a/src/geometry/bezier.h b/src/geometry/bezier.h deleted file mode 100644 index 9fee019..0000000 --- a/src/geometry/bezier.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include - -#include - -#include - -#include - -namespace geometry -{ - -class Bezier : public common::Aggregable -{ -private: - using Complex = std::complex; - using InflexionPoints = std::array; - - QVector2D m_point1; - QVector2D m_point2; - QVector2D m_control1; - QVector2D m_control2; - - static bool isRealInflexionPoint(const Bezier::Complex& point); - - InflexionPoints inflexions() const; - QVector2D derivativeAt(float t) const; - - QVector2D findNearestPointWithTangent(const QVector2D& point, const QVector2D& tangent, float maxError) const; - -public: - explicit Bezier(const QVector2D& p1, const QVector2D& c1, const QVector2D& c2, const QVector2D& p2); - Bezier() = default; - - const QVector2D& point1() const; - const QVector2D& point2() const; - const QVector2D& control1() const; - const QVector2D& control2() const; - - QVector2D at(float t) const; - - float approximateLength() const; - - Pair split(float t) const; - Pair splitHalf() const; - /// Split bezier keeping only convex shape. - List splitToConvex() const; - - std::optional toBiarc() const; - Polyline toLine() const; - - bool isPoint() const; - - float maxError(const Biarc& biarc) const; -}; - -} diff --git a/src/geometry/biarc.cpp b/src/geometry/biarc.cpp index a096deb..a2ca7d4 100644 --- a/src/geometry/biarc.cpp +++ b/src/geometry/biarc.cpp @@ -1,75 +1,94 @@ -#include -#include -#include +module; +#include +#include #include #include -namespace geometry +export module geometry.biarc; + +import common.aggregable; +import geometry.bulge; +import geometry.polyline; +import geometry.utils; + +export namespace geometry { -Orientation Biarc::orientation() const +class Biarc : public common::Aggregable { - const float det = (m_middle.x() - m_point1.x()) * (m_middle.y() + m_point1.y()) - + (m_point2.x() - m_middle.x()) * (m_point2.y() + m_middle.y()) - + (m_point1.x() - m_point2.x()) * (m_point1.y() + m_point2.y()); +private: + QVector2D m_point1; + QVector2D m_point2; + /// Intersection point of the arcs + QVector2D m_middle; + /// Tangent at point1 + QVector2D m_tangent1; + /// Tangent at point1 + QVector2D m_tangent2; + /// Line from point1 to middle + QVector2D m_line1; + /// Line from point2 to middle + QVector2D m_line2; - return (det < 0.0f) ? Orientation::CW : Orientation::CCW; -} + Orientation orientation() const + { + const float det = (m_middle.x() - m_point1.x()) * (m_middle.y() + m_point1.y()) + + (m_point2.x() - m_middle.x()) * (m_point2.y() + m_middle.y()) + + (m_point1.x() - m_point2.x()) * (m_point1.y() + m_point2.y()); -Biarc::Biarc(const QVector2D& point1, const QVector2D& middle, const QVector2D& point2, const QVector2D& tangent1, - const QVector2D& tangent2) - : m_point1(point1) - , m_point2(point2) - , m_middle(middle) - , m_tangent1(tangent1) - , m_tangent2(tangent2) - , m_line1(middle - point1) - , m_line2(middle - point2) -{ -} + return (det < 0.0f) ? Orientation::CW : Orientation::CCW; + } -const QVector2D& Biarc::middle() const -{ - return m_middle; -} +public: + explicit Biarc(const QVector2D& point1, const QVector2D& middle, const QVector2D& point2, const QVector2D& tangent1, + const QVector2D& tangent2) + : m_point1(point1) + , m_point2(point2) + , m_middle(middle) + , m_tangent1(tangent1) + , m_tangent2(tangent2) + , m_line1(middle - point1) + , m_line2(middle - point2) + { + } -QVector2D Biarc::tangentAtMiddle() const -{ - // Rotate line by PI/2 - const QVector2D normalizedLine1 = m_line1.normalized(); - const QVector2D perpendicularLine1 = PerpendicularLine(normalizedLine1); + const QVector2D& middle() const { return m_middle; } - // Tangent at middle is the reflect of tangent at start by perpendicular line start to end. - return ReflectLine(m_tangent1.normalized(), perpendicularLine1); -} + QVector2D tangentAtMiddle() const + { + // Rotate line by PI/2 + const QVector2D normalizedLine1 = m_line1.normalized(); + const QVector2D perpendicularLine1 = PerpendicularLine(normalizedLine1); -float Biarc::approximateLength() const -{ - return (m_point1 - m_middle).length() + (m_point2 - m_middle).length(); -} + // Tangent at middle is the reflect of tangent at start by perpendicular line start to end. + return ReflectLine(m_tangent1.normalized(), perpendicularLine1); + } -Polyline Biarc::toLinePolyline() const -{ - const Bulge bulge(m_point1, m_point2, 0.0f); - return Polyline({ bulge }); -} + float approximateLength() const { return (m_point1 - m_middle).length() + (m_point2 - m_middle).length(); } -Polyline Biarc::toPolyline() const -{ - /* Angle from end to start line with arc tangent at start point is double of - * bulge tangent angle. - */ + Polyline toLinePolyline() const + { + const Bulge bulge(m_point1, m_point2, 0.0f); + return Polyline({ bulge }); + } - // Half angle from tangent1 to line1 - const float thetab1 = (LineAngle(m_line1) - LineAngle(m_tangent1)) / 2.0f; - // Half angle from line2 to tangent2 - const float thetab2 = (LineAngle(m_tangent2) - LineAngle(m_line2)) / 2.0f; + Polyline toPolyline() const + { + /* Angle from end to start line with arc tangent at start point is double of + * bulge tangent angle. + */ - const Bulge b1(m_point1, m_middle, std::tan(thetab1)); - const Bulge b2(m_middle, m_point2, std::tan(thetab2)); + // Half angle from tangent1 to line1 + const float thetab1 = (LineAngle(m_line1) - LineAngle(m_tangent1)) / 2.0f; + // Half angle from line2 to tangent2 + const float thetab2 = (LineAngle(m_tangent2) - LineAngle(m_line2)) / 2.0f; - return Polyline({ b1, b2 }); -} + const Bulge b1(m_point1, m_middle, std::tan(thetab1)); + const Bulge b2(m_middle, m_point2, std::tan(thetab2)); + + return Polyline({ b1, b2 }); + } +}; } diff --git a/src/geometry/biarc.h b/src/geometry/biarc.h deleted file mode 100644 index 8276cca..0000000 --- a/src/geometry/biarc.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -namespace geometry -{ - -class Biarc : public common::Aggregable -{ -private: - QVector2D m_point1; - QVector2D m_point2; - /// Intersection point of the arcs - QVector2D m_middle; - /// Tangent at point1 - QVector2D m_tangent1; - /// Tangent at point1 - QVector2D m_tangent2; - /// Line from point1 to middle - QVector2D m_line1; - /// Line from point2 to middle - QVector2D m_line2; - - Orientation orientation() const; - -public: - explicit Biarc(const QVector2D& point1, const QVector2D& middle, const QVector2D& point2, const QVector2D& tangent1, - const QVector2D& tangent2); - - const QVector2D& middle() const; - QVector2D tangentAtMiddle() const; - - float approximateLength() const; - - Polyline toLinePolyline() const; - Polyline toPolyline() const; -}; - -} diff --git a/src/geometry/bulge.cpp b/src/geometry/bulge.cpp index d90d75d..1bb7981 100644 --- a/src/geometry/bulge.cpp +++ b/src/geometry/bulge.cpp @@ -1,251 +1,257 @@ -#include +module; + #include +#include #include -#include #include +#include +#include +#include +#include +#include -#include -#include +export module geometry.bulge; + +import common.aggregable; +import geometry.arc; +import geometry.circle; +import geometry.line; +import geometry.rect; +import geometry.utils; namespace geometry { -float Bulge::arcRadius() const +Rect boundingRectPoints(const Point2DList& points) { - const float absTangent = std::abs(m_tangent); + Point2DList::const_iterator it = points.begin(); + const QVector2D firstPoint = *(it++); + + return std::accumulate(it, points.end(), Rect(firstPoint)); +} - const QVector2D line = m_end - m_start; - const float lineLength = line.length(); - const float radius = (lineLength * (1.0f + m_tangent * m_tangent)) / (4.0f * absTangent); +inline QVector2D mapVector2D(const QVector2D& vect, const QTransform& matrix) +{ + const QPointF point = vect.toPointF(); + return QVector2D(matrix.map(point)); +} - return radius; } -QVector2D Bulge::relativeArcCenter() const +export namespace geometry { - const QVector2D line = m_end - m_start; - const QVector2D halfLine = line / 2.0f; - const float theta4 = std::atan(m_tangent); - const float lineToCenterAngle = M_PI_2 - theta4 * 2.0f; +class Bulge : public common::Aggregable +{ + friend serializer::Access; - if (lineToCenterAngle < precision::fuzzyAngle) { - return halfLine; - } else { - const float lineToCenterTangent = std::tan(lineToCenterAngle); +private: + QVector2D m_start; + QVector2D m_end; - const QVector2D perpendicularHalfLine(-halfLine.y(), halfLine.x()); - const QVector2D relativeCenter = halfLine + perpendicularHalfLine * lineToCenterTangent; - return relativeCenter; - } -} + float m_tangent; -Point2DList Bulge::arcBoundingPoints() const -{ - using QVector2D4 = std::array; + float arcRadius() const + { + const float absTangent = std::abs(m_tangent); - Point2DList points; + const QVector2D line = m_end - m_start; + const float lineLength = line.length(); + const float radius = (lineLength * (1.0f + m_tangent * m_tangent)) / (4.0f * absTangent); - const QVector2D relativeCenter = relativeArcCenter(); - const QVector2D line = m_end - m_start; - const float radius = arcRadius(); + return radius; + } - static const QVector2D4 quadrantPointsUnit - = { QVector2D(1, 0), QVector2D(0, 1), QVector2D(-1, 0), QVector2D(0, -1) }; + QVector2D relativeArcCenter() const + { + const QVector2D line = m_end - m_start; + const QVector2D halfLine = line / 2.0f; - const bool centerLineSide = std::signbit(CrossProduct(line, relativeCenter)); + const float theta4 = std::atan(m_tangent); + const float lineToCenterAngle = M_PI_2 - theta4 * 2.0f; - QVector2D4 quadrantPoints; - std::transform(quadrantPointsUnit.begin(), quadrantPointsUnit.end(), quadrantPoints.begin(), - [&relativeCenter, radius](const QVector2D& pointUnit) { - return relativeCenter + pointUnit * radius; - }); + if (lineToCenterAngle < precision::fuzzyAngle) { + return halfLine; + } else { + const float lineToCenterTangent = std::tan(lineToCenterAngle); - 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); + const QVector2D perpendicularHalfLine(-halfLine.y(), halfLine.x()); + const QVector2D relativeCenter = halfLine + perpendicularHalfLine * lineToCenterTangent; + return relativeCenter; + } + } - return oppositeSideToCenter; - }); + Point2DList arcBoundingPoints() const + { + using QVector2D4 = std::array; - std::transform(points.begin(), points.end(), points.begin(), [this](const QVector2D& point) { - return point + m_start; - }); + Point2DList points; - return points; -} + const QVector2D relativeCenter = relativeArcCenter(); + const QVector2D line = m_end - m_start; + const float radius = arcRadius(); -Bulge::Bulge(const QVector2D& start, const QVector2D& end, float tangent) - : m_start(start) - , m_end(end) - , m_tangent(tangent) -{ - assert(-1.0f <= tangent && tangent <= 1.0f); -} + static const QVector2D4 quadrantPointsUnit + = { QVector2D(1, 0), QVector2D(0, 1), QVector2D(-1, 0), QVector2D(0, -1) }; -Bulge::Bulge(const cavc::PlineVertex& v1, const cavc::PlineVertex& v2) - : m_start(v1.x(), v1.y()) - , m_end(v2.x(), v2.y()) - , m_tangent(v1.bulge()) -{ -} + const bool centerLineSide = std::signbit(CrossProduct(line, relativeCenter)); -const QVector2D& Bulge::start() const -{ - return m_start; -} + QVector2D4 quadrantPoints; + std::transform(quadrantPointsUnit.begin(), quadrantPointsUnit.end(), quadrantPoints.begin(), + [&relativeCenter, radius](const QVector2D& pointUnit) { + return relativeCenter + pointUnit * radius; + }); -QVector2D& Bulge::start() -{ - return m_start; -} + 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); -const QVector2D& Bulge::end() const -{ - return m_end; -} + return oppositeSideToCenter; + }); -QVector2D& Bulge::end() -{ - return m_end; -} + std::transform(points.begin(), points.end(), points.begin(), [this](const QVector2D& point) { + return point + m_start; + }); -float Bulge::tangent() const -{ - return m_tangent; -} + return points; + } -float& Bulge::tangent() -{ - return m_tangent; -} +public: + /** Define a bulge + * @param start Starting point of the bulge + * @param end Ending point of the bulge + * @param tangent Tangent of a thourth of the arc angle. + * Negative tangent means the arc goes clockwise from start to end, + * otherwise anti clockwise from start to end. + */ + explicit Bulge(const QVector2D& start, const QVector2D& end, float tangent) + : m_start(start) + , m_end(end) + , m_tangent(tangent) + { + assert(-1.0f <= tangent && tangent <= 1.0f); + } -float Bulge::length() const -{ - if (isLine()) { - return m_start.distanceToPoint(m_end); + explicit Bulge(const cavc::PlineVertex& v1, const cavc::PlineVertex& v2) + : m_start(v1.x(), v1.y()) + , m_end(v2.x(), v2.y()) + , m_tangent(v1.bulge()) + { } - // Radius is half line length * 1 + t^2 / (4 * |t|) - const float radius = m_start.distanceToPoint(m_end) * (1.0f + m_tangent * m_tangent) / m_tangent; - const float angle = std::atan(m_tangent); - return radius * angle; -} + explicit Bulge() = default; -Rect boundingRectPoints(const Point2DList& points) -{ - Point2DList::const_iterator it = points.begin(); - const QVector2D firstPoint = *(it++); + const QVector2D& start() const { return m_start; } - return std::accumulate(it, points.end(), Rect(firstPoint)); -} + QVector2D& start() { return m_start; } -Rect Bulge::boundingRect() const -{ - const Rect lineBoundingRect(m_start, m_end); - if (isArc()) { - const Point2DList arcPoints = arcBoundingPoints(); + const QVector2D& end() const { return m_end; } + + QVector2D& end() { return m_end; } + + float tangent() const { return m_tangent; } + + float& tangent() { return m_tangent; } - if (arcPoints.empty()) { - return lineBoundingRect; + float length() const + { + if (isLine()) { + return m_start.distanceToPoint(m_end); } - const Rect arcPointBoundingRect = boundingRectPoints(arcPoints); - return lineBoundingRect | arcPointBoundingRect; + // Radius is half line length * 1 + t^2 / (4 * |t|) + const float radius = m_start.distanceToPoint(m_end) * (1.0f + m_tangent * m_tangent) / m_tangent; + const float angle = std::atan(m_tangent); + return radius * angle; } - return lineBoundingRect; -} + Rect boundingRect() const + { + const Rect lineBoundingRect(m_start, m_end); + if (isArc()) { + const Point2DList arcPoints = arcBoundingPoints(); -void Bulge::invert() -{ - std::swap(m_start, m_end); - m_tangent = -m_tangent; -} + if (arcPoints.empty()) { + return lineBoundingRect; + } -void Bulge::linify() -{ - m_tangent = 0.0f; -} + const Rect arcPointBoundingRect = boundingRectPoints(arcPoints); + return lineBoundingRect | arcPointBoundingRect; + } -Bulge Bulge::extendStart(const QVector2D& start) const -{ - return Bulge(start, m_end, m_tangent); -} + return lineBoundingRect; + } -Bulge Bulge::extendEnd(const QVector2D& end) const -{ - return Bulge(m_start, end, m_tangent); -} + /// Change direction + void invert() + { + std::swap(m_start, m_end); + m_tangent = -m_tangent; + } -bool Bulge::isLine() const -{ - return std::abs(m_tangent) < std::numeric_limits::epsilon(); // TODO utils -} + /// Transform to line, means tangent is 0. + void linify() { m_tangent = 0.0f; } -bool Bulge::isArc() const -{ - return !isLine(); -} + // Extend bulge start point + Bulge extendStart(const QVector2D& start) const { return Bulge(start, m_end, m_tangent); } -Orientation Bulge::orientation() const -{ - return (m_tangent < 0.0f) ? Orientation::CW : Orientation::CCW; -} + // Extend bulge end point + Bulge extendEnd(const QVector2D& end) const { return Bulge(m_start, end, m_tangent); } -Circle Bulge::toCircle() const -{ - const Orientation ori = orientation(); - const float radius = arcRadius(); - const QVector2D center = m_start + relativeArcCenter(); + bool isLine() const + { + return std::abs(m_tangent) < std::numeric_limits::epsilon(); // TODO utils + } - return Circle(center, radius, ori); -} + bool isArc() const { return !isLine(); } -Arc Bulge::toArc() const -{ - const Circle circle = toCircle(); - const QVector2D& center = circle.center(); + Orientation orientation() const { return (m_tangent < 0.0f) ? Orientation::CW : Orientation::CCW; } - const float startAngle = LineAngle(m_start - center); - const float endAngle = LineAngle(m_end - center); + Circle toCircle() const + { + const Orientation ori = orientation(); + const float radius = arcRadius(); + const QVector2D center = m_start + relativeArcCenter(); - return Arc(circle, m_start, m_end, startAngle, endAngle); -} + return Circle(center, radius, ori); + } -Line Bulge::toLine() const -{ - return Line(m_start, m_end); -} + Arc toArc() const + { + const Circle circle = toCircle(); + const QVector2D& center = circle.center(); -inline QVector2D mapVector2D(const QVector2D& vect, const QTransform& matrix) -{ - const QPointF point = vect.toPointF(); - return QVector2D(matrix.map(point)); -} + const float startAngle = LineAngle(m_start - center); + const float endAngle = LineAngle(m_end - center); -void Bulge::transform(const QTransform& matrix) -{ - m_start = mapVector2D(m_start, matrix); - m_end = mapVector2D(m_end, matrix); + return Arc(circle, m_start, m_end, startAngle, endAngle); + } - if (matrix.isScaling()) { - const bool invertTagent = (matrix.m11() * matrix.m22()) < 0.0f; - if (invertTagent) { - m_tangent = -m_tangent; + Line toLine() const { return Line(m_start, m_end); } + + void transform(const QTransform& matrix) + { + m_start = mapVector2D(m_start, matrix); + m_end = mapVector2D(m_end, matrix); + + if (matrix.isScaling()) { + const bool invertTagent = (matrix.m11() * matrix.m22()) < 0.0f; + if (invertTagent) { + m_tangent = -m_tangent; + } } } -} -bool Bulge::operator==(const Bulge& other) const -{ - return m_start == other.m_start && m_end == other.m_end && m_tangent == other.m_tangent; -} + bool operator==(const Bulge& other) const + { + return m_start == other.m_start && m_end == other.m_end && m_tangent == other.m_tangent; + } -bool Bulge::equalsInversed(const Bulge& other) const -{ - return m_start == other.m_end && m_end == other.m_start && m_tangent == -other.m_tangent; -} + bool equalsInversed(const Bulge& other) const + { + return m_start == other.m_end && m_end == other.m_start && m_tangent == -other.m_tangent; + } +}; } diff --git a/src/geometry/bulge.h b/src/geometry/bulge.h deleted file mode 100644 index 53577ba..0000000 --- a/src/geometry/bulge.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include - -namespace geometry -{ - -class Bulge : public common::Aggregable -{ - friend serializer::Access; - -private: - QVector2D m_start; - QVector2D m_end; - - float m_tangent; - - float arcRadius() const; - QVector2D relativeArcCenter() const; - Point2DList arcBoundingPoints() const; - -public: - /** Define a bulge - * @param start Starting point of the bulge - * @param end Ending point of the bulge - * @param tangent Tangent of a thourth of the arc angle. - * Negative tangent means the arc goes clockwise from start to end, - * otherwise anti clockwise from start to end. - */ - explicit Bulge(const QVector2D& start, const QVector2D& end, float tangent); - explicit Bulge(const cavc::PlineVertex& v1, const cavc::PlineVertex& v2); - explicit Bulge() = default; - - const QVector2D& start() const; - QVector2D& start(); - const QVector2D& end() const; - QVector2D& end(); - float tangent() const; - float& tangent(); - - float length() const; - - Rect boundingRect() const; - - /// Change direction - void invert(); - /// Transform to line, means tangent is 0. - void linify(); - - // Extend bulge start point - Bulge extendStart(const QVector2D& start) const; - // Extend bulge end point - Bulge extendEnd(const QVector2D& end) const; - - bool isLine() const; - bool isArc() const; - Orientation orientation() const; - - Circle toCircle() const; - Arc toArc() const; - Line toLine() const; - - void transform(const QTransform& matrix); - - bool operator==(const Bulge& other) const; - - bool equalsInversed(const Bulge& other) const; -}; - -} diff --git a/src/geometry/circle.cpp b/src/geometry/circle.cpp index 66567d7..c2d634d 100644 --- a/src/geometry/circle.cpp +++ b/src/geometry/circle.cpp @@ -1,28 +1,34 @@ -#include +module; -namespace geometry -{ +#include -Circle::Circle(const QVector2D& center, float radius, Orientation orientation) - : m_center(center) - , m_radius(radius) - , m_orientation(orientation) -{ -} +export module geometry.circle; -const QVector2D& Circle::center() const -{ - return m_center; -} +import geometry.utils; -float Circle::radius() const +export namespace geometry { - return m_radius; -} -Orientation Circle::orientation() const +class Circle { - return m_orientation; -} +private: + QVector2D m_center; + float m_radius; + Orientation m_orientation; + +public: + explicit Circle(const QVector2D& center, float radius, Orientation orientation) + : m_center(center) + , m_radius(radius) + , m_orientation(orientation) + { + } + + const QVector2D& center() const { return m_center; } + + float radius() const { return m_radius; } + + Orientation orientation() const { return m_orientation; } +}; } diff --git a/src/geometry/circle.h b/src/geometry/circle.h deleted file mode 100644 index 3d20b7b..0000000 --- a/src/geometry/circle.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -namespace geometry -{ - -class Circle -{ -private: - QVector2D m_center; - float m_radius; - Orientation m_orientation; - -public: - explicit Circle(const QVector2D& center, float radius, Orientation orientation); - - const QVector2D& center() const; - float radius() const; - Orientation orientation() const; -}; - -} diff --git a/src/geometry/cubicspline.cpp b/src/geometry/cubicspline.cpp index b680d0d..2dd5f5e 100644 --- a/src/geometry/cubicspline.cpp +++ b/src/geometry/cubicspline.cpp @@ -1,115 +1,129 @@ -#include +module; +#include #include -namespace geometry -{ +export module geometry.cubicspline; -CubicSpline::CubicSpline(Point2DList&& points, bool closed) - : Spline(std::move(points), closed) -{ -} +import geometry.bezier; +import geometry.spline; +import geometry.utils; -Point2DList CubicSpline::convertClosedToCubicBezierPoints() const +export namespace geometry { - const Point2DList& allControlPoints = controlPoints(); - const int nbcontrol = allControlPoints.size(); - const int size = nbcontrol * 3 + 1; - - Point2DList bezierPoints(size); +class CubicSpline : public Spline +{ +private: + Point2DList convertClosedToCubicBezierPoints() const + { + const Point2DList& allControlPoints = controlPoints(); + const int nbcontrol = allControlPoints.size(); - // Copy of 0 ... -1 0 to 0 3 6 ... -10 -7 -4 0 - for (int i = 0; i <= nbcontrol; ++i) { - const int src = i % nbcontrol; - const int dst = i * 3; - bezierPoints[dst] = allControlPoints[src]; - } + const int size = nbcontrol * 3 + 1; - for (int src = 0, dst = 1; src < nbcontrol; ++src, dst += 3) { - const int srcnext = (src + 1) % nbcontrol; - bezierPoints[dst] = (allControlPoints[src] * 2.0f + allControlPoints[srcnext]) / 3.0f; - bezierPoints[dst + 1] = (allControlPoints[src] + allControlPoints[srcnext] * 2.0f) / 3.0f; - } + Point2DList bezierPoints(size); - for (int i = 3; i < size - 3; i += 3) { - bezierPoints[i] = ((bezierPoints[i - 1] + bezierPoints[i + 1]) / 2.0f); - } + // Copy of 0 ... -1 0 to 0 3 6 ... -10 -7 -4 0 + for (int i = 0; i <= nbcontrol; ++i) { + const int src = i % nbcontrol; + const int dst = i * 3; + bezierPoints[dst] = allControlPoints[src]; + } - bezierPoints.front() = (bezierPoints[size - 2] + bezierPoints[1]) / 2.0f; - bezierPoints.back() = bezierPoints.front(); + for (int src = 0, dst = 1; src < nbcontrol; ++src, dst += 3) { + const int srcnext = (src + 1) % nbcontrol; + bezierPoints[dst] = (allControlPoints[src] * 2.0f + allControlPoints[srcnext]) / 3.0f; + bezierPoints[dst + 1] = (allControlPoints[src] + allControlPoints[srcnext] * 2.0f) / 3.0f; + } - return bezierPoints; -} + for (int i = 3; i < size - 3; i += 3) { + bezierPoints[i] = ((bezierPoints[i - 1] + bezierPoints[i + 1]) / 2.0f); + } -Bezier::List CubicSpline::pointsToBeziers(const Point2DList& bezierPoints) const -{ - const int size = bezierPoints.size(); - Bezier::List beziers(size / 3); + bezierPoints.front() = (bezierPoints[size - 2] + bezierPoints[1]) / 2.0f; + bezierPoints.back() = bezierPoints.front(); - for (int src = 0, dst = 0; src < (size - 1); src += 3, ++dst) { - beziers[dst] = Bezier(bezierPoints[src], bezierPoints[src + 1], bezierPoints[src + 2], bezierPoints[src + 3]); + return bezierPoints; } - return beziers; -} - -Point2DList CubicSpline::convertOpenedToCubicBezierPoints() const -{ - const Point2DList& allControlPoints = controlPoints(); - const int nbcontrol = allControlPoints.size(); - const int lastcontrol = nbcontrol - 1; - - int size = nbcontrol; - if (nbcontrol > 4) { - const int nbpairs = nbcontrol - 1; - - size += 2; // Point deuxième et penultième - size += (nbpairs - 4) * 2; + Point2DList convertOpenedToCubicBezierPoints() const + { + const Point2DList& allControlPoints = controlPoints(); + const int nbcontrol = allControlPoints.size(); + const int lastcontrol = nbcontrol - 1; + + int size = nbcontrol; + if (nbcontrol > 4) { + const int nbpairs = nbcontrol - 1; + + size += 2; // Point deuxième et penultième + size += (nbpairs - 4) * 2; + } + + Point2DList bezierPoints(size); + + const int last = size - 1; + + // Copy of two begin and end raw points 0 1 -2 -1 to 0 1 -2 -1 + for (int src = 0; src < 2; ++src) { + // Copy from begin + bezierPoints[src] = allControlPoints[src]; + // Copy from end + bezierPoints[last - src] = allControlPoints[lastcontrol - src]; + } + + // Copy of remaining raw points from 2 ... -3 to 3 6 9 ... -10 -7 -4 (every 3 with 6 point distance from begin + // and end). + for (int src = 2, dst = 3; src < (nbcontrol - 2); ++src, dst += 3) { + bezierPoints[dst] = allControlPoints[src]; + } + + if (nbcontrol > 4) { + // Copy half points 2 and -3 + bezierPoints[2] = (allControlPoints[1] + allControlPoints[2]) / 2.0f; + bezierPoints[last - 2] = (allControlPoints[lastcontrol - 1] + allControlPoints[lastcontrol - 2]) / 2.0f; + } + + // Copy third point 2 ... -3 to (4 5) (7 8) .. (-9 -8) (-6 -5) + for (int src = 2, dst = 4; src < (nbcontrol - 3); ++src, dst += 3) { + bezierPoints[dst] = (allControlPoints[src] * 2.0f + allControlPoints[src + 1]) / 3.0f; + bezierPoints[dst + 1] = (allControlPoints[src] + allControlPoints[src + 1] * 2.0f) / 3.0f; + } + + for (int i = 3; i < (size - 1); i += 3) { + bezierPoints[i] = ((bezierPoints[i - 1] + bezierPoints[i + 1]) / 2.0f); + } + + return bezierPoints; } - Point2DList bezierPoints(size); + Bezier::List pointsToBeziers(const Point2DList& bezierPoints) const + { + const int size = bezierPoints.size(); + Bezier::List beziers(size / 3); - const int last = size - 1; + for (int src = 0, dst = 0; src < (size - 1); src += 3, ++dst) { + beziers[dst] + = Bezier(bezierPoints[src], bezierPoints[src + 1], bezierPoints[src + 2], bezierPoints[src + 3]); + } - // Copy of two begin and end raw points 0 1 -2 -1 to 0 1 -2 -1 - for (int src = 0; src < 2; ++src) { - // Copy from begin - bezierPoints[src] = allControlPoints[src]; - // Copy from end - bezierPoints[last - src] = allControlPoints[lastcontrol - src]; + return beziers; } - // Copy of remaining raw points from 2 ... -3 to 3 6 9 ... -10 -7 -4 (every 3 with 6 point distance from begin and - // end). - for (int src = 2, dst = 3; src < (nbcontrol - 2); ++src, dst += 3) { - bezierPoints[dst] = allControlPoints[src]; +public: + explicit CubicSpline(Point2DList&& points, bool closed) + : Spline(std::move(points), closed) + { } - if (nbcontrol > 4) { - // Copy half points 2 and -3 - bezierPoints[2] = (allControlPoints[1] + allControlPoints[2]) / 2.0f; - bezierPoints[last - 2] = (allControlPoints[lastcontrol - 1] + allControlPoints[lastcontrol - 2]) / 2.0f; - } - - // Copy third point 2 ... -3 to (4 5) (7 8) .. (-9 -8) (-6 -5) - for (int src = 2, dst = 4; src < (nbcontrol - 3); ++src, dst += 3) { - bezierPoints[dst] = (allControlPoints[src] * 2.0f + allControlPoints[src + 1]) / 3.0f; - bezierPoints[dst + 1] = (allControlPoints[src] + allControlPoints[src + 1] * 2.0f) / 3.0f; - } + Bezier::List toBeziers() const + { + const Point2DList bezierPoints + = closed() ? convertClosedToCubicBezierPoints() : convertOpenedToCubicBezierPoints(); - for (int i = 3; i < (size - 1); i += 3) { - bezierPoints[i] = ((bezierPoints[i - 1] + bezierPoints[i + 1]) / 2.0f); + return pointsToBeziers(bezierPoints); } - - return bezierPoints; -} - -Bezier::List CubicSpline::toBeziers() const -{ - const Point2DList bezierPoints = closed() ? convertClosedToCubicBezierPoints() : convertOpenedToCubicBezierPoints(); - - return pointsToBeziers(bezierPoints); -} +}; } diff --git a/src/geometry/cubicspline.h b/src/geometry/cubicspline.h deleted file mode 100644 index 4792776..0000000 --- a/src/geometry/cubicspline.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -namespace geometry -{ - -class CubicSpline : public Spline -{ -private: - Point2DList convertClosedToCubicBezierPoints() const; - Point2DList convertOpenedToCubicBezierPoints() const; - - Bezier::List pointsToBeziers(const Point2DList& bezierPoints) const; - -public: - explicit CubicSpline(Point2DList&& points, bool closed); - - Bezier::List toBeziers() const; -}; - -} diff --git a/src/geometry/filter/CMakeLists.txt b/src/geometry/filter/CMakeLists.txt index be943ae..cf563d3 100644 --- a/src/geometry/filter/CMakeLists.txt +++ b/src/geometry/filter/CMakeLists.txt @@ -2,11 +2,8 @@ set(SRC assembler.cpp cleaner.cpp removeexactduplicate.cpp - - assembler.h - cleaner.h - removeexactduplicate.h ) -add_library(geometry-filter ${SRC}) +add_module_library(geometry-filter ${SRC}) +target_link_libraries(geometry-filter PUBLIC geometry) add_coverage(geometry-filter) diff --git a/src/geometry/filter/assembler.cpp b/src/geometry/filter/assembler.cpp index 3de8c9a..caa2b50 100644 --- a/src/geometry/filter/assembler.cpp +++ b/src/geometry/filter/assembler.cpp @@ -1,26 +1,20 @@ -#include +module; #include +#include +#include +#include +#include -namespace geometry::filter -{ +export module geometry.filter.assembler; -Assembler::ChainBuilder::ChainBuilder(const Tip::List& tips, std::set& unconnectedPolylines, - const KDTree& tree, PolylineIndex index, float closeTolerance) - : m_chain { { index, Item::Direction::NORMAL } } - , m_tips(tips) - , m_unconnectedPolylines(unconnectedPolylines) - , m_tree(tree) - , m_startIndex(index) - , m_closeTolerance(closeTolerance) - , - // Expand chain before polyline - m_closed(expandSide(std::front_inserter(m_chain), Tip::Type::START) - || expandSide(std::back_inserter(m_chain), Tip::Type::END)) +import common.aggregable; +import geometry.polyline; +import geometry.utils; + +namespace geometry::filter { -} -/// Average point at p1 end and p2 start and assign middle point to both static void averageStartEndPolyline(Polyline& first, Polyline& second) { const QVector2D middlePoint = (first.end() + second.start()) / 2.0f; @@ -28,114 +22,266 @@ static void averageStartEndPolyline(Polyline& first, Polyline& second) second.start() = middlePoint; } -Polyline Assembler::ChainBuilder::mergedPolyline(const Polyline::List& polylines) const +} + +export namespace geometry::filter { - // Initialise with first polyline - const Item& firstItem = m_chain.front(); - Polyline mergedPolyline = polylines[firstItem.polylineIndex]; - if (firstItem.dir == Item::Direction::INVERT) { - mergedPolyline.invert(); + +class Assembler +{ +private: + using PolylineIndex = int; + using TipIndex = int; + + struct Tip : common::Aggregable + { + PolylineIndex polylineIndex; + // Original point from polyline. + QVector2D point; + + enum class Type { START = 0, END } type; + }; + + static inline TipIndex tipIndexFromPolylineSide(PolylineIndex index, Tip::Type side) + { + return index * 2 + static_cast(side); } - // Merge remaining polylines - for (List::const_iterator it = ++m_chain.begin(), end = m_chain.end(); it != end; ++it) { - Polyline polyline = polylines[it->polylineIndex]; - if (it->dir == Item::Direction::INVERT) { - polyline.invert(); + class TipAdaptor + { + private: + const Tip::List& m_tips; + + public: + explicit TipAdaptor(const Tip::List& tips) + : m_tips(tips) + { } - averageStartEndPolyline(mergedPolyline, polyline); - mergedPolyline += polyline; - } + size_t kdtree_get_point_count() const { return m_tips.size(); } - if (m_closed) { - averageStartEndPolyline(mergedPolyline, mergedPolyline); - } + float kdtree_get_pt(const size_t idx, const size_t dim) const { return m_tips[idx].point[dim]; } - return mergedPolyline; -} + template + bool kdtree_get_bbox([[maybe_unused]] BBOX& bb) const + { + return false; + } + }; -Assembler::TipAdaptor::TipAdaptor(const Tip::List& tips) - : m_tips(tips) -{ -} + using KDTree = nanoflann::KDTreeSingleIndexAdaptor, TipAdaptor, 2>; -size_t Assembler::TipAdaptor::kdtree_get_point_count() const -{ - return m_tips.size(); -} + class ChainBuilder + { + private: + struct Item + { + PolylineIndex polylineIndex; + enum class Direction { + NORMAL = 0, + INVERT ///< Inverted with previous polyline + } dir; + }; -float Assembler::TipAdaptor::kdtree_get_pt(const size_t idx, const size_t dim) const -{ - return m_tips[idx].point[dim]; -} + using List = std::list; -Assembler::Tip::List Assembler::constructTips() -{ - Tip::List tips; // TODO reserve and std::transform + List m_chain; + const Tip::List& m_tips; + std::set& m_unconnectedPolylines; + const KDTree& m_tree; + const PolylineIndex m_startIndex; + const float m_closeTolerance; + bool m_closed; - for (int i = 0, size = m_unmergedPolylines.size(); i < size; ++i) { - const Polyline& polyline = m_unmergedPolylines[i]; - tips.push_back({ {}, i, polyline.start(), Tip::Type::START }); - tips.push_back({ {}, i, polyline.end(), Tip::Type::END }); - } + template + bool expandSide(Inserter inserter, Tip::Type side) + { + // Direction of polyline, at first normal direction. + Item::Direction direction = Item::Direction::NORMAL; - return tips; -} + PolylineIndex index = m_startIndex; + while (index != -1) { + const size_t tipIndex = tipIndexFromPolylineSide(index, side); + // Tips of the current polyline at the right side. + const Tip& tip = m_tips[tipIndex]; -Polyline::List Assembler::connectTips(const Tip::List& tips, const KDTree& tree) const -{ - // Generate all unconnected polyline index. - std::set unconnectedPolylines; - for (PolylineIndex index = 0, size = m_unmergedPolylines.size(); index < size; ++index) { - unconnectedPolylines.insert(index); - } + // Coordinate of search point. + const float coord[2] = { tip.point.x(), tip.point.y() }; + + // Nearest neighbour with distance. + std::array matchIndices; + std::array matchDistances; + + // Search for the nearest neighbours. + const int nbMatches = m_tree.knnSearch(coord, 2, matchIndices.data(), matchDistances.data()); + if (nbMatches == 2) { + // Find neighbour that it's not ourself. + const int neighbourMatchIndex = (matchIndices[0] == tipIndex) ? 1 : 0; + const TipIndex neighbourTipIndex = matchIndices[neighbourMatchIndex]; + // Check if neighbour is not further than tolerance + if (matchDistances[neighbourMatchIndex] <= m_closeTolerance) { + const Tip& neighbour = m_tips[neighbourTipIndex]; - Polyline::List mergedPolylines; + assert(&tip != &neighbour); - while (!unconnectedPolylines.empty()) { - // Pick a polyline index. - const PolylineIndex index = *unconnectedPolylines.begin(); - unconnectedPolylines.erase(unconnectedPolylines.begin()); + const PolylineIndex neighbourIndex = neighbour.polylineIndex; + // Stop when finding opposite tip of current polyline + if (index == neighbourIndex) { + return false; + } + /* If polyline is already connected (in case of circular shape + * one side can already connect all polylines) we discard. + */ + else if (m_unconnectedPolylines.find(neighbourIndex) == m_unconnectedPolylines.end()) { + // The chain might be closed + return true; + } else { + // If end matchs start then polylines are in same direction, otherwise they are opposed. + const bool isOpposed = (tip.type == neighbour.type); + Item::Direction neighbourDirection + = static_cast((static_cast(direction) + isOpposed) % 2); - ChainBuilder builder(tips, unconnectedPolylines, tree, index, m_closeTolerance); - mergedPolylines.push_back(builder.mergedPolyline(m_unmergedPolylines)); + // Insert the polyline. + *inserter = { neighbourIndex, neighbourDirection }; + + // Remove polyline from unconnected list. + m_unconnectedPolylines.erase(neighbourIndex); + + // Continue with the neighbour polyline. + index = neighbourIndex; + // Change to opposite side if polylines are opposed. + side = static_cast((static_cast(side) + isOpposed) % 2); + // Update direction + direction = neighbourDirection; + } + } else { + // Neighbour too far + index = -1; + } + } else { + // None or too many neighbour + index = -1; + } + } + + // The chain is fully expanded on one side without closing + return false; + } + + public: + explicit ChainBuilder(const Tip::List& tips, std::set& unconnectedPolylines, const KDTree& tree, + PolylineIndex index, float closeTolerance) + : m_chain { { index, Item::Direction::NORMAL } } + , m_tips(tips) + , m_unconnectedPolylines(unconnectedPolylines) + , m_tree(tree) + , m_startIndex(index) + , m_closeTolerance(closeTolerance) + , + // Expand chain before polyline + m_closed(expandSide(std::front_inserter(m_chain), Tip::Type::START) + || expandSide(std::back_inserter(m_chain), Tip::Type::END)) + { + } + + Polyline mergedPolyline(const Polyline::List& polylines) const + { + // Initialise with first polyline + const Item& firstItem = m_chain.front(); + Polyline mergedPolyline = polylines[firstItem.polylineIndex]; + if (firstItem.dir == Item::Direction::INVERT) { + mergedPolyline.invert(); + } + + // Merge remaining polylines + for (List::const_iterator it = ++m_chain.begin(), end = m_chain.end(); it != end; ++it) { + Polyline polyline = polylines[it->polylineIndex]; + if (it->dir == Item::Direction::INVERT) { + polyline.invert(); + } + + averageStartEndPolyline(mergedPolyline, polyline); + mergedPolyline += polyline; + } + + if (m_closed) { + averageStartEndPolyline(mergedPolyline, mergedPolyline); + } + + return mergedPolyline; + } + }; + + Polyline::List m_unmergedPolylines; + const float m_closeTolerance; + + Polyline::List m_mergedPolylines; + + Assembler::Tip::List constructTips() + { + Tip::List tips; // TODO reserve and std::transform + + for (int i = 0, size = m_unmergedPolylines.size(); i < size; ++i) { + const Polyline& polyline = m_unmergedPolylines[i]; + tips.push_back({ {}, i, polyline.start(), Tip::Type::START }); + tips.push_back({ {}, i, polyline.end(), Tip::Type::END }); + } + + return tips; } - return mergedPolylines; -} + Polyline::List connectTips(const Tip::List& tips, const KDTree& tree) const + { + // Generate all unconnected polyline index. + std::set unconnectedPolylines; + for (PolylineIndex index = 0, size = m_unmergedPolylines.size(); index < size; ++index) { + unconnectedPolylines.insert(index); + } -Assembler::Assembler(Polyline::List&& polylines, float closeTolerance) - : m_closeTolerance(closeTolerance) -{ - // Dispatch polylines to already merged or not merged. - for (Polyline& polyline : polylines) { - // Point polylines cannot be merged to others and so are ignored. - if (polyline.isPoint()) { - m_mergedPolylines.emplace_back(std::move(polyline)); - } else { - m_unmergedPolylines.emplace_back(std::move(polyline)); + Polyline::List mergedPolylines; + + while (!unconnectedPolylines.empty()) { + // Pick a polyline index. + const PolylineIndex index = *unconnectedPolylines.begin(); + unconnectedPolylines.erase(unconnectedPolylines.begin()); + + ChainBuilder builder(tips, unconnectedPolylines, tree, index, m_closeTolerance); + mergedPolylines.push_back(builder.mergedPolyline(m_unmergedPolylines)); } + + return mergedPolylines; } - // Build tips based on unmerged polylines. - const Tip::List tips = constructTips(); +public: + explicit Assembler(Polyline::List&& polylines, float closeTolerance) + : m_closeTolerance(closeTolerance) + { + // Dispatch polylines to already merged or not merged. + for (Polyline& polyline : polylines) { + // Point polylines cannot be merged to others and so are ignored. + if (polyline.isPoint()) { + m_mergedPolylines.emplace_back(std::move(polyline)); + } else { + m_unmergedPolylines.emplace_back(std::move(polyline)); + } + } - // Build kdtree of tips for faster query. - TipAdaptor adaptor(tips); - KDTree tree(2, adaptor); - tree.buildIndex(); + // Build tips based on unmerged polylines. + const Tip::List tips = constructTips(); - // Merge all unmerged polylines. - const Polyline::List mergedPolylines = connectTips(tips, tree); - // Concat all merged polylines. - m_mergedPolylines.insert(m_mergedPolylines.end(), std::move_iterator(mergedPolylines.begin()), - std::move_iterator(mergedPolylines.end())); -} + // Build kdtree of tips for faster query. + TipAdaptor adaptor(tips); + KDTree tree(2, adaptor); + tree.buildIndex(); -Polyline::List&& Assembler::polylines() -{ - return std::move(m_mergedPolylines); -} + // Merge all unmerged polylines. + const Polyline::List mergedPolylines = connectTips(tips, tree); + // Concat all merged polylines. + m_mergedPolylines.insert(m_mergedPolylines.end(), std::move_iterator(mergedPolylines.begin()), + std::move_iterator(mergedPolylines.end())); + } + + Polyline::List&& polylines() { return std::move(m_mergedPolylines); } +}; } diff --git a/src/geometry/filter/assembler.h b/src/geometry/filter/assembler.h deleted file mode 100644 index 4034148..0000000 --- a/src/geometry/filter/assembler.h +++ /dev/null @@ -1,172 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -namespace geometry::filter -{ - -class Assembler -{ -private: - using PolylineIndex = int; - using TipIndex = int; - - struct Tip : common::Aggregable - { - PolylineIndex polylineIndex; - // Original point from polyline. - QVector2D point; - - enum class Type { START = 0, END } type; - }; - - static inline TipIndex tipIndexFromPolylineSide(PolylineIndex index, Tip::Type side) - { - return index * 2 + static_cast(side); - } - - class TipAdaptor - { - private: - const Tip::List& m_tips; - - public: - explicit TipAdaptor(const Tip::List& tips); - - size_t kdtree_get_point_count() const; - float kdtree_get_pt(const size_t idx, const size_t dim) const; - - template - bool kdtree_get_bbox([[maybe_unused]] BBOX& bb) const - { - return false; - } - }; - - using KDTree = nanoflann::KDTreeSingleIndexAdaptor, TipAdaptor, 2>; - - class ChainBuilder - { - private: - struct Item - { - PolylineIndex polylineIndex; - enum class Direction { - NORMAL = 0, - INVERT ///< Inverted with previous polyline - } dir; - }; - - using List = std::list; - - List m_chain; - const Tip::List& m_tips; - std::set& m_unconnectedPolylines; - const KDTree& m_tree; - const PolylineIndex m_startIndex; - const float m_closeTolerance; - bool m_closed; - - template - bool expandSide(Inserter inserter, Tip::Type side) - { - // Direction of polyline, at first normal direction. - Item::Direction direction = Item::Direction::NORMAL; - - PolylineIndex index = m_startIndex; - while (index != -1) { - const size_t tipIndex = tipIndexFromPolylineSide(index, side); - // Tips of the current polyline at the right side. - const Tip& tip = m_tips[tipIndex]; - - // Coordinate of search point. - const float coord[2] = { tip.point.x(), tip.point.y() }; - - // Nearest neighbour with distance. - std::array matchIndices; - std::array matchDistances; - - // Search for the nearest neighbours. - const int nbMatches = m_tree.knnSearch(coord, 2, matchIndices.data(), matchDistances.data()); - if (nbMatches == 2) { - // Find neighbour that it's not ourself. - const int neighbourMatchIndex = (matchIndices[0] == tipIndex) ? 1 : 0; - const TipIndex neighbourTipIndex = matchIndices[neighbourMatchIndex]; - // Check if neighbour is not further than tolerance - if (matchDistances[neighbourMatchIndex] <= m_closeTolerance) { - const Tip& neighbour = m_tips[neighbourTipIndex]; - - assert(&tip != &neighbour); - - const PolylineIndex neighbourIndex = neighbour.polylineIndex; - // Stop when finding opposite tip of current polyline - if (index == neighbourIndex) { - return false; - } - /* If polyline is already connected (in case of circular shape - * one side can already connect all polylines) we discard. - */ - else if (m_unconnectedPolylines.find(neighbourIndex) == m_unconnectedPolylines.end()) { - // The chain might be closed - return true; - } else { - // If end matchs start then polylines are in same direction, otherwise they are opposed. - const bool isOpposed = (tip.type == neighbour.type); - Item::Direction neighbourDirection - = static_cast((static_cast(direction) + isOpposed) % 2); - - // Insert the polyline. - *inserter = { neighbourIndex, neighbourDirection }; - - // Remove polyline from unconnected list. - m_unconnectedPolylines.erase(neighbourIndex); - - // Continue with the neighbour polyline. - index = neighbourIndex; - // Change to opposite side if polylines are opposed. - side = static_cast((static_cast(side) + isOpposed) % 2); - // Update direction - direction = neighbourDirection; - } - } else { - // Neighbour too far - index = -1; - } - } else { - // None or too many neighbour - index = -1; - } - } - - // The chain is fully expanded on one side without closing - return false; - } - - public: - explicit ChainBuilder(const Tip::List& tips, std::set& unconnectedPolylines, const KDTree& tree, - PolylineIndex index, float closeTolerance); - - Polyline mergedPolyline(const Polyline::List& polylines) const; - }; - - Polyline::List m_unmergedPolylines; - const float m_closeTolerance; - - Polyline::List m_mergedPolylines; - - Tip::List constructTips(); - - Polyline::List connectTips(const Tip::List& tips, const KDTree& tree) const; - -public: - explicit Assembler(Polyline::List&& polylines, float closeTolerance); - - Polyline::List&& polylines(); -}; - -} diff --git a/src/geometry/filter/cleaner.cpp b/src/geometry/filter/cleaner.cpp index 7f2f40c..6b6e9e5 100644 --- a/src/geometry/filter/cleaner.cpp +++ b/src/geometry/filter/cleaner.cpp @@ -1,8 +1,14 @@ -#include +module; #include +#include -#include // TODO +export module geometry.filter.cleaner; + +import geometry.bulge; +import common.aggregable; +import geometry.polyline; +import geometry.utils; namespace geometry::filter { @@ -150,22 +156,33 @@ class ArcLengthCleaner Polyline&& polyline() { return std::move(m_polyline); } }; -Cleaner::Cleaner(Polyline::List&& polylines, float minimumPolylineLength, float minimumArcLength) +} + +export namespace geometry::filter { - for (const Polyline& polyline : polylines) { - // Prune small polyline length - PolylineLengthCleaner lengthCleaner(polyline, minimumPolylineLength); - // Convert small arcs to lines - ArcLengthCleaner arcCleaner(lengthCleaner.polyline(), minimumArcLength); +/** @brief Clean polyline via merging of small bulges. + */ +class Cleaner +{ +private: + Polyline::List m_polylines; + +public: + explicit Cleaner(Polyline::List&& polylines, float minimumPolylineLength, float minimumArcLength) + { + for (const Polyline& polyline : polylines) { + // Prune small polyline length + PolylineLengthCleaner lengthCleaner(polyline, minimumPolylineLength); + + // Convert small arcs to lines + ArcLengthCleaner arcCleaner(lengthCleaner.polyline(), minimumArcLength); - m_polylines.push_back(arcCleaner.polyline()); + m_polylines.push_back(arcCleaner.polyline()); + } } -} -Polyline::List&& Cleaner::polylines() -{ - return std::move(m_polylines); -} + Polyline::List&& polylines() { return std::move(m_polylines); } +}; } diff --git a/src/geometry/filter/cleaner.h b/src/geometry/filter/cleaner.h deleted file mode 100644 index cace14b..0000000 --- a/src/geometry/filter/cleaner.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -namespace geometry::filter -{ - -/** @brief Clean polyline via merging of small bulges. - */ -class Cleaner -{ -private: - Polyline::List m_polylines; - -public: - explicit Cleaner(Polyline::List&& polylines, float minimumPolylineLength, float minimumArcLength); - - Polyline::List&& polylines(); -}; - -} diff --git a/src/geometry/filter/removeexactduplicate.cpp b/src/geometry/filter/removeexactduplicate.cpp index 70fb33f..ed031cb 100644 --- a/src/geometry/filter/removeexactduplicate.cpp +++ b/src/geometry/filter/removeexactduplicate.cpp @@ -1,19 +1,18 @@ -#include +module; +#include +#include #include -namespace geometry::filter -{ +export module geometry.filter.removeexactduplicate; + +import common.aggregable; +import geometry.bulge; +import geometry.polyline; +import geometry.utils; -RemoveExactDuplicate::UndirectedPolyline::UndirectedPolyline(const QVector2D& start, const QVector2D& end, - const Polyline& polyline, int index, Direction direction) - : start(start) - , end(end) - , direction(direction) - , index(index) - , polyline(&polyline) +namespace geometry::filter { -} bool operator<(const QVector2D& p1, const QVector2D& p2) { @@ -23,95 +22,127 @@ bool operator<(const QVector2D& p1, const QVector2D& p2) return p1.x() < p2.x(); } -bool RemoveExactDuplicate::UndirectedPolyline::operator<(const UndirectedPolyline& other) const -{ - if (start == other.start) { - return end < other.end; - } - return start < other.start; } -bool RemoveExactDuplicate::UndirectedPolyline::operator==(const UndirectedPolyline& other) const +export namespace geometry::filter { - if (index == other.index) { - return false; - } - - const bool sameTip = (start == other.start && end == other.end); - if (!sameTip) { - return false; - } - - return polyline->equals(*other.polyline, direction == Direction::Opposite); -} -RemoveExactDuplicate::UndirectedPolyline::List -RemoveExactDuplicate::buildPolylinesMap(const Polyline::List& polylines) const +/** @brief Remove exact duplicate of polylines + */ +class RemoveExactDuplicate { - const int nbPolylines = polylines.size(); - RemoveExactDuplicate::UndirectedPolyline::List polylinesMap; - polylinesMap.reserve(nbPolylines * 2); - - for (int i = 0; i < nbPolylines; ++i) { - const Polyline& polyline = polylines[i]; - if (!polyline.isPoint()) { - const QVector2D& start = polyline.start(); - const QVector2D& end = polyline.end(); - - polylinesMap.emplace_back(start, end, polyline, i, UndirectedPolyline::Direction::Normal); - polylinesMap.emplace_back(end, start, polyline, i, UndirectedPolyline::Direction::Opposite); +private: + Polyline::List m_polylines; + + struct UndirectedPolyline : common::Aggregable + { + QVector2D start; + QVector2D end; + + enum class Direction { Normal, Opposite } direction; + int index; + const Polyline* polyline; + + UndirectedPolyline() = default; + explicit UndirectedPolyline(const QVector2D& start, const QVector2D& end, const Polyline& polyline, int index, + Direction direction) + : start(start) + , end(end) + , direction(direction) + , index(index) + , polyline(&polyline) + { } - } - std::sort(polylinesMap.begin(), polylinesMap.end()); + bool operator<(const UndirectedPolyline& other) const + { + if (start == other.start) { + return end < other.end; + } + return start < other.start; + } - return polylinesMap; -} + bool operator==(const UndirectedPolyline& other) const + { + if (index == other.index) { + return false; + } -RemoveExactDuplicate::PolylineMaskList -RemoveExactDuplicate::buildPolylineMaskWithoutDuplicate(const UndirectedPolyline::List polylinesMap, - int nbPolylines) const -{ - const int nbUndirectedPolylines = polylinesMap.size(); - PolylineMaskList mask(nbPolylines, true); + const bool sameTip = (start == other.start && end == other.end); + if (!sameTip) { + return false; + } + + return polyline->equals(*other.polyline, direction == Direction::Opposite); + } + }; - for (int i = 0; i < (nbUndirectedPolylines - 1); ++i) { - const UndirectedPolyline& undirectedPolyline = polylinesMap[i]; - const UndirectedPolyline& nextUndirectedPolyline = polylinesMap[i + 1]; + using PolylineMaskList = std::vector; - if (undirectedPolyline == nextUndirectedPolyline) { - mask[undirectedPolyline.index] = false; + RemoveExactDuplicate::UndirectedPolyline::List buildPolylinesMap(const Polyline::List& polylines) const + { + const int nbPolylines = polylines.size(); + RemoveExactDuplicate::UndirectedPolyline::List polylinesMap; + polylinesMap.reserve(nbPolylines * 2); + + for (int i = 0; i < nbPolylines; ++i) { + const Polyline& polyline = polylines[i]; + if (!polyline.isPoint()) { + const QVector2D& start = polyline.start(); + const QVector2D& end = polyline.end(); + + polylinesMap.emplace_back(start, end, polyline, i, UndirectedPolyline::Direction::Normal); + polylinesMap.emplace_back(end, start, polyline, i, UndirectedPolyline::Direction::Opposite); + } } + + std::sort(polylinesMap.begin(), polylinesMap.end()); + + return polylinesMap; } - return mask; -} + RemoveExactDuplicate::PolylineMaskList + buildPolylineMaskWithoutDuplicate(const UndirectedPolyline::List polylinesMap, int nbPolylines) const + { + const int nbUndirectedPolylines = polylinesMap.size(); + PolylineMaskList mask(nbPolylines, true); -void RemoveExactDuplicate::filterPolylines(Polyline::List&& polylines, PolylineMaskList polylineMask) -{ - int removedPolylines = 0; - for (int i = 0, size = polylines.size(); i < size; ++i) { - if (polylineMask[i]) { - m_polylines.emplace_back(std::move(polylines[i])); - } else { - ++removedPolylines; + for (int i = 0; i < (nbUndirectedPolylines - 1); ++i) { + const UndirectedPolyline& undirectedPolyline = polylinesMap[i]; + const UndirectedPolyline& nextUndirectedPolyline = polylinesMap[i + 1]; + + if (undirectedPolyline == nextUndirectedPolyline) { + mask[undirectedPolyline.index] = false; + } } + + return mask; } - qInfo() << "Removed" << removedPolylines << "duplicated polylines"; -} + void filterPolylines(Polyline::List&& polylines, PolylineMaskList polylineMask) + { + int removedPolylines = 0; + for (int i = 0, size = polylines.size(); i < size; ++i) { + if (polylineMask[i]) { + m_polylines.emplace_back(std::move(polylines[i])); + } else { + ++removedPolylines; + } + } -RemoveExactDuplicate::RemoveExactDuplicate(Polyline::List&& polylines) -{ - UndirectedPolyline::List polylinesMap = buildPolylinesMap(polylines); - const int nbPolylines = polylines.size(); - PolylineMaskList polylineMask = buildPolylineMaskWithoutDuplicate(polylinesMap, nbPolylines); - filterPolylines(std::move(polylines), polylineMask); -} + qInfo() << "Removed" << removedPolylines << "duplicated polylines"; + } -Polyline::List&& RemoveExactDuplicate::polylines() -{ - return std::move(m_polylines); -} +public: + explicit RemoveExactDuplicate(Polyline::List&& polylines) + { + UndirectedPolyline::List polylinesMap = buildPolylinesMap(polylines); + const int nbPolylines = polylines.size(); + PolylineMaskList polylineMask = buildPolylineMaskWithoutDuplicate(polylinesMap, nbPolylines); + filterPolylines(std::move(polylines), polylineMask); + } + + Polyline::List&& polylines() { return std::move(m_polylines); } +}; } diff --git a/src/geometry/filter/removeexactduplicate.h b/src/geometry/filter/removeexactduplicate.h deleted file mode 100644 index d9ff3a7..0000000 --- a/src/geometry/filter/removeexactduplicate.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include - -namespace geometry::filter -{ - -/** @brief Remove exact duplicate of polylines - */ -class RemoveExactDuplicate -{ -private: - Polyline::List m_polylines; - - struct UndirectedPolyline : common::Aggregable - { - QVector2D start; - QVector2D end; - - enum class Direction { Normal, Opposite } direction; - int index; - const Polyline* polyline; - - UndirectedPolyline() = default; - explicit UndirectedPolyline(const QVector2D& start, const QVector2D& end, const Polyline& polyline, int index, - Direction direction); - - bool operator<(const UndirectedPolyline& other) const; - bool operator==(const UndirectedPolyline& other) const; - }; - - using PolylineMaskList = std::vector; - - UndirectedPolyline::List buildPolylinesMap(const Polyline::List& polylines) const; - PolylineMaskList buildPolylineMaskWithoutDuplicate(const UndirectedPolyline::List polylineMap, - int nbPolylines) const; - void filterPolylines(Polyline::List&& polylines, PolylineMaskList polylineMask); - -public: - explicit RemoveExactDuplicate(Polyline::List&& polylines); - - Polyline::List&& polylines(); -}; - -} diff --git a/src/geometry/line.cpp b/src/geometry/line.cpp index 39d3053..e00d741 100644 --- a/src/geometry/line.cpp +++ b/src/geometry/line.cpp @@ -1,32 +1,32 @@ -#include +module; -namespace geometry -{ +#include -Line::Line(const QVector2D& start, const QVector2D& end) - : m_start(start) - , m_end(end) -{ -} +export module geometry.line; -const QVector2D& Line::start() const +export namespace geometry { - return m_start; -} -const QVector2D& Line::end() const +class Line { - return m_end; -} +private: + QVector2D m_start; + QVector2D m_end; -bool Line::lengthNonZero() const -{ - return (m_start != m_end); -} +public: + explicit Line(const QVector2D& start, const QVector2D& end) + : m_start(start) + , m_end(end) + { + } -float Line::length() const -{ - return m_start.distanceToPoint(m_end); -} + const QVector2D& start() const { return m_start; } + + const QVector2D& end() const { return m_end; } + + bool lengthNonZero() const { return (m_start != m_end); } + + float length() const { return m_start.distanceToPoint(m_end); } +}; } diff --git a/src/geometry/line.h b/src/geometry/line.h deleted file mode 100644 index c5f83b4..0000000 --- a/src/geometry/line.h +++ /dev/null @@ -1,24 +0,0 @@ -#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; - - bool lengthNonZero() const; - float length() const; -}; - -} diff --git a/src/geometry/orderoptimizer.cpp b/src/geometry/orderoptimizer.cpp index e95c2b5..2fe69b8 100644 --- a/src/geometry/orderoptimizer.cpp +++ b/src/geometry/orderoptimizer.cpp @@ -1,131 +1,183 @@ -#include +module; -#include // TODO +#include +#include +#include "ortools/sat/cp_model.h" -namespace geometry -{ +export module geometry.orderoptimizer; + +import common.aggregable; -int OrderOptimizer::nodeDistance(const Node& n1, const Node& n2) +namespace ortools = operations_research::sat; + +namespace geometry { - return (n1.position - n2.position).lengthSquared() * 1e4; // TODO mutiplier -} static int homeNodeId(int nbNodes) { return nbNodes; } -OrderOptimizer::ModelBuilder::ModelBuilder(int nbNodes) - : circuit(builder.AddCircuitConstraint()) - , arcsByNodes(nbNodes + 1) - , nbNodes(nbNodes) -{ } -void OrderOptimizer::ModelBuilder::addArc(const Node& n1, const Node& n2) +export namespace geometry { - ortools::BoolVar literal = builder.NewBoolVar(); - circuit.AddArc(n1.id, n2.id, literal); - const int distance = nodeDistance(n1, n2); - pathLength += literal * distance; +class OrderOptimizer +{ +public: + struct Node : common::Aggregable + { + int id; + QVector2D position; - arcsByNodes[n1.id].push_back({ {}, n2.id, literal }); -} + inline bool operator==(const Node& other) const { return id == other.id; } + }; -geometry::OrderOptimizer::Model OrderOptimizer::ModelBuilder::build() -{ - builder.Minimize(pathLength); + using NodesPerGroup = std::vector; - return { builder.Build(), arcsByNodes, nbNodes }; -} +private: + std::vector m_order; -OrderOptimizer::Model OrderOptimizer::buildModel(const NodesPerGroup& nodesPerGroup, int nbNodes) const -{ - ModelBuilder modelBuilder(nbNodes); + static int nodeDistance(const Node& n1, const Node& n2) + { + return (n1.position - n2.position).lengthSquared() * 1e4; // TODO mutiplier + } - const int nbGroup = nodesPerGroup.size(); - const int lastGroupId = nbGroup - 1; + struct ArcTo : common::Aggregable + { + int target; + ortools::BoolVar literal; + }; + + using ArcsByNodes = std::vector; + + struct Model + { + ortools::CpModelProto proto; + ArcsByNodes arcsByNodes; + int nbNodes; + }; + + struct ModelBuilder + { + ortools::CpModelBuilder builder; + ortools::CircuitConstraint circuit; + ArcsByNodes arcsByNodes; + ortools::LinearExpr pathLength; + int nbNodes; + + explicit ModelBuilder(int nbNodes) + : circuit(builder.AddCircuitConstraint()) + , arcsByNodes(nbNodes + 1) + , nbNodes(nbNodes) + { + } - // Group intra connections - for (const Node::List& nodes : nodesPerGroup) { - for (const Node& n1 : nodes) { - for (const Node& n2 : nodes) { - if (n1 == n2) { - continue; - } + void addArc(const Node& n1, const Node& n2) + { + ortools::BoolVar literal = builder.NewBoolVar(); + circuit.AddArc(n1.id, n2.id, literal); - modelBuilder.addArc(n1, n2); - } + const int distance = nodeDistance(n1, n2); + pathLength += literal * distance; + + arcsByNodes[n1.id].push_back({ {}, n2.id, literal }); } - } - // Group inter connections with group of id + 1 - for (int groupId = 0; groupId < lastGroupId; ++groupId) { - const Node::List& curNodes = nodesPerGroup[groupId]; - const Node::List& nextNodes = nodesPerGroup[groupId + 1]; + geometry::OrderOptimizer::Model build() + { + builder.Minimize(pathLength); + + return { builder.Build(), arcsByNodes, nbNodes }; + } + }; + + OrderOptimizer::Model buildModel(const NodesPerGroup& nodesPerGroup, int nbNodes) const + { + ModelBuilder modelBuilder(nbNodes); + + const int nbGroup = nodesPerGroup.size(); + const int lastGroupId = nbGroup - 1; + + // Group intra connections + for (const Node::List& nodes : nodesPerGroup) { + for (const Node& n1 : nodes) { + for (const Node& n2 : nodes) { + if (n1 == n2) { + continue; + } - for (const Node& n1 : curNodes) { - for (const Node& n2 : nextNodes) { - modelBuilder.addArc(n1, n2); + modelBuilder.addArc(n1, n2); + } } } - } - const Node home { {}, homeNodeId(nbNodes), { 0.0f, 0.0f } }; + // Group inter connections with group of id + 1 + for (int groupId = 0; groupId < lastGroupId; ++groupId) { + const Node::List& curNodes = nodesPerGroup[groupId]; + const Node::List& nextNodes = nodesPerGroup[groupId + 1]; - // Home to first group nodes - const Node::List& firstGroupsNodes = nodesPerGroup.front(); - for (const Node& node : firstGroupsNodes) { - modelBuilder.addArc(home, node); - } + for (const Node& n1 : curNodes) { + for (const Node& n2 : nextNodes) { + modelBuilder.addArc(n1, n2); + } + } + } - // Last group nodes to home - const Node::List& lastGroupsNodes = nodesPerGroup.back(); - for (const Node& node : lastGroupsNodes) { - modelBuilder.addArc(node, home); - } + const Node home { {}, homeNodeId(nbNodes), { 0.0f, 0.0f } }; - return modelBuilder.build(); -} + // Home to first group nodes + const Node::List& firstGroupsNodes = nodesPerGroup.front(); + for (const Node& node : firstGroupsNodes) { + modelBuilder.addArc(home, node); + } -std::vector OrderOptimizer::solveAndExtractOrder(const Model& model) -{ - const ortools::CpSolverResponse response = ortools::Solve(model.proto); + // Last group nodes to home + const Node::List& lastGroupsNodes = nodesPerGroup.back(); + for (const Node& node : lastGroupsNodes) { + modelBuilder.addArc(node, home); + } - if (response.status() != ortools::CpSolverStatus::OPTIMAL - && response.status() != ortools::CpSolverStatus::FEASIBLE) { - return {}; + return modelBuilder.build(); } - std::vector order; - - const ArcsByNodes arcsByNodes = model.arcsByNodes; - // Last node id is home - const int homeId = homeNodeId(model.nbNodes); - int curNodeId = homeId; - while (order.size() < homeId) { - for (const ArcTo& arc : arcsByNodes[curNodeId]) { - if (ortools::SolutionIntegerValue(response, arc.literal)) { - curNodeId = arc.target; - order.push_back(curNodeId); - continue; + std::vector solveAndExtractOrder(const Model& model) + { + const ortools::CpSolverResponse response = ortools::Solve(model.proto); + + if (response.status() != ortools::CpSolverStatus::OPTIMAL + && response.status() != ortools::CpSolverStatus::FEASIBLE) { + return {}; + } + + std::vector order; + + const ArcsByNodes arcsByNodes = model.arcsByNodes; + // Last node id is home + const int homeId = homeNodeId(model.nbNodes); + int curNodeId = homeId; + while (order.size() < homeId) { + for (const ArcTo& arc : arcsByNodes[curNodeId]) { + if (ortools::SolutionIntegerValue(response, arc.literal)) { + curNodeId = arc.target; + order.push_back(curNodeId); + continue; + } } } - } - return order; -} + return order; + } -OrderOptimizer::OrderOptimizer(const NodesPerGroup& nodesPerGroup, int nbNodes) -{ - const Model model = buildModel(nodesPerGroup, nbNodes); - m_order = solveAndExtractOrder(model); -} +public: + explicit OrderOptimizer(const NodesPerGroup& nodesPerGroup, int nbNodes) + { + const Model model = buildModel(nodesPerGroup, nbNodes); + m_order = solveAndExtractOrder(model); + } -const std::vector& OrderOptimizer::order() const -{ - return m_order; -} + const std::vector& order() const { return m_order; } +}; } diff --git a/src/geometry/orderoptimizer.h b/src/geometry/orderoptimizer.h deleted file mode 100644 index fe7ef76..0000000 --- a/src/geometry/orderoptimizer.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include -#include - -#include "ortools/sat/cp_model.h" - -namespace ortools = operations_research::sat; - -namespace geometry -{ - -class OrderOptimizer -{ -public: - struct Node : common::Aggregable - { - int id; - QVector2D position; - - inline bool operator==(const Node& other) const { return id == other.id; } - }; - - using NodesPerGroup = std::vector; - -private: - std::vector m_order; - - static int nodeDistance(const Node& n1, const Node& n2); - - struct ArcTo : common::Aggregable - { - int target; - ortools::BoolVar literal; - }; - - using ArcsByNodes = std::vector; - - struct Model - { - ortools::CpModelProto proto; - ArcsByNodes arcsByNodes; - int nbNodes; - }; - - struct ModelBuilder - { - ortools::CpModelBuilder builder; - ortools::CircuitConstraint circuit; - ArcsByNodes arcsByNodes; - ortools::LinearExpr pathLength; - int nbNodes; - - explicit ModelBuilder(int nbNodes); - - void addArc(const Node& n1, const Node& n2); - Model build(); - }; - - Model buildModel(const NodesPerGroup& nodesPerGroup, int nbNodes) const; - std::vector solveAndExtractOrder(const Model& model); - -public: - explicit OrderOptimizer(const NodesPerGroup& nodesPerGroup, int nbNodes); - - const std::vector& order() const; -}; - -} diff --git a/src/geometry/pocketer.cpp b/src/geometry/pocketer.cpp index 059079e..91ac6d1 100644 --- a/src/geometry/pocketer.cpp +++ b/src/geometry/pocketer.cpp @@ -1,151 +1,172 @@ -#include +module; + +#include +#include +#include +#include + +export module geometry.pocketer; + +import geometry.polyline; +import geometry.utils; namespace geometry { -bool Pocketer::isCapable(const Polyline& polyline) +int maxOffsetStep(const cavc::OffsetLoopSet& loopSet, float margin) { - return !polyline.isPoint() && polyline.isClosed(); -} + const cavc::Polyline& border = loopSet.ccwLoops.front().polyline; -bool Pocketer::isBorderAndInslandsCapable() const -{ - return (isCapable(m_border) && std::all_of(m_islands.begin(), m_islands.end(), [](const Polyline* polyline) { - return isCapable(*polyline); - })); -} + const cavc::AABB boundingBox = cavc::getExtents(border); + const float maxSize = std::max(boundingBox.xMax - boundingBox.xMin, boundingBox.yMax - boundingBox.yMin); -cavc::OffsetLoop Pocketer::polylineToLoop(const Polyline& polyline, Orientation expectedOrientation) -{ - const cavc::Polyline loop = polyline.toCavc(expectedOrientation); - return { 0, loop, cavc::createApproxSpatialIndex(loop) }; + return maxSize / margin / 2.0f; } -cavc::OffsetLoop Pocketer::polylineToLoop(const Polyline& polyline, bool inverse) -{ - const cavc::Polyline loop = inverse ? polyline.inverse().toCavc() : polyline.toCavc(); - return { 0, loop, cavc::createApproxSpatialIndex(loop) }; } -Polyline Pocketer::loopToPolyline(const cavc::OffsetLoop& loop) +export namespace geometry { - return Polyline(loop.polyline); -} -Polyline::List Pocketer::loopsToPolylines(const std::vector>& loops) +class Pocketer { - Polyline::List polylines(loops.size()); - std::transform(loops.begin(), loops.end(), polylines.begin(), loopToPolyline); +private: + const Polyline& m_border; + const Orientation m_borderOrientation; + const Polyline::ListCPtr& m_islands; + const float m_margin; + const float m_minimumPolylineLength; + cavc::ParallelOffsetIslands m_offseter; - return polylines; -} + Polyline::List m_polylines; + Polyline::List m_ccwPolylines; + Polyline::List m_cwPolylines; -bool Pocketer::canContinueOffsetting(const cavc::OffsetLoopSet& loopSet) -{ - return !loopSet.ccwLoops.empty() || !loopSet.cwLoops.empty(); -} + static bool isCapable(const Polyline& polyline) { return !polyline.isPoint() && polyline.isClosed(); } -void Pocketer::pruneSingularities(std::vector>& loops) const -{ - std::for_each(loops.begin(), loops.end(), - [minimumPolylineLength = (double)m_minimumPolylineLength](cavc::OffsetLoop& loop) { - loop.polyline = cavc::pruneSingularities(loop.polyline, minimumPolylineLength); - loop.spatialIndex = cavc::createApproxSpatialIndex(loop.polyline); - }); -} + bool isBorderAndInslandsCapable() const + { + return (isCapable(m_border) && std::all_of(m_islands.begin(), m_islands.end(), [](const Polyline* polyline) { + return isCapable(*polyline); + })); + } -cavc::OffsetLoopSet Pocketer::baseLoopSet() const -{ - cavc::OffsetLoopSet loopSet; - const bool inverseBorder = m_borderOrientation != Orientation::CCW; - loopSet.ccwLoops.push_back(polylineToLoop(m_border, inverseBorder)); + static cavc::OffsetLoop polylineToLoop(const Polyline& polyline, Orientation expectedOrientation) + { + const cavc::Polyline loop = polyline.toCavc(expectedOrientation); + return { 0, loop, cavc::createApproxSpatialIndex(loop) }; + } - std::transform(m_islands.begin(), m_islands.end(), std::back_inserter(loopSet.cwLoops), [](const Polyline* island) { - return polylineToLoop(*island, Orientation::CW); - }); + static cavc::OffsetLoop polylineToLoop(const Polyline& polyline, bool inverse) + { + const cavc::Polyline loop = inverse ? polyline.inverse().toCavc() : polyline.toCavc(); + return { 0, loop, cavc::createApproxSpatialIndex(loop) }; + } - return loopSet; -} + static Polyline loopToPolyline(const cavc::OffsetLoop& loop) { return Polyline(loop.polyline); } -int maxOffsetStep(const cavc::OffsetLoopSet& loopSet, float margin) -{ - const cavc::Polyline& border = loopSet.ccwLoops.front().polyline; + static Polyline::List loopsToPolylines(const std::vector>& loops) + { + Polyline::List polylines(loops.size()); + std::transform(loops.begin(), loops.end(), polylines.begin(), loopToPolyline); - const cavc::AABB boundingBox = cavc::getExtents(border); - const float maxSize = std::max(boundingBox.xMax - boundingBox.xMin, boundingBox.yMax - boundingBox.yMin); + return polylines; + } - return maxSize / margin / 2.0f; -} + static bool canContinueOffsetting(const cavc::OffsetLoopSet& loopSet) + { + return !loopSet.ccwLoops.empty() || !loopSet.cwLoops.empty(); + } -cavc::OffsetLoopSet Pocketer::computeNextLoopSet(const cavc::OffsetLoopSet& loopSet) -{ - cavc::OffsetLoopSet newLoopSet = m_offseter.compute(loopSet, m_margin); + void pruneSingularities(std::vector>& loops) const + { + std::for_each(loops.begin(), loops.end(), + [minimumPolylineLength = (double)m_minimumPolylineLength](cavc::OffsetLoop& loop) { + loop.polyline = cavc::pruneSingularities(loop.polyline, minimumPolylineLength); + loop.spatialIndex = cavc::createApproxSpatialIndex(loop.polyline); + }); + } - pruneSingularities(newLoopSet.cwLoops); - pruneSingularities(newLoopSet.ccwLoops); + cavc::OffsetLoopSet baseLoopSet() const + { + cavc::OffsetLoopSet loopSet; + const bool inverseBorder = m_borderOrientation != Orientation::CCW; + loopSet.ccwLoops.push_back(polylineToLoop(m_border, inverseBorder)); - return newLoopSet; -} + std::transform(m_islands.begin(), m_islands.end(), std::back_inserter(loopSet.cwLoops), + [](const Polyline* island) { + return polylineToLoop(*island, Orientation::CW); + }); -void Pocketer::appendLoopSetPolylines(const cavc::OffsetLoopSet& loopSet) -{ - const Polyline::List currentStepOffsettedCcwPolylines = loopsToPolylines(loopSet.ccwLoops); - const Polyline::List currentStepOffsettedCwPolylines = loopsToPolylines(loopSet.cwLoops); + return loopSet; + } - m_ccwPolylines.insert(m_ccwPolylines.end(), currentStepOffsettedCcwPolylines.begin(), - currentStepOffsettedCcwPolylines.end()); - m_cwPolylines.insert(m_cwPolylines.end(), currentStepOffsettedCwPolylines.begin(), - currentStepOffsettedCwPolylines.end()); -} + cavc::OffsetLoopSet computeNextLoopSet(const cavc::OffsetLoopSet& loopSet) + { + cavc::OffsetLoopSet newLoopSet = m_offseter.compute(loopSet, m_margin); -void Pocketer::appendPolylines(const Polyline::List& polylines, bool inverse) -{ - if (inverse) { - std::transform(polylines.begin(), polylines.end(), std::back_inserter(m_polylines), - [](const Polyline& polyline) { - return polyline.inverse(); - }); - } else { - m_polylines.insert(m_polylines.end(), std::make_move_iterator(polylines.begin()), - std::make_move_iterator(polylines.end())); + pruneSingularities(newLoopSet.cwLoops); + pruneSingularities(newLoopSet.ccwLoops); + + return newLoopSet; } -} -void Pocketer::ensurePolylinesOrientationSameAsBorder() -{ - const bool inverseCcw = (m_borderOrientation != Orientation::CCW); - appendPolylines(m_ccwPolylines, inverseCcw); - appendPolylines(m_cwPolylines, !inverseCcw); -} + void appendLoopSetPolylines(const cavc::OffsetLoopSet& loopSet) + { + const Polyline::List currentStepOffsettedCcwPolylines = loopsToPolylines(loopSet.ccwLoops); + const Polyline::List currentStepOffsettedCwPolylines = loopsToPolylines(loopSet.cwLoops); -Pocketer::Pocketer(const Polyline& border, const Polyline::ListCPtr& islands, float margin, float minimumPolylineLength) - : m_border(border) - , m_borderOrientation(m_border.orientation()) - , m_islands(islands) - , m_margin(margin) - , m_minimumPolylineLength(minimumPolylineLength) -{ - if (isBorderAndInslandsCapable()) { - cavc::OffsetLoopSet loopSet = baseLoopSet(); - const int maxIteration = maxOffsetStep(loopSet, margin); + m_ccwPolylines.insert(m_ccwPolylines.end(), currentStepOffsettedCcwPolylines.begin(), + currentStepOffsettedCcwPolylines.end()); + m_cwPolylines.insert(m_cwPolylines.end(), currentStepOffsettedCwPolylines.begin(), + currentStepOffsettedCwPolylines.end()); + } - for (int i = 0; i < maxIteration && canContinueOffsetting(loopSet); ++i) { - loopSet = computeNextLoopSet(loopSet); - appendLoopSetPolylines(loopSet); + void appendPolylines(const Polyline::List& polylines, bool inverse) + { + if (inverse) { + std::transform(polylines.begin(), polylines.end(), std::back_inserter(m_polylines), + [](const Polyline& polyline) { + return polyline.inverse(); + }); + } else { + m_polylines.insert(m_polylines.end(), std::make_move_iterator(polylines.begin()), + std::make_move_iterator(polylines.end())); } + } - ensurePolylinesOrientationSameAsBorder(); + void ensurePolylinesOrientationSameAsBorder() + { + const bool inverseCcw = (m_borderOrientation != Orientation::CCW); + appendPolylines(m_ccwPolylines, inverseCcw); + appendPolylines(m_cwPolylines, !inverseCcw); } -} -Orientation Pocketer::borderOrientation() const -{ - return m_borderOrientation; -} +public: + explicit Pocketer(const Polyline& border, const Polyline::ListCPtr& islands, float margin, + float minimumPolylineLength) + : m_border(border) + , m_borderOrientation(m_border.orientation()) + , m_islands(islands) + , m_margin(margin) + , m_minimumPolylineLength(minimumPolylineLength) + { + if (isBorderAndInslandsCapable()) { + cavc::OffsetLoopSet loopSet = baseLoopSet(); + const int maxIteration = maxOffsetStep(loopSet, margin); + + for (int i = 0; i < maxIteration && canContinueOffsetting(loopSet); ++i) { + loopSet = computeNextLoopSet(loopSet); + appendLoopSetPolylines(loopSet); + } + + ensurePolylinesOrientationSameAsBorder(); + } + } -Polyline::List&& Pocketer::polylines() -{ - return std::move(m_polylines); -} + Orientation borderOrientation() const { return m_borderOrientation; } + + Polyline::List&& polylines() { return std::move(m_polylines); } +}; } diff --git a/src/geometry/pocketer.h b/src/geometry/pocketer.h deleted file mode 100644 index f6bfee8..0000000 --- a/src/geometry/pocketer.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include - -#include - -namespace geometry -{ - -class Pocketer -{ -private: - const Polyline& m_border; - const Orientation m_borderOrientation; - const Polyline::ListCPtr& m_islands; - const float m_margin; - const float m_minimumPolylineLength; - cavc::ParallelOffsetIslands m_offseter; - - Polyline::List m_polylines; - Polyline::List m_ccwPolylines; - Polyline::List m_cwPolylines; - - static bool isCapable(const Polyline& polyline); - bool isBorderAndInslandsCapable() const; - static cavc::OffsetLoop polylineToLoop(const Polyline& polyline, Orientation expectedOrientation); - static cavc::OffsetLoop polylineToLoop(const Polyline& polyline, bool inverse); - static Polyline loopToPolyline(const cavc::OffsetLoop& loop); - static Polyline::List loopsToPolylines(const std::vector>& loops); - static bool canContinueOffsetting(const cavc::OffsetLoopSet& loopSet); - void pruneSingularities(std::vector>& loops) const; - cavc::OffsetLoopSet baseLoopSet() const; - - cavc::OffsetLoopSet computeNextLoopSet(const cavc::OffsetLoopSet& loopSet); - void appendLoopSetPolylines(const cavc::OffsetLoopSet& loopSet); - void appendPolylines(const Polyline::List& polylines, bool inverse); - void ensurePolylinesOrientationSameAsBorder(); - -public: - explicit Pocketer(const Polyline& border, const Polyline::ListCPtr& islands, float margin, - float minimumPolylineLength); - - Orientation borderOrientation() const; - Polyline::List&& polylines(); -}; - -} diff --git a/src/geometry/polyline.cpp b/src/geometry/polyline.cpp index 557aecd..12d2150 100644 --- a/src/geometry/polyline.cpp +++ b/src/geometry/polyline.cpp @@ -1,209 +1,258 @@ -#include -#include -#include +module; + +#include +#include +#include +#include +#include +#include +#include +#include + +export module geometry.polyline; + +import common.aggregable; +import geometry.bulge; +import geometry.rect; +import geometry.utils; namespace geometry { -Polyline::Polyline(const cavc::Polyline& polyline) +inline float winding(const QVector2D& start, const QVector2D& end) { - m_bulges.resize(polyline.isClosed() ? polyline.size() : polyline.size() - 1); - - polyline.visitSegIndices([&polyline, this](size_t i, size_t j) { - m_bulges[i] = Bulge(polyline[i], polyline[j]); - return true; - }); + return (end.x() - start.x()) * (end.y() + start.y()); } -cavc::Polyline Polyline::toCavc() const +inline float winding(const Bulge& bulge) { - cavc::Polyline ccPolyline; + return winding(bulge.start(), bulge.end()); +} - // Convert to CAVC polyline - forEachBulge([&ccPolyline](const Bulge& bulge) { - const QVector2D& start = bulge.start(); - ccPolyline.addVertex(start.x(), start.y(), bulge.tangent()); - }); +} - const bool closed = isClosed(); - if (!closed) { - const QVector2D& endV = end(); - ccPolyline.addVertex(endV.x(), endV.y(), 0.0f); +export namespace geometry +{ + +class Polyline : public common::Aggregable +{ + friend serializer::Access; + +private: + Bulge::List m_bulges; + +public: + /* cavc interop, consumed by geometry.pocketer. Public rather than a friend declaration: + * befriending Pocketer here would declare it in this module and clash with its definition + * in geometry.pocketer. + */ + explicit Polyline(const cavc::Polyline& polyline) + { + m_bulges.resize(polyline.isClosed() ? polyline.size() : polyline.size() - 1); + + polyline.visitSegIndices([&polyline, this](size_t i, size_t j) { + m_bulges[i] = Bulge(polyline[i], polyline[j]); + return true; + }); } - ccPolyline.isClosed() = closed; + cavc::Polyline toCavc() const + { + cavc::Polyline ccPolyline; - return ccPolyline; -} + // Convert to CAVC polyline + forEachBulge([&ccPolyline](const Bulge& bulge) { + const QVector2D& start = bulge.start(); + ccPolyline.addVertex(start.x(), start.y(), bulge.tangent()); + }); -cavc::Polyline Polyline::toCavc(Orientation expectedOrientation) const -{ - if (orientation() != expectedOrientation) { - return inverse().toCavc(); - } - return toCavc(); -} + const bool closed = isClosed(); + if (!closed) { + const QVector2D& endV = end(); + ccPolyline.addVertex(endV.x(), endV.y(), 0.0f); + } -Polyline::Polyline(Bulge::List&& bulges) - : m_bulges(bulges) -{ - assert(!m_bulges.empty()); -} + ccPolyline.isClosed() = closed; -const QVector2D& Polyline::start() const -{ - assert(!m_bulges.empty()); + return ccPolyline; + } - return m_bulges.front().start(); -} + cavc::Polyline toCavc(Orientation expectedOrientation) const + { + if (orientation() != expectedOrientation) { + return inverse().toCavc(); + } + return toCavc(); + } -QVector2D& Polyline::start() -{ - assert(!m_bulges.empty()); + cavc::Polyline toCavc(bool inverse) const; - return m_bulges.front().start(); -} + explicit Polyline() = default; + explicit Polyline(Bulge::List&& bulges) + : m_bulges(bulges) + { + assert(!m_bulges.empty()); + } -const QVector2D& Polyline::end() const -{ - assert(!m_bulges.empty()); + const QVector2D& start() const + { + assert(!m_bulges.empty()); - return m_bulges.back().end(); -} + return m_bulges.front().start(); + } -QVector2D& Polyline::end() -{ - assert(!m_bulges.empty()); + QVector2D& start() + { + assert(!m_bulges.empty()); - return m_bulges.back().end(); -} + return m_bulges.front().start(); + } -bool Polyline::isClosed() const -{ - assert(!m_bulges.empty()); + const QVector2D& end() const + { + assert(!m_bulges.empty()); - return (start() == end()); -} + return m_bulges.back().end(); + } -bool Polyline::isPoint() const -{ - assert(!m_bulges.empty()); + QVector2D& end() + { + assert(!m_bulges.empty()); - return isClosed() && (m_bulges.size() == 1); -} + return m_bulges.back().end(); + } -bool Polyline::isLine() const -{ - assert(!m_bulges.empty()); + bool isClosed() const + { + assert(!m_bulges.empty()); - return (m_bulges.size() == 1) && m_bulges.front().isLine(); -} + return (start() == end()); + } -float Polyline::length() const -{ - assert(!m_bulges.empty()); + bool isPoint() const + { + assert(!m_bulges.empty()); - return std::accumulate(m_bulges.begin(), m_bulges.end(), 0.0f, [](float sum, const Bulge& bulge) { - return sum + bulge.length(); - }); -} + return isClosed() && (m_bulges.size() == 1); + } -inline float winding(const QVector2D& start, const QVector2D& end) -{ - return (end.x() - start.x()) * (end.y() + start.y()); -} + bool isLine() const + { + assert(!m_bulges.empty()); -inline float winding(const Bulge& bulge) -{ - return winding(bulge.start(), bulge.end()); -} + return (m_bulges.size() == 1) && m_bulges.front().isLine(); + } -Orientation Polyline::orientation() const -{ - assert(!m_bulges.empty() && isClosed()); + float length() const + { + assert(!m_bulges.empty()); - const float windingSum = std::accumulate(m_bulges.begin(), m_bulges.end(), 0.0f, [](float sum, const Bulge& bulge) { - return sum + winding(bulge); - }); + return std::accumulate(m_bulges.begin(), m_bulges.end(), 0.0f, [](float sum, const Bulge& bulge) { + return sum + bulge.length(); + }); + } - return (windingSum > 0) ? Orientation::CW : Orientation::CCW; -} + Orientation orientation() const + { + assert(!m_bulges.empty() && isClosed()); -Rect Polyline::boundingRect() const -{ - Bulge::List::const_iterator it = m_bulges.begin(); - const Rect firstBoundingRest = (it++)->boundingRect(); + const float windingSum + = std::accumulate(m_bulges.begin(), m_bulges.end(), 0.0f, [](float sum, const Bulge& bulge) { + return sum + winding(bulge); + }); - return std::transform_reduce(it, m_bulges.end(), firstBoundingRest, std::bit_or(), [](const Bulge& bulge) { - return bulge.boundingRect(); - }); -} + return (windingSum > 0) ? Orientation::CW : Orientation::CCW; + } -Polyline& Polyline::invert() -{ - for (Bulge& bulge : m_bulges) { - bulge.invert(); + Rect boundingRect() const + { + Bulge::List::const_iterator it = m_bulges.begin(); + const Rect firstBoundingRest = (it++)->boundingRect(); + + return std::transform_reduce(it, m_bulges.end(), firstBoundingRest, std::bit_or(), [](const Bulge& bulge) { + return bulge.boundingRect(); + }); } - std::reverse(m_bulges.begin(), m_bulges.end()); + Polyline& invert() + { + for (Bulge& bulge : m_bulges) { + bulge.invert(); + } - return *this; -} + std::reverse(m_bulges.begin(), m_bulges.end()); -Polyline Polyline::inverse() const -{ - Polyline inversed = *this; - return inversed.invert(); -} + return *this; + } -Polyline& Polyline::operator+=(const Polyline& other) -{ - m_bulges.insert(m_bulges.end(), other.m_bulges.begin(), other.m_bulges.end()); + Polyline inverse() const + { + Polyline inversed = *this; + return inversed.invert(); + } - return *this; -} + Polyline& operator+=(const Polyline& other) + { + m_bulges.insert(m_bulges.end(), other.m_bulges.begin(), other.m_bulges.end()); -Polyline::List Polyline::offsetted(float margin) const -{ - if (isPoint()) { - return { *this }; + return *this; } - // Offset CAVC polyline - std::vector> offsettedCcPolylines = cavc::parallelOffset(toCavc(), (double)margin); + template + void forEachBulge(Functor&& functor) const + { + for (const Bulge& bulge : m_bulges) { + functor(bulge); + } + } - // Convert back to polylines - Polyline::List offsettedPolylines(offsettedCcPolylines.size()); - std::transform(offsettedCcPolylines.begin(), offsettedCcPolylines.end(), offsettedPolylines.begin(), - [](const cavc::Polyline& polyline) { - return Polyline(polyline); - }); + template + void transformBulge(Functor&& functor) + { + for (Bulge& bulge : m_bulges) { + functor(bulge); + } + } - return offsettedPolylines; -} + Polyline::List offsetted(float margin) const + { + if (isPoint()) { + return { *this }; + } -void Polyline::transform(const QTransform& matrix) -{ - transformBulge([&matrix](Bulge& bulge) { - bulge.transform(matrix); - }); -} + // Offset CAVC polyline + std::vector> offsettedCcPolylines = cavc::parallelOffset(toCavc(), (double)margin); -bool Polyline::operator==(const Polyline& other) const -{ - return m_bulges == other.m_bulges; -} + // Convert back to polylines + Polyline::List offsettedPolylines(offsettedCcPolylines.size()); + std::transform(offsettedCcPolylines.begin(), offsettedCcPolylines.end(), offsettedPolylines.begin(), + [](const cavc::Polyline& polyline) { + return Polyline(polyline); + }); -bool Polyline::equals(const Polyline& other, bool inverse) const -{ - if (inverse) { - return std::equal(m_bulges.begin(), m_bulges.end(), other.m_bulges.rbegin(), other.m_bulges.rend(), - [](const Bulge& b1, const Bulge& b2) { - return b1.equalsInversed(b2); - }); + return offsettedPolylines; } - return *this == other; -} + void transform(const QTransform& matrix) + { + transformBulge([&matrix](Bulge& bulge) { + bulge.transform(matrix); + }); + } + + bool operator==(const Polyline& other) const { return m_bulges == other.m_bulges; } + + bool equals(const Polyline& other, bool inverse) const + { + if (inverse) { + return std::equal(m_bulges.begin(), m_bulges.end(), other.m_bulges.rbegin(), other.m_bulges.rend(), + [](const Bulge& b1, const Bulge& b2) { + return b1.equalsInversed(b2); + }); + } + + return *this == other; + } +}; } diff --git a/src/geometry/polyline.h b/src/geometry/polyline.h deleted file mode 100644 index 0890ffe..0000000 --- a/src/geometry/polyline.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include - -#include - -namespace geometry -{ - -class Polyline : public common::Aggregable -{ - friend serializer::Access; - friend class Pocketer; - -private: - Bulge::List m_bulges; - - explicit Polyline(const cavc::Polyline& polyline); - cavc::Polyline toCavc() const; - cavc::Polyline toCavc(Orientation expectedOrientation) const; - cavc::Polyline toCavc(bool inverse) const; - -public: - explicit Polyline() = default; - explicit Polyline(Bulge::List&& bulges); - - const QVector2D& start() const; - QVector2D& start(); - const QVector2D& end() const; - QVector2D& end(); - - bool isClosed() const; - bool isPoint() const; - bool isLine() const; - - float length() const; - - Orientation orientation() const; - - Rect boundingRect() const; - - Polyline& invert(); - Polyline inverse() const; - - Polyline& operator+=(const Polyline& other); - - template - void forEachBulge(Functor&& functor) const - { - for (const Bulge& bulge : m_bulges) { - functor(bulge); - } - } - - template - void transformBulge(Functor&& functor) - { - for (Bulge& bulge : m_bulges) { - functor(bulge); - } - } - - Polyline::List offsetted(float margin) const; - - void transform(const QTransform& matrix); - - bool operator==(const Polyline& other) const; - - bool equals(const Polyline& other, bool inverse) const; -}; - -} diff --git a/src/geometry/quadraticspline.cpp b/src/geometry/quadraticspline.cpp index c94e5af..08e2159 100644 --- a/src/geometry/quadraticspline.cpp +++ b/src/geometry/quadraticspline.cpp @@ -1,88 +1,107 @@ -#include +module; + +#include +#include + +export module geometry.quadraticspline; + +import geometry.bezier; +import geometry.spline; +import geometry.utils; namespace geometry { -Point2DList QuadraticSpline::convertClosedToQuadraticBezierPoints() const +static Bezier quadraticBezierPointToCubicBezier(const QVector2D& q0, const QVector2D& q1, const QVector2D& q2) { - const Point2DList& allControlPoints = controlPoints(); - const int nbcontrol = allControlPoints.size(); - - const int size = nbcontrol * 2 + 1; - Point2DList bezierPoints(size); - - // Copy 1 ... -2 -1 to 1 3 .. -4 -2 - for (int i = 1, dst = 1; i <= nbcontrol; ++i, dst += 2) { - const int src = i % nbcontrol; - bezierPoints[dst] = allControlPoints[src]; - } + const QVector2D c1 = q0 + 2.0f * (q1 - q0) / 3.0f; + const QVector2D c2 = q2 + 2.0f * (q1 - q2) / 3.0f; - for (int i = 0; i <= nbcontrol; ++i) { - const int src = i % nbcontrol; - const int dst = i * 2; - const int srcnext = (src + 1) % nbcontrol; - bezierPoints[dst] = (allControlPoints[src] + allControlPoints[srcnext]) / 2.0f; - } + return Bezier(q0, c1, c2, q2); +} - return bezierPoints; } -Point2DList QuadraticSpline::convertOpenedToQuadraticBezierPoints() const +export namespace geometry { - const Point2DList& allControlPoints = controlPoints(); - const int nbcontrol = allControlPoints.size(); - - const int size = (nbcontrol - 1) * 2 - 1; - Point2DList bezierPoints(size); - for (int src = 1, dst = 1; src < (nbcontrol - 1); ++src, dst += 2) { - bezierPoints[dst] = allControlPoints[src]; - } - - for (int src = 1; src < (nbcontrol - 2); ++src) { - const int dst = src * 2; - const int srcnext = src + 1; - bezierPoints[dst] = (allControlPoints[src] + allControlPoints[srcnext]) / 2.0f; +class QuadraticSpline : public Spline +{ +private: + Point2DList convertClosedToQuadraticBezierPoints() const + { + const Point2DList& allControlPoints = controlPoints(); + const int nbcontrol = allControlPoints.size(); + + const int size = nbcontrol * 2 + 1; + Point2DList bezierPoints(size); + + // Copy 1 ... -2 -1 to 1 3 .. -4 -2 + for (int i = 1, dst = 1; i <= nbcontrol; ++i, dst += 2) { + const int src = i % nbcontrol; + bezierPoints[dst] = allControlPoints[src]; + } + + for (int i = 0; i <= nbcontrol; ++i) { + const int src = i % nbcontrol; + const int dst = i * 2; + const int srcnext = (src + 1) % nbcontrol; + bezierPoints[dst] = (allControlPoints[src] + allControlPoints[srcnext]) / 2.0f; + } + + return bezierPoints; } - bezierPoints.front() = allControlPoints.front(); - bezierPoints.back() = allControlPoints.back(); + Point2DList convertOpenedToQuadraticBezierPoints() const + { + const Point2DList& allControlPoints = controlPoints(); + const int nbcontrol = allControlPoints.size(); - return bezierPoints; -} + const int size = (nbcontrol - 1) * 2 - 1; + Point2DList bezierPoints(size); -static Bezier quadraticBezierPointToCubicBezier(const QVector2D& q0, const QVector2D& q1, const QVector2D& q2) -{ - const QVector2D c1 = q0 + 2.0f * (q1 - q0) / 3.0f; - const QVector2D c2 = q2 + 2.0f * (q1 - q2) / 3.0f; + for (int src = 1, dst = 1; src < (nbcontrol - 1); ++src, dst += 2) { + bezierPoints[dst] = allControlPoints[src]; + } - return Bezier(q0, c1, c2, q2); -} + for (int src = 1; src < (nbcontrol - 2); ++src) { + const int dst = src * 2; + const int srcnext = src + 1; + bezierPoints[dst] = (allControlPoints[src] + allControlPoints[srcnext]) / 2.0f; + } -Bezier::List QuadraticSpline::pointsToBeziers(const Point2DList& bezierPoints) const -{ - const int size = bezierPoints.size(); - Bezier::List beziers(size / 2); + bezierPoints.front() = allControlPoints.front(); + bezierPoints.back() = allControlPoints.back(); - for (int src = 0, dst = 0; src < (size - 1); src += 2, ++dst) { - beziers[dst] - = quadraticBezierPointToCubicBezier(bezierPoints[src], bezierPoints[src + 1], bezierPoints[src + 2]); + return bezierPoints; } - return beziers; -} + Bezier::List pointsToBeziers(const Point2DList& bezierPoints) const + { + const int size = bezierPoints.size(); + Bezier::List beziers(size / 2); -QuadraticSpline::QuadraticSpline(Point2DList&& points, bool closed) - : Spline(std::move(points), closed) -{ -} + for (int src = 0, dst = 0; src < (size - 1); src += 2, ++dst) { + beziers[dst] + = quadraticBezierPointToCubicBezier(bezierPoints[src], bezierPoints[src + 1], bezierPoints[src + 2]); + } -Bezier::List QuadraticSpline::toBeziers() const -{ - const Point2DList bezierPoints - = closed() ? convertClosedToQuadraticBezierPoints() : convertOpenedToQuadraticBezierPoints(); + return beziers; + } - return pointsToBeziers(bezierPoints); -} +public: + explicit QuadraticSpline(Point2DList&& points, bool closed) + : Spline(std::move(points), closed) + { + } + + Bezier::List toBeziers() const + { + const Point2DList bezierPoints + = closed() ? convertClosedToQuadraticBezierPoints() : convertOpenedToQuadraticBezierPoints(); + + return pointsToBeziers(bezierPoints); + } +}; } diff --git a/src/geometry/quadraticspline.h b/src/geometry/quadraticspline.h deleted file mode 100644 index 2c97c3e..0000000 --- a/src/geometry/quadraticspline.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -namespace geometry -{ - -class QuadraticSpline : public Spline -{ -private: - Point2DList convertClosedToQuadraticBezierPoints() const; - Point2DList convertOpenedToQuadraticBezierPoints() const; - - Bezier::List pointsToBeziers(const Point2DList& bezierPoints) const; - -public: - explicit QuadraticSpline(Point2DList&& points, bool closed); - - Bezier::List toBeziers() const; -}; - -} diff --git a/src/geometry/rect.cpp b/src/geometry/rect.cpp index fbb0ba4..dae9b39 100644 --- a/src/geometry/rect.cpp +++ b/src/geometry/rect.cpp @@ -1,4 +1,10 @@ -#include +module; + +#include +#include +#include + +export module geometry.rect; namespace geometry { @@ -13,73 +19,68 @@ QVector2D maxVect(const QVector2D& v1, const QVector2D& v2) return { std::max(v1.x(), v2.x()), std::max(v1.y(), v2.y()) }; } -Rect::Rect(const QVector2D& v) - : m_min(v) - , m_max(v) -{ } -Rect::Rect(const QVector2D& v1, const QVector2D& v2) - : m_min(minVect(v1, v2)) - , m_max(maxVect(v1, v2)) +export namespace geometry { -} -const QVector2D& Rect::min() const +class Rect { - return m_min; -} +private: + QVector2D m_min; + QVector2D m_max; -const QVector2D& Rect::max() const -{ - return m_max; -} +public: + Rect() = default; + explicit Rect(const QVector2D& v) + : m_min(v) + , m_max(v) + { + } -QVector2D Rect::bottomLeft() const -{ - return m_min; -} + explicit Rect(const QVector2D& v1, const QVector2D& v2) + : m_min(minVect(v1, v2)) + , m_max(maxVect(v1, v2)) + { + } -QVector2D Rect::topLeft() const -{ - return QVector2D(m_min.x(), m_max.y()); -} -QVector2D Rect::topRight() const -{ - return m_max; -} -QVector2D Rect::bottomRight() const -{ - return QVector2D(m_max.x(), m_min.y()); -} + const QVector2D& min() const { return m_min; } -QRectF Rect::toQt() const -{ - const QPointF topLeft(m_min.x(), m_max.y()); - const QPointF bottomRight(m_max.x(), m_min.y()); + const QVector2D& max() const { return m_max; } - return QRectF(topLeft, bottomRight); -} + QVector2D bottomLeft() const { return m_min; } -Rect Rect::operator|(const Rect& other) const -{ - return Rect(minVect(min(), other.min()), maxVect(max(), other.max())); -} + QVector2D topLeft() const { return QVector2D(m_min.x(), m_max.y()); } -Rect& Rect::operator|=(const Rect& other) -{ - m_min = minVect(min(), other.min()); - m_max = maxVect(max(), other.max()); - return *this; -} + QVector2D topRight() const { return m_max; } -Rect Rect::operator+(const QVector2D& v) -{ - Rect extendedRect; - extendedRect.m_min = minVect(m_min, v); - extendedRect.m_max = maxVect(m_max, v); + QVector2D bottomRight() const { return QVector2D(m_max.x(), m_min.y()); } - return extendedRect; -} + QRectF toQt() const + { + const QPointF topLeft(m_min.x(), m_max.y()); + const QPointF bottomRight(m_max.x(), m_min.y()); + + return QRectF(topLeft, bottomRight); + } + + Rect operator|(const Rect& other) const { return Rect(minVect(min(), other.min()), maxVect(max(), other.max())); } + + Rect& operator|=(const Rect& other) + { + m_min = minVect(min(), other.min()); + m_max = maxVect(max(), other.max()); + return *this; + } + + Rect operator+(const QVector2D& v) + { + Rect extendedRect; + extendedRect.m_min = minVect(m_min, v); + extendedRect.m_max = maxVect(m_max, v); + + return extendedRect; + } +}; } diff --git a/src/geometry/rect.h b/src/geometry/rect.h deleted file mode 100644 index 9a96a0a..0000000 --- a/src/geometry/rect.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include - -namespace geometry -{ - -class Rect -{ -private: - QVector2D m_min; - QVector2D m_max; - -public: - Rect() = default; - explicit Rect(const QVector2D& v); - explicit Rect(const QVector2D& v1, const QVector2D& v2); - - const QVector2D& min() const; - const QVector2D& max() const; - - QVector2D bottomLeft() const; - QVector2D topLeft() const; - QVector2D topRight() const; - QVector2D bottomRight() const; - - QRectF toQt() const; - - Rect operator|(const Rect& other) const; - Rect& operator|=(const Rect& other); - Rect operator+(const QVector2D& v); -}; - -} diff --git a/src/geometry/spline.cpp b/src/geometry/spline.cpp index 17bb67d..709e47b 100644 --- a/src/geometry/spline.cpp +++ b/src/geometry/spline.cpp @@ -1,22 +1,33 @@ -#include +module; -namespace geometry -{ +#include +#include -const Point2DList& Spline::controlPoints() const -{ - return m_controlPoints; -} +export module geometry.spline; -bool Spline::closed() const +import geometry.bezier; +import geometry.utils; + +export namespace geometry { - return m_closed; -} -Spline::Spline(Point2DList&& points, bool closed) - : m_controlPoints(points) - , m_closed(closed) +class Spline { -} +private: + Point2DList m_controlPoints; + bool m_closed; + +protected: + const Point2DList& controlPoints() const { return m_controlPoints; } + + bool closed() const { return m_closed; } + +public: + explicit Spline(Point2DList&& points, bool closed) + : m_controlPoints(points) + , m_closed(closed) + { + } +}; } diff --git a/src/geometry/spline.h b/src/geometry/spline.h deleted file mode 100644 index b2cc852..0000000 --- a/src/geometry/spline.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include - -namespace geometry -{ - -class Spline -{ -private: - Point2DList m_controlPoints; - bool m_closed; - -protected: - const Point2DList& controlPoints() const; - bool closed() const; - -public: - explicit Spline(Point2DList&& points, bool closed); -}; - -} diff --git a/src/geometry/utils.h b/src/geometry/utils.cpp similarity index 89% rename from src/geometry/utils.h rename to src/geometry/utils.cpp index 97f99e8..5e62b2f 100644 --- a/src/geometry/utils.h +++ b/src/geometry/utils.cpp @@ -1,20 +1,21 @@ -#pragma once +module; -#define _USE_MATH_DEFINES // To include M_PI -#include +#include #include - -#include +#include #include +#include #include -#include +#include #include #include -#include +export module geometry.utils; + +import common.enumerate; -namespace geometry +export namespace geometry { namespace precision { @@ -88,7 +89,7 @@ inline QVector2D ReflectLine(const QVector2D& vector, const QVector2D& normal) constexpr float NormalizedAngle(float angle) { - return (angle < 0.0f) ? (angle + M_PI * 2.0f) : angle; + return (angle < 0.0f) ? (angle + std::numbers::pi_v * 2.0f) : angle; } inline float LineAngle(const QVector2D& line) @@ -103,7 +104,7 @@ constexpr float EnsureEndGreater(float start, float end) { if (end < start) { // Add pi*2 ensuring end is greater than start. - end += M_PI * 2.0f; + end += std::numbers::pi_v * 2.0f; } assert(start <= end); @@ -124,6 +125,8 @@ constexpr Vector Lerp(const Vector& startPoint, const Vector& endPoint, float fa } } +// Explicit specialisations attach to the module of their primary template, so they must not be +// marked export; they stay reachable to any importer of this module. namespace common::enumerate { diff --git a/src/importer/dxf/CMakeLists.txt b/src/importer/dxf/CMakeLists.txt index 44e860d..d45d56f 100644 --- a/src/importer/dxf/CMakeLists.txt +++ b/src/importer/dxf/CMakeLists.txt @@ -1,14 +1,10 @@ set(SRC + utils.cpp + layer.cpp entityimporter.cpp importer.cpp interface.cpp - layer.cpp - - entityimporter.h - importer.h - interface.h - utils.h - layer.h ) -add_library(importer-dxf ${SRC}) +add_module_library(importer-dxf ${SRC} NON_MODULE_SOURCES importerimpl.cpp) +target_link_libraries(importer-dxf PUBLIC common geometry model) diff --git a/src/importer/dxf/entityimporter.cpp b/src/importer/dxf/entityimporter.cpp index 0050886..956c2a0 100644 --- a/src/importer/dxf/entityimporter.cpp +++ b/src/importer/dxf/entityimporter.cpp @@ -1,17 +1,305 @@ -#include +module; -namespace importer::dxf +#include +#include +#include + +#include +#include +#include +#include +#include + +export module importer.dxf.entityimporter; + +import geometry.bezier; +import geometry.biarc; +import geometry.bulge; +import geometry.cubicspline; +import geometry.polyline; +import geometry.quadraticspline; +import geometry.utils; +import importer.dxf.layer; +import importer.dxf.utils; + +export namespace importer::dxf +{ + +class BaseEntityImporter +{ +public: + struct Settings + { + const float splineToArcPrecision; + const float minimumSplineLength; + const float minimumArcLength; + }; + +protected: + Layer& m_layer; + const Settings& m_settings; + + void addPolyline(const geometry::Polyline& polyline) { m_layer.addPolyline(polyline); } + +public: + explicit BaseEntityImporter(Layer& layer, const Settings& settings) + : m_layer(layer) + , m_settings(settings) + { + } +}; + +template +class EntityImporter : public BaseEntityImporter +{ +public: + using BaseEntityImporter::BaseEntityImporter; + + void operator()(const Entity& entity); +}; + +template<> +inline void EntityImporter::operator()(const DRW_Point& point) +{ + const QVector2D pos(toVector2D(point.basePoint)); + const geometry::Bulge bulge(pos, pos, 0.0f); + + addPolyline(geometry::Polyline({ bulge })); +} + +template<> +inline void EntityImporter::operator()(const DRW_Line& line) +{ + const geometry::Bulge bulge(toVector2D(line.basePoint), toVector2D(line.secPoint), 0.0f); + + addPolyline(geometry::Polyline({ bulge })); +} + +template<> +inline void EntityImporter::operator()(const DRW_LWPolyline& lwpolyline) { + const int size = lwpolyline.vertlist.size(); + if (size <= 1) { + return; + } + + const bool opened = !(lwpolyline.flags & (1 << 0)); + + // One bulge more if openeded polyline, to connect last vertex to first vertex. + geometry::Bulge::List bulges(size - ((int)opened)); + + const std::shared_ptr& firstVertex = lwpolyline.vertlist.front(); + std::shared_ptr vertex = firstVertex; + QVector2D start(vertex->x, vertex->y); + + for (int i = 1; i < size; ++i) { + const std::shared_ptr& nextVertex = lwpolyline.vertlist[i]; + const QVector2D end(nextVertex->x, nextVertex->y); + + bulges[i - 1] = geometry::Bulge(start, end, vertex->bulge); + + // Pass to next vertex begin + vertex = nextVertex; + start = end; + } + + // Create end to start bulge if closed polyline. + if (!opened) { + const QVector2D end(firstVertex->x, firstVertex->y); + bulges.back() = geometry::Bulge(start, end, 0.0f); + } -BaseEntityImporter::BaseEntityImporter(Layer& layer, const Settings& settings) - : m_layer(layer) - , m_settings(settings) + addPolyline(geometry::Polyline(std::move(bulges))); +} + +template<> +inline void EntityImporter::operator()(const DRW_Circle& circle) { + const float radius = circle.radious; + const QVector2D center(toVector2D(circle.basePoint)); + + const QVector2D startPoint(center.x() - radius, center.y()); + const QVector2D endPoint(center.x() + radius, center.y()); + + const geometry::Bulge b1(startPoint, endPoint, 1.0f); + const geometry::Bulge b2(endPoint, startPoint, 1.0f); + + addPolyline(geometry::Polyline({ b1, b2 })); } -void BaseEntityImporter::addPolyline(const geometry::Polyline& polyline) +template<> +inline void EntityImporter::operator()(const DRW_Arc& arc) { - m_layer.addPolyline(polyline); + const float radius = arc.radious; + + if (radius > 0.0f) { + const float startAngle = arc.staangle; + const float endAngle = arc.endangle; + const QVector2D center(toVector2D(arc.basePoint)); + + const QVector2D relativeStart = QVector2D(std::cos(startAngle), std::sin(startAngle)) * radius; + const QVector2D relativeEnd = QVector2D(std::cos(endAngle), std::sin(endAngle)) * radius; + + const QVector2D start = relativeStart + center; + const QVector2D end = relativeEnd + center; + + const float theta = geometry::DeltaAngle(startAngle, endAngle); + + // Dxf arcs are CCW + assert(theta > 0.0f); + + // Split arc in two to avoid |tangent| > 1 + if (theta > M_PI) { + const float newtheta = theta / 2.0f; + const float theta4 = newtheta / 4.0f; + const float tangent = std::tan(theta4); + + const float middleAngle = startAngle + newtheta; + const QVector2D relativeMiddle = QVector2D(std::cos(middleAngle), std::sin(middleAngle)) * radius; + const QVector2D middle = relativeMiddle + center; + + const geometry::Bulge bulge1(start, middle, tangent); + const geometry::Bulge bulge2(middle, end, tangent); + + addPolyline(geometry::Polyline({ bulge1, bulge2 })); + } else { + const float theta4 = theta / 4.0f; + const float tangent = std::tan(theta4); + + const geometry::Bulge bulge(start, end, tangent); + + addPolyline(geometry::Polyline({ bulge })); + } + } +} + +inline std::optional biarcToPolylineIfCloseEnough(const geometry::Biarc& biarc, + const geometry::Bezier& bezier, + const BaseEntityImporter::Settings& settings) +{ + const float approximateBiarcLength = biarc.approximateLength(); + if (approximateBiarcLength < settings.minimumArcLength) { + return std::make_optional(biarc.toLinePolyline()); + } else { + const float error = bezier.maxError(biarc); + if (error < settings.splineToArcPrecision) { + // The approximation is close enough. + return std::make_optional(biarc.toPolyline()); + } + } + + return std::nullopt; +} + +inline geometry::Polyline bezierToPolyline(const geometry::Bezier& rootBezier, + const BaseEntityImporter::Settings& settings) +{ + // Queue of bezier to convert to biarc + std::stack bezierStack({ rootBezier }); + + geometry::Polyline polyline; + + while (!bezierStack.empty()) { + const geometry::Bezier bezier = bezierStack.top(); + bezierStack.pop(); + + if (bezier.approximateLength() < settings.minimumSplineLength) { + if (!bezier.isPoint()) { + polyline += bezier.toLine(); + } + continue; + } else { + if (const std::optional optBiarc = bezier.toBiarc()) { + if (const std::optional optBiarcPolyline + = biarcToPolylineIfCloseEnough(*optBiarc, bezier, settings)) { + polyline += *optBiarcPolyline; + continue; + } + } + } + + // Split bezier and schedule to conversion + const geometry::Bezier::Pair splitted = bezier.splitHalf(); + bezierStack.push(splitted[1]); + bezierStack.push(splitted[0]); + } + + return polyline; +} + +template<> +inline void EntityImporter::operator()(const DRW_Spline& spline) +{ + const bool closed = spline.flags & (1 << 0); + + geometry::Point2DList controlPoints(spline.ncontrol); + std::transform(spline.controllist.begin(), spline.controllist.end(), controlPoints.begin(), + [](const std::shared_ptr& coord) { + return toVector2D(*coord); + }); + + geometry::Bezier::List beziers; + const int degree = spline.degree; + switch (degree) { + case 2: { + geometry::QuadraticSpline spline(std::move(controlPoints), closed); + beziers = spline.toBeziers(); + break; + } + case 3: { + geometry::CubicSpline spline(std::move(controlPoints), closed); + beziers = spline.toBeziers(); + break; + } + default: { + throw std::logic_error(fmt::format("Conversion of {}d spline not implemented", degree)); + break; + } + } + + geometry::Bezier::List convexBeziers; + for (const geometry::Bezier& bezier : beziers) { + geometry::Bezier::List splitted = bezier.splitToConvex(); + convexBeziers.insert(convexBeziers.end(), splitted.begin(), splitted.end()); + } + + // Full spline polyline + geometry::Polyline polyline; + for (const geometry::Bezier& bezier : convexBeziers) { + polyline += bezierToPolyline(bezier, m_settings); + } + + addPolyline(polyline); +} + +inline geometry::Polyline ellipseQuadrantToPolyline(const QVector2D& center, const QVector2D& axisStart, + const QVector2D& axisEnd, + const BaseEntityImporter::Settings& settings) +{ + const QVector2D quadrantStart = center + axisStart; + const QVector2D quadrantEnd = center + axisEnd; + constexpr float magicNumber = 0.55228475f; + const geometry::Bezier quadrantBezier(quadrantStart, quadrantStart + axisEnd * magicNumber, + quadrantEnd + axisStart * magicNumber, quadrantEnd); + + return bezierToPolyline(quadrantBezier, settings); +} + +template<> +inline void EntityImporter::operator()(const DRW_Ellipse& ellipse) +{ + const QVector2D center(toVector2D(ellipse.basePoint)); + const QVector2D axisMajor(toVector2D(ellipse.secPoint)); + const QVector2D axisMinor = geometry::PerpendicularLine(axisMajor) * ellipse.ratio; + + const QVector2D quadrantAxes[4] = { axisMajor, axisMinor, -axisMajor, -axisMinor }; + + geometry::Polyline polyline; + for (int i = 3, j = 0; j < 4; i = j, ++j) { + polyline += ellipseQuadrantToPolyline(center, quadrantAxes[i], quadrantAxes[j], m_settings); + } + + addPolyline(polyline); } } diff --git a/src/importer/dxf/entityimporter.h b/src/importer/dxf/entityimporter.h deleted file mode 100644 index 0c45a34..0000000 --- a/src/importer/dxf/entityimporter.h +++ /dev/null @@ -1,289 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#include - -#include - -namespace importer::dxf -{ - -class BaseEntityImporter -{ -public: - struct Settings - { - const float splineToArcPrecision; - const float minimumSplineLength; - const float minimumArcLength; - }; - -protected: - Layer& m_layer; - const Settings& m_settings; - - void addPolyline(const geometry::Polyline& polyline); - -public: - explicit BaseEntityImporter(Layer& layer, const Settings& settings); -}; - -template -class EntityImporter : public BaseEntityImporter -{ -public: - using BaseEntityImporter::BaseEntityImporter; - - void operator()(const Entity& entity); -}; - -template<> -inline void EntityImporter::operator()(const DRW_Point& point) -{ - const QVector2D pos(toVector2D(point.basePoint)); - const geometry::Bulge bulge(pos, pos, 0.0f); - - addPolyline(geometry::Polyline({ bulge })); -} - -template<> -inline void EntityImporter::operator()(const DRW_Line& line) -{ - const geometry::Bulge bulge(toVector2D(line.basePoint), toVector2D(line.secPoint), 0.0f); - - addPolyline(geometry::Polyline({ bulge })); -} - -template<> -inline void EntityImporter::operator()(const DRW_LWPolyline& lwpolyline) -{ - const int size = lwpolyline.vertlist.size(); - if (size <= 1) { - return; - } - - const bool opened = !(lwpolyline.flags & (1 << 0)); - - // One bulge more if openeded polyline, to connect last vertex to first vertex. - geometry::Bulge::List bulges(size - ((int)opened)); - - const std::shared_ptr& firstVertex = lwpolyline.vertlist.front(); - std::shared_ptr vertex = firstVertex; - QVector2D start(vertex->x, vertex->y); - - for (int i = 1; i < size; ++i) { - const std::shared_ptr& nextVertex = lwpolyline.vertlist[i]; - const QVector2D end(nextVertex->x, nextVertex->y); - - bulges[i - 1] = geometry::Bulge(start, end, vertex->bulge); - - // Pass to next vertex begin - vertex = nextVertex; - start = end; - } - - // Create end to start bulge if closed polyline. - if (!opened) { - const QVector2D end(firstVertex->x, firstVertex->y); - bulges.back() = geometry::Bulge(start, end, 0.0f); - } - - addPolyline(geometry::Polyline(std::move(bulges))); -} - -template<> -inline void EntityImporter::operator()(const DRW_Circle& circle) -{ - const float radius = circle.radious; - const QVector2D center(toVector2D(circle.basePoint)); - - const QVector2D startPoint(center.x() - radius, center.y()); - const QVector2D endPoint(center.x() + radius, center.y()); - - const geometry::Bulge b1(startPoint, endPoint, 1.0f); - const geometry::Bulge b2(endPoint, startPoint, 1.0f); - - addPolyline(geometry::Polyline({ b1, b2 })); -} - -template<> -inline void EntityImporter::operator()(const DRW_Arc& arc) -{ - const float radius = arc.radious; - - if (radius > 0.0f) { - const float startAngle = arc.staangle; - const float endAngle = arc.endangle; - const QVector2D center(toVector2D(arc.basePoint)); - - const QVector2D relativeStart = QVector2D(std::cos(startAngle), std::sin(startAngle)) * radius; - const QVector2D relativeEnd = QVector2D(std::cos(endAngle), std::sin(endAngle)) * radius; - - const QVector2D start = relativeStart + center; - const QVector2D end = relativeEnd + center; - - const float theta = geometry::DeltaAngle(startAngle, endAngle); - - // Dxf arcs are CCW - assert(theta > 0.0f); - - // Split arc in two to avoid |tangent| > 1 - if (theta > M_PI) { - const float newtheta = theta / 2.0f; - const float theta4 = newtheta / 4.0f; - const float tangent = std::tan(theta4); - - const float middleAngle = startAngle + newtheta; - const QVector2D relativeMiddle = QVector2D(std::cos(middleAngle), std::sin(middleAngle)) * radius; - const QVector2D middle = relativeMiddle + center; - - const geometry::Bulge bulge1(start, middle, tangent); - const geometry::Bulge bulge2(middle, end, tangent); - - addPolyline(geometry::Polyline({ bulge1, bulge2 })); - } else { - const float theta4 = theta / 4.0f; - const float tangent = std::tan(theta4); - - const geometry::Bulge bulge(start, end, tangent); - - addPolyline(geometry::Polyline({ bulge })); - } - } -} - -inline std::optional biarcToPolylineIfCloseEnough(const geometry::Biarc& biarc, - const geometry::Bezier& bezier, - const BaseEntityImporter::Settings& settings) -{ - const float approximateBiarcLength = biarc.approximateLength(); - if (approximateBiarcLength < settings.minimumArcLength) { - return std::make_optional(biarc.toLinePolyline()); - } else { - const float error = bezier.maxError(biarc); - if (error < settings.splineToArcPrecision) { - // The approximation is close enough. - return std::make_optional(biarc.toPolyline()); - } - } - - return std::nullopt; -} - -inline geometry::Polyline bezierToPolyline(const geometry::Bezier& rootBezier, - const BaseEntityImporter::Settings& settings) -{ - // Queue of bezier to convert to biarc - std::stack bezierStack({ rootBezier }); - - geometry::Polyline polyline; - - while (!bezierStack.empty()) { - const geometry::Bezier bezier = bezierStack.top(); - bezierStack.pop(); - - if (bezier.approximateLength() < settings.minimumSplineLength) { - if (!bezier.isPoint()) { - polyline += bezier.toLine(); - } - continue; - } else { - if (const std::optional optBiarc = bezier.toBiarc()) { - if (const std::optional optBiarcPolyline - = biarcToPolylineIfCloseEnough(*optBiarc, bezier, settings)) { - polyline += *optBiarcPolyline; - continue; - } - } - } - - // Split bezier and schedule to conversion - const geometry::Bezier::Pair splitted = bezier.splitHalf(); - bezierStack.push(splitted[1]); - bezierStack.push(splitted[0]); - } - - return polyline; -} - -template<> -inline void EntityImporter::operator()(const DRW_Spline& spline) -{ - const bool closed = spline.flags & (1 << 0); - - geometry::Point2DList controlPoints(spline.ncontrol); - std::transform(spline.controllist.begin(), spline.controllist.end(), controlPoints.begin(), - [](const std::shared_ptr& coord) { - return toVector2D(*coord); - }); - - geometry::Bezier::List beziers; - const int degree = spline.degree; - switch (degree) { - case 2: { - geometry::QuadraticSpline spline(std::move(controlPoints), closed); - beziers = spline.toBeziers(); - break; - } - case 3: { - geometry::CubicSpline spline(std::move(controlPoints), closed); - beziers = spline.toBeziers(); - break; - } - default: { - throw std::logic_error(fmt::format("Conversion of {}d spline not implemented", degree)); - break; - } - } - - geometry::Bezier::List convexBeziers; - for (const geometry::Bezier& bezier : beziers) { - geometry::Bezier::List splitted = bezier.splitToConvex(); - convexBeziers.insert(convexBeziers.end(), splitted.begin(), splitted.end()); - } - - // Full spline polyline - geometry::Polyline polyline; - for (const geometry::Bezier& bezier : convexBeziers) { - polyline += bezierToPolyline(bezier, m_settings); - } - - addPolyline(polyline); -} - -inline geometry::Polyline ellipseQuadrantToPolyline(const QVector2D& center, const QVector2D& axisStart, - const QVector2D& axisEnd, - const BaseEntityImporter::Settings& settings) -{ - const QVector2D quadrantStart = center + axisStart; - const QVector2D quadrantEnd = center + axisEnd; - constexpr float magicNumber = 0.55228475f; - const geometry::Bezier quadrantBezier(quadrantStart, quadrantStart + axisEnd * magicNumber, - quadrantEnd + axisStart * magicNumber, quadrantEnd); - - return bezierToPolyline(quadrantBezier, settings); -} - -template<> -inline void EntityImporter::operator()(const DRW_Ellipse& ellipse) -{ - const QVector2D center(toVector2D(ellipse.basePoint)); - const QVector2D axisMajor(toVector2D(ellipse.secPoint)); - const QVector2D axisMinor = geometry::PerpendicularLine(axisMajor) * ellipse.ratio; - - const QVector2D quadrantAxes[4] = { axisMajor, axisMinor, -axisMajor, -axisMinor }; - - geometry::Polyline polyline; - for (int i = 3, j = 0; j < 4; i = j, ++j) { - polyline += ellipseQuadrantToPolyline(center, quadrantAxes[i], quadrantAxes[j], m_settings); - } - - addPolyline(polyline); -} - -} diff --git a/src/importer/dxf/importer.cpp b/src/importer/dxf/importer.cpp index 05f6e07..21fe4b6 100644 --- a/src/importer/dxf/importer.cpp +++ b/src/importer/dxf/importer.cpp @@ -1,58 +1,57 @@ -#include -#include +module; -#include +#include +#include +#include +#include +#include -#include +export module importer.dxf.importer; -namespace importer::dxf -{ +import common.exception; +import geometry.polyline; +import importer.dxf.entityimporter; +import importer.dxf.layer; +import importer.dxf.utils; -void Importer::addLayer(const DRW_Layer& layer) +export namespace importer::dxf { - if (layer.plotF) { - const std::string& name = layer.name; - m_nameToLayers.emplace(name, Layer(name)); - } -} -Importer::Importer(const std::string& filename, float splineToArcPrecision, float minimumSplineLength, - float minimumArcLength) - : m_entityImporterSettings({ splineToArcPrecision, minimumSplineLength, minimumArcLength }) - , m_ignoreEntities(false) +class Importer { - Interface interface(*this); +private: + const BaseEntityImporter::Settings m_entityImporterSettings; - dxfRW rw(filename.c_str()); - if (!rw.read(&interface, false)) { - throw common::FileCouldNotOpenException(); - } -} + std::unordered_map m_nameToLayers; + bool m_ignoreEntities; -Layer::List Importer::layers() const -{ - Layer::List layers(m_nameToLayers.size()); - std::transform(m_nameToLayers.begin(), m_nameToLayers.end(), layers.begin(), [](const auto& pair) { - return pair.second; - }); + void addLayer(const DRW_Layer& layer); - return layers; -} +public: + explicit Importer(const std::string& filename, float splineToArcPrecision, float minimumSplineLength, + float minimumArcLength); -void Importer::startBlock() -{ - m_ignoreEntities = true; -} + Layer::List layers() const; -void Importer::endBlock() -{ - m_ignoreEntities = false; -} + template + void processEntity(const Entity& entity) + { + if (!m_ignoreEntities) { + auto it = m_nameToLayers.find(entity.layer); + if (it != m_nameToLayers.end()) { + Layer& layer = it->second; + + EntityImporter entityImporter(layer, m_entityImporterSettings); + entityImporter(entity); + } + } + } + + void startBlock(); + void endBlock(); +}; template<> -void Importer::processEntity(const DRW_Layer& layer) -{ - addLayer(layer); -} +void Importer::processEntity(const DRW_Layer& layer); } diff --git a/src/importer/dxf/importer.h b/src/importer/dxf/importer.h deleted file mode 100644 index c614f91..0000000 --- a/src/importer/dxf/importer.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include -#include - -#include -#include - -namespace importer::dxf -{ - -class Importer -{ -private: - const BaseEntityImporter::Settings m_entityImporterSettings; - - std::unordered_map m_nameToLayers; - bool m_ignoreEntities; - - void addLayer(const DRW_Layer& layer); - -public: - explicit Importer(const std::string& filename, float splineToArcPrecision, float minimumSplineLength, - float minimumArcLength); - - Layer::List layers() const; - - template - void processEntity(const Entity& entity) - { - if (!m_ignoreEntities) { - auto it = m_nameToLayers.find(entity.layer); - if (it != m_nameToLayers.end()) { - Layer& layer = it->second; - - EntityImporter entityImporter(layer, m_entityImporterSettings); - entityImporter(entity); - } - } - } - - void startBlock(); - void endBlock(); -}; - -template<> -void Importer::processEntity(const DRW_Layer& layer); - -} diff --git a/src/importer/dxf/importerimpl.cpp b/src/importer/dxf/importerimpl.cpp new file mode 100644 index 0000000..a20023c --- /dev/null +++ b/src/importer/dxf/importerimpl.cpp @@ -0,0 +1,70 @@ +module; + +#include +#include +#include +#include + +#include +#include +#include + +module importer.dxf.importer; + +import common.exception; +import geometry.polyline; +import importer.dxf.entityimporter; +import importer.dxf.interface; +import importer.dxf.layer; +import importer.dxf.utils; +namespace importer::dxf +{ + +void Importer::addLayer(const DRW_Layer& layer) +{ + if (layer.plotF) { + const std::string& name = layer.name; + m_nameToLayers.emplace(name, Layer(name)); + } +} + +Importer::Importer(const std::string& filename, float splineToArcPrecision, float minimumSplineLength, + float minimumArcLength) + : m_entityImporterSettings({ splineToArcPrecision, minimumSplineLength, minimumArcLength }) + , m_ignoreEntities(false) +{ + Interface interface(*this); + + dxfRW rw(filename.c_str()); + if (!rw.read(&interface, false)) { + throw common::FileCouldNotOpenException(); + } +} + +Layer::List Importer::layers() const +{ + Layer::List layers(m_nameToLayers.size()); + std::transform(m_nameToLayers.begin(), m_nameToLayers.end(), layers.begin(), [](const auto& pair) { + return pair.second; + }); + + return layers; +} + +void Importer::startBlock() +{ + m_ignoreEntities = true; +} + +void Importer::endBlock() +{ + m_ignoreEntities = false; +} + +template<> +void Importer::processEntity(const DRW_Layer& layer) +{ + addLayer(layer); +} + +} diff --git a/src/importer/dxf/interface.cpp b/src/importer/dxf/interface.cpp index 586d1f4..26c69de 100644 --- a/src/importer/dxf/interface.cpp +++ b/src/importer/dxf/interface.cpp @@ -1,286 +1,175 @@ -#include -#include +module; -#include +#include + +export module importer.dxf.interface; + +import importer.dxf.importer; namespace importer::dxf { -Interface::Interface(Importer& importer) - : m_importer(importer) -{ -} - #define PRINT_FUNC // std::cout << __func__ << std::endl; -void Interface::addHeader(const DRW_Header* data) -{ - PRINT_FUNC; } -void Interface::addLType(const DRW_LType& data) +export namespace importer::dxf { - PRINT_FUNC; -} -void Interface::addLayer(const DRW_Layer& data) +class Interface : public DRW_Interface { - m_importer.processEntity(data); - PRINT_FUNC; -} +private: + Importer& m_importer; -void Interface::addDimStyle(const DRW_Dimstyle& data) -{ - PRINT_FUNC; -} + void addHeader(const DRW_Header* data) override { PRINT_FUNC; } -void Interface::addVport(const DRW_Vport& data) -{ - PRINT_FUNC; -} + void addLType(const DRW_LType& data) override { PRINT_FUNC; } -void Interface::addTextStyle(const DRW_Textstyle& data) -{ - PRINT_FUNC; -} + void addLayer(const DRW_Layer& data) override + { + m_importer.processEntity(data); + PRINT_FUNC; + } -void Interface::addAppId(const DRW_AppId& data) -{ - PRINT_FUNC; -} + void addDimStyle(const DRW_Dimstyle& data) override { PRINT_FUNC; } -void Interface::addBlock(const DRW_Block& data) -{ - PRINT_FUNC; - m_importer.startBlock(); -} + void addVport(const DRW_Vport& data) override { PRINT_FUNC; } -void Interface::setBlock(const int handle) -{ - PRINT_FUNC; -} + void addTextStyle(const DRW_Textstyle& data) override { PRINT_FUNC; } -void Interface::endBlock() -{ - PRINT_FUNC; - m_importer.endBlock(); -} + void addAppId(const DRW_AppId& data) override { PRINT_FUNC; } -void Interface::addPoint(const DRW_Point& data) -{ - PRINT_FUNC; - m_importer.processEntity(data); -} + void addBlock(const DRW_Block& data) override + { + PRINT_FUNC; + m_importer.startBlock(); + } -void Interface::addLine(const DRW_Line& data) -{ - PRINT_FUNC; - m_importer.processEntity(data); -} + void setBlock(const int handle) override { PRINT_FUNC; } -void Interface::addRay(const DRW_Ray& data) -{ - PRINT_FUNC; -} + void endBlock() override + { + PRINT_FUNC; + m_importer.endBlock(); + } -void Interface::addXline(const DRW_Xline& data) -{ - PRINT_FUNC; -} + void addPoint(const DRW_Point& data) override + { + PRINT_FUNC; + m_importer.processEntity(data); + } -void Interface::addArc(const DRW_Arc& data) -{ - PRINT_FUNC; - m_importer.processEntity(data); -} + void addLine(const DRW_Line& data) override + { + PRINT_FUNC; + m_importer.processEntity(data); + } -void Interface::addCircle(const DRW_Circle& data) -{ - PRINT_FUNC; - m_importer.processEntity(data); -} + void addRay(const DRW_Ray& data) override { PRINT_FUNC; } -void Interface::addEllipse(const DRW_Ellipse& data) -{ - PRINT_FUNC; - m_importer.processEntity(data); -} + void addXline(const DRW_Xline& data) override { PRINT_FUNC; } -void Interface::addLWPolyline(const DRW_LWPolyline& data) -{ - PRINT_FUNC; - m_importer.processEntity(data); -} + void addArc(const DRW_Arc& data) override + { + PRINT_FUNC; + m_importer.processEntity(data); + } -void Interface::addPolyline(const DRW_Polyline& data) -{ - PRINT_FUNC; -} + void addCircle(const DRW_Circle& data) override + { + PRINT_FUNC; + m_importer.processEntity(data); + } -void Interface::addSpline(const DRW_Spline* data) -{ - PRINT_FUNC; - m_importer.processEntity(*data); -} + void addEllipse(const DRW_Ellipse& data) override + { + PRINT_FUNC; + m_importer.processEntity(data); + } -void Interface::addKnot(const DRW_Entity& data) -{ - PRINT_FUNC; -} + void addLWPolyline(const DRW_LWPolyline& data) override + { + PRINT_FUNC; + m_importer.processEntity(data); + } -void Interface::addInsert(const DRW_Insert& data) -{ - PRINT_FUNC; -} + void addPolyline(const DRW_Polyline& data) override { PRINT_FUNC; } -void Interface::addTrace(const DRW_Trace& data) -{ - PRINT_FUNC; -} + void addSpline(const DRW_Spline* data) override + { + PRINT_FUNC; + m_importer.processEntity(*data); + } -void Interface::add3dFace(const DRW_3Dface& data) -{ - PRINT_FUNC; -} + void addKnot(const DRW_Entity& data) override { PRINT_FUNC; } -void Interface::addSolid(const DRW_Solid& data) -{ - PRINT_FUNC; -} + void addInsert(const DRW_Insert& data) override { PRINT_FUNC; } -void Interface::addMText(const DRW_MText& data) -{ - PRINT_FUNC; -} + void addTrace(const DRW_Trace& data) override { PRINT_FUNC; } -void Interface::addText(const DRW_Text& data) -{ - PRINT_FUNC; -} + void add3dFace(const DRW_3Dface& data) override { PRINT_FUNC; } -void Interface::addDimAlign(const DRW_DimAligned* data) -{ - PRINT_FUNC; -} + void addSolid(const DRW_Solid& data) override { PRINT_FUNC; } -void Interface::addDimLinear(const DRW_DimLinear* data) -{ - PRINT_FUNC; -} + void addMText(const DRW_MText& data) override { PRINT_FUNC; } -void Interface::addDimRadial(const DRW_DimRadial* data) -{ - PRINT_FUNC; -} + void addText(const DRW_Text& data) override { PRINT_FUNC; } -void Interface::addDimDiametric(const DRW_DimDiametric* data) -{ - PRINT_FUNC; -} + void addDimAlign(const DRW_DimAligned* data) override { PRINT_FUNC; } -void Interface::addDimAngular(const DRW_DimAngular* data) -{ - PRINT_FUNC; -} + void addDimLinear(const DRW_DimLinear* data) override { PRINT_FUNC; } -void Interface::addDimAngular3P(const DRW_DimAngular3p* data) -{ - PRINT_FUNC; -} + void addDimRadial(const DRW_DimRadial* data) override { PRINT_FUNC; } -void Interface::addDimOrdinate(const DRW_DimOrdinate* data) -{ - PRINT_FUNC; -} + void addDimDiametric(const DRW_DimDiametric* data) override { PRINT_FUNC; } -void Interface::addLeader(const DRW_Leader* data) -{ - PRINT_FUNC; -} + void addDimAngular(const DRW_DimAngular* data) override { PRINT_FUNC; } -void Interface::addHatch(const DRW_Hatch* data) -{ - PRINT_FUNC; -} + void addDimAngular3P(const DRW_DimAngular3p* data) override { PRINT_FUNC; } -void Interface::addViewport(const DRW_Viewport& data) -{ - PRINT_FUNC; -} + void addDimOrdinate(const DRW_DimOrdinate* data) override { PRINT_FUNC; } -void Interface::addImage(const DRW_Image* data) -{ - PRINT_FUNC; -} + void addLeader(const DRW_Leader* data) override { PRINT_FUNC; } -void Interface::linkImage(const DRW_ImageDef* data) -{ - PRINT_FUNC; -} + void addHatch(const DRW_Hatch* data) override { PRINT_FUNC; } -void Interface::addComment(const char* comment) -{ - PRINT_FUNC; -} + void addViewport(const DRW_Viewport& data) override { PRINT_FUNC; } -void Interface::addPlotSettings(const DRW_PlotSettings* data) -{ - PRINT_FUNC; -} + void addImage(const DRW_Image* data) override { PRINT_FUNC; } -void Interface::writeHeader(DRW_Header& data) -{ - PRINT_FUNC; -} + void linkImage(const DRW_ImageDef* data) override { PRINT_FUNC; } -void Interface::writeBlocks() -{ - PRINT_FUNC; -} + void addComment(const char* comment) override { PRINT_FUNC; } -void Interface::writeBlockRecords() -{ - PRINT_FUNC; -} + void addPlotSettings(const DRW_PlotSettings* data) override { PRINT_FUNC; } -void Interface::writeEntities() -{ - PRINT_FUNC; -} + void writeHeader(DRW_Header& data) override { PRINT_FUNC; } -void Interface::writeLTypes() -{ - PRINT_FUNC; -} + void writeBlocks() override { PRINT_FUNC; } -void Interface::writeLayers() -{ - PRINT_FUNC; -} + void writeBlockRecords() override { PRINT_FUNC; } -void Interface::writeTextstyles() -{ - PRINT_FUNC; -} + void writeEntities() override { PRINT_FUNC; } -void Interface::writeVports() -{ - PRINT_FUNC; -} + void writeLTypes() override { PRINT_FUNC; } -void Interface::writeDimstyles() -{ - PRINT_FUNC; -} + void writeLayers() override { PRINT_FUNC; } -void Interface::writeObjects() -{ - PRINT_FUNC; -} + void writeTextstyles() override { PRINT_FUNC; } -void Interface::writeAppId() -{ - PRINT_FUNC; -} + void writeVports() override { PRINT_FUNC; } + + void writeDimstyles() override { PRINT_FUNC; } + + void writeObjects() override { PRINT_FUNC; } + + void writeAppId() override { PRINT_FUNC; } + +public: + explicit Interface(Importer& importer) + : m_importer(importer) + { + } +}; } diff --git a/src/importer/dxf/interface.h b/src/importer/dxf/interface.h deleted file mode 100644 index 25c7dc8..0000000 --- a/src/importer/dxf/interface.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include - -namespace importer::dxf -{ - -class Importer; - -class Interface : public DRW_Interface -{ -private: - Importer& m_importer; - - void addHeader(const DRW_Header* data) override; - void addLType(const DRW_LType& data) override; - void addLayer(const DRW_Layer& data) override; - void addDimStyle(const DRW_Dimstyle& data) override; - void addVport(const DRW_Vport& data) override; - void addTextStyle(const DRW_Textstyle& data) override; - void addAppId(const DRW_AppId& data) override; - void addBlock(const DRW_Block& data) override; - void setBlock(const int handle) override; - void endBlock() override; - void addPoint(const DRW_Point& data) override; - void addLine(const DRW_Line& data) override; - void addRay(const DRW_Ray& data) override; - void addXline(const DRW_Xline& data) override; - void addArc(const DRW_Arc& data) override; - void addCircle(const DRW_Circle& data) override; - void addEllipse(const DRW_Ellipse& data) override; - void addLWPolyline(const DRW_LWPolyline& data) override; - void addPolyline(const DRW_Polyline& data) override; - void addSpline(const DRW_Spline* data) override; - void addKnot(const DRW_Entity& data) override; - void addInsert(const DRW_Insert& data) override; - void addTrace(const DRW_Trace& data) override; - void add3dFace(const DRW_3Dface& data) override; - void addSolid(const DRW_Solid& data) override; - void addMText(const DRW_MText& data) override; - void addText(const DRW_Text& data) override; - void addDimAlign(const DRW_DimAligned* data) override; - void addDimLinear(const DRW_DimLinear* data) override; - void addDimRadial(const DRW_DimRadial* data) override; - void addDimDiametric(const DRW_DimDiametric* data) override; - void addDimAngular(const DRW_DimAngular* data) override; - void addDimAngular3P(const DRW_DimAngular3p* data) override; - void addDimOrdinate(const DRW_DimOrdinate* data) override; - void addLeader(const DRW_Leader* data) override; - void addHatch(const DRW_Hatch* data) override; - void addViewport(const DRW_Viewport& data) override; - void addImage(const DRW_Image* data) override; - void linkImage(const DRW_ImageDef* data) override; - void addComment(const char* comment) override; - void addPlotSettings(const DRW_PlotSettings* data) override; - - void writeHeader(DRW_Header& data) override; - void writeBlocks() override; - void writeBlockRecords() override; - void writeEntities() override; - void writeLTypes() override; - void writeLayers() override; - void writeTextstyles() override; - void writeVports() override; - void writeDimstyles() override; - void writeObjects() override; - void writeAppId() override; - -public: - explicit Interface(Importer& importer); -}; - -} diff --git a/src/importer/dxf/layer.cpp b/src/importer/dxf/layer.cpp index ab6ad7c..4b25a9b 100644 --- a/src/importer/dxf/layer.cpp +++ b/src/importer/dxf/layer.cpp @@ -1,26 +1,35 @@ -#include +module; -namespace importer::dxf -{ +#include +#include -Layer::Layer(const std::string& name) - : m_name(name) -{ -} +export module importer.dxf.layer; -void Layer::addPolyline(const geometry::Polyline& polyline) -{ - m_polylines.push_back(polyline); -} +import common.aggregable; +import geometry.polyline; -geometry::Polyline::List&& Layer::polylines() +export namespace importer::dxf { - return std::move(m_polylines); -} -const std::string& Layer::name() const +class Layer : public common::Aggregable { - return m_name; -} +private: + geometry::Polyline::List m_polylines; + std::string m_name; + +public: + Layer() = default; + + explicit Layer(const std::string& name) + : m_name(name) + { + } + + void addPolyline(const geometry::Polyline& polyline) { m_polylines.push_back(polyline); } + + geometry::Polyline::List&& polylines() { return std::move(m_polylines); } + + const std::string& name() const { return m_name; } +}; } diff --git a/src/importer/dxf/layer.h b/src/importer/dxf/layer.h deleted file mode 100644 index 8e6fb3a..0000000 --- a/src/importer/dxf/layer.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include - -#include - -namespace importer::dxf -{ - -class Layer : public common::Aggregable -{ -private: - geometry::Polyline::List m_polylines; - std::string m_name; - -public: - Layer() = default; - - explicit Layer(const std::string& name); - - void addPolyline(const geometry::Polyline& polyline); - - geometry::Polyline::List&& polylines(); - - const std::string& name() const; -}; - -} diff --git a/src/importer/dxf/utils.h b/src/importer/dxf/utils.cpp similarity index 67% rename from src/importer/dxf/utils.h rename to src/importer/dxf/utils.cpp index caf6802..9e1fbd6 100644 --- a/src/importer/dxf/utils.h +++ b/src/importer/dxf/utils.cpp @@ -1,10 +1,12 @@ -#pragma once +module; + +#include #include -#include +export module importer.dxf.utils; -namespace importer::dxf +export namespace importer::dxf { inline QVector2D toVector2D(const DRW_Coord& coord) diff --git a/src/importer/dxfplot/CMakeLists.txt b/src/importer/dxfplot/CMakeLists.txt index 873978e..5fd889e 100644 --- a/src/importer/dxfplot/CMakeLists.txt +++ b/src/importer/dxfplot/CMakeLists.txt @@ -1,8 +1,6 @@ set(SRC importer.cpp - - importer.h ) -add_library(importer-dxfplot ${SRC}) -target_link_libraries(importer-dxfplot PRIVATE config) +add_module_library(importer-dxfplot ${SRC}) +target_link_libraries(importer-dxfplot PUBLIC model PRIVATE config common geometry serializer) diff --git a/src/importer/dxfplot/importer.cpp b/src/importer/dxfplot/importer.cpp index 25f104d..518d75b 100644 --- a/src/importer/dxfplot/importer.cpp +++ b/src/importer/dxfplot/importer.cpp @@ -1,54 +1,68 @@ -#include +module; -#include +#include +#include #include -#include -#include +export module importer.dxfplot.importer; -#include +import common.exception; +import config.config; +import model.document; +import serializer.task; +import model.task; -namespace importer::dxfplot +export namespace importer::dxfplot { -Importer::Importer(const config::Tools& tools, const config::Profiles& profiles) - : m_tools(tools) - , m_profiles(profiles) +class Importer { -} +private: + using Archive = cereal::JSONInputArchive; -model::Document::UPtr Importer::operator()(const std::string& fileName) const -{ - std::ifstream input(fileName); + const config::Tools& m_tools; + const config::Profiles& m_profiles; - return (*this)(input); -} +public: + explicit Importer(const config::Tools& tools, const config::Profiles& profiles) + : m_tools(tools) + , m_profiles(profiles) + { + } -model::Document::UPtr Importer::operator()(std::istream& input) const -{ - Archive archive(input); + model::Document::UPtr operator()(const std::string& fileName) const + { + std::ifstream input(fileName); - model::Task::UPtr task = std::make_unique(); - archive(cereal::make_nvp("task", *task)); + return (*this)(input); + } - std::string profileName; - archive(cereal::make_nvp("profile_name", profileName)); + model::Document::UPtr operator()(std::istream& input) const + { + Archive archive(input); - std::string toolName; - archive(cereal::make_nvp("tool_name", toolName)); + model::Task::UPtr task = std::make_unique(); + archive(cereal::make_nvp("task", *task)); - const config::Tools::Tool* tool = m_tools.get(toolName); - const config::Profiles::Profile* profile = m_profiles.get(profileName); + std::string profileName; + archive(cereal::make_nvp("profile_name", profileName)); - if (!tool) { - throw common::ImportCouldNotFindToolConfigException(); - } - if (!profile) { - throw common::ImportCouldNotFindProfileConfigException(); - } + std::string toolName; + archive(cereal::make_nvp("tool_name", toolName)); - return std::make_unique(std::move(task), *tool, *profile); -} + const config::Tools::Tool* tool = m_tools.get(toolName); + const config::Profiles::Profile* profile = m_profiles.get(profileName); + + if (!tool) { + throw common::ImportCouldNotFindToolConfigException(); + } + if (!profile) { + throw common::ImportCouldNotFindProfileConfigException(); + } + + return std::make_unique(std::move(task), *tool, *profile); + } +}; } diff --git a/src/importer/dxfplot/importer.h b/src/importer/dxfplot/importer.h deleted file mode 100644 index de5480f..0000000 --- a/src/importer/dxfplot/importer.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -#include - -#include // TODO - -namespace importer::dxfplot -{ - -class Importer -{ -private: - using Archive = cereal::JSONInputArchive; - - const config::Tools& m_tools; - const config::Profiles& m_profiles; - -public: - explicit Importer(const config::Tools& tools, const config::Profiles& profiles); - - model::Document::UPtr operator()(const std::string& fileName) const; - model::Document::UPtr operator()(std::istream& input) const; -}; - -} diff --git a/src/main.cpp b/src/main.cpp index 10ffda8..223f88e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,3 @@ -#include -#include #include #include @@ -9,7 +7,8 @@ #include #include -import model.module; +import model.application; +import view.mainwindow; void setDarkPalette(QApplication& qapp) { diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt index 1bafa14..9596980 100644 --- a/src/model/CMakeLists.txt +++ b/src/model/CMakeLists.txt @@ -1,32 +1,17 @@ +add_subdirectory(application) + set(SRC - application.cpp - document.cpp - documenthistory.cpp - layer.cpp - offsettedpath.cpp - path.cpp pathsettings.cpp - pathgroupsettings.cpp renderable.cpp - simulation.cpp + offsettedpath.cpp + path.cpp + layer.cpp task.cpp - - application.h - document.h - documenthistory.h - layer.h - path.h - offsettedpath.h - pathsettings.h - pathgroupsettings.h - renderable.h - simulation.h - task.h - documentmodelobserver.h + document.cpp + documenthistory.cpp + pathgroupsettings.cpp ) -add_library(model ${SRC}) -target_link_libraries(model PUBLIC geometry PRIVATE config) +add_module_library(model ${SRC}) +target_link_libraries(model PUBLIC geometry common PRIVATE config geometry-filter) add_coverage(model) - -add_module_library(model_module module.cpp) diff --git a/src/model/application.cpp b/src/model/application.cpp deleted file mode 100644 index f255465..0000000 --- a/src/model/application.cpp +++ /dev/null @@ -1,420 +0,0 @@ -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include - -namespace model -{ - -static const QString configFileName = "config.yml"; - -/** Retrieves application config file path - * @return config file path - */ -static std::string configFilePath() -{ - const QDir dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)); - - // Ensure the path exists - dir.mkpath("."); - - const QString path = dir.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; -} - -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); - emit toolChanged(); - } - 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) -{ - qInfo() << "Opening " << fileName; - - const QMimeDatabase db; - const QMimeType mime = db.mimeTypeForFile(fileName); - const QString mineName = mime.name(); - - if (mineName == "image/vnd.dxf") { - if (!loadFromDxf(fileName)) { - return false; - } - } else if (mineName == "text/plain") { - loadFromDxfplot(fileName); - } else { - qCritical() << "Invalid file type: " << fileName; - return false; - } - - 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(const QString& fileName) -{ - const config::Import::Dxf& dxf = m_config.root().import().dxf(); - - 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; - } - - 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(); -} - -void Application::optimizeOrder() -{ - const config::Optimize& optimize = m_config.root().optimize(); - - Task& task = m_openedDocument->task(); - task.optimizeOrder(optimize.maintainPathLengthOrder(), optimize.lengthPrecision(), optimize.distancePrecision()); -} - -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 deleted file mode 100644 index b92a108..0000000 --- a/src/model/application.h +++ /dev/null @@ -1,131 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include -#include - -#include - -namespace importer::dxf -{ -class Importer; -} - -namespace model -{ - -class Application : public QObject -{ - Q_OBJECT; - -private: - /// Global configuration - config::Config m_config; - - const config::Tools::Tool* m_defaultToolConfig; - const config::Profiles::Profile* m_defaultProfileConfig; - - // Last opened or saved file base name. - QString m_lastHandledFileBaseName; - QString m_lastSavedGcodeFileName; - 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(); - - PathSettings defaultPathSettings() const; - - const config::Tools::Tool* findTool(const std::string& name) const; - const config::Profiles::Profile* findProfile(const std::string& name) const; - - void cutterCompensation(float scale); - - geometry::Polyline::List postProcessImportedPolylines(geometry::Polyline::List&& rawPolylines) const; - Task::UPtr createTaskFromDxfImporter(const importer::dxf::Importer& importer); - - template - bool saveToFile(Exporter&& exporter, const QString& fileName) - { - qInfo() << "Saving to " << fileName; - std::ofstream output(fileName.toStdString()); - if (output) { - exporter(*m_openedDocument, output); - m_lastHandledFileBaseName = baseName(fileName); - emit fileSaved(fileName); - return true; - } - - return false; - } - -public: - struct FileExtension - { - inline static const QString Gcode = ".ngc"; - inline static const QString Dxfplot = ".dxfplot"; - }; - - explicit Application(); - - config::Config& config(); - void setConfig(config::Config&& config); - - /// Select tool used as configuration for further operations - bool selectTool(const QString& toolName); - void defaultToolFromCmd(const QString& toolName); - - /// Select profile used as configuration for further operations - bool selectProfile(const QString& profileName); - void defaultProfileFromCmd(const QString& profileName); - - const QString& lastHandledFileBaseName() const; - const QString& lastSavedDxfplotFileName() const; - const QString& lastSavedGcodeFileName() const; - void loadFileFromCmd(const QString& fileName); - bool loadFile(const QString& fileName); - bool loadFromDxf(const QString& fileName); - bool loadFromDxfplot(const QString& fileName); - - bool saveToGcode(const QString& fileName); - bool saveToDxfplot(const QString& fileName); - - void leftCutterCompensation(); - void rightCutterCompensation(); - void resetCutterCompensation(); - void pocketSelection(); - - geometry::Rect selectionBoundingRect() const; - void transformSelection(const QTransform& matrix); - - void hideSelection(); - void showHidden(); - - void optimizeOrder(); - - Simulation createSimulation(); - - void takeDocumentSnapshot(); - void undoDocumentChanges(); - void redoDocumentChanges(); - -Q_SIGNALS: - void newDocumentOpened(Document* newDocument); - void documentRestoredFromHistory(Document* newDocument); - void titleChanged(QString title); - void configChanged(config::Config& config); - void toolChanged(); - void errorRaised(const QString& message) const; - void fileSaved(const QString& fileName); -}; - -} diff --git a/src/model/application/CMakeLists.txt b/src/model/application/CMakeLists.txt new file mode 100644 index 0000000..06468db --- /dev/null +++ b/src/model/application/CMakeLists.txt @@ -0,0 +1,23 @@ +# Application and Simulation orchestrate the importers/exporters, which in turn consume +# model types. Keeping them in their own target keeps the CMake target graph acyclic. +set(SRC + simulation.cpp + application.cpp + documentmodelobserver.cpp +) + +add_module_library(model-application ${SRC}) +target_link_libraries(model-application + PUBLIC model + PRIVATE + config + common + geometry + geometry-filter + importer-dxf + importer-dxfplot + exporter-gcode + exporter-dxfplot + exporter-renderer +) +add_coverage(model-application) diff --git a/src/model/application/application.cpp b/src/model/application/application.cpp new file mode 100644 index 0000000..cb7615d --- /dev/null +++ b/src/model/application/application.cpp @@ -0,0 +1,469 @@ +module; + +#include +import importer.dxf.importer; +import importer.dxfplot.importer; +import exporter.gcode.exporter; +import exporter.dxfplot.exporter; +#include +#include +#include +#include +#include +#include +#include + +export module model.application; + +import common.exception; +import config.config; +import geometry.filter.assembler; +import geometry.filter.cleaner; +import geometry.filter.removeexactduplicate; +import model.document; +import model.documenthistory; +import model.simulation; +import geometry.polyline; +import geometry.rect; +import importer.dxf.layer; +import model.layer; +import model.path; +import model.pathsettings; +import model.task; +namespace model +{ + +static const QString configFileName = "config.yml"; + +static std::string configFilePath() +{ + const QDir dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)); + + // Ensure the path exists + dir.mkpath("."); + + const QString path = dir.filePath(configFileName); + + return path.toStdString(); +} + +} + +export namespace model +{ + +class Application : public QObject +{ + Q_OBJECT; + +private: + /// Global configuration + config::Config m_config; + + const config::Tools::Tool* m_defaultToolConfig; + const config::Profiles::Profile* m_defaultProfileConfig; + + // Last opened or saved file base name. + QString m_lastHandledFileBaseName; + QString m_lastSavedGcodeFileName; + QString m_lastSavedDxfplotFileName; + + Document::UPtr m_openedDocument; + DocumentHistory::UPtr m_documentHistory; + + void setOpenedDocument(Document::UPtr&& document) + { + m_openedDocument = std::move(document); + m_documentHistory = std::make_unique(*m_openedDocument); + + emit newDocumentOpened(m_openedDocument.get()); + } + + void setRestoredDocument(const Document& documentVersion) + { + m_openedDocument = std::make_unique(documentVersion); + emit documentRestoredFromHistory(m_openedDocument.get()); + } + + static QString baseName(const QString& fileName) + { + const QFileInfo fileInfo(fileName); + return fileInfo.absoluteDir().filePath(fileInfo.baseName()); + } + + void resetLastSavedFileNames() + { + m_lastSavedDxfplotFileName.clear(); + m_lastSavedGcodeFileName.clear(); + } + + PathSettings 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* 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* findProfile(const std::string& name) const + { + const config::Profiles& profiles = m_config.root().profiles(); + if (profiles.has(name)) { + return &profiles[name]; + } + + return nullptr; + } + + void 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 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 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; + } + + template + bool saveToFile(Exporter&& exporter, const QString& fileName) + { + qInfo() << "Saving to " << fileName; + std::ofstream output(fileName.toStdString()); + if (output) { + exporter(*m_openedDocument, output); + m_lastHandledFileBaseName = baseName(fileName); + emit fileSaved(fileName); + return true; + } + + return false; + } + +public: + struct FileExtension + { + inline static const QString Gcode = ".ngc"; + inline static const QString Dxfplot = ".dxfplot"; + }; + + explicit 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& config() { return m_config; } + + void setConfig(config::Config&& config) + { + m_config = std::move(config); + emit configChanged(m_config); + } + + /// Select tool used as configuration for further operations + bool 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); + emit toolChanged(); + } + m_defaultToolConfig = tool; + + return true; + } + + return false; + } + + void defaultToolFromCmd(const QString& toolName) + { + if (!selectTool(toolName)) { + qCritical() << "Invalid tool name " << toolName; + } + } + + /// Select profile used as configuration for further operations + bool 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 defaultProfileFromCmd(const QString& profileName) + { + if (!selectProfile(profileName)) { + qCritical() << "Invalid profile name " << profileName; + } + } + + const QString& lastHandledFileBaseName() const { return m_lastHandledFileBaseName; } + + const QString& lastSavedDxfplotFileName() const { return m_lastSavedDxfplotFileName; } + + const QString& lastSavedGcodeFileName() const { return m_lastSavedGcodeFileName; } + + void loadFileFromCmd(const QString& fileName) + { + if (!fileName.isEmpty()) { + loadFile(fileName); + } + } + + bool loadFile(const QString& fileName) + { + qInfo() << "Opening " << fileName; + + const QMimeDatabase db; + const QMimeType mime = db.mimeTypeForFile(fileName); + const QString mineName = mime.name(); + + if (mineName == "image/vnd.dxf") { + if (!loadFromDxf(fileName)) { + return false; + } + } else if (mineName == "text/plain") { + loadFromDxfplot(fileName); + } else { + qCritical() << "Invalid file type: " << fileName; + return false; + } + + 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 loadFromDxf(const QString& fileName) + { + const config::Import::Dxf& dxf = m_config.root().import().dxf(); + + 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; + } + + return true; + } + + bool 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 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 saveToDxfplot(const QString& fileName) + { + exporter::dxfplot::Exporter exporter; + + const bool saved = saveToFile(exporter, fileName); + if (saved) { + m_lastSavedDxfplotFileName = fileName; + } + + return saved; + } + + void leftCutterCompensation() + { + cutterCompensation(1.0f); + + takeDocumentSnapshot(); + } + + void rightCutterCompensation() + { + cutterCompensation(-1.0f); + + takeDocumentSnapshot(); + } + + void resetCutterCompensation() + { + Task& task = m_openedDocument->task(); + task.resetCutterCompensationSelection(); + + takeDocumentSnapshot(); + } + + void 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 selectionBoundingRect() const + { + Task& task = m_openedDocument->task(); + return task.selectionBoundingRect(); + } + + void transformSelection(const QTransform& matrix) + { + Task& task = m_openedDocument->task(); + task.transformSelection(matrix); + + takeDocumentSnapshot(); + } + + void hideSelection() + { + Task& task = m_openedDocument->task(); + task.hideSelection(); + + takeDocumentSnapshot(); + } + + void showHidden() + { + Task& task = m_openedDocument->task(); + task.showHidden(); + + takeDocumentSnapshot(); + } + + void optimizeOrder() + { + const config::Optimize& optimize = m_config.root().optimize(); + + Task& task = m_openedDocument->task(); + task.optimizeOrder(optimize.maintainPathLengthOrder(), optimize.lengthPrecision(), + optimize.distancePrecision()); + } + + Simulation createSimulation() + { + const float fastMoveFeedRate = m_config.root().simulation().fastMoveFeedRate(); + return Simulation(*m_openedDocument, fastMoveFeedRate); + } + + void takeDocumentSnapshot() { m_documentHistory->takeSnapshot(*m_openedDocument); } + + void undoDocumentChanges() { setRestoredDocument(m_documentHistory->undo()); } + + void redoDocumentChanges() { setRestoredDocument(m_documentHistory->redo()); } + +Q_SIGNALS: + void newDocumentOpened(Document* newDocument); + void documentRestoredFromHistory(Document* newDocument); + void titleChanged(QString title); + void configChanged(config::Config& config); + void toolChanged(); + void errorRaised(const QString& message) const; + void fileSaved(const QString& fileName); +}; + +} + +#include "application.moc" diff --git a/src/model/documentmodelobserver.h b/src/model/application/documentmodelobserver.cpp similarity index 92% rename from src/model/documentmodelobserver.h rename to src/model/application/documentmodelobserver.cpp index 6c91bc7..d0c43b9 100644 --- a/src/model/documentmodelobserver.h +++ b/src/model/application/documentmodelobserver.cpp @@ -1,14 +1,15 @@ -#pragma once +module; #include -#include +export module model.documentmodelobserver; -namespace model +import model.application; +import model.document; +import model.task; +export namespace model { -class Document; - /** @brief A observer on Document changes * e.g when a file is opened the document is changed and this observers is notified. * Function @ref documentChanged is called at every changes diff --git a/src/model/application/simulation.cpp b/src/model/application/simulation.cpp new file mode 100644 index 0000000..1bc1204 --- /dev/null +++ b/src/model/application/simulation.cpp @@ -0,0 +1,389 @@ +module; + +#include +#include +#include +import exporter.renderer.renderer; + +#include + +export module model.simulation; + +import common.aggregable; +import config.config; +import geometry.arc; +import geometry.bulge; +import geometry.line; +import geometry.polyline; +import geometry.utils; +import model.document; +namespace model +{ + +float mmPerMinTommPerSec(float mmPerMin) +{ + return mmPerMin / 60.0f; +} + +float entityDuration(const geometry::Line& line, float feedRateMmPerMin) +{ + return line.length() / mmPerMinTommPerSec(feedRateMmPerMin); +} + +float entityDuration(float start, float end, float feedRateMmPerMin) +{ + return std::abs(end - start) / mmPerMinTommPerSec(feedRateMmPerMin); +} + +float entityDuration(const geometry::Arc& arc, float feedRateMmPerMin) +{ + return arc.length() / mmPerMinTommPerSec(feedRateMmPerMin); +} + +} + +export namespace model +{ + +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, model::Simulation::MoveType moveType) + : position(position) + , moveType(moveType) + { + } + }; + +private: + class Traversable + { + protected: + float m_startTime; + float m_duration; + MoveType m_moveType; + + float timeFactor(float time) const { return (time - m_startTime) / m_duration; } + + public: + explicit Traversable(float startTime, float duration, MoveType moveType) + : m_startTime(startTime) + , m_duration(duration) + , m_moveType(moveType) + { + } + + float startTime() const { return m_startTime; } + + float endTime() const { return m_startTime + m_duration; } + + float duration() const { return m_duration; } + }; + + class PlaneLineMotion : public Traversable + { + private: + geometry::Line m_line; + float m_depth; + + public: + explicit 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 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 endDepth() const { return m_depth; } + + const QVector2D& endPlanePos() const { return m_line.end(); } + + template + void approximateToLinesVisit(float maxError, Visitor&& visitor) const + { + visitor(QVector3D(m_line.end(), m_depth), m_moveType); + } + }; + + class PlaneArcMotion : public Traversable + { + private: + geometry::Arc m_arc; + float m_depth; + + public: + explicit 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 pointAtTime(float time) const + { + 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); + } + + float endDepth() const { return m_depth; } + + const QVector2D& endPlanePos() const { return m_arc.end(); } + + template + void approximateToLinesVisit(float maxError, Visitor&& visitor) const + { + m_arc.approximateToLinesVisit(maxError, [this, &visitor](const QVector2D& point) { + visitor(QVector3D(point, m_depth), m_moveType); + }); + } + }; + + class DepthMotion : public Traversable + { + private: + QVector2D m_planePos; + float m_fromDepth; + float m_toDepth; + + public: + explicit 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 pointAtTime(float time) const + { + const float depth = geometry::Lerp(m_fromDepth, m_toDepth, timeFactor(time)); + return ToolPathPoint3D(QVector3D(m_planePos, depth), m_moveType); + } + + float endDepth() const { return m_toDepth; } + + const QVector2D& endPlanePos() const { return m_planePos; } + + template + void approximateToLinesVisit(float maxError, Visitor&& visitor) const + { + visitor(QVector3D(m_planePos, m_toDepth), m_moveType); + } + }; + + using Motion = std::variant; + + using MotionList = std::vector; + + class RenderVisitor + { + private: + const float m_fastMoveFeedRate; + + MotionList m_motions; + + 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, MoveType moveType) + { + addMotion(DepthMotion(m_cursorInPlane, m_cursorDepth, toDepth, feedRate, m_cursorTime, moveType)); + } + + 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, moveType)); + } + } + + void moveCursorInPlaneAlong(const geometry::Line& line, float feedRate, MoveType moveType) + { + if (line.lengthNonZero()) { + addMotion(PlaneLineMotion(m_cursorDepth, line, feedRate, m_cursorTime, moveType)); + } + } + + void moveCursorInPlaneAlong(const geometry::Arc& arc, float feedRate, MoveType moveType) + { + addMotion(PlaneArcMotion(m_cursorDepth, arc, feedRate, m_cursorTime, moveType)); + } + + public: + explicit RenderVisitor(float fastMoveFeedRate) + : m_fastMoveFeedRate(fastMoveFeedRate) + { + } + + void start(const QVector2D& from, float safetyDepth) + { + initCursor(from, 0.0f); + linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate, MoveType::FastWithoutCut); + } + + void end(const QVector2D& to, float safetyDepth) + { + linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate, MoveType::FastWithoutCut); + } + + void startOperation(const QVector2D& to, float intensity) + { + linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate, MoveType::FastWithoutCut); + } + + void endOperation(float safetyDepth) + { + linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate, MoveType::FastWithoutCut); + } + + void processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, + float depthFeedRate) + { + linearMoveCursorInDepthTo(depth, depthFeedRate, MoveType::NormalWithCut); + + polyline.forEachBulge([this, planeFeedRate](const geometry::Bulge& bulge) { + if (bulge.isArc()) { + moveCursorInPlaneAlong(bulge.toArc(), planeFeedRate, MoveType::NormalWithCut); + } else { + moveCursorInPlaneAlong(bulge.toLine(), planeFeedRate, MoveType::NormalWithCut); + } + }); + } + + MotionList&& motions() { return std::move(m_motions); } + }; + MotionList m_motions; + float m_duration; + float m_toolRadius; + + const Simulation::Motion& 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; + } + + static Simulation::MotionList renderDocumentToMotions(const Document& document, float fastMoveFeedRate) + { + const config::Tools::Tool& tool = document.toolConfig(); + const config::Profiles::Profile& profile = document.profileConfig(); + + RenderVisitor visitor(fastMoveFeedRate); + exporter::renderer::Renderer renderer(tool, profile, visitor); + + renderer.render(document); + + return visitor.motions(); + } + + static float totalDurationOfMotions(const MotionList& motions) + { + const Motion& lastMotion = motions.back(); + return std::visit( + [](const auto& motion) { + return motion.endTime(); + }, + lastMotion); + } + +public: + Simulation() = default; + explicit Simulation(const Document& document, float fastMoveFeedRate) + : m_motions(renderDocumentToMotions(document, fastMoveFeedRate)) + , m_duration(totalDurationOfMotions(m_motions)) + , m_toolRadius(document.toolConfig().general().radius()) + { + } + + Simulation::ToolPathPoint3D toolPositionAtTime(float time) const + { + const Motion& motion = findMotionAtTime(time); + return std::visit( + [time](auto& arg) { + return arg.pointAtTime(time); + }, + motion); + } + + float duration() const { return m_duration; } + + float toolRadius() const { return m_toolRadius; } + + Simulation::ToolPathPoint3D::List 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) { + motion.approximateToLinesVisit(maxError, pushBackPoint); + }, + motionVariant); + }); + + return points; + } +}; + +} diff --git a/src/model/document.cpp b/src/model/document.cpp index e2251bf..7de389b 100644 --- a/src/model/document.cpp +++ b/src/model/document.cpp @@ -1,51 +1,53 @@ -#include +module; -namespace model -{ +#include -Document::Document(Task::UPtr&& task, const config::Tools::Tool& toolConfig, - const config::Profiles::Profile& profileConfig) - : m_task(std::move(task)) - , m_toolConfig(&toolConfig) - , m_profileConfig(&profileConfig) -{ -} +export module model.document; -Document::Document(const Document& other) - : m_task(std::make_unique(other.task())) - , m_toolConfig(&other.toolConfig()) - , m_profileConfig(&other.profileConfig()) +import config.config; +import model.path; +import model.task; +import common.aggregable; +export namespace model { -} -Task& Document::task() +class Document : public common::Aggregable { - return *m_task; -} +private: + Task::UPtr m_task; + const config::Tools::Tool* m_toolConfig; + const config::Profiles::Profile* m_profileConfig; -const Task& Document::task() const -{ - return *m_task; -} +public: + explicit Document(Task::UPtr&& task, const config::Tools::Tool& toolConfig, + const config::Profiles::Profile& profileConfig) + : m_task(std::move(task)) + , m_toolConfig(&toolConfig) + , m_profileConfig(&profileConfig) + { + } -const config::Tools::Tool& Document::toolConfig() const -{ - return *m_toolConfig; -} + Document() = default; + explicit Document(const Document& other) + : m_task(std::make_unique(other.task())) + , m_toolConfig(&other.toolConfig()) + , m_profileConfig(&other.profileConfig()) + { + } -const config::Profiles::Profile& Document::profileConfig() const -{ - return *m_profileConfig; -} + Document& operator=(Document&&) = default; -void Document::setToolConfig(const config::Tools::Tool& tool) -{ - m_toolConfig = &tool; -} + Task& task() { return *m_task; } -void Document::setProfileConfig(const config::Profiles::Profile& profile) -{ - m_profileConfig = &profile; -} + const Task& task() const { return *m_task; } + + const config::Tools::Tool& toolConfig() const { return *m_toolConfig; } + + const config::Profiles::Profile& profileConfig() const { return *m_profileConfig; } + + void setToolConfig(const config::Tools::Tool& tool) { m_toolConfig = &tool; } + + void setProfileConfig(const config::Profiles::Profile& profile) { m_profileConfig = &profile; } +}; } diff --git a/src/model/document.h b/src/model/document.h deleted file mode 100644 index b6be5aa..0000000 --- a/src/model/document.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include - -namespace model -{ - -class Document : public common::Aggregable -{ -private: - Task::UPtr m_task; - const config::Tools::Tool* m_toolConfig; - const config::Profiles::Profile* m_profileConfig; - -public: - explicit Document(Task::UPtr&& task, const config::Tools::Tool& toolConfig, - const config::Profiles::Profile& profileConfig); - Document() = default; - explicit Document(const Document& other); - - Document& operator=(Document&&) = default; - - Task& task(); - const Task& task() const; - - const config::Tools::Tool& toolConfig() const; - const config::Profiles::Profile& profileConfig() const; - void setToolConfig(const config::Tools::Tool& tool); - void setProfileConfig(const config::Profiles::Profile& profile); -}; - -} diff --git a/src/model/documenthistory.cpp b/src/model/documenthistory.cpp index c5bd231..869449d 100644 --- a/src/model/documenthistory.cpp +++ b/src/model/documenthistory.cpp @@ -1,55 +1,62 @@ -#include +module; #include +#include -namespace model -{ +export module model.documenthistory; -bool DocumentHistory::isCurrentDocumentLastOfHistory() const +import model.document; +import common.aggregable; +export namespace model { - return m_currentDocumentIt == (m_documentHistory.end() - 1); -} -bool DocumentHistory::isCurrentDocumentFirstOfHistory() const +class DocumentHistory : public common::Aggregable { - return m_currentDocumentIt == m_documentHistory.begin(); -} +private: + Document::List m_documentHistory; + Document::List::iterator m_currentDocumentIt; -DocumentHistory::DocumentHistory(const Document& initialDocument) - : m_currentDocumentIt(m_documentHistory.insert(m_documentHistory.end(), initialDocument)) -{ -} + bool isCurrentDocumentLastOfHistory() const { return m_currentDocumentIt == (m_documentHistory.end() - 1); } -void DocumentHistory::takeSnapshot(const Document& currentDocument) -{ - if (!isCurrentDocumentLastOfHistory()) { - m_documentHistory.erase(m_currentDocumentIt + 1, m_documentHistory.end()); - } + bool isCurrentDocumentFirstOfHistory() const { return m_currentDocumentIt == m_documentHistory.begin(); } - constexpr int MaximumSnapshots = 100; - if (m_documentHistory.size() == MaximumSnapshots) { - m_documentHistory.erase(m_documentHistory.begin()); +public: + explicit DocumentHistory(const Document& initialDocument) + : m_currentDocumentIt(m_documentHistory.insert(m_documentHistory.end(), initialDocument)) + { } - m_currentDocumentIt = m_documentHistory.insert(m_documentHistory.end(), currentDocument); -} + void takeSnapshot(const Document& currentDocument) + { + if (!isCurrentDocumentLastOfHistory()) { + m_documentHistory.erase(m_currentDocumentIt + 1, m_documentHistory.end()); + } -const Document& DocumentHistory::undo() -{ - if (!isCurrentDocumentFirstOfHistory()) { - --m_currentDocumentIt; + constexpr int MaximumSnapshots = 100; + if (m_documentHistory.size() == MaximumSnapshots) { + m_documentHistory.erase(m_documentHistory.begin()); + } + + m_currentDocumentIt = m_documentHistory.insert(m_documentHistory.end(), currentDocument); } - return *m_currentDocumentIt; -} + const Document& undo() + { + if (!isCurrentDocumentFirstOfHistory()) { + --m_currentDocumentIt; + } -const Document& DocumentHistory::redo() -{ - if (!isCurrentDocumentLastOfHistory()) { - ++m_currentDocumentIt; + return *m_currentDocumentIt; } - return *m_currentDocumentIt; -} + const Document& redo() + { + if (!isCurrentDocumentLastOfHistory()) { + ++m_currentDocumentIt; + } + + return *m_currentDocumentIt; + } +}; } diff --git a/src/model/documenthistory.h b/src/model/documenthistory.h deleted file mode 100644 index e44dfd1..0000000 --- a/src/model/documenthistory.h +++ /dev/null @@ -1,27 +0,0 @@ -#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/layer.cpp b/src/model/layer.cpp index 23e3603..630e880 100644 --- a/src/model/layer.cpp +++ b/src/model/layer.cpp @@ -1,6 +1,54 @@ -#include +module; -#include +#include +#include +#include +#include +#include +#include +#include +#include + +export module model.layer; + +import common.aggregable; +import common.copy; +import model.path; +import model.renderable; +export namespace model +{ + +class Layer : public Renderable, public common::Aggregable +{ + Q_OBJECT; + + friend serializer::Access; + +private: + Path::ListUPtr m_children; + + void assignSelfToChildren(); + +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); + const Path& childrenAt(int index) const; + int childIndexFor(const Path& child) const; + + template + void forEachChild(Functor&& functor) + { + for (Path::UPtr& child : m_children) { + functor(*child); + } + } +}; + +} namespace model { @@ -8,7 +56,7 @@ namespace model void Layer::assignSelfToChildren() { for (Path::UPtr& child : m_children) { - child->setLayer(*this); + child->setParent(this); } } @@ -23,9 +71,7 @@ Layer::Layer(const Layer& other) : Renderable(other) , m_children(common::deepcopy(other.m_children)) { - for (Path::UPtr& child : m_children) { - child->setLayer(*this); - } + assignSelfToChildren(); } int Layer::childrenCount() const @@ -60,3 +106,5 @@ int Layer::childIndexFor(const Path& child) const } } + +#include "layer.moc" diff --git a/src/model/layer.h b/src/model/layer.h deleted file mode 100644 index d2055be..0000000 --- a/src/model/layer.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include - -namespace model -{ - -class Layer : public Renderable, public common::Aggregable -{ - Q_OBJECT; - - friend serializer::Access; - -private: - Path::ListUPtr m_children; - - void assignSelfToChildren(); - -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); - const Path& childrenAt(int index) const; - int childIndexFor(const Path& child) const; - - template - void forEachChild(Functor&& functor) - { - for (Path::UPtr& child : m_children) { - functor(*child); - } - } -}; - -} diff --git a/src/model/offsettedpath.cpp b/src/model/offsettedpath.cpp index 27fd6ab..b3fff65 100644 --- a/src/model/offsettedpath.cpp +++ b/src/model/offsettedpath.cpp @@ -1,54 +1,86 @@ -#include +module; -namespace model -{ +#include +#include +#include +#include -OffsettedPath::OffsettedPath(geometry::Polyline::List&& offsettedPolylines, Direction direction) - : m_polylines(offsettedPolylines) - , m_direction(direction) -{ -} +export module model.offsettedpath; -OffsettedPath::OffsettedPath(const OffsettedPath& other) - : QObject() - , m_polylines(other.m_polylines) - , m_direction(other.m_direction) +import common.aggregable; +import geometry.polyline; +import geometry.rect; +import geometry.utils; +import model.pathsettings; +import model.renderable; +export namespace model { -} -const geometry::Polyline::List& OffsettedPath::polylines() const +class OffsettedPath : public QObject { - return m_polylines; -} + Q_OBJECT -geometry::CuttingDirection OffsettedPath::cuttingDirection() const -{ - static const geometry::CuttingDirection offsetDirectionToCuttingDirection[] = { - geometry::CuttingDirection::FORWARD, // OffsettedPath::Direction::LEFT - geometry::CuttingDirection::BACKWARD // OffsettedPath::Direction::RIGHT - }; + friend serializer::Access; - return offsetDirectionToCuttingDirection[static_cast(m_direction)]; -} +public: + enum class Direction { LEFT = 0, RIGHT }; -void OffsettedPath::transform(const QTransform& matrix) -{ - for (geometry::Polyline& polyline : m_polylines) { - polyline.transform(matrix); +private: + geometry::Polyline::List m_polylines; + Direction m_direction; + +public: + explicit OffsettedPath(geometry::Polyline::List&& offsettedPolylines, Direction direction) + : m_polylines(offsettedPolylines) + , m_direction(direction) + { } - emit polylinesTransformed(); -} + explicit OffsettedPath(const OffsettedPath& other) + : QObject() + , m_polylines(other.m_polylines) + , m_direction(other.m_direction) + { + } -geometry::Rect OffsettedPath::boundingRect() const -{ - geometry::Polyline::List::const_iterator it = m_polylines.begin(); - const geometry::Rect firstBoundingRest = (it++)->boundingRect(); + explicit OffsettedPath() = default; - return std::transform_reduce(it, m_polylines.end(), firstBoundingRest, std::bit_or(), - [](const geometry::Polyline& polyline) { - return polyline.boundingRect(); - }); -} + const geometry::Polyline::List& polylines() const { return m_polylines; } + + geometry::CuttingDirection cuttingDirection() const + { + static const geometry::CuttingDirection offsetDirectionToCuttingDirection[] = { + geometry::CuttingDirection::FORWARD, // OffsettedPath::Direction::LEFT + geometry::CuttingDirection::BACKWARD // OffsettedPath::Direction::RIGHT + }; + + return offsetDirectionToCuttingDirection[static_cast(m_direction)]; + } + + void transform(const QTransform& matrix) + { + for (geometry::Polyline& polyline : m_polylines) { + polyline.transform(matrix); + } + + emit polylinesTransformed(); + } + + geometry::Rect boundingRect() const + { + geometry::Polyline::List::const_iterator it = m_polylines.begin(); + const geometry::Rect firstBoundingRest = (it++)->boundingRect(); + + return std::transform_reduce(it, m_polylines.end(), firstBoundingRest, std::bit_or(), + [](const geometry::Polyline& polyline) { + return polyline.boundingRect(); + }); + } + +Q_SIGNALS: + void polylinesTransformed(); +}; } + +#include "offsettedpath.moc" diff --git a/src/model/offsettedpath.h b/src/model/offsettedpath.h deleted file mode 100644 index 23cca08..0000000 --- a/src/model/offsettedpath.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -#include - -namespace model -{ - -class Layer; - -class OffsettedPath : public QObject -{ - Q_OBJECT - - friend serializer::Access; - -public: - enum class Direction { LEFT = 0, RIGHT }; - -private: - geometry::Polyline::List m_polylines; - Direction m_direction; - -public: - explicit OffsettedPath(geometry::Polyline::List&& offsettedPolylines, Direction direction); - explicit OffsettedPath(const OffsettedPath& other); - explicit OffsettedPath() = default; - - const geometry::Polyline::List& polylines() const; - geometry::CuttingDirection cuttingDirection() const; - - void transform(const QTransform& matrix); - - geometry::Rect boundingRect() const; - -Q_SIGNALS: - void polylinesTransformed(); -}; - -} diff --git a/src/model/path.cpp b/src/model/path.cpp index 1be90ba..df02fdf 100644 --- a/src/model/path.cpp +++ b/src/model/path.cpp @@ -1,182 +1,160 @@ -#include -#include -#include - -#include -#include - -namespace model -{ +module; -void Path::updateGlobalVisibility() -{ - const bool newGloballyVisible = visible() && m_layer->visible(); - if (m_globallyVisible != newGloballyVisible) { - m_globallyVisible = newGloballyVisible; - - emit globalVisibilityChanged(m_globallyVisible); +#include +#include +#include +#include +#include +#include + +export module model.path; + +import common.aggregable; +import geometry.bulge; +import geometry.filter.cleaner; +import geometry.pocketer; +import geometry.polyline; +import geometry.rect; +import geometry.utils; +import model.offsettedpath; +import model.pathsettings; +import model.renderable; +import common.copy; +export namespace model +{ + +class Path : public Renderable, public common::Aggregable +{ + Q_OBJECT; + + friend serializer::Access; + +private: + geometry::Polyline m_basePolyline; + std::unique_ptr m_offsettedPath; + PathSettings m_settings; + +public: + explicit Path(geometry::Polyline&& basePolyline, const std::string& name, const PathSettings& settings) + : Renderable(name) + , m_basePolyline(basePolyline) + , m_settings(settings) + { } -} -Path::Path(geometry::Polyline&& basePolyline, const std::string& name, const PathSettings& settings) - : Renderable(name) - , m_basePolyline(basePolyline) - , m_settings(settings) - , m_globallyVisible(true) -{ - 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); + explicit Path(const Path& other) + : Renderable(other) + , m_basePolyline(other.m_basePolyline) + , m_settings(other.m_settings) + { + if (other.m_offsettedPath) { + m_offsettedPath = std::make_unique(*other.m_offsettedPath); + } } -} - -Path::Path() -{ - connect(this, &Path::visibilityChanged, this, &Path::updateGlobalVisibility); -} - -Path::ListUPtr Path::FromPolylines(geometry::Polyline::List&& polylines, const PathSettings& settings, - const std::string& layerName) -{ - const int size = polylines.size(); - Path::ListUPtr paths(size); - for (int i = 0; i < size; ++i) { - static constexpr char pathNameFormat[] = "({}) {}"; - const std::string pathName = fmt::format(pathNameFormat, layerName, i); - paths[i].reset(new Path(std::move(polylines[i]), pathName, settings)); - } + explicit Path() = default; - return paths; -} + static Path::ListUPtr FromPolylines(geometry::Polyline::List&& polylines, const PathSettings& settings, + const std::string& layerName) + { + const int size = polylines.size(); + Path::ListUPtr paths(size); -Layer& Path::layer() -{ - return *m_layer; -} + for (int i = 0; i < size; ++i) { + static constexpr char pathNameFormat[] = "({}) {}"; + const std::string pathName = fmt::format(pathNameFormat, layerName, i); + paths[i].reset(new Path(std::move(polylines[i]), pathName, settings)); + } -const Layer& Path::layer() const -{ - return *m_layer; -} + return paths; + } -void Path::setLayer(Layer& layer) -{ - m_layer = &layer; - updateGlobalVisibility(); - connect(m_layer, &Layer::visibilityChanged, this, &Path::updateGlobalVisibility); -} + const geometry::Polyline& basePolyline() const { return m_basePolyline; } -const geometry::Polyline& Path::basePolyline() const -{ - return m_basePolyline; -} + geometry::Polyline::List finalPolylines() const + { + return m_offsettedPath ? m_offsettedPath->polylines() : geometry::Polyline::List { m_basePolyline }; + } -geometry::Polyline::List Path::finalPolylines() const -{ - return m_offsettedPath ? m_offsettedPath->polylines() : geometry::Polyline::List { m_basePolyline }; -} + model::OffsettedPath* offsettedPath() const { return m_offsettedPath.get(); } -model::OffsettedPath* Path::offsettedPath() const -{ - return m_offsettedPath.get(); -} + void offset(float margin, float minimumPolylineLength, float minimumArcLength) + { + geometry::Polyline::List offsettedPolylines = m_basePolyline.offsetted(margin); + geometry::filter::Cleaner cleaner(std::move(offsettedPolylines), minimumPolylineLength, minimumArcLength); -void Path::offset(float margin, float minimumPolylineLength, float minimumArcLength) -{ - geometry::Polyline::List offsettedPolylines = m_basePolyline.offsetted(margin); - geometry::filter::Cleaner cleaner(std::move(offsettedPolylines), minimumPolylineLength, minimumArcLength); + const OffsettedPath::Direction direction + = (margin > 0.0f) ? OffsettedPath::Direction::LEFT : OffsettedPath::Direction::RIGHT; - const OffsettedPath::Direction direction - = (margin > 0.0f) ? OffsettedPath::Direction::LEFT : OffsettedPath::Direction::RIGHT; + geometry::Polyline::List cleanedPolylines = cleaner.polylines(); + if (!cleanedPolylines.empty()) { + m_offsettedPath = std::make_unique(std::move(cleanedPolylines), direction); + } - geometry::Polyline::List cleanedPolylines = cleaner.polylines(); - if (!cleanedPolylines.empty()) { - m_offsettedPath = std::make_unique(std::move(cleanedPolylines), direction); + emit offsettedPathChanged(); } - emit offsettedPathChanged(); -} + void resetOffset() + { + m_offsettedPath.reset(); -void Path::resetOffset() -{ - m_offsettedPath.reset(); + emit offsettedPathChanged(); + } - emit offsettedPathChanged(); -} + void pocket(const Path::ListCPtr& islands, float scaledRadius, float minimumPolylineLength, float minimumArcLength) + { + geometry::Polyline::ListCPtr polylineIslands(islands.size()); + std::transform(islands.begin(), islands.end(), polylineIslands.begin(), [](const Path* path) { + return &path->basePolyline(); + }); -void Path::pocket(const Path::ListCPtr& islands, float scaledRadius, float minimumPolylineLength, - float minimumArcLength) -{ - geometry::Polyline::ListCPtr polylineIslands(islands.size()); - std::transform(islands.begin(), islands.end(), polylineIslands.begin(), [](const Path* path) { - return &path->basePolyline(); - }); + geometry::Pocketer pocketer(m_basePolyline, polylineIslands, scaledRadius, minimumPolylineLength); + geometry::filter::Cleaner cleaner(std::move(pocketer.polylines()), minimumPolylineLength, minimumArcLength); - geometry::Pocketer pocketer(m_basePolyline, polylineIslands, scaledRadius, minimumPolylineLength); - geometry::filter::Cleaner cleaner(std::move(pocketer.polylines()), minimumPolylineLength, minimumArcLength); + // Pocket is left when polyline is CCW winded. + static const OffsettedPath::Direction basePolylineOrientationToPocketDirection[] = { + OffsettedPath::Direction::RIGHT, // Orientation::CW + OffsettedPath::Direction::LEFT // Orientation::CCW + }; + const OffsettedPath::Direction direction + = basePolylineOrientationToPocketDirection[static_cast(pocketer.borderOrientation())]; - // Pocket is left when polyline is CCW winded. - static const OffsettedPath::Direction basePolylineOrientationToPocketDirection[] = { - OffsettedPath::Direction::RIGHT, // Orientation::CW - OffsettedPath::Direction::LEFT // Orientation::CCW - }; - const OffsettedPath::Direction direction - = basePolylineOrientationToPocketDirection[static_cast(pocketer.borderOrientation())]; + m_offsettedPath = std::make_unique(cleaner.polylines(), direction); - m_offsettedPath = std::make_unique(cleaner.polylines(), direction); + emit offsettedPathChanged(); + } - emit offsettedPathChanged(); -} + void transform(const QTransform& matrix) + { + m_basePolyline.transform(matrix); + emit basePolylineTransformed(); -void Path::transform(const QTransform& matrix) -{ - m_basePolyline.transform(matrix); - emit basePolylineTransformed(); + if (m_offsettedPath) { + m_offsettedPath->transform(matrix); + } + } - if (m_offsettedPath) { - m_offsettedPath->transform(matrix); + geometry::Rect boundingRect() const + { + return (m_offsettedPath) ? m_offsettedPath->boundingRect() : m_basePolyline.boundingRect(); } -} -geometry::Rect Path::boundingRect() const -{ - return (m_offsettedPath) ? m_offsettedPath->boundingRect() : m_basePolyline.boundingRect(); -} + bool isPoint() const { return m_basePolyline.isPoint(); } -bool Path::isPoint() const -{ - return m_basePolyline.isPoint(); -} + const model::PathSettings& settings() const { return m_settings; } -const model::PathSettings& Path::settings() const -{ - return m_settings; -} + model::PathSettings& settings() { return m_settings; } -model::PathSettings& Path::settings() -{ - return m_settings; -} + geometry::CuttingDirection cuttingDirection() const + { + return (m_offsettedPath) ? m_offsettedPath->cuttingDirection() : geometry::CuttingDirection::FORWARD; + } -geometry::CuttingDirection Path::cuttingDirection() const -{ - return (m_offsettedPath) ? m_offsettedPath->cuttingDirection() : geometry::CuttingDirection::FORWARD; -} +Q_SIGNALS: + void offsettedPathChanged(); + void basePolylineTransformed(); +}; -bool Path::globallyVisible() const -{ - return m_globallyVisible; } -} +#include "path.moc" diff --git a/src/model/path.h b/src/model/path.h deleted file mode 100644 index 9e270a7..0000000 --- a/src/model/path.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include -#include - -#include - -#include - -namespace model -{ - -class Layer; - -class Path : public Renderable, public common::Aggregable -{ - Q_OBJECT; - - friend serializer::Access; - -private: - geometry::Polyline m_basePolyline; - std::unique_ptr m_offsettedPath; - PathSettings m_settings; - Layer* m_layer; - bool m_globallyVisible; - - void updateGlobalVisibility(); - -public: - explicit Path(geometry::Polyline&& basePolyline, const std::string& name, const PathSettings& settings); - explicit Path(const Path& other); - explicit Path(); - - static ListUPtr FromPolylines(geometry::Polyline::List&& polylines, const PathSettings& settings, - const std::string& layerName); - - Layer& layer(); - const Layer& layer() const; - void setLayer(Layer& layer); - - const geometry::Polyline& basePolyline() const; - geometry::Polyline::List finalPolylines() const; - - model::OffsettedPath* offsettedPath() const; - void offset(float margin, float minimumPolylineLength, float minimumArcLength); - void resetOffset(); - void pocket(const Path::ListCPtr& islands, float scaledRadius, float minimumPolylineLength, float minimumArcLength); - - void transform(const QTransform& matrix); - - geometry::Rect boundingRect() const; - - bool isPoint() const; - - const PathSettings& settings() const; - PathSettings& settings(); - - geometry::CuttingDirection cuttingDirection() const; - - bool globallyVisible() const; - -Q_SIGNALS: - void globalVisibilityChanged(bool globallyVisible); - void offsettedPathChanged(); - void basePolylineTransformed(); -}; - -} diff --git a/src/model/pathgroupsettings.cpp b/src/model/pathgroupsettings.cpp index bb16110..d4e85ca 100644 --- a/src/model/pathgroupsettings.cpp +++ b/src/model/pathgroupsettings.cpp @@ -1,53 +1,86 @@ -#include +module; -#include // TODO +#include +#include +#include +#include -namespace model -{ +export module model.pathgroupsettings; -PathGroupSettings::PathGroupSettings(const Task& task) - : m_task(task) +import common.function; +import model.path; +import model.pathsettings; +import model.task; +export namespace model { -} -std::optional PathGroupSettings::planeFeedRate() const +class PathGroupSettings : public QObject { - return valueIfAllEqual(&PathSettings::planeFeedRate); -} + Q_OBJECT; -void PathGroupSettings::setPlaneFeedRate(float planeFeedRate) -{ - setValue(&PathSettings::setPlaneFeedRate, planeFeedRate); -} +private: + const model::Task& m_task; -std::optional PathGroupSettings::depthFeedRate() const -{ - return valueIfAllEqual(&PathSettings::depthFeedRate); -} + /** Return value of a path settings property if all path have + * the same value for the given property. + * + * @tparam T The returned value type + * @tparam Getter The member function used to acces the poperty. + */ + template::Return> + std::optional valueIfAllEqual(Getter&& getter) const + { + // Reference value of last path to compare with. + Return lastValue; + bool firstValue = true; + bool allEqual = true; -void PathGroupSettings::setDepthFeedRate(float depthFeedRate) -{ - setValue(&PathSettings::setDepthFeedRate, depthFeedRate); -} + m_task.forEachSelectedPath([&lastValue, &firstValue, &allEqual, &getter](const model::Path& path) { + const Return& value = (path.settings().*getter)(); + if (!firstValue && lastValue != value) { + allEqual = false; + } + firstValue = false; + lastValue = value; + }); -std::optional PathGroupSettings::intensity() const -{ - return valueIfAllEqual(&PathSettings::intensity); -} + if (allEqual) { + return std::make_optional(lastValue); + } + return std::nullopt; + } -void PathGroupSettings::setIntensity(float intensity) -{ - setValue(&PathSettings::setIntensity, intensity); -} + template + void setValue(Setter&& setter, T value) + { + m_task.forEachSelectedPath([value, &setter](model::Path& path) { + (path.settings().*(std::forward(setter)))(value); + }); + } -std::optional PathGroupSettings::depth() const -{ - return valueIfAllEqual(&PathSettings::depth); -} +public: + explicit PathGroupSettings(const Task& task) + : m_task(task) + { + } -void PathGroupSettings::setDepth(float depth) -{ - setValue(&PathSettings::setDepth, depth); -} + std::optional planeFeedRate() const { return valueIfAllEqual(&PathSettings::planeFeedRate); } + + void setPlaneFeedRate(float planeFeedRate) { setValue(&PathSettings::setPlaneFeedRate, planeFeedRate); } + + std::optional depthFeedRate() const { return valueIfAllEqual(&PathSettings::depthFeedRate); } + + void setDepthFeedRate(float depthFeedRate) { setValue(&PathSettings::setDepthFeedRate, depthFeedRate); } + + std::optional intensity() const { return valueIfAllEqual(&PathSettings::intensity); } + + void setIntensity(float intensity) { setValue(&PathSettings::setIntensity, intensity); } + + std::optional depth() const { return valueIfAllEqual(&PathSettings::depth); } + + void setDepth(float depth) { setValue(&PathSettings::setDepth, depth); } +}; } + +#include "pathgroupsettings.moc" diff --git a/src/model/pathgroupsettings.h b/src/model/pathgroupsettings.h deleted file mode 100644 index e3e2687..0000000 --- a/src/model/pathgroupsettings.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#include - -namespace model -{ - -class PathGroupSettings : public QObject -{ - Q_OBJECT; - -private: - const model::Task& m_task; - - /** Return value of a path settings property if all path have - * the same value for the given property. - * - * @tparam T The returned value type - * @tparam Getter The member function used to acces the poperty. - */ - template::Return> - std::optional valueIfAllEqual(Getter&& getter) const - { - // Reference value of last path to compare with. - Return lastValue; - bool firstValue = true; - bool allEqual = true; - - m_task.forEachSelectedPath([&lastValue, &firstValue, &allEqual, &getter](const model::Path& path) { - const Return& value = (path.settings().*getter)(); - if (!firstValue && lastValue != value) { - allEqual = false; - } - firstValue = false; - lastValue = value; - }); - - if (allEqual) { - return std::make_optional(lastValue); - } - return std::nullopt; - } - - template - void setValue(Setter&& setter, T value) - { - m_task.forEachSelectedPath([value, &setter](model::Path& path) { - (path.settings().*(std::forward(setter)))(value); - }); - } - -public: - explicit PathGroupSettings(const Task& task); - - std::optional planeFeedRate() const; - void setPlaneFeedRate(float planeFeedRate); - - std::optional depthFeedRate() const; - void setDepthFeedRate(float depthFeedRate); - - std::optional intensity() const; - void setIntensity(float intensity); - - std::optional depth() const; - void setDepth(float depth); -}; - -} diff --git a/src/model/pathsettings.cpp b/src/model/pathsettings.cpp index cf1bfb6..6701365 100644 --- a/src/model/pathsettings.cpp +++ b/src/model/pathsettings.cpp @@ -1,54 +1,48 @@ -#include +module; -namespace model -{ +#include +#include -PathSettings::PathSettings(float planeFeedRate, float depthFeedRate, float intensity, float depth) - : m_planeFeedRate(planeFeedRate) - , m_depthFeedRate(depthFeedRate) - , m_intensity(intensity) - , m_depth(depth) -{ -} +export module model.pathsettings; -float PathSettings::planeFeedRate() const +export namespace model { - return m_planeFeedRate; -} -void PathSettings::setPlaneFeedRate(float planeFeedRate) +class PathSettings { - m_planeFeedRate = planeFeedRate; -} + friend serializer::Access; -float PathSettings::depthFeedRate() const -{ - return m_depthFeedRate; -} +private: + float m_planeFeedRate; + float m_depthFeedRate; + float m_intensity; + float m_depth; -void PathSettings::setDepthFeedRate(float depthFeedRate) -{ - m_depthFeedRate = depthFeedRate; -} +public: + explicit PathSettings() = default; + explicit PathSettings(float planeFeedRate, float depthFeedRate, float intensity, float depth) + : m_planeFeedRate(planeFeedRate) + , m_depthFeedRate(depthFeedRate) + , m_intensity(intensity) + , m_depth(depth) + { + } -float PathSettings::intensity() const -{ - return m_intensity; -} + float planeFeedRate() const { return m_planeFeedRate; } -void PathSettings::setIntensity(float intensity) -{ - m_intensity = intensity; -} + void setPlaneFeedRate(float planeFeedRate) { m_planeFeedRate = planeFeedRate; } -float PathSettings::depth() const -{ - return m_depth; -} + float depthFeedRate() const { return m_depthFeedRate; } -void PathSettings::setDepth(float depth) -{ - m_depth = depth; -} + void setDepthFeedRate(float depthFeedRate) { m_depthFeedRate = depthFeedRate; } + + float intensity() const { return m_intensity; } + + void setIntensity(float intensity) { m_intensity = intensity; } + + float depth() const { return m_depth; } + + void setDepth(float depth) { m_depth = depth; } +}; } diff --git a/src/model/pathsettings.h b/src/model/pathsettings.h deleted file mode 100644 index 8fa2444..0000000 --- a/src/model/pathsettings.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include - -namespace model -{ - -class PathSettings -{ - friend serializer::Access; - -private: - float m_planeFeedRate; - float m_depthFeedRate; - float m_intensity; - float m_depth; - -public: - explicit PathSettings() = default; - explicit PathSettings(float planeFeedRate, float depthFeedRate, float intensity, float depth); - - float planeFeedRate() const; - void setPlaneFeedRate(float planeFeedRate); - - float depthFeedRate() const; - void setDepthFeedRate(float depthFeedRate); - - float intensity() const; - void setIntensity(float intensity); - - float depth() const; - void setDepth(float depth); -}; - -} diff --git a/src/model/renderable.cpp b/src/model/renderable.cpp index ce77e98..1ebbb5e 100644 --- a/src/model/renderable.cpp +++ b/src/model/renderable.cpp @@ -1,64 +1,120 @@ -#include +module; -namespace model -{ +#include +#include +#include +#include +#include -Renderable::Renderable(const std::string& name) - : m_name(name) - , m_selected(false) - , m_visible(true) -{ -} +export module model.renderable; -Renderable::Renderable(const Renderable& other) - : QObject() - , m_name(other.name()) - , m_selected(false) - , m_visible(other.visible()) +import common.aggregable; +import geometry.polyline; +import model.pathsettings; +export namespace model { -} -const std::string& Renderable::name() const +class Renderable : public QObject { - return m_name; -} + Q_OBJECT; -bool Renderable::visible() const -{ - return m_visible; -} + friend serializer::Access; -void Renderable::setVisible(bool visible) -{ - if (m_visible != visible) { - m_visible = visible; +private: + std::string m_name; + Renderable* m_parent; + + struct + { + bool m_selected : 1; + bool m_visible : 1; + bool m_globallyVisible : 1; + }; - emit visibilityChanged(m_visible); +public: + explicit Renderable(const std::string& name) + : m_name(name) + , m_parent(nullptr) + , m_selected(false) + , m_visible(true) + , m_globallyVisible(true) + { } -} -void Renderable::toggleVisible() -{ - setVisible(!m_visible); -} + explicit Renderable() = default; + explicit Renderable(const Renderable& other) + : QObject() + , m_name(other.name()) + , m_selected(false) + , m_visible(other.visible()) + , m_globallyVisible(other.m_globallyVisible) -bool Renderable::selected() const -{ - return m_selected; -} + { + setParent(other.m_parent); + } -void Renderable::setSelected(bool selected) -{ - if (m_selected != selected) { - m_selected = selected; + const std::string& name() const { return m_name; } + Renderable* parent() const { return m_parent; } + void setParent(Renderable* parent) + { + if (m_parent) { + disconnect(m_parent, &Renderable::globalVisibilityChanged, this, &Renderable::updateGlobalVisibility); + } - emit selectedChanged(m_selected); + m_parent = parent; + updateGlobalVisibility(); + + if (m_parent) { + connect(m_parent, &Renderable::globalVisibilityChanged, this, &Renderable::updateGlobalVisibility); + } } -} -void Renderable::toggleSelect() -{ - setSelected(!m_selected); -} + bool visible() const { return m_visible; } + + void setVisible(bool visible) + { + if (m_visible != visible) { + m_visible = visible; + + updateGlobalVisibility(); + emit visibilityChanged(m_visible); + } + } + + void updateGlobalVisibility() + { + const bool newGloballyVisible = visible() && (!m_parent || m_parent->globallyVisible()); + if (m_globallyVisible != newGloballyVisible) { + m_globallyVisible = newGloballyVisible; + + emit globalVisibilityChanged(m_globallyVisible); + } + } + + bool globallyVisible() const { return m_globallyVisible; } + + void toggleVisible() { setVisible(!m_visible); } + + bool selected() const { return m_selected; } + + void setSelected(bool selected) + { + if (m_selected != selected) { + m_selected = selected; + + emit selectedChanged(m_selected); + } + } + + void deselect(); + void toggleSelect() { setSelected(!m_selected); } + +Q_SIGNALS: + void selectedChanged(bool selected); + void visibilityChanged(bool visible); + void globalVisibilityChanged(bool globallyVisible); +}; } + +#include "renderable.moc" diff --git a/src/model/renderable.h b/src/model/renderable.h deleted file mode 100644 index cef53f1..0000000 --- a/src/model/renderable.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include - -#include - -#include - -#include - -#include - -namespace model -{ - -class Renderable : public QObject -{ - Q_OBJECT; - - friend serializer::Access; - -private: - std::string m_name; - - struct - { - bool m_selected : 1; - bool m_visible : 1; - }; - -public: - explicit Renderable(const std::string& name); - explicit Renderable() = default; - explicit Renderable(const Renderable& other); - - const std::string& name() const; - - bool visible() const; - void setVisible(bool visible); - void toggleVisible(); - - bool selected() const; - void setSelected(bool selected); - void deselect(); - void toggleSelect(); - -Q_SIGNALS: - void selectedChanged(bool selected); - void visibilityChanged(bool visible); -}; - -} diff --git a/src/model/simulation.cpp b/src/model/simulation.cpp deleted file mode 100644 index 2cfea4f..0000000 --- a/src/model/simulation.cpp +++ /dev/null @@ -1,329 +0,0 @@ -#include -#include -#include - -#include // TODO - -namespace model -{ - -Simulation::ToolPathPoint3D::ToolPathPoint3D(const QVector3D& position, model::Simulation::MoveType moveType) - : position(position) - , moveType(moveType) -{ -} - -float mmPerMinTommPerSec(float mmPerMin) -{ - return mmPerMin / 60.0f; -} - -float entityDuration(const geometry::Line& line, float feedRateMmPerMin) -{ - return line.length() / mmPerMinTommPerSec(feedRateMmPerMin); -} - -float entityDuration(float start, float end, float feedRateMmPerMin) -{ - 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) - : m_startTime(startTime) - , 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; -} - -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, - 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; -} - -const QVector2D& Simulation::PlaneLineMotion::endPlanePos() const -{ - return m_line.end(); -} - -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 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); -} - -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, 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; -} - -const QVector2D& Simulation::DepthMotion::endPlanePos() const -{ - return m_planePos; -} - -class Simulation::RenderVisitor -{ -private: - const float m_fastMoveFeedRate; - - MotionList m_motions; - - 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, MoveType moveType) - { - addMotion(DepthMotion(m_cursorInPlane, m_cursorDepth, toDepth, feedRate, m_cursorTime, moveType)); - } - - 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, moveType)); - } - } - - void moveCursorInPlaneAlong(const geometry::Line& line, float feedRate, MoveType moveType) - { - if (line.lengthNonZero()) { - addMotion(PlaneLineMotion(m_cursorDepth, line, feedRate, m_cursorTime, moveType)); - } - } - - void moveCursorInPlaneAlong(const geometry::Arc& arc, float feedRate, MoveType moveType) - { - addMotion(PlaneArcMotion(m_cursorDepth, arc, feedRate, m_cursorTime, moveType)); - } - -public: - explicit RenderVisitor(float fastMoveFeedRate) - : m_fastMoveFeedRate(fastMoveFeedRate) - { - } - - void start(const QVector2D& from, float safetyDepth) - { - initCursor(from, 0.0f); - linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate, MoveType::FastWithoutCut); - } - - void end(const QVector2D& to, float safetyDepth) - { - linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate, MoveType::FastWithoutCut); - } - - void startOperation(const QVector2D& to, float intensity) - { - linearMoveCursorInPlaneTo(to, m_fastMoveFeedRate, MoveType::FastWithoutCut); - } - - void endOperation(float safetyDepth) - { - linearMoveCursorInDepthTo(safetyDepth, m_fastMoveFeedRate, MoveType::FastWithoutCut); - } - - void processPathAtDepth(const geometry::Polyline& polyline, float depth, float planeFeedRate, float depthFeedRate) - { - linearMoveCursorInDepthTo(depth, depthFeedRate, MoveType::NormalWithCut); - - polyline.forEachBulge([this, planeFeedRate](const geometry::Bulge& bulge) { - if (bulge.isArc()) { - moveCursorInPlaneAlong(bulge.toArc(), planeFeedRate, MoveType::NormalWithCut); - } else { - moveCursorInPlaneAlong(bulge.toLine(), planeFeedRate, MoveType::NormalWithCut); - } - }); - } - - MotionList&& motions() { return std::move(m_motions); } -}; - -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, float fastMoveFeedRate) -{ - const config::Tools::Tool& tool = document.toolConfig(); - const config::Profiles::Profile& profile = document.profileConfig(); - - RenderVisitor visitor(fastMoveFeedRate); - exporter::renderer::Renderer renderer(tool, profile, visitor); - - renderer.render(document); - - 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, float fastMoveFeedRate) - : m_motions(renderDocumentToMotions(document, fastMoveFeedRate)) - , m_duration(totalDurationOfMotions(m_motions)) - , m_toolRadius(document.toolConfig().general().radius()) -{ -} - -Simulation::ToolPathPoint3D Simulation::toolPositionAtTime(float time) const -{ - const Motion& motion = findMotionAtTime(time); - return std::visit( - [time](auto& arg) { - return arg.pointAtTime(time); - }, - motion); -} - -float Simulation::duration() const -{ - 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) { - motion.approximateToLinesVisit(maxError, pushBackPoint); - }, - motionVariant); - }); - - return points; -} - -} diff --git a/src/model/simulation.h b/src/model/simulation.h deleted file mode 100644 index 2cc282e..0000000 --- a/src/model/simulation.h +++ /dev/null @@ -1,143 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -#include - -namespace model -{ - -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; - - class Traversable - { - protected: - float m_startTime; - float m_duration; - MoveType m_moveType; - - float timeFactor(float time) const; - - public: - explicit Traversable(float startTime, float duration, MoveType moveType); - - float startTime() const; - float endTime() const; - float duration() const; - }; - - class PlaneLineMotion : public Traversable - { - private: - geometry::Line m_line; - float m_depth; - - public: - explicit PlaneLineMotion(float depth, const geometry::Line& line, float feedRate, float startTime, - MoveType moveType); - - ToolPathPoint3D pointAtTime(float time) const; - - float endDepth() const; - const QVector2D& endPlanePos() const; - - template - void approximateToLinesVisit(float maxError, Visitor&& visitor) const - { - visitor(QVector3D(m_line.end(), m_depth), m_moveType); - } - }; - - class PlaneArcMotion : public Traversable - { - private: - geometry::Arc m_arc; - float m_depth; - - public: - explicit PlaneArcMotion(float depth, const geometry::Arc& arc, float feedRate, float startTime, - MoveType moveType); - - ToolPathPoint3D pointAtTime(float time) const; - - 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), m_moveType); - }); - } - }; - - class DepthMotion : public Traversable - { - private: - QVector2D m_planePos; - float m_fromDepth; - float m_toDepth; - - public: - explicit DepthMotion(const QVector2D& planePos, float fromDepth, float toDepth, float feedRate, float startTime, - MoveType moveType); - - ToolPathPoint3D pointAtTime(float time) const; - - float endDepth() const; - const QVector2D& endPlanePos() const; - - template - void approximateToLinesVisit(float maxError, Visitor&& visitor) const - { - visitor(QVector3D(m_planePos, m_toDepth), m_moveType); - } - }; - - using Motion = std::variant; - - using MotionList = std::vector; - MotionList m_motions; - float m_duration; - float m_toolRadius; - - const Motion& findMotionAtTime(float time) const; - - static MotionList renderDocumentToMotions(const Document& document, float fastMoveFeedRate); - static float totalDurationOfMotions(const MotionList& motions); - -public: - Simulation() = default; - explicit Simulation(const Document& document, float fastMoveFeedRate); - - ToolPathPoint3D toolPositionAtTime(float time) const; - float duration() const; - - float toolRadius() const; - - ToolPathPoint3D::List approximatedToolPathToLines(float maxError) const; -}; - -} diff --git a/src/model/task.cpp b/src/model/task.cpp index fead5ac..06699e9 100644 --- a/src/model/task.cpp +++ b/src/model/task.cpp @@ -1,315 +1,354 @@ -#include +module; +#include #include -#include +#include +#include +#include + +export module model.task; + +import common.aggregable; +import common.copy; +import geometry.polyline; +import geometry.rect; +import model.layer; +import model.path; #ifdef WITH_ORTOOLS - #include +import geometry.orderoptimizer; #endif - namespace model { -void Task::initPathsFromLayers() +#ifdef WITH_ORTOOLS + +geometry::OrderOptimizer::NodesPerGroup generateNodesSingleGroup(const Path::ListPtr& paths) { - for (const Layer::UPtr& layer : m_layers) { - layer->forEachChild([this](Path& path) { - m_paths.push_back(&path); - }); + geometry::OrderOptimizer::Node::List group(paths.size()); + + for (int i = 0; i < paths.size(); ++i) { + group[i] = { {}, i, paths[i]->basePolyline().start() }; } - // Register selection/deselection on all paths. - forEachPath([this](Path& path) { - connect(&path, &Path::selectedChanged, this, [this, &path](bool selected) { - emit pathSelectedChanged(path, selected); - emit selectionChanged(pathSelectionEmpty()); - }); - }); + return { group }; } -bool Task::pathSelectionEmpty() const +geometry::OrderOptimizer::NodesPerGroup generateNodesPerGroupOfLength(const Path::ListPtr& paths, float lengthPrecision) { - return !std::any_of(m_paths.begin(), m_paths.end(), [](const Path* path) { - return path->selected(); - }); -} + struct PathRoundedLength : common::Aggregable + { + int id; + Path* path; + int roundedLength; -Task::Task(Layer::ListUPtr&& layers) - : m_layers(std::move(layers)) -{ - initPathsFromLayers(); + PathRoundedLength() = default; - m_stack = m_paths; -} + explicit PathRoundedLength(Path* path, int id, float lengthPrecision) + : id(id) + , path(path) + , roundedLength(path->basePolyline().length() / lengthPrecision) + { + } -Task::Task(const Task& other) - : QObject() - , m_layers(common::deepcopy(other.m_layers)) - , m_stack(other.m_stack.size()) -{ - initPathsFromLayers(); + bool operator<(const PathRoundedLength& other) const { return roundedLength < other.roundedLength; } + }; - // 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 }); + PathRoundedLength::List sortedPathsRoundedLength(paths.size()); + for (int pathId = 0, nbPaths = paths.size(); pathId < nbPaths; ++pathId) { + Path* path = paths[pathId]; + const float length = path->basePolyline().length(); + sortedPathsRoundedLength[pathId] = PathRoundedLength(path, pathId, lengthPrecision); } - std::transform(other.m_stack.begin(), other.m_stack.end(), m_stack.begin(), [&pathRemapping](Path* path) { - return pathRemapping.find(path)->second; - }); -} + std::sort(sortedPathsRoundedLength.begin(), sortedPathsRoundedLength.end()); -int Task::pathCount() const -{ - return m_paths.size(); + geometry::OrderOptimizer::NodesPerGroup nodesPerGroup; + float currentGroupLength = -1.0f; + for (const PathRoundedLength& pathRoundedLength : sortedPathsRoundedLength) { + if (pathRoundedLength.roundedLength != currentGroupLength) { + // Create new group + nodesPerGroup.push_back({}); + currentGroupLength = pathRoundedLength.roundedLength; + } + + nodesPerGroup.back().push_back({ {}, pathRoundedLength.id, pathRoundedLength.path->basePolyline().start() }); + } + + return nodesPerGroup; } -const Path& Task::pathAt(int index) const -{ - assert(0 <= index && index < pathCount()); - return *m_stack[index]; +#endif + } -Path& Task::pathAt(int index) +export namespace model { - assert(0 <= index && index < pathCount()); - return *m_stack[index]; -} -int Task::pathIndexFor(const Path& path) const +class Task : public QObject, public common::Aggregable { - const Path::ListPtr::const_iterator it = std::find(m_stack.cbegin(), m_stack.cend(), &path); + Q_OBJECT; - assert(it != m_stack.cend()); + friend serializer::Access; - return std::distance(m_stack.cbegin(), it); -} +private: + Path::ListPtr m_paths; + Layer::ListUPtr m_layers; -void Task::movePath(int index, MoveDirection direction) -{ - assert(0 <= index && index < pathCount()); + Path::ListPtr m_stack; - const int newIndex = index + direction; + void initPathsFromLayers() + { + for (const Layer::UPtr& layer : m_layers) { + layer->forEachChild([this](Path& path) { + m_paths.push_back(&path); + }); + } - if (0 <= newIndex && newIndex < pathCount()) { - std::swap(m_stack[index], m_stack[newIndex]); + // Register selection/deselection on all paths. + forEachPath([this](Path& path) { + connect(&path, &Path::selectedChanged, this, [this, &path](bool selected) { + emit pathSelectedChanged(path, selected); + emit selectionChanged(pathSelectionEmpty()); + }); + }); } -} -void Task::movePathToTip(int index, MoveTip tip) -{ - assert(0 <= index && index < pathCount()); + bool pathSelectionEmpty() const + { + return !std::any_of(m_paths.begin(), m_paths.end(), [](const Path* path) { + return path->selected(); + }); + } - Path* path = m_stack[index]; - m_stack.erase(m_stack.begin() + index); +public: + enum class MoveDirection { UP = -1, DOWN = 1 }; - switch (tip) { - case MoveTip::Bottom: { - m_stack.push_back(path); - break; - } - case MoveTip::Top: { - m_stack.insert(m_stack.begin(), path); - break; - } - } -} + enum class MoveTip { Top, Bottom }; -void Task::sortPathsByLength() -{ - struct PathLength + explicit Task() = default; + explicit Task(Layer::ListUPtr&& layers) + : m_layers(std::move(layers)) { - Path* path; - float length; + initPathsFromLayers(); - PathLength() = default; + m_stack = m_paths; + } - explicit PathLength(Path* path) - : path(path) - , length(path->basePolyline().length()) - { + explicit 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 }); } - bool operator<(const PathLength& other) const { return length < other.length; } - }; + std::transform(other.m_stack.begin(), other.m_stack.end(), m_stack.begin(), [&pathRemapping](Path* path) { + return pathRemapping.find(path)->second; + }); + } - std::vector pathsLength(m_paths.size()); - std::transform(m_paths.begin(), m_paths.end(), pathsLength.begin(), [](Path* path) { - return PathLength(path); - }); + int pathCount() const { return m_paths.size(); } - std::sort(pathsLength.begin(), pathsLength.end()); + const Path& pathAt(int index) const + { + assert(0 <= index && index < pathCount()); + return *m_stack[index]; + } - std::transform(pathsLength.begin(), pathsLength.end(), m_stack.begin(), [](PathLength& pathLength) { - return pathLength.path; - }); -} + Path& pathAt(int index) + { + assert(0 <= index && index < pathCount()); + return *m_stack[index]; + } -void Task::resetCutterCompensationSelection() -{ - forEachSelectedPath([](model::Path& path) { - path.resetOffset(); - }); -} + int pathIndexFor(const Path& path) const + { + const Path::ListPtr::const_iterator it = std::find(m_stack.cbegin(), m_stack.cend(), &path); -void Task::cutterCompensationSelection(float scaledRadius, float minimumPolylineLength, float minimumArcLength) -{ - forEachSelectedPath([scaledRadius, minimumPolylineLength, minimumArcLength](Path& path) { - path.offset(scaledRadius, minimumPolylineLength, minimumArcLength); - }); -} + assert(it != m_stack.cend()); -void Task::pocketSelection(float radius, float minimumPolylineLength, float minimumArcLength) -{ - const Path::ListPtr::iterator it = m_paths.begin(); - const Path::ListPtr::iterator end = m_paths.end(); + return std::distance(m_stack.cbegin(), it); + } - const auto isSelectedPred = [](const Path* path) { - return path->selected(); - }; + void movePath(int index, MoveDirection direction); + void movePathToTip(int index, MoveTip tip) + { + assert(0 <= index && index < pathCount()); - const Path::ListPtr::iterator borderIt = std::find_if(it, end, isSelectedPred); - if (borderIt == end) { - // No selected path found - return; + 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; + } + } } - Path::ListCPtr islands; - std::copy_if(borderIt + 1, end, std::back_inserter(islands), isSelectedPred); + void sortPathsByLength() + { + struct PathLength + { + Path* path; + float length; - Path* border = *borderIt; - border->pocket(islands, radius, minimumPolylineLength, minimumArcLength); -} + PathLength() = default; -void Task::transformSelection(const QTransform& matrix) -{ - forEachSelectedPath([&matrix](Path& path) { - path.transform(matrix); - }); -} + explicit PathLength(Path* path) + : path(path) + , length(path->basePolyline().length()) + { + } -void Task::hideSelection() -{ - forEachSelectedPath([](Path& path) { - path.setVisible(false); - }); -} + bool operator<(const PathLength& other) const { return length < other.length; } + }; -void Task::showHidden() -{ - forEachPath([](Path& path) { - if (!path.visible()) { - path.setVisible(true); - path.setSelected(true); - } - }); -} + std::vector pathsLength(m_paths.size()); + std::transform(m_paths.begin(), m_paths.end(), pathsLength.begin(), [](Path* path) { + return PathLength(path); + }); -#ifdef WITH_ORTOOLS -geometry::OrderOptimizer::NodesPerGroup generateNodesSingleGroup(const Path::ListPtr& paths) -{ - geometry::OrderOptimizer::Node::List group(paths.size()); + std::sort(pathsLength.begin(), pathsLength.end()); - for (int i = 0; i < paths.size(); ++i) { - group[i] = { {}, i, paths[i]->basePolyline().start() }; + std::transform(pathsLength.begin(), pathsLength.end(), m_stack.begin(), [](PathLength& pathLength) { + return pathLength.path; + }); } - return { group }; -} + template + void forEachPathInStack(Functor&& functor) const + { + for (const Path* path : m_stack) { + functor(*path); + } + } -geometry::OrderOptimizer::NodesPerGroup generateNodesPerGroupOfLength(const Path::ListPtr& paths, float lengthPrecision) -{ - struct PathRoundedLength : common::Aggregable + template + void forEachPath(Functor&& functor) { - int id; - Path* path; - int roundedLength; + for (Path* path : m_paths) { + functor(*path); + } + } - PathRoundedLength() = default; + template + void forEachPath(Functor&& functor) const + { + for (const Path* path : m_paths) { + functor(static_cast(*path)); + } + } - explicit PathRoundedLength(Path* path, int id, float lengthPrecision) - : id(id) - , path(path) - , roundedLength(path->basePolyline().length() / lengthPrecision) - { + template + void forEachSelectedPath(Functor&& functor) const + { + const Path::ListPtr paths(m_paths); + for (Path* path : paths) { + if (path->selected()) { + functor(*path); + } } + } - bool operator<(const PathRoundedLength& other) const { return roundedLength < other.roundedLength; } - }; + void resetCutterCompensationSelection() + { + forEachSelectedPath([](model::Path& path) { + path.resetOffset(); + }); + } - PathRoundedLength::List sortedPathsRoundedLength(paths.size()); - for (int pathId = 0, nbPaths = paths.size(); pathId < nbPaths; ++pathId) { - Path* path = paths[pathId]; - const float length = path->basePolyline().length(); - sortedPathsRoundedLength[pathId] = PathRoundedLength(path, pathId, lengthPrecision); + void cutterCompensationSelection(float scaledRadius, float minimumPolylineLength, float minimumArcLength) + { + forEachSelectedPath([scaledRadius, minimumPolylineLength, minimumArcLength](Path& path) { + path.offset(scaledRadius, minimumPolylineLength, minimumArcLength); + }); } - std::sort(sortedPathsRoundedLength.begin(), sortedPathsRoundedLength.end()); + void pocketSelection(float radius, float minimumPolylineLength, float minimumArcLength) + { + const Path::ListPtr::iterator it = m_paths.begin(); + const Path::ListPtr::iterator end = m_paths.end(); - geometry::OrderOptimizer::NodesPerGroup nodesPerGroup; - float currentGroupLength = -1.0f; - for (const PathRoundedLength& pathRoundedLength : sortedPathsRoundedLength) { - if (pathRoundedLength.roundedLength != currentGroupLength) { - // Create new group - nodesPerGroup.push_back({}); - currentGroupLength = pathRoundedLength.roundedLength; + 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; } - nodesPerGroup.back().push_back({ {}, pathRoundedLength.id, pathRoundedLength.path->basePolyline().start() }); - } + Path::ListCPtr islands; + std::copy_if(borderIt + 1, end, std::back_inserter(islands), isSelectedPred); - return nodesPerGroup; -} -#endif + Path* border = *borderIt; + border->pocket(islands, radius, minimumPolylineLength, minimumArcLength); + } -void Task::optimizeOrder(bool maintainPathLengthOrder, float lengthPrecision, float distancePrecision) -{ -#ifdef WITH_ORTOOLS - const geometry::OrderOptimizer::NodesPerGroup nodesPerGroup = maintainPathLengthOrder - ? generateNodesPerGroupOfLength(m_paths, lengthPrecision) - : generateNodesSingleGroup(m_paths); + void transformSelection(const QTransform& matrix) + { + forEachSelectedPath([&matrix](Path& path) { + path.transform(matrix); + }); + } - const int nbPath = pathCount(); - geometry::OrderOptimizer optimizer(nodesPerGroup, nbPath); - const std::vector order = optimizer.order(); + void hideSelection() + { + forEachSelectedPath([](Path& path) { + path.setVisible(false); + }); + } - Path::ListPtr newPaths(m_paths.size()); - for (int i = 0; i < nbPath; ++i) { - newPaths[i] = m_paths[order[i]]; + void showHidden() + { + forEachPath([](Path& path) { + if (!path.visible()) { + path.setVisible(true); + path.setSelected(true); + } + }); } - std::swap(m_stack, newPaths); - emit pathOrderChanged(); -#endif -} + void optimizeOrder(bool maintainPathLengthOrder, float lengthPrecision, float distancePrecision) + { +#ifdef WITH_ORTOOLS + const geometry::OrderOptimizer::NodesPerGroup nodesPerGroup = maintainPathLengthOrder + ? generateNodesPerGroupOfLength(m_paths, lengthPrecision) + : generateNodesSingleGroup(m_paths); -geometry::Rect Task::selectionBoundingRect() const -{ - bool isFirstPath = true; - geometry::Rect boundingRect; - - forEachSelectedPath([&isFirstPath, &boundingRect](Path& path) { - const geometry::Rect pathBoundingRect = path.boundingRect(); - if (isFirstPath) { - boundingRect = pathBoundingRect; - isFirstPath = false; - } else { - boundingRect |= pathBoundingRect; + const int nbPath = pathCount(); + geometry::OrderOptimizer optimizer(nodesPerGroup, nbPath); + const std::vector order = optimizer.order(); + + Path::ListPtr newPaths(m_paths.size()); + for (int i = 0; i < nbPath; ++i) { + newPaths[i] = m_paths[order[i]]; } - }); - return boundingRect; -} + std::swap(m_stack, newPaths); + emit pathOrderChanged(); +#endif + } -geometry::Rect Task::visibleBoundingRect() const -{ - bool isFirstPath = true; - geometry::Rect boundingRect; + geometry::Rect selectionBoundingRect() const + { + bool isFirstPath = true; + geometry::Rect boundingRect; - forEachPathInStack([&isFirstPath, &boundingRect](const model::Path& path) { - if (path.globallyVisible()) { + forEachSelectedPath([&isFirstPath, &boundingRect](Path& path) { const geometry::Rect pathBoundingRect = path.boundingRect(); if (isFirstPath) { boundingRect = pathBoundingRect; @@ -317,54 +356,100 @@ geometry::Rect Task::visibleBoundingRect() const } else { boundingRect |= pathBoundingRect; } + }); + + return boundingRect; + } + + geometry::Rect 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 layerCount() const { return m_layers.size(); } + + const Layer& layerAt(int index) const + { + assert(0 <= index && index < layerCount()); + return *m_layers[index]; + } + + Layer& layerAt(int index) + { + assert(0 <= index && index < layerCount()); + return *m_layers[index]; + } + + int layerIndexFor(const Layer& layer) const + { + const Layer::ListUPtr::const_iterator it + = std::find_if(m_layers.cbegin(), m_layers.cend(), [&layer](const Layer::UPtr& ptr) { + return ptr.get() == &layer; + }); + + assert(it != m_layers.cend()); + + return std::distance(m_layers.cbegin(), it); + } + + std::pair layerAndPathIndexFor(const Path& path) const + { + for (int layerIndex = 0, size = m_layers.size(); layerIndex < size; ++layerIndex) { + const Layer& layer = *m_layers[layerIndex]; + const int childIndex = layer.childIndexFor(path); + if (childIndex != -1) { + return std::make_pair(layerIndex, childIndex); + } } - }); - return boundingRect; -} + assert(false && "layer not found"); -int Task::layerCount() const -{ - return m_layers.size(); -} + return std::make_pair(-1, -1); + } -const Layer& Task::layerAt(int index) const +Q_SIGNALS: + void pathSelectedChanged(Path& path, bool selected); + void selectionChanged(bool empty); + void pathOrderChanged(); +}; + +template +inline T operator+(const T& a, const Task::MoveDirection& direction) { - assert(0 <= index && index < layerCount()); - return *m_layers[index]; + return a + static_cast(direction); } -Layer& Task::layerAt(int index) -{ - assert(0 <= index && index < layerCount()); - return *m_layers[index]; } -int Task::layerIndexFor(const Layer& layer) const +namespace model { - const Layer::ListUPtr::const_iterator it - = std::find_if(m_layers.cbegin(), m_layers.cend(), [&layer](const Layer::UPtr& ptr) { - return ptr.get() == &layer; - }); - - assert(it != m_layers.cend()); - return std::distance(m_layers.cbegin(), it); -} - -std::pair Task::layerAndPathIndexFor(const Path& path) const +void Task::movePath(int index, MoveDirection direction) { - for (int layerIndex = 0, size = m_layers.size(); layerIndex < size; ++layerIndex) { - const Layer& layer = *m_layers[layerIndex]; - const int childIndex = layer.childIndexFor(path); - if (childIndex != -1) { - return std::make_pair(layerIndex, childIndex); - } - } + assert(0 <= index && index < pathCount()); - assert(false && "layer not found"); + const int newIndex = index + direction; - return std::make_pair(-1, -1); + if (0 <= newIndex && newIndex < pathCount()) { + std::swap(m_stack[index], m_stack[newIndex]); + } } } + +#include "task.moc" diff --git a/src/model/task.h b/src/model/task.h deleted file mode 100644 index 9f35524..0000000 --- a/src/model/task.h +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -#include -#include - -#include - -namespace model -{ - -class Task : public QObject, public common::Aggregable -{ - Q_OBJECT; - - friend serializer::Access; - -private: - Path::ListPtr m_paths; - Layer::ListUPtr m_layers; - - Path::ListPtr m_stack; - - void initPathsFromLayers(); - bool pathSelectionEmpty() const; - -public: - enum class MoveDirection { UP = -1, 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; - Path& pathAt(int index); - 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 - { - for (const Path* path : m_stack) { - functor(*path); - } - } - - template - void forEachPath(Functor&& functor) - { - for (Path* path : m_paths) { - functor(*path); - } - } - - template - void forEachPath(Functor&& functor) const - { - for (const Path* path : m_paths) { - functor(static_cast(*path)); - } - } - - template - void forEachSelectedPath(Functor&& functor) const - { - const Path::ListPtr paths(m_paths); - for (Path* path : paths) { - if (path->selected()) { - functor(*path); - } - } - } - - void resetCutterCompensationSelection(); - void cutterCompensationSelection(float scaledRadius, float minimumPolylineLength, float minimumArcLength); - void pocketSelection(float radius, float minimumPolylineLength, float minimumArcLength); - void transformSelection(const QTransform& matrix); - void hideSelection(); - void showHidden(); - void optimizeOrder(bool maintainPathLengthOrder, float lengthPrecision, float distancePrecision); - - geometry::Rect selectionBoundingRect() const; - geometry::Rect visibleBoundingRect() const; - - int layerCount() const; - const Layer& layerAt(int index) const; - Layer& layerAt(int index); - int layerIndexFor(const Layer& layer) const; - std::pair layerAndPathIndexFor(const Path& path) const; - -Q_SIGNALS: - void pathSelectedChanged(Path& path, bool selected); - void selectionChanged(bool empty); - void pathOrderChanged(); -}; - -template -inline T operator+(const T& a, const Task::MoveDirection& direction) -{ - return a + static_cast(direction); -} - -} diff --git a/src/serializer/CMakeLists.txt b/src/serializer/CMakeLists.txt index 8b13789..4c853a3 100644 --- a/src/serializer/CMakeLists.txt +++ b/src/serializer/CMakeLists.txt @@ -1 +1,17 @@ +set(SRC + qvector2d.cpp + bulge.cpp + polyline.cpp + renderable.cpp + pathsettings.cpp + offsettedpath.cpp + path.cpp + layer.cpp + task.cpp +) +# access.h stays a plain header: geometry's module interfaces friend-declare serializer::Access, +# so modularising it would make geometry and serializer circular CMake targets. +add_module_library(serializer ${SRC}) +target_link_libraries(serializer PUBLIC geometry model) +add_coverage(serializer) diff --git a/src/serializer/bulge.h b/src/serializer/bulge.cpp similarity index 71% rename from src/serializer/bulge.h rename to src/serializer/bulge.cpp index 637f5ec..01996e3 100644 --- a/src/serializer/bulge.h +++ b/src/serializer/bulge.cpp @@ -1,13 +1,17 @@ -#pragma once +module; +#include #include -#include - #include -#include +export module serializer.bulge; + +import geometry.bulge; +import serializer.qvector2d; + +import geometry.bulge; -namespace serializer +export namespace serializer { template<> diff --git a/src/serializer/layer.h b/src/serializer/layer.cpp similarity index 80% rename from src/serializer/layer.h rename to src/serializer/layer.cpp index 6d3e9ca..e87e9cd 100644 --- a/src/serializer/layer.h +++ b/src/serializer/layer.cpp @@ -1,13 +1,17 @@ -#pragma once +module; +#include #include -#include - #include -#include +export module serializer.layer; + +import model.layer; +import model.path; +import model.renderable; +import serializer.path; -namespace serializer +export namespace serializer { template<> diff --git a/src/serializer/offsettedpath.h b/src/serializer/offsettedpath.cpp similarity index 73% rename from src/serializer/offsettedpath.h rename to src/serializer/offsettedpath.cpp index 882b141..c65cd39 100644 --- a/src/serializer/offsettedpath.h +++ b/src/serializer/offsettedpath.cpp @@ -1,13 +1,15 @@ -#pragma once +module; +#include #include -#include - #include -#include +export module serializer.offsettedpath; + +import model.offsettedpath; +import serializer.polyline; -namespace serializer +export namespace serializer { template<> diff --git a/src/serializer/path.h b/src/serializer/path.cpp similarity index 70% rename from src/serializer/path.h rename to src/serializer/path.cpp index 94616ee..23b794f 100644 --- a/src/serializer/path.h +++ b/src/serializer/path.cpp @@ -1,15 +1,18 @@ -#pragma once +module; +#include #include -#include -#include -#include - #include -#include +export module serializer.path; + +import model.path; +import serializer.offsettedpath; +import serializer.pathsettings; +import model.renderable; +import serializer.renderable; -namespace serializer +export namespace serializer { template<> diff --git a/src/serializer/pathsettings.h b/src/serializer/pathsettings.cpp similarity index 77% rename from src/serializer/pathsettings.h rename to src/serializer/pathsettings.cpp index cfd20ac..6ef1b1e 100644 --- a/src/serializer/pathsettings.h +++ b/src/serializer/pathsettings.cpp @@ -1,13 +1,16 @@ -#pragma once +module; +#include #include -#include - #include -#include +export module serializer.pathsettings; + +import model.path; +import model.pathsettings; +import serializer.polyline; -namespace serializer +export namespace serializer { template<> diff --git a/src/serializer/polyline.h b/src/serializer/polyline.cpp similarity index 65% rename from src/serializer/polyline.h rename to src/serializer/polyline.cpp index 569b8c5..47c8b65 100644 --- a/src/serializer/polyline.h +++ b/src/serializer/polyline.cpp @@ -1,13 +1,17 @@ -#pragma once +module; +#include #include -#include - #include -#include +export module serializer.polyline; + +import geometry.polyline; +import serializer.bulge; + +import geometry.polyline; -namespace serializer +export namespace serializer { template<> diff --git a/src/serializer/qvector2d.h b/src/serializer/qvector2d.cpp similarity index 87% rename from src/serializer/qvector2d.h rename to src/serializer/qvector2d.cpp index 0c6d75a..4b5766e 100644 --- a/src/serializer/qvector2d.h +++ b/src/serializer/qvector2d.cpp @@ -1,12 +1,13 @@ -#pragma once +module; +#include #include - #include - #include -namespace serializer +export module serializer.qvector2d; + +export namespace serializer { template<> diff --git a/src/serializer/renderable.h b/src/serializer/renderable.cpp similarity index 86% rename from src/serializer/renderable.h rename to src/serializer/renderable.cpp index f161cd9..9fe4c8b 100644 --- a/src/serializer/renderable.h +++ b/src/serializer/renderable.cpp @@ -1,12 +1,14 @@ -#pragma once +module; +#include #include - #include -#include +export module serializer.renderable; + +import model.renderable; -namespace serializer +export namespace serializer { template<> diff --git a/src/serializer/task.h b/src/serializer/task.cpp similarity index 92% rename from src/serializer/task.h rename to src/serializer/task.cpp index d6e42af..afd5f1d 100644 --- a/src/serializer/task.h +++ b/src/serializer/task.cpp @@ -1,19 +1,21 @@ -#pragma once +module; +#include +#include #include -#include - #include #include #include #include -#include -#include +export module serializer.task; -#include +import model.task; +import serializer.layer; +import model.layer; +import model.path; -namespace serializer +export namespace serializer { using IndexStackList = std::vector; diff --git a/src/view/CMakeLists.txt b/src/view/CMakeLists.txt index ac2db79..c10f8e3 100644 --- a/src/view/CMakeLists.txt +++ b/src/view/CMakeLists.txt @@ -7,13 +7,13 @@ set(SRC info.cpp mainwindow.cpp profile.cpp - - info.h - mainwindow.h - profile.h + transform.cpp ) -add_library(view ${SRC}) -target_link_libraries(view PRIVATE config) +add_module_library(view ${SRC}) +target_link_libraries(view + PUBLIC model model-application + PRIVATE config geometry + view-dialogs view-dialogs-settings view-task view-view2d view-simulation) add_dependencies(view uic) diff --git a/src/view/dialogs/CMakeLists.txt b/src/view/dialogs/CMakeLists.txt index bd3b0ee..9c7280a 100644 --- a/src/view/dialogs/CMakeLists.txt +++ b/src/view/dialogs/CMakeLists.txt @@ -4,12 +4,9 @@ set(SRC mirror.cpp transform.cpp setorigin.cpp - - mirror.h - transform.h - setorigin.h ) -add_library(view-dialogs ${SRC}) +add_module_library(view-dialogs ${SRC}) +target_link_libraries(view-dialogs PUBLIC geometry model model-application) add_dependencies(view-dialogs uic-dialogs) diff --git a/src/view/dialogs/mirror.cpp b/src/view/dialogs/mirror.cpp index fcb45fd..c37932a 100644 --- a/src/view/dialogs/mirror.cpp +++ b/src/view/dialogs/mirror.cpp @@ -1,25 +1,31 @@ -#include +module; -namespace view::dialogs -{ +#include +#include -Mirror::Mirror() -{ - setupUi(this); -} +export module view.dialogs.mirror; -const QTransform& Mirror::matrix() const +export namespace view::dialogs { - return m_matrix; -} -void Mirror::accept() +class Mirror : public QDialog, private Ui::Mirror { - QDialog::accept(); +private: + QTransform m_matrix; - const float scaleX = mirrorX->isChecked() ? -1.0f : 1.0f; - const float scaleY = mirrorY->isChecked() ? -1.0f : 1.0f; - m_matrix.scale(scaleX, scaleY); -} +public: + explicit Mirror() { setupUi(this); } + + const QTransform& matrix() const { return m_matrix; } + + void accept() override + { + QDialog::accept(); + + const float scaleX = mirrorX->isChecked() ? -1.0f : 1.0f; + const float scaleY = mirrorY->isChecked() ? -1.0f : 1.0f; + m_matrix.scale(scaleX, scaleY); + } +}; } diff --git a/src/view/dialogs/mirror.h b/src/view/dialogs/mirror.h deleted file mode 100644 index cb82bcd..0000000 --- a/src/view/dialogs/mirror.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -#include - -namespace view::dialogs -{ - -class Mirror : public QDialog, private Ui::Mirror -{ -private: - QTransform m_matrix; - -public: - explicit Mirror(); - - const QTransform& matrix() const; - - void accept() override; -}; - -} diff --git a/src/view/dialogs/setorigin.cpp b/src/view/dialogs/setorigin.cpp index 0398838..b1e8631 100644 --- a/src/view/dialogs/setorigin.cpp +++ b/src/view/dialogs/setorigin.cpp @@ -1,53 +1,70 @@ -#include +module; -namespace view::dialogs -{ +#include +#include +#include + +export module view.dialogs.setorigin; + +import geometry.rect; -void SetOrigin::setupButtonGroup() +export namespace view::dialogs { - m_buttonGroup.addButton(topLeftCheckBox, static_cast(Corner::TopLeft)); - m_buttonGroup.addButton(bottomLeftCheckBox, static_cast(Corner::BottomLeft)); - m_buttonGroup.addButton(topRightCheckBox, static_cast(Corner::TopRight)); - m_buttonGroup.addButton(bottomRightCheckBox, static_cast(Corner::BottomRight)); -} -QVector2D SetOrigin::boundingRectCornerPosition(Corner corner) const +class SetOrigin : public QDialog, private Ui::SetOrigin { - switch (corner) { - case Corner::TopLeft: - return m_selectionBoundingRect.topLeft(); - case Corner::BottomLeft: - return m_selectionBoundingRect.bottomLeft(); - case Corner::TopRight: - return m_selectionBoundingRect.topRight(); - case Corner::BottomRight: - default: - return m_selectionBoundingRect.bottomRight(); +private: + const geometry::Rect& m_selectionBoundingRect; + QTransform m_matrix; + + QButtonGroup m_buttonGroup; + + enum class Corner { BottomLeft = 0, TopLeft, BottomRight, TopRight }; + + void setupButtonGroup() + { + m_buttonGroup.addButton(topLeftCheckBox, static_cast(Corner::TopLeft)); + m_buttonGroup.addButton(bottomLeftCheckBox, static_cast(Corner::BottomLeft)); + m_buttonGroup.addButton(topRightCheckBox, static_cast(Corner::TopRight)); + m_buttonGroup.addButton(bottomRightCheckBox, static_cast(Corner::BottomRight)); } -} -SetOrigin::SetOrigin(const geometry::Rect& selectionBoundingRect) - : m_selectionBoundingRect(selectionBoundingRect) -{ - setupUi(this); + QVector2D boundingRectCornerPosition(Corner corner) const + { + switch (corner) { + case Corner::TopLeft: + return m_selectionBoundingRect.topLeft(); + case Corner::BottomLeft: + return m_selectionBoundingRect.bottomLeft(); + case Corner::TopRight: + return m_selectionBoundingRect.topRight(); + case Corner::BottomRight: + default: + return m_selectionBoundingRect.bottomRight(); + } + } - setupButtonGroup(); -} +public: + explicit SetOrigin(const geometry::Rect& selectionBoundingRect) + : m_selectionBoundingRect(selectionBoundingRect) + { + setupUi(this); -const QTransform& SetOrigin::matrix() const -{ - return m_matrix; -} + setupButtonGroup(); + } -void SetOrigin::accept() -{ - QDialog::accept(); + const QTransform& matrix() const { return m_matrix; } - const int buttonId = m_buttonGroup.checkedId(); - const Corner corner = static_cast(buttonId); - const QVector2D offset = boundingRectCornerPosition(corner); + void accept() override + { + QDialog::accept(); - m_matrix.translate(-offset.x(), -offset.y()); -} + const int buttonId = m_buttonGroup.checkedId(); + const Corner corner = static_cast(buttonId); + const QVector2D offset = boundingRectCornerPosition(corner); + + m_matrix.translate(-offset.x(), -offset.y()); + } +}; } diff --git a/src/view/dialogs/setorigin.h b/src/view/dialogs/setorigin.h deleted file mode 100644 index ba3c3be..0000000 --- a/src/view/dialogs/setorigin.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include - -namespace view::dialogs -{ - -class SetOrigin : public QDialog, private Ui::SetOrigin -{ -private: - const geometry::Rect& m_selectionBoundingRect; - QTransform m_matrix; - - QButtonGroup m_buttonGroup; - - enum class Corner { BottomLeft = 0, TopLeft, BottomRight, TopRight }; - - void setupButtonGroup(); - QVector2D boundingRectCornerPosition(Corner corner) const; - -public: - explicit SetOrigin(const geometry::Rect& selectionBoundingRect); - - const QTransform& matrix() const; - - void accept() override; -}; - -} diff --git a/src/view/dialogs/settings/CMakeLists.txt b/src/view/dialogs/settings/CMakeLists.txt index 31e558e..d2af6f2 100644 --- a/src/view/dialogs/settings/CMakeLists.txt +++ b/src/view/dialogs/settings/CMakeLists.txt @@ -2,14 +2,10 @@ set(SRC list.cpp settings.cpp treemodel.cpp - - entry.h - group.h - list.h - settings.h - treemodel.h + entry.cpp + group.cpp ) -add_library(view-dialogs-settings ${SRC}) -target_link_libraries(view-dialogs-settings PRIVATE config) +add_module_library(view-dialogs-settings ${SRC}) +target_link_libraries(view-dialogs-settings PUBLIC common model model-application PRIVATE config geometry) add_dependencies(view-dialogs-settings uic-dialogs-settings) diff --git a/src/view/dialogs/settings/entry.h b/src/view/dialogs/settings/entry.cpp similarity index 94% rename from src/view/dialogs/settings/entry.h rename to src/view/dialogs/settings/entry.cpp index 7c3295d..e442b54 100644 --- a/src/view/dialogs/settings/entry.h +++ b/src/view/dialogs/settings/entry.cpp @@ -1,16 +1,18 @@ -#pragma once +module; #include - #include #include #include #include -#include -#include +export module view.dialogs.settings.entry; + +import common.aggregable; +import common.enumerate; +import config.property; -namespace view::settings +export namespace view::settings { /** @brief Interface for setting entry, expose saving to configuration destructor diff --git a/src/view/dialogs/settings/group.h b/src/view/dialogs/settings/group.cpp similarity index 85% rename from src/view/dialogs/settings/group.h rename to src/view/dialogs/settings/group.cpp index b502345..cabb8f6 100644 --- a/src/view/dialogs/settings/group.h +++ b/src/view/dialogs/settings/group.cpp @@ -1,12 +1,14 @@ -#pragma once +module; #include +#include -#include +export module view.dialogs.settings.group; -#include +import view.dialogs.settings.entry; +import config.property; -namespace view::settings +export namespace view::settings { class Group : public QGroupBox, private Ui::Group diff --git a/src/view/dialogs/settings/list.cpp b/src/view/dialogs/settings/list.cpp index 75b98a4..c18d09a 100644 --- a/src/view/dialogs/settings/list.cpp +++ b/src/view/dialogs/settings/list.cpp @@ -1,6 +1,24 @@ -#include +module; -namespace view::settings +#include +#include +#include + +export module view.dialogs.settings.list; + +export namespace view::settings { +class List : public QGroupBox, private Ui::List +{ +public: + template + explicit List(Node& node) + { + setupUi(this); + + setTitle(QString::fromStdString(node.name())); + } +}; + } diff --git a/src/view/dialogs/settings/list.h b/src/view/dialogs/settings/list.h deleted file mode 100644 index 4b72f72..0000000 --- a/src/view/dialogs/settings/list.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace view::settings -{ - -class List : public QGroupBox, private Ui::List -{ -public: - template - explicit List(Node& node) - { - setupUi(this); - - setTitle(QString::fromStdString(node.name())); - } -}; - -} diff --git a/src/view/dialogs/settings/settings.cpp b/src/view/dialogs/settings/settings.cpp index 254379b..eba8124 100644 --- a/src/view/dialogs/settings/settings.cpp +++ b/src/view/dialogs/settings/settings.cpp @@ -1,133 +1,148 @@ -#include -#include -#include -#include +module; +#include +#include #include #include #include -#include // TODO +#include -namespace view::settings +export module view.dialogs.settings.settings; +import view.dialogs.settings.group; +import view.dialogs.settings.list; +import view.dialogs.settings.treemodel; + +import config.config; +import view.dialogs.settings.entry; +import config.group; +import config.list; + +export namespace view::settings { -/// Node visitor selecting list UI or group UI -class Settings::NodeVisitor +class Settings : public QDialog, private Ui::Settings { private: - QWidget* m_newWidget; - -public: - template - void operator()(config::Group& node) - { - m_newWidget = new Group(node); - } - - template - void operator()(config::List& node) + class NodeVisitor { - m_newWidget = new List(node); - } + private: + QWidget* m_newWidget; + + public: + template + void operator()(config::Group& node) + { + m_newWidget = new Group(node); + } - QWidget* newWidget() const { return m_newWidget; } -}; + template + void operator()(config::List& node) + { + m_newWidget = new List(node); + } -void Settings::setupUi() -{ - Ui::Settings::setupUi(this); + QWidget* newWidget() const { return m_newWidget; } + }; - treeView->setModel(m_model.get()); - treeView->expandAll(); - treeView->resizeColumnToContents(0); -} + void setupUi() + { + Ui::Settings::setupUi(this); -Settings::Settings(config::Config& newConfig) - : m_newConfig(newConfig) - , m_model(new TreeModel(m_newConfig.root(), this)) -{ - setupUi(); + treeView->setModel(m_model.get()); + treeView->expandAll(); + treeView->resizeColumnToContents(0); + } - connect(treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &Settings::currentChanged); -} + // Modified config + config::Config& m_newConfig; + std::unique_ptr m_model; -Settings::~Settings() = default; +public: + explicit Settings(config::Config& newConfig) + : m_newConfig(newConfig) + , m_model(new TreeModel(m_newConfig.root(), this)) + { + setupUi(); -void Settings::currentChanged(const QModelIndex& current, const QModelIndex&) -{ - // Create new widget to be displayed at center - NodeVisitor visitor; - m_model->visit(current, visitor); - - // Replace old center widget - QWidget* newWidget = visitor.newWidget(); - QLayoutItem* item = gridLayout->replaceWidget(center, newWidget); - center = newWidget; - - // Delete old center widget - delete item->widget(); - - const bool isList = m_model->isList(current); - addButton->disconnect(); - addButton->setEnabled(isList); - - if (isList) { - connect(addButton, &QPushButton::pressed, this, [current, this]() { - addItem(current); - }); + connect(treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &Settings::currentChanged); } - const bool isItem = m_model->isItem(current); - removeButton->disconnect(); - removeButton->setEnabled(isItem); - copyButton->disconnect(); - copyButton->setEnabled(isItem); - - if (isItem) { - connect(removeButton, &QPushButton::pressed, this, [current, this]() { - removeItem(current); - }); - connect(copyButton, &QPushButton::pressed, this, [current, this]() { - copyItem(current); - }); - } -} + ~Settings() = default; -void Settings::addItem(const QModelIndex& index) -{ - bool ok; - const QString text - = QInputDialog::getText(this, tr("QInputDialog::getText()"), tr("New name:"), QLineEdit::Normal, "", &ok); - - if (ok) { - if (text.isEmpty()) { - QMessageBox::critical(this, "Error", "Invalid name"); - } else { - m_model->addItem(index, text); +protected Q_SLOTS: + void currentChanged(const QModelIndex& current, const QModelIndex&) + { + // Create new widget to be displayed at center + NodeVisitor visitor; + m_model->visit(current, visitor); + + // Replace old center widget + QWidget* newWidget = visitor.newWidget(); + QLayoutItem* item = gridLayout->replaceWidget(center, newWidget); + center = newWidget; + + // Delete old center widget + delete item->widget(); + + const bool isList = m_model->isList(current); + addButton->disconnect(); + addButton->setEnabled(isList); + + if (isList) { + connect(addButton, &QPushButton::pressed, this, [current, this]() { + addItem(current); + }); } - } -} -void Settings::removeItem(const QModelIndex& index) -{ - m_model->removeItem(index); -} + const bool isItem = m_model->isItem(current); + removeButton->disconnect(); + removeButton->setEnabled(isItem); + copyButton->disconnect(); + copyButton->setEnabled(isItem); + + if (isItem) { + connect(removeButton, &QPushButton::pressed, this, [current, this]() { + removeItem(current); + }); + connect(copyButton, &QPushButton::pressed, this, [current, this]() { + copyItem(current); + }); + } + } -void Settings::copyItem(const QModelIndex& index) -{ - const QString sourceName = m_model->data(index).toString(); + void addItem(const QModelIndex& index) + { + bool ok; + const QString text + = QInputDialog::getText(this, tr("QInputDialog::getText()"), tr("New name:"), QLineEdit::Normal, "", &ok); + + if (ok) { + if (text.isEmpty()) { + QMessageBox::critical(this, "Error", "Invalid name"); + } else { + m_model->addItem(index, text); + } + } + } - bool ok; - const QString text = QInputDialog::getText(this, tr("QInputDialog::getText()"), tr("New name:"), QLineEdit::Normal, - sourceName, &ok); + void removeItem(const QModelIndex& index) { m_model->removeItem(index); } - if (ok) { - if (text.isEmpty() || text == sourceName) { - QMessageBox::critical(this, "Error", "Invalid name"); - } else { - m_model->copyItem(index, text); + void copyItem(const QModelIndex& index) + { + const QString sourceName = m_model->data(index).toString(); + + bool ok; + const QString text = QInputDialog::getText(this, tr("QInputDialog::getText()"), tr("New name:"), + QLineEdit::Normal, sourceName, &ok); + + if (ok) { + if (text.isEmpty() || text == sourceName) { + QMessageBox::critical(this, "Error", "Invalid name"); + } else { + m_model->copyItem(index, text); + } } } -} +}; } diff --git a/src/view/dialogs/settings/settings.h b/src/view/dialogs/settings/settings.h deleted file mode 100644 index b7c2b5e..0000000 --- a/src/view/dialogs/settings/settings.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include - -#include - -#include - -#include - -namespace view::settings -{ - -class TreeModel; - -class Settings : public QDialog, private Ui::Settings -{ -private: - class NodeVisitor; - - void setupUi(); - - // Modified config - config::Config& m_newConfig; - std::unique_ptr m_model; - -public: - explicit Settings(config::Config& newConfig); - ~Settings(); - -protected Q_SLOTS: - void currentChanged(const QModelIndex& index, const QModelIndex& previous); - void addItem(const QModelIndex& index); - void removeItem(const QModelIndex& index); - void copyItem(const QModelIndex& index); -}; - -} diff --git a/src/view/dialogs/settings/treemodel.cpp b/src/view/dialogs/settings/treemodel.cpp index 05f56a0..35488f1 100644 --- a/src/view/dialogs/settings/treemodel.cpp +++ b/src/view/dialogs/settings/treemodel.cpp @@ -1,266 +1,335 @@ -#include +module; -#include // TODO +#include +#include +#include -namespace view::settings +export module view.dialogs.settings.treemodel; + +import common.aggregable; +import config.config; +import config.group; +import config.list; +import config.node; +import config.property; + +export namespace view::settings { -struct TreeModel::ConstructorItemVisitor +class TreeModel : public QAbstractItemModel { - Node& parent; - // Row of next generated child node - int row = 0; + Q_OBJECT - template - void operator()(config::Property&) +private: + // ItemModel requests each item to know its parent and also its row in parent + struct Node : common::Aggregable { - } - - template - void operator()(ConfigNode& node) + int row; + enum class Type { Group, List } type; + + config::NodePtrVariant configNode; + Node* parent; + ListUPtr children; + + template + static constexpr Node::Type nodeType(const config::Group&) + { + return Node::Type::Group; + } + + template + static constexpr Node::Type nodeType(const config::List&) + { + return Node::Type::List; + }; + + Node() = default; + + template + explicit Node(int _row, ConfigNode& _node, Node* _parent) + : row(_row) + , type(nodeType(_node)) + , configNode(&_node) + , parent(_parent) + { + } + }; + + config::Root& m_configRoot; + Node m_root; + + struct ConstructorItemVisitor + { + Node& parent; + // Row of next generated child node + int row = 0; + + template + void operator()(config::Property&) + { + } + + template + void operator()(ConfigNode& node) + { + // Create node linked to config node + Node::UPtr n = std::make_unique(row++, node, &parent); + + // Create all children + ConstructorItemVisitor visitor { *n }; + node.visitChildren(visitor); + + parent.children.push_back(std::move(n)); + } + }; + + struct AddItemVisitor { - // Create node linked to config node - Node::UPtr n = std::make_unique(row++, node, &parent); + const QString& name; + Node& parent; - // Create all children - ConstructorItemVisitor visitor { *n }; - node.visitChildren(visitor); + template + void operator()(config::List* list) + { + const int nextRow = static_cast(parent.children.size()); - parent.children.push_back(std::move(n)); - } -}; + Child& child = list->createChild(name.toStdString()); -struct TreeModel::AddItemVisitor -{ - const QString& name; - Node& parent; + Node::UPtr n = std::make_unique(nextRow, child, &parent); - template - void operator()(config::List* list) - { - const int nextRow = static_cast(parent.children.size()); + // Create all children + ConstructorItemVisitor visitor { *n }; + child.visitChildren(visitor); - Child& child = list->createChild(name.toStdString()); + parent.children.push_back(std::move(n)); + } - Node::UPtr n = std::make_unique(nextRow, child, &parent); + template + void operator()(config::Group*) + { + } + }; - // Create all children - ConstructorItemVisitor visitor { *n }; - child.visitChildren(visitor); + struct RemoveItemVisitor + { + const config::Node& configNode; - parent.children.push_back(std::move(n)); - } + template + void operator()(config::List* list) + { + list->removeChild(static_cast(configNode)); + } - template - void operator()(config::Group*) + template + void operator()(config::Group*) + { + } + }; + + struct CopyItemVisitor { - } -}; + const QString& name; + Node& parent; + Node& source; -struct TreeModel::RemoveItemVisitor -{ - const config::Node& configNode; + template + void operator()(config::List* list) + { + const int nextRow = parent.children.size(); + + const Child& sourceChild = *std::get(source.configNode); + Child& child = list->copyChild(sourceChild, name.toStdString()); - template - void operator()(config::List* list) + Node::UPtr n = std::make_unique(nextRow, child, &parent); + + // Create all children + ConstructorItemVisitor visitor { *n }; + child.visitChildren(visitor); + + parent.children.push_back(std::move(n)); + } + + template + void operator()(config::Group*) + { + } + }; + + void constructNodes() { - list->removeChild(static_cast(configNode)); + // Construct root node + m_root = Node(0, m_configRoot, nullptr); + ConstructorItemVisitor visitor { m_root }; + m_configRoot.visitChildren(visitor); } - template - void operator()(config::Group*) +public: + explicit TreeModel(config::Root& root, QObject* parent = nullptr) + : QAbstractItemModel(parent) + , m_configRoot(root) { + constructNodes(); } -}; -struct TreeModel::CopyItemVisitor -{ - const QString& name; - Node& parent; - Node& source; - - template - void operator()(config::List* list) + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { - const int nextRow = parent.children.size(); - - const Child& sourceChild = *std::get(source.configNode); - Child& child = list->copyChild(sourceChild, name.toStdString()); + if (role == Qt::DisplayRole && index.isValid() && index.column() == 0) { + Node* node = static_cast(index.internalPointer()); - Node::UPtr n = std::make_unique(nextRow, child, &parent); + const std::string& name = std::visit( + [](const auto& node) { + return node->name(); + }, + node->configNode); - // Create all children - ConstructorItemVisitor visitor { *n }; - child.visitChildren(visitor); + return QString::fromStdString(name); + } - parent.children.push_back(std::move(n)); + return QVariant(); } - template - void operator()(config::Group*) + Qt::ItemFlags flags(const QModelIndex& index) const override { + if (!index.isValid()) { + return Qt::NoItemFlags; + } + + return QAbstractItemModel::flags(index); } -}; -void TreeModel::constructNodes() -{ - // Construct root node - m_root = Node(0, m_configRoot, nullptr); - ConstructorItemVisitor visitor { m_root }; - m_configRoot.visitChildren(visitor); -} + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override + { + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + if (parent.isValid()) { + const Node* parentNode = static_cast(parent.internalPointer()); + const Node* childNode = parentNode->children[row].get(); + return createIndex(row, column, const_cast(childNode)); + } + return createIndex(row, column, const_cast(&m_root)); + } -TreeModel::TreeModel(config::Root& root, QObject* parent) - : QAbstractItemModel(parent) - , m_configRoot(root) -{ - constructNodes(); -} + QModelIndex parent(const QModelIndex& index) const override + { + if (!index.isValid()) { + return QModelIndex(); + } -QVariant TreeModel::data(const QModelIndex& index, int role) const -{ - if (role == Qt::DisplayRole && index.isValid() && index.column() == 0) { Node* node = static_cast(index.internalPointer()); - const std::string& name = std::visit( - [](const auto& node) { - return node->name(); - }, - node->configNode); - - return QString::fromStdString(name); - } + // Reached top level + if (node == &m_root) { + return QModelIndex(); + } - return QVariant(); -} + Node* parentNode = node->parent; -Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const -{ - if (!index.isValid()) { - return Qt::NoItemFlags; + return createIndex(parentNode->row, 0, parentNode); } - return QAbstractItemModel::flags(index); -} + int rowCount(const QModelIndex& parent = QModelIndex()) const override + { + if (parent.isValid()) { + Node* node = static_cast(parent.internalPointer()); -QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const -{ - if (!hasIndex(row, column, parent)) { - return QModelIndex(); - } + return node->children.size(); + } - if (parent.isValid()) { - const Node* parentNode = static_cast(parent.internalPointer()); - const Node* childNode = parentNode->children[row].get(); - return createIndex(row, column, const_cast(childNode)); + return 1; } - return createIndex(row, column, const_cast(&m_root)); -} -QModelIndex TreeModel::parent(const QModelIndex& index) const -{ - if (!index.isValid()) { - return QModelIndex(); - } + int columnCount(const QModelIndex& = QModelIndex()) const override { return 1; } - Node* node = static_cast(index.internalPointer()); + //// Visit configuration node at index + template + void visit(const QModelIndex& index, Visitor&& visitor) const + { + Node* node = static_cast(index.internalPointer()); - // Reached top level - if (node == &m_root) { - return QModelIndex(); + std::visit( + [&visitor](auto& node) { + visitor(*node); + }, + node->configNode); } - Node* parentNode = node->parent; - - return createIndex(parentNode->row, 0, parentNode); -} - -int TreeModel::rowCount(const QModelIndex& parent) const -{ - if (parent.isValid()) { - Node* node = static_cast(parent.internalPointer()); - - return node->children.size(); + /// Check if index contains a config list + bool isList(const QModelIndex& index) const + { + Node* node = static_cast(index.internalPointer()); + return (node->type == Node::Type::List); } - return 1; -} - -int TreeModel::columnCount(const QModelIndex&) const -{ - return 1; -} - -bool TreeModel::isList(const QModelIndex& index) const -{ - Node* node = static_cast(index.internalPointer()); - return (node->type == Node::Type::List); -} + /// Check if index contains a config item (parent is a list) + bool isItem(const QModelIndex& index) const + { + Node* node = static_cast(index.internalPointer()); + return (node->parent && node->parent->type == Node::Type::List); + } -bool TreeModel::isItem(const QModelIndex& index) const -{ - Node* node = static_cast(index.internalPointer()); - return (node->parent && node->parent->type == Node::Type::List); -} + void addItem(const QModelIndex& parent, const QString& newName) + { + assert(isList(parent)); -void TreeModel::addItem(const QModelIndex& parent, const QString& newName) -{ - assert(isList(parent)); + const int row = rowCount(parent); + beginInsertRows(parent, row, row); - const int row = rowCount(parent); - beginInsertRows(parent, row, row); + Node& node = *static_cast(parent.internalPointer()); - Node& node = *static_cast(parent.internalPointer()); + std::visit(AddItemVisitor { newName, node }, node.configNode); - std::visit(AddItemVisitor { newName, node }, node.configNode); + endInsertRows(); + } - endInsertRows(); -} + void removeItem(const QModelIndex& index) + { + assert(isItem(index)); -void TreeModel::removeItem(const QModelIndex& index) -{ - assert(isItem(index)); + const QModelIndex& parent = index.parent(); + const int row = index.row(); - const QModelIndex& parent = index.parent(); - const int row = index.row(); + Node& parentNode = *static_cast(parent.internalPointer()); + Node& node = *static_cast(index.internalPointer()); - Node& parentNode = *static_cast(parent.internalPointer()); - Node& node = *static_cast(index.internalPointer()); + beginRemoveRows(parent, row, row); - beginRemoveRows(parent, row, row); + // Remove item in config list + std::visit( + [&parentNode](auto* configNode) { + std::visit(RemoveItemVisitor { *configNode }, parentNode.configNode); + }, + node.configNode); - // Remove item in config list - std::visit( - [&parentNode](auto* configNode) { - std::visit(RemoveItemVisitor { *configNode }, parentNode.configNode); - }, - node.configNode); + // Remove child and retrieve position after. + auto it = parentNode.children.erase(parentNode.children.begin() + row); - // Remove child and retrieve position after. - auto it = parentNode.children.erase(parentNode.children.begin() + row); + // Remap row indices (-1) + for (const auto& end = parentNode.children.end(); it != end; ++it) { + --(*it)->row; + } - // Remap row indices (-1) - for (const auto& end = parentNode.children.end(); it != end; ++it) { - --(*it)->row; + endRemoveRows(); } - endRemoveRows(); -} + void copyItem(const QModelIndex& index, const QString& newName) + { + const QModelIndex& parent = index.parent(); + assert(isList(parent)); -void TreeModel::copyItem(const QModelIndex& index, const QString& newName) -{ - const QModelIndex& parent = index.parent(); - assert(isList(parent)); + const int row = rowCount(parent); + beginInsertRows(parent, row, row); - const int row = rowCount(parent); - beginInsertRows(parent, row, row); + Node& parentNode = *static_cast(parent.internalPointer()); + Node& node = *static_cast(index.internalPointer()); - Node& parentNode = *static_cast(parent.internalPointer()); - Node& node = *static_cast(index.internalPointer()); + std::visit(CopyItemVisitor { newName, parentNode, node }, parentNode.configNode); - std::visit(CopyItemVisitor { newName, parentNode, node }, parentNode.configNode); + endInsertRows(); + } +}; - endInsertRows(); } -} +#include "treemodel.moc" diff --git a/src/view/dialogs/settings/treemodel.h b/src/view/dialogs/settings/treemodel.h deleted file mode 100644 index c1cbd91..0000000 --- a/src/view/dialogs/settings/treemodel.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace view::settings -{ - -class TreeModel : public QAbstractItemModel -{ - Q_OBJECT - -private: - struct ConstructorItemVisitor; - struct AddItemVisitor; - struct RemoveItemVisitor; - struct CopyItemVisitor; - - // ItemModel requests each item to know its parent and also its row in parent - struct Node : common::Aggregable - { - int row; - enum class Type { Group, List } type; - - config::NodePtrVariant configNode; - Node* parent; - ListUPtr children; - - template - static constexpr Node::Type nodeType(const config::Group&) - { - return Node::Type::Group; - } - - template - static constexpr Node::Type nodeType(const config::List&) - { - return Node::Type::List; - }; - - Node() = default; - - template - explicit Node(int _row, ConfigNode& _node, Node* _parent) - : row(_row) - , type(nodeType(_node)) - , configNode(&_node) - , parent(_parent) - { - } - }; - - config::Root& m_configRoot; - Node m_root; - - void constructNodes(); - -public: - explicit TreeModel(config::Root& root, QObject* parent = nullptr); - - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex& index) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& index) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - - //// Visit configuration node at index - template - void visit(const QModelIndex& index, Visitor&& visitor) const - { - Node* node = static_cast(index.internalPointer()); - - std::visit( - [&visitor](auto& node) { - visitor(*node); - }, - node->configNode); - } - - /// Check if index contains a config list - bool isList(const QModelIndex& index) const; - /// Check if index contains a config item (parent is a list) - bool isItem(const QModelIndex& index) const; - - void addItem(const QModelIndex& parent, const QString& newName); - void removeItem(const QModelIndex& index); - void copyItem(const QModelIndex& index, const QString& newName); -}; - -} diff --git a/src/view/dialogs/transform.cpp b/src/view/dialogs/transform.cpp index bbb48e8..89ca366 100644 --- a/src/view/dialogs/transform.cpp +++ b/src/view/dialogs/transform.cpp @@ -1,24 +1,30 @@ -#include +module; -namespace view::dialogs -{ +#include +#include -Transform::Transform() -{ - setupUi(this); -} +export module view.dialogs.transform; -const QTransform& Transform::matrix() const +export namespace view::dialogs { - return m_matrix; -} -void Transform::accept() +class Transform : public QDialog, private Ui::Transform { - QDialog::accept(); +private: + QTransform m_matrix; - m_matrix.translate(offsetXSpinBox->value(), offsetYSpinBox->value()); - m_matrix.rotate(angleSpinBox->value()); -} +public: + explicit Transform() { setupUi(this); } + + const QTransform& matrix() const { return m_matrix; } + + void accept() override + { + QDialog::accept(); + + m_matrix.translate(offsetXSpinBox->value(), offsetYSpinBox->value()); + m_matrix.rotate(angleSpinBox->value()); + } +}; } diff --git a/src/view/dialogs/transform.h b/src/view/dialogs/transform.h deleted file mode 100644 index 3b8c660..0000000 --- a/src/view/dialogs/transform.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -#include - -namespace view::dialogs -{ - -class Transform : public QDialog, private Ui::Transform -{ -private: - QTransform m_matrix; - -public: - explicit Transform(); - - const QTransform& matrix() const; - - void accept() override; -}; - -} diff --git a/src/view/info.cpp b/src/view/info.cpp index 3d9c00b..9a84e62 100644 --- a/src/view/info.cpp +++ b/src/view/info.cpp @@ -1,42 +1,51 @@ -#include -#include -#include - -namespace view -{ - -void Info::showTimedMessage(const QString& content) -{ - message->setText(content); - stackedWidget->setCurrentWidget(messagePage); - - constexpr int showMessageDelay = 2000; - m_timer.start(showMessageDelay); -} +module; -Info::Info(const view2d::Viewport& viewport, const model::Application& app) -{ - setupUi(this); +#include +#include +#include +#include - connect(&viewport, &view2d::Viewport::cursorMoved, this, &Info::cursorMoved); - connect(&app, &model::Application::fileSaved, this, &Info::fileSaved); - connect(&m_timer, &QTimer::timeout, this, &Info::hideMessage); -} +export module view.info; +import model.application; +import view.view2d.viewport; -void Info::cursorMoved(const QPointF& position) +export namespace view { - cursorX->setText(QString::number(position.x())); - cursorY->setText(QString::number(position.y())); -} -void Info::fileSaved(const QString& fileName) +class Info : public QWidget, private Ui::Info { - showTimedMessage(QString("Saved %1").arg(fileName)); -} - -void Info::hideMessage() -{ - stackedWidget->setCurrentWidget(cursorPage); -} +private: + QTimer m_timer; + + void showTimedMessage(const QString& content) + { + message->setText(content); + stackedWidget->setCurrentWidget(messagePage); + + constexpr int showMessageDelay = 2000; + m_timer.start(showMessageDelay); + } + +public: + explicit Info(const view2d::Viewport& viewport, const model::Application& app) + { + setupUi(this); + + connect(&viewport, &view2d::Viewport::cursorMoved, this, &Info::cursorMoved); + connect(&app, &model::Application::fileSaved, this, &Info::fileSaved); + connect(&m_timer, &QTimer::timeout, this, &Info::hideMessage); + } + +protected Q_SLOTS: + void cursorMoved(const QPointF& position) + { + cursorX->setText(QString::number(position.x())); + cursorY->setText(QString::number(position.y())); + } + + void fileSaved(const QString& fileName) { showTimedMessage(QString("Saved %1").arg(fileName)); } + + void hideMessage() { stackedWidget->setCurrentWidget(cursorPage); } +}; } diff --git a/src/view/info.h b/src/view/info.h deleted file mode 100644 index e03b31b..0000000 --- a/src/view/info.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace model -{ - -class Application; - -} - -namespace view -{ - -namespace view2d -{ - - class Viewport; - -} - -class Info : public QWidget, private Ui::Info -{ -private: - QTimer m_timer; - - void showTimedMessage(const QString& content); - -public: - explicit Info(const view2d::Viewport& viewport, const model::Application& app); - -protected Q_SLOTS: - void cursorMoved(const QPointF& position); - void fileSaved(const QString& fileName); - void hideMessage(); -}; - -} diff --git a/src/view/mainwindow.cpp b/src/view/mainwindow.cpp index b21c34d..3791987 100644 --- a/src/view/mainwindow.cpp +++ b/src/view/mainwindow.cpp @@ -1,15 +1,8 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +module; +#include +#include +#include #include #include #include @@ -19,259 +12,285 @@ #include #include -namespace view +export module view.mainwindow; +import view.dialogs.mirror; +import view.dialogs.setorigin; +import view.dialogs.settings.settings; +import view.dialogs.transform; +import view.info; +import view.profile; +import view.simulation.simulation; +import view.task.path; +import view.task.task; +import view.view2d.viewport; + +import model.simulation; +import geometry.utils; +import geometry.rect; +import model.application; +import config.config; +import model.document; + +export namespace view { -QWidget* MainWindow::setupLeftPanel() +class MainWindow : public QMainWindow, private Ui::MainWindow { - task::Task* task = new task::Task(m_app); - task::Path* path = new task::Path(m_app); +private: + model::Application& m_app; - QSplitter* vertSplitter = new QSplitter(Qt::Vertical, this); - vertSplitter->addWidget(task); - vertSplitter->addWidget(path); - vertSplitter->setStretchFactor(0, 1); - vertSplitter->setStretchFactor(1, 0); + simulation::Simulation* m_simulation; - return vertSplitter; -} + QActionGroup m_openedDocumentActions; -QWidget* MainWindow::setupCenterPanel() -{ - view2d::Viewport* viewport2d = new view2d::Viewport(m_app); - m_simulation = new simulation::Simulation(); - Info* info = new Info(*viewport2d, m_app); + QWidget* setupLeftPanel() + { + task::Task* task = new task::Task(m_app); + task::Path* path = new task::Path(m_app); - QSplitter* splitter = new QSplitter(Qt::Horizontal, this); - splitter->addWidget(viewport2d); - splitter->addWidget(m_simulation); - splitter->setStretchFactor(0, 1); - splitter->setStretchFactor(1, 0); + QSplitter* vertSplitter = new QSplitter(Qt::Vertical, this); + vertSplitter->addWidget(task); + vertSplitter->addWidget(path); + vertSplitter->setStretchFactor(0, 1); + vertSplitter->setStretchFactor(1, 0); - QVBoxLayout* layout = new QVBoxLayout(); - layout->addWidget(splitter); - layout->addWidget(info); - layout->setStretch(0, 1); + return vertSplitter; + } - QWidget* container = new QWidget(this); - container->setLayout(layout); + QWidget* setupCenterPanel() + { + view2d::Viewport* viewport2d = new view2d::Viewport(m_app); + m_simulation = new simulation::Simulation(); + Info* info = new Info(*viewport2d, m_app); - return container; -} + QSplitter* splitter = new QSplitter(Qt::Horizontal, this); + splitter->addWidget(viewport2d); + splitter->addWidget(m_simulation); + splitter->setStretchFactor(0, 1); + splitter->setStretchFactor(1, 0); -void MainWindow::setupToolBar() -{ - Profile* profileBar = new Profile(m_app); - toolBar->addWidget(profileBar); -} + QVBoxLayout* layout = new QVBoxLayout(); + layout->addWidget(splitter); + layout->addWidget(info); + layout->setStretch(0, 1); -void MainWindow::setupUi() -{ - Ui::MainWindow::setupUi(this); + QWidget* container = new QWidget(this); + container->setLayout(layout); - QSplitter* horiSplitter = new QSplitter(Qt::Horizontal, this); - horiSplitter->addWidget(setupLeftPanel()); - horiSplitter->addWidget(setupCenterPanel()); - horiSplitter->setStretchFactor(0, 0); - horiSplitter->setStretchFactor(1, 1); + return container; + } - horizontalLayout->addWidget(horiSplitter); + void setupToolBar() + { + Profile* profileBar = new Profile(m_app); + toolBar->addWidget(profileBar); + } - setupToolBar(); -} + void setupUi() + { + Ui::MainWindow::setupUi(this); -void MainWindow::setupMenuActions() -{ - // File actions - connect(actionOpenFile, &QAction::triggered, this, &MainWindow::openFile); - 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); - connect(actionOpenSettings, &QAction::triggered, this, &MainWindow::openSettings); - - // Edit actions - connect(actionLeftCutterCompensation, &QAction::triggered, &m_app, &model::Application::leftCutterCompensation); - connect(actionRightCutterCompensation, &QAction::triggered, &m_app, &model::Application::rightCutterCompensation); - connect(actionResetCutterCompensation, &QAction::triggered, &m_app, &model::Application::resetCutterCompensation); - connect(actionPocketSelection, &QAction::triggered, &m_app, &model::Application::pocketSelection); - connect(actionHideSelection, &QAction::triggered, &m_app, &model::Application::hideSelection); - connect(actionShowHidden, &QAction::triggered, &m_app, &model::Application::showHidden); - 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); - connect(actionUndo, &QAction::triggered, &m_app, &model::Application::undoDocumentChanges); - connect(actionRedo, &QAction::triggered, &m_app, &model::Application::redoDocumentChanges); - connect(actionOptimizeOrder, &QAction::triggered, this, &MainWindow::optimizeOrder); -} + QSplitter* horiSplitter = new QSplitter(Qt::Horizontal, this); + horiSplitter->addWidget(setupLeftPanel()); + horiSplitter->addWidget(setupCenterPanel()); + horiSplitter->setStretchFactor(0, 0); + horiSplitter->setStretchFactor(1, 1); -void MainWindow::setupOpenedDocumentActions() -{ - m_openedDocumentActions.addAction(actionExportFile); - m_openedDocumentActions.addAction(actionExportAsFile); - m_openedDocumentActions.addAction(actionSaveFile); - m_openedDocumentActions.addAction(actionSaveAsFile); - m_openedDocumentActions.addAction(actionLeftCutterCompensation); - m_openedDocumentActions.addAction(actionRightCutterCompensation); - m_openedDocumentActions.addAction(actionResetCutterCompensation); - m_openedDocumentActions.addAction(actionPocketSelection); - m_openedDocumentActions.addAction(actionHideSelection); - m_openedDocumentActions.addAction(actionShowHidden); - m_openedDocumentActions.addAction(actionTransformSelection); - m_openedDocumentActions.addAction(actionMirrorSelection); - m_openedDocumentActions.addAction(actionSetSelectionOrigin); - m_openedDocumentActions.addAction(actionSimulate); - m_openedDocumentActions.addAction(actionUndo); - m_openedDocumentActions.addAction(actionRedo); - m_openedDocumentActions.addAction(actionOptimizeOrder); - - m_openedDocumentActions.setExclusive(true); -} + horizontalLayout->addWidget(horiSplitter); -void MainWindow::setDocumentToolsEnabled(bool enabled) -{ - m_openedDocumentActions.setEnabled(enabled); -} + setupToolBar(); + } -QString MainWindow::defaultFileName(const QString& extension) const -{ - return m_app.lastHandledFileBaseName() + extension; -} + void setupMenuActions() + { + // File actions + connect(actionOpenFile, &QAction::triggered, this, &MainWindow::openFile); + 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); + connect(actionOpenSettings, &QAction::triggered, this, &MainWindow::openSettings); + + // Edit actions + connect(actionLeftCutterCompensation, &QAction::triggered, &m_app, &model::Application::leftCutterCompensation); + connect(actionRightCutterCompensation, &QAction::triggered, &m_app, + &model::Application::rightCutterCompensation); + connect(actionResetCutterCompensation, &QAction::triggered, &m_app, + &model::Application::resetCutterCompensation); + connect(actionPocketSelection, &QAction::triggered, &m_app, &model::Application::pocketSelection); + connect(actionHideSelection, &QAction::triggered, &m_app, &model::Application::hideSelection); + connect(actionShowHidden, &QAction::triggered, &m_app, &model::Application::showHidden); + 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); + connect(actionUndo, &QAction::triggered, &m_app, &model::Application::undoDocumentChanges); + connect(actionRedo, &QAction::triggered, &m_app, &model::Application::redoDocumentChanges); + connect(actionOptimizeOrder, &QAction::triggered, this, &MainWindow::optimizeOrder); + } -MainWindow::MainWindow(model::Application& app) - : m_app(app) - , m_openedDocumentActions(this) -{ - setupUi(); - setupMenuActions(); - setupOpenedDocumentActions(); - showMaximized(); + void setupOpenedDocumentActions() + { + m_openedDocumentActions.addAction(actionExportFile); + m_openedDocumentActions.addAction(actionExportAsFile); + m_openedDocumentActions.addAction(actionSaveFile); + m_openedDocumentActions.addAction(actionSaveAsFile); + m_openedDocumentActions.addAction(actionLeftCutterCompensation); + m_openedDocumentActions.addAction(actionRightCutterCompensation); + m_openedDocumentActions.addAction(actionResetCutterCompensation); + m_openedDocumentActions.addAction(actionPocketSelection); + m_openedDocumentActions.addAction(actionHideSelection); + m_openedDocumentActions.addAction(actionShowHidden); + m_openedDocumentActions.addAction(actionTransformSelection); + m_openedDocumentActions.addAction(actionMirrorSelection); + m_openedDocumentActions.addAction(actionSetSelectionOrigin); + m_openedDocumentActions.addAction(actionSimulate); + m_openedDocumentActions.addAction(actionUndo); + m_openedDocumentActions.addAction(actionRedo); + m_openedDocumentActions.addAction(actionOptimizeOrder); + + m_openedDocumentActions.setExclusive(true); + } - setDocumentToolsEnabled(false); + void setDocumentToolsEnabled(bool enabled) { m_openedDocumentActions.setEnabled(enabled); } - connect(&m_app, &model::Application::titleChanged, this, &MainWindow::setWindowTitle); - connect(&m_app, &model::Application::newDocumentOpened, this, &MainWindow::newDocumentOpened); - connect(&m_app, &model::Application::errorRaised, this, &MainWindow::displayError); -} + QString defaultFileName(const QString& extension) const { return m_app.lastHandledFileBaseName() + extension; } -void MainWindow::openFile() -{ - const QString fileName = QFileDialog::getOpenFileName(this); - if (!fileName.isEmpty() && !m_app.loadFile(fileName)) { - QMessageBox::critical(this, "Error", "Invalid file type " + fileName); +public: + explicit MainWindow(model::Application& app) + : m_app(app) + , m_openedDocumentActions(this) + { + setupUi(); + setupMenuActions(); + setupOpenedDocumentActions(); + showMaximized(); + + setDocumentToolsEnabled(false); + + connect(&m_app, &model::Application::titleChanged, this, &MainWindow::setWindowTitle); + connect(&m_app, &model::Application::newDocumentOpened, this, &MainWindow::newDocumentOpened); + connect(&m_app, &model::Application::errorRaised, this, &MainWindow::displayError); } -} -void MainWindow::saveFile() -{ - const QString lastFileName = m_app.lastSavedDxfplotFileName(); - if (lastFileName.isEmpty()) { - saveAsFile(); - } else if (const QString fileName = defaultFileName(model::Application::FileExtension::Dxfplot); - !m_app.saveToDxfplot(fileName)) { - QMessageBox::critical(this, "Error", "Couldn't save " + fileName); +protected Q_SLOTS: + void openFile() + { + const QString fileName = QFileDialog::getOpenFileName(this); + if (!fileName.isEmpty() && !m_app.loadFile(fileName)) { + QMessageBox::critical(this, "Error", "Invalid file type " + fileName); + } } -} -void MainWindow::saveAsFile() -{ - const QString defaultPath = defaultFileName(model::Application::FileExtension::Dxfplot); - const QString fileName = QFileDialog::getSaveFileName(this, "Save As File", defaultPath, "Text files (*.dxfplot)"); + void saveFile() + { + const QString lastFileName = m_app.lastSavedDxfplotFileName(); + if (lastFileName.isEmpty()) { + saveAsFile(); + } else if (const QString fileName = defaultFileName(model::Application::FileExtension::Dxfplot); + !m_app.saveToDxfplot(fileName)) { + QMessageBox::critical(this, "Error", "Couldn't save " + fileName); + } + } + + void saveAsFile() + { + const QString defaultPath = defaultFileName(model::Application::FileExtension::Dxfplot); + const QString fileName + = QFileDialog::getSaveFileName(this, "Save As File", defaultPath, "Text files (*.dxfplot)"); - if (!fileName.isEmpty() && !m_app.saveToDxfplot(fileName)) { - QMessageBox::critical(this, "Error", "Couldn't save " + fileName); + if (!fileName.isEmpty() && !m_app.saveToDxfplot(fileName)) { + QMessageBox::critical(this, "Error", "Couldn't save " + fileName); + } } -} -void MainWindow::exportFile() -{ - const QString lastFileName = m_app.lastSavedGcodeFileName(); - if (lastFileName.isEmpty()) { - exportAsFile(); - } else if (const QString fileName = defaultFileName(model::Application::FileExtension::Gcode); - !m_app.saveToGcode(fileName)) { - QMessageBox::critical(this, "Error", "Couldn't save " + fileName); + void exportFile() + { + const QString lastFileName = m_app.lastSavedGcodeFileName(); + if (lastFileName.isEmpty()) { + exportAsFile(); + } else if (const QString fileName = defaultFileName(model::Application::FileExtension::Gcode); + !m_app.saveToGcode(fileName)) { + QMessageBox::critical(this, "Error", "Couldn't save " + fileName); + } } -} -void MainWindow::exportAsFile() -{ - const QString defaultPath = defaultFileName(model::Application::FileExtension::Gcode); - const QString fileName = QFileDialog::getSaveFileName(this, "Export File", defaultPath, "Text files (*.ngc *.txt)"); + void exportAsFile() + { + const QString defaultPath = defaultFileName(model::Application::FileExtension::Gcode); + const QString fileName + = QFileDialog::getSaveFileName(this, "Export File", defaultPath, "Text files (*.ngc *.txt)"); - if (!fileName.isEmpty() && !m_app.saveToGcode(fileName)) { - QMessageBox::critical(this, "Error", "Couldn't save " + fileName); + if (!fileName.isEmpty() && !m_app.saveToGcode(fileName)) { + QMessageBox::critical(this, "Error", "Couldn't save " + fileName); + } } -} -void MainWindow::openSettings() -{ - config::Config newConfig = m_app.config(); + void openSettings() + { + config::Config newConfig = m_app.config(); - const bool accepted = [&newConfig] { - settings::Settings settings(newConfig); - return (settings.exec() == QDialog::Accepted); - }(); + const bool accepted = [&newConfig] { + settings::Settings settings(newConfig); + return (settings.exec() == QDialog::Accepted); + }(); - if (accepted) { - newConfig.save(); - m_app.setConfig(std::move(newConfig)); + if (accepted) { + newConfig.save(); + m_app.setConfig(std::move(newConfig)); + } } -} -void MainWindow::transformSelection() -{ - dialogs::Transform transform; - if (transform.exec() == QDialog::Accepted) { - m_app.takeDocumentSnapshot(); - m_app.transformSelection(transform.matrix()); + void transformSelection() + { + dialogs::Transform transform; + if (transform.exec() == QDialog::Accepted) { + m_app.takeDocumentSnapshot(); + m_app.transformSelection(transform.matrix()); + } } -} -void MainWindow::mirrorSelection() -{ - dialogs::Mirror mirror; - if (mirror.exec() == QDialog::Accepted) { - m_app.takeDocumentSnapshot(); - m_app.transformSelection(mirror.matrix()); + void mirrorSelection() + { + dialogs::Mirror mirror; + if (mirror.exec() == QDialog::Accepted) { + m_app.takeDocumentSnapshot(); + m_app.transformSelection(mirror.matrix()); + } } -} -void MainWindow::setSelectionOrigin() -{ - const geometry::Rect selectionBoundingRect = m_app.selectionBoundingRect(); + void setSelectionOrigin() + { + const geometry::Rect selectionBoundingRect = m_app.selectionBoundingRect(); - dialogs::SetOrigin setOrigin(selectionBoundingRect); - if (setOrigin.exec() == QDialog::Accepted) { - m_app.takeDocumentSnapshot(); - m_app.transformSelection(setOrigin.matrix()); // TODO common function + dialogs::SetOrigin setOrigin(selectionBoundingRect); + if (setOrigin.exec() == QDialog::Accepted) { + m_app.takeDocumentSnapshot(); + m_app.transformSelection(setOrigin.matrix()); // TODO common function + } } -} -void MainWindow::newDocumentOpened(model::Document* newDocument) -{ - setDocumentToolsEnabled((newDocument != nullptr)); + void newDocumentOpened(model::Document* newDocument) + { + setDocumentToolsEnabled((newDocument != nullptr)); - m_simulation->hide(); -} + m_simulation->hide(); + } -void MainWindow::displayError(const QString& message) -{ - QMessageBox messageBox; - messageBox.critical(this, "Error", message); -} + void displayError(const QString& message) + { + QMessageBox messageBox; + messageBox.critical(this, "Error", message); + } -void MainWindow::simulate() -{ - model::Simulation simulation = m_app.createSimulation(); - m_simulation->setSimulation(std::move(simulation)); - m_simulation->show(); -} + void simulate() + { + model::Simulation simulation = m_app.createSimulation(); + m_simulation->setSimulation(std::move(simulation)); + m_simulation->show(); + } -void MainWindow::optimizeOrder() -{ - m_app.optimizeOrder(); -} + void optimizeOrder() { m_app.optimizeOrder(); } +}; } diff --git a/src/view/mainwindow.h b/src/view/mainwindow.h deleted file mode 100644 index 7820049..0000000 --- a/src/view/mainwindow.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -class QComboBox; - -namespace view -{ - -namespace simulation -{ - - class Simulation; - -} - -class MainWindow : public QMainWindow, private Ui::MainWindow -{ -private: - model::Application& m_app; - - simulation::Simulation* m_simulation; - - QActionGroup m_openedDocumentActions; - - QWidget* setupLeftPanel(); - QWidget* setupCenterPanel(); - void setupToolBar(); - void setupUi(); - void setupMenuActions(); - void setupOpenedDocumentActions(); - - void setDocumentToolsEnabled(bool enabled); - - QString defaultFileName(const QString& extension) const; - -public: - explicit MainWindow(model::Application& app); - -protected Q_SLOTS: - void openFile(); - void saveFile(); - void saveAsFile(); - void exportFile(); - void exportAsFile(); - void openSettings(); - void transformSelection(); - void mirrorSelection(); - void setSelectionOrigin(); - void newDocumentOpened(model::Document* newDocument); - void displayError(const QString& message); - void simulate(); - void optimizeOrder(); -}; - -} diff --git a/src/view/profile.cpp b/src/view/profile.cpp index 624f6a1..82fd671 100644 --- a/src/view/profile.cpp +++ b/src/view/profile.cpp @@ -1,49 +1,72 @@ -#include +module; -#include +#include -namespace view -{ +export module view.profile; + +import config.config; +import model.documentmodelobserver; +import model.application; -void Profile::updateAllComboBoxesItems() +export namespace view { - updateComboBoxItems(m_app.config().root().tools(), toolComboBox); - updateComboBoxItems(m_app.config().root().profiles(), profileComboBox); -} -Profile::Profile(model::Application& app) - : DocumentModelObserver(app) - , m_app(app) +class Profile : public model::DocumentModelObserver, public Ui::Profile { - setupUi(this); +private: + model::Application& m_app; - updateAllComboBoxesItems(); + template + void updateComboBoxItems(const ConfigList& list, QComboBox* comboBox) + { + // Keep current item selected. + const QString& currentItemName = comboBox->currentText(); - connect(&app, &model::Application::configChanged, this, &Profile::configChanged); - connect(toolComboBox, &QComboBox::currentTextChanged, this, &Profile::currentToolTextChanged); - connect(profileComboBox, &QComboBox::currentTextChanged, this, &Profile::currentProfileTextChanged); -} + comboBox->clear(); -void Profile::documentChanged() -{ - toolComboBox->setCurrentText(QString::fromStdString(document()->toolConfig().name())); - profileComboBox->setCurrentText( - QString::fromStdString(document()->profileConfig().name())); // TODO updateTextFromProfileConfig -} + list.visitChildren([comboBox](const auto& item) { + const QString name = QString::fromStdString(item.name()); + comboBox->addItem(name, name); + }); -void Profile::configChanged([[maybe_unused]] const config::Config& config) -{ - updateAllComboBoxesItems(); -} + // Try to restore selected tool name + comboBox->setCurrentText(currentItemName); + } -void Profile::currentToolTextChanged(const QString& toolName) -{ - m_app.selectTool(toolName); -} + void updateAllComboBoxesItems() + { + updateComboBoxItems(m_app.config().root().tools(), toolComboBox); + updateComboBoxItems(m_app.config().root().profiles(), profileComboBox); + } -void Profile::currentProfileTextChanged(const QString& profileName) -{ - m_app.selectProfile(profileName); -} +public: + explicit Profile(model::Application& app) + : DocumentModelObserver(app) + , m_app(app) + { + setupUi(this); + + updateAllComboBoxesItems(); + + connect(&app, &model::Application::configChanged, this, &Profile::configChanged); + connect(toolComboBox, &QComboBox::currentTextChanged, this, &Profile::currentToolTextChanged); + connect(profileComboBox, &QComboBox::currentTextChanged, this, &Profile::currentProfileTextChanged); + } + +protected: + void documentChanged() override + { + toolComboBox->setCurrentText(QString::fromStdString(document()->toolConfig().name())); + profileComboBox->setCurrentText( + QString::fromStdString(document()->profileConfig().name())); // TODO updateTextFromProfileConfig + } + +public Q_SLOTS: + void configChanged([[maybe_unused]] const config::Config& config) { updateAllComboBoxesItems(); } + + void currentToolTextChanged(const QString& toolName) { m_app.selectTool(toolName); } + + void currentProfileTextChanged(const QString& profileName) { m_app.selectProfile(profileName); } +}; } diff --git a/src/view/profile.h b/src/view/profile.h deleted file mode 100644 index 07e4912..0000000 --- a/src/view/profile.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include - -#include - -namespace view -{ - -class Profile : public model::DocumentModelObserver, public Ui::Profile -{ -private: - model::Application& m_app; - - template - void updateComboBoxItems(const ConfigList& list, QComboBox* comboBox) - { - // Keep current item selected. - const QString& currentItemName = comboBox->currentText(); - - comboBox->clear(); - - list.visitChildren([comboBox](const auto& item) { - const QString name = QString::fromStdString(item.name()); - comboBox->addItem(name, name); - }); - - // Try to restore selected tool name - comboBox->setCurrentText(currentItemName); - } - - void updateAllComboBoxesItems(); - -public: - explicit Profile(model::Application& app); - -protected: - void documentChanged() override; - -public Q_SLOTS: - void configChanged(const config::Config& config); - void currentToolTextChanged(const QString& toolName); - void currentProfileTextChanged(const QString& profileName); -}; - -} diff --git a/src/view/simulation/CMakeLists.txt b/src/view/simulation/CMakeLists.txt index d44f2d2..fdd9bd0 100644 --- a/src/view/simulation/CMakeLists.txt +++ b/src/view/simulation/CMakeLists.txt @@ -2,10 +2,8 @@ add_subdirectory(internal) set(SRC simulation.cpp - - simulation.h ) -add_library(view-simulation ${SRC}) -target_link_libraries(view-simulation PRIVATE config) +add_module_library(view-simulation ${SRC}) +target_link_libraries(view-simulation PUBLIC model model-application PRIVATE config geometry view-simulation-internal) add_dependencies(view-simulation uic) diff --git a/src/view/simulation/internal/CMakeLists.txt b/src/view/simulation/internal/CMakeLists.txt index e58229a..80fa6c9 100644 --- a/src/view/simulation/internal/CMakeLists.txt +++ b/src/view/simulation/internal/CMakeLists.txt @@ -3,12 +3,7 @@ set(SRC toolpath.cpp tool.cpp viewport.cpp - - scene.h - toolpath.h - tool.h - viewport.h ) -add_library(view-simulation-internal ${SRC}) -target_link_libraries(view-simulation-internal Qt6::3DCore Qt6::Widgets Qt6::3DRender) \ No newline at end of file +add_module_library(view-simulation-internal ${SRC}) +target_link_libraries(view-simulation-internal Qt6::3DCore Qt6::Widgets Qt6::3DRender common geometry model model-application) \ No newline at end of file diff --git a/src/view/simulation/internal/scene.cpp b/src/view/simulation/internal/scene.cpp index 428af72..782d45a 100644 --- a/src/view/simulation/internal/scene.cpp +++ b/src/view/simulation/internal/scene.cpp @@ -1,19 +1,31 @@ -#include +module; #include +#include -namespace view::simulation::internal -{ +export module view.simulation.internal.scene; + +import view.simulation.internal.tool; +import view.simulation.internal.toolpath; +import model.simulation; -Scene::Scene(const model::Simulation& simulation) - : m_tool(this, simulation.toolRadius(), 1.0f) - , m_toolPath(this, simulation.approximatedToolPathToLines(0.01)) +export namespace view::simulation::internal { -} -void Scene::setToolPosition(const model::Simulation::ToolPathPoint3D& position) +class Scene : public Qt3DCore::QEntity { - m_tool.setPosition(position); -} +private: + Tool m_tool; + ToolPath m_toolPath; + +public: + explicit Scene(const model::Simulation& simulation) + : m_tool(this, simulation.toolRadius(), 1.0f) + , m_toolPath(this, simulation.approximatedToolPathToLines(0.01)) + { + } + + void 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 deleted file mode 100644 index 4d12de8..0000000 --- a/src/view/simulation/internal/scene.h +++ /dev/null @@ -1,21 +0,0 @@ -#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 9c74564..f39385c 100644 --- a/src/view/simulation/internal/tool.cpp +++ b/src/view/simulation/internal/tool.cpp @@ -1,36 +1,52 @@ -#include +module; +#include +#include +#include #include -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_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); +export module view.simulation.internal.tool; - addComponent(m_transform); - addComponent(mesh); - addComponent(m_material); -} +import model.simulation; -void Tool::setPosition(const model::Simulation::ToolPathPoint3D& position) +export namespace view::simulation::internal { - m_transform->setTranslation(position.position + m_halfHeight); - - static const QColor colorsByMoveType[] = { { 240, 248, 255 }, { 105, 105, 105 } }; - const QColor& colorForCurrentMoveType = colorsByMoveType[static_cast(position.moveType)]; - m_material->setDiffuse(colorForCurrentMoveType); -} +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) + : Qt3DCore::QEntity(parent) + , m_transform(new Qt3DCore::QTransform(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); + addComponent(m_material); + } + + void setPosition(const model::Simulation::ToolPathPoint3D& position) + { + m_transform->setTranslation(position.position + m_halfHeight); + + static const QColor colorsByMoveType[] = { { 240, 248, 255 }, { 105, 105, 105 } }; + + 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 deleted file mode 100644 index 5c2a7c4..0000000 --- a/src/view/simulation/internal/tool.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -namespace view::simulation::internal -{ - -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); - - void setPosition(const model::Simulation::ToolPathPoint3D& position); -}; - -} diff --git a/src/view/simulation/internal/toolpath.cpp b/src/view/simulation/internal/toolpath.cpp index 18ac0a9..c177cf7 100644 --- a/src/view/simulation/internal/toolpath.cpp +++ b/src/view/simulation/internal/toolpath.cpp @@ -1,18 +1,19 @@ -#include +module; +#include #include #include #include #include - #include - #include -#include +#include -#include // TODO +export module view.simulation.internal.toolpath; -namespace view::simulation::internal +import model.simulation; + +export namespace view::simulation::internal { struct PackedVector3D @@ -31,77 +32,87 @@ struct PackedVector3D } }; -void ToolPath::createPolylineFromPoints(const model::Simulation::ToolPathPoint3D::List& points) +class ToolPath : public Qt3DCore::QEntity { - static const uint32_t colorsByMoveType[] - = { (uint32_t)QColor(255, 69, 0).value(), (uint32_t)QColor(72, 61, 139).value() }; +private: + double m_boundingBox[6]; - const int nbPoints = points.size(); + std::unique_ptr m_packedPoints; + std::unique_ptr m_colors; + std::unique_ptr m_indices; - m_packedPoints = std::make_unique(nbPoints); - m_colors = std::make_unique(nbPoints); - m_indices = std::make_unique(nbPoints); + void 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() }; - for (int i = 0; i < nbPoints; ++i) { - const model::Simulation::ToolPathPoint3D& point = points[i]; - m_packedPoints[i] = PackedVector3D(point.position); - m_colors[i] = colorsByMoveType[static_cast(point.moveType)]; - m_indices[i] = i; - } + const int nbPoints = points.size(); - Qt3DCore::QGeometry* geometry = new Qt3DCore::QGeometry(this); + m_packedPoints = std::make_unique(nbPoints); + m_colors = std::make_unique(nbPoints); + m_indices = std::make_unique(nbPoints); - const QByteArray vertexData - = QByteArray::fromRawData((const char*)m_packedPoints.get(), sizeof(PackedVector3D) * nbPoints); - Qt3DCore::QBuffer* vertexBuffer = new Qt3DCore::QBuffer(geometry); - vertexBuffer->setData(vertexData); + for (int i = 0; i < nbPoints; ++i) { + const model::Simulation::ToolPathPoint3D& point = points[i]; + m_packedPoints[i] = PackedVector3D(point.position); + m_colors[i] = colorsByMoveType[static_cast(point.moveType)]; + m_indices[i] = i; + } - const QByteArray colorData = QByteArray::fromRawData((const char*)m_colors.get(), sizeof(uint32_t) * nbPoints); - Qt3DCore::QBuffer* colorBuffer = new Qt3DCore::QBuffer(geometry); - colorBuffer->setData(colorData); + Qt3DCore::QGeometry* geometry = new Qt3DCore::QGeometry(this); - const QByteArray indexData = QByteArray::fromRawData((const char*)m_indices.get(), sizeof(uint32_t) * nbPoints); - Qt3DCore::QBuffer* indexBuffer = new Qt3DCore::QBuffer(geometry); - indexBuffer->setData(indexData); + const QByteArray vertexData + = QByteArray::fromRawData((const char*)m_packedPoints.get(), sizeof(PackedVector3D) * nbPoints); + Qt3DCore::QBuffer* vertexBuffer = new Qt3DCore::QBuffer(geometry); + vertexBuffer->setData(vertexData); - Qt3DCore::QAttribute* vertexAttribute = new Qt3DCore::QAttribute( - vertexBuffer, Qt3DCore::QAttribute::defaultPositionAttributeName(), Qt3DCore::QAttribute::Float, 3, nbPoints); - vertexAttribute->setAttributeType(Qt3DCore::QAttribute::VertexAttribute); + const QByteArray colorData = QByteArray::fromRawData((const char*)m_colors.get(), sizeof(uint32_t) * nbPoints); + Qt3DCore::QBuffer* colorBuffer = new Qt3DCore::QBuffer(geometry); + colorBuffer->setData(colorData); - Qt3DCore::QAttribute* colorAttribute - = new Qt3DCore::QAttribute(colorBuffer, Qt3DCore::QAttribute::defaultColorAttributeName(), - Qt3DCore::QAttribute::UnsignedByte, 4, nbPoints); - colorAttribute->setAttributeType(Qt3DCore::QAttribute::VertexAttribute); + const QByteArray indexData = QByteArray::fromRawData((const char*)m_indices.get(), sizeof(uint32_t) * nbPoints); + Qt3DCore::QBuffer* indexBuffer = new Qt3DCore::QBuffer(geometry); + indexBuffer->setData(indexData); - Qt3DCore::QAttribute* indexAttribute - = new Qt3DCore::QAttribute(indexBuffer, Qt3DCore::QAttribute::UnsignedInt, 3, nbPoints); - indexAttribute->setAttributeType(Qt3DCore::QAttribute::IndexAttribute); + Qt3DCore::QAttribute* vertexAttribute + = new Qt3DCore::QAttribute(vertexBuffer, Qt3DCore::QAttribute::defaultPositionAttributeName(), + Qt3DCore::QAttribute::Float, 3, nbPoints); + vertexAttribute->setAttributeType(Qt3DCore::QAttribute::VertexAttribute); - geometry->addAttribute(vertexAttribute); - geometry->addAttribute(colorAttribute); - geometry->addAttribute(indexAttribute); + Qt3DCore::QAttribute* colorAttribute + = new Qt3DCore::QAttribute(colorBuffer, Qt3DCore::QAttribute::defaultColorAttributeName(), + Qt3DCore::QAttribute::UnsignedByte, 4, nbPoints); + colorAttribute->setAttributeType(Qt3DCore::QAttribute::VertexAttribute); - Qt3DRender::QGeometryRenderer* renderer = new Qt3DRender::QGeometryRenderer(this); - renderer->setGeometry(geometry); - renderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::LineStrip); + Qt3DCore::QAttribute* indexAttribute + = new Qt3DCore::QAttribute(indexBuffer, Qt3DCore::QAttribute::UnsignedInt, 3, nbPoints); + indexAttribute->setAttributeType(Qt3DCore::QAttribute::IndexAttribute); - Qt3DExtras::QPerVertexColorMaterial* material = new Qt3DExtras::QPerVertexColorMaterial(this); + geometry->addAttribute(vertexAttribute); + geometry->addAttribute(colorAttribute); + geometry->addAttribute(indexAttribute); - addComponent(material); - addComponent(renderer); -} + Qt3DRender::QGeometryRenderer* renderer = new Qt3DRender::QGeometryRenderer(this); + renderer->setGeometry(geometry); + renderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::LineStrip); -ToolPath::ToolPath(Qt3DCore::QEntity* parent, const model::Simulation::ToolPathPoint3D::List& points) - : Qt3DCore::QEntity(parent) -{ - createPolylineFromPoints(points); -} + Qt3DExtras::QPerVertexColorMaterial* material = new Qt3DExtras::QPerVertexColorMaterial(this); -ToolPath::~ToolPath() = default; + addComponent(material); + addComponent(renderer); + } -const double (&ToolPath::boundingBox() const)[6] -{ - return m_boundingBox; -} +public: + ToolPath() = default; + explicit ToolPath(Qt3DCore::QEntity* parent, const model::Simulation::ToolPathPoint3D::List& points) + : Qt3DCore::QEntity(parent) + { + createPolylineFromPoints(points); + } + + ~ToolPath() = default; + + const double (&boundingBox() const)[6] { return m_boundingBox; } +}; } diff --git a/src/view/simulation/internal/toolpath.h b/src/view/simulation/internal/toolpath.h deleted file mode 100644 index 1a885e4..0000000 --- a/src/view/simulation/internal/toolpath.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include - -#include - -namespace view::simulation::internal -{ - -struct PackedVector3D; - -class ToolPath : public Qt3DCore::QEntity -{ -private: - 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(Qt3DCore::QEntity* parent, const model::Simulation::ToolPathPoint3D::List& points); - ~ToolPath(); - - const double (&boundingBox() const)[6]; -}; - -} diff --git a/src/view/simulation/internal/viewport.cpp b/src/view/simulation/internal/viewport.cpp index f35e5b1..a8c2023 100644 --- a/src/view/simulation/internal/viewport.cpp +++ b/src/view/simulation/internal/viewport.cpp @@ -1,83 +1,82 @@ -#include -#include +module; +#include #include #include #include - #include #include #include -namespace view::simulation::internal +export module view.simulation.internal.viewport; +import view.simulation.internal.scene; + +export namespace view::simulation::internal { -void Viewport::mousePressEvent(QMouseEvent* e) +class Viewport : public Qt3DExtras::Qt3DWindow { - if (e->buttons() & Qt::MiddleButton) { - m_lastMousePos = e->pos(); +private: + void mousePressEvent(QMouseEvent* e) final + { + 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 mouseMoveEvent(QMouseEvent* e) final + { + 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(); + void wheelEvent(QWheelEvent* e) final + { + const QPoint numPixels = e->pixelDelta(); - const bool slowMotion = e->modifiers() & Qt::ControlModifier; - const float factor = slowMotion ? 0.001f : 0.1f; + const bool slowMotion = e->modifiers() & Qt::ControlModifier; + const float factor = slowMotion ? 0.001f : 0.1f; - const QVector3D translation(0.0f, 0.0f, numPixels.y() * factor); - camera()->translate(translation, Qt3DRender::QCamera::DontTranslateViewCenter); -} + 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; + QPoint m_lastMousePos; + QPoint computeMouseDelta(const QPoint& pos) + { + const QPoint delta = pos - m_lastMousePos; + m_lastMousePos = pos; - return delta; -} + return delta; + } -Viewport::Viewport() -{ - defaultFrameGraph()->setClearColor(QColor(0, 0, 0)); -} +public: + explicit Viewport() { defaultFrameGraph()->setClearColor(QColor(0, 0, 0)); } -QWidget* Viewport::container() -{ - return QWidget::createWindowContainer(this); -} + QWidget* container() { return QWidget::createWindowContainer(this); } -void Viewport::setScene(Scene* scene) -{ - setRootEntity(scene); + void setScene(Scene* scene) + { + setRootEntity(scene); - camera()->setPosition(QVector3D(0, 0, 40.0f)); - camera()->viewAll(); - camera()->setViewCenter(QVector3D(0.0f, 0.0f, 0.0f)); -} + camera()->setPosition(QVector3D(0, 0, 40.0f)); + camera()->viewAll(); + camera()->setViewCenter(QVector3D(0.0f, 0.0f, 0.0f)); + } -bool Viewport::event(QEvent* e) -{ - return Qt3DWindow::event(e); -} + bool event(QEvent* e) override { return Qt3DWindow::event(e); } +}; } diff --git a/src/view/simulation/internal/viewport.h b/src/view/simulation/internal/viewport.h deleted file mode 100644 index 08a310d..0000000 --- a/src/view/simulation/internal/viewport.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include - -class QMouseEvent; -class QWheelEvent; - -namespace view::simulation::internal -{ - -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(); - - 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 5ba681e..8b6946b 100644 --- a/src/view/simulation/simulation.cpp +++ b/src/view/simulation/simulation.cpp @@ -1,9 +1,16 @@ -#include -#include -#include +module; +#include "uic/simulation/ui_simulation.h" +#include +#include #include +export module view.simulation.simulation; +import view.simulation.internal.scene; +import view.simulation.internal.viewport; + +import model.simulation; + namespace view::simulation { @@ -13,62 +20,80 @@ static void setLabelTimeText(QLabel* label, int ms) label->setText(timeText); } -void Simulation::moveToolAtTime(int ms) +} + +export namespace view::simulation +{ + +class Simulation : private Ui::Simulation, public QWidget { - setLabelTimeText(timeLabel, ms); +private: + model::Simulation m_simulation; - const float seconds = float(ms) / 1e3; - const model::Simulation::ToolPathPoint3D toolPosition = m_simulation.toolPositionAtTime(seconds); + std::unique_ptr m_viewport; + std::unique_ptr m_scene; - m_scene->setToolPosition(toolPosition); -} + QTimer m_timer; -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")); +protected slots: + void moveToolAtTime(int ms) + { + setLabelTimeText(timeLabel, ms); + + const float seconds = float(ms) / 1e3; + const model::Simulation::ToolPathPoint3D toolPosition = m_simulation.toolPositionAtTime(seconds); + + m_scene->setToolPosition(toolPosition); } -} -Simulation::Simulation() - : m_viewport(new internal::Viewport()) -{ - setupUi(this); + void 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")); + } + } - connect(timeSlider, &QSlider::valueChanged, this, &Simulation::moveToolAtTime); - connect(startStopButton, &QPushButton::clicked, this, &Simulation::startStopToolAnimation); +public: + explicit Simulation() + : m_viewport(new internal::Viewport()) + { + setupUi(this); - addAction(actionPauseResume); + connect(timeSlider, &QSlider::valueChanged, this, &Simulation::moveToolAtTime); + connect(startStopButton, &QPushButton::clicked, this, &Simulation::startStopToolAnimation); - container->addWidget(m_viewport->container()); + addAction(actionPauseResume); - static const int timerBaseIntervalMs = 30; - m_timer.setInterval(timerBaseIntervalMs); - m_timer.callOnTimeout([this]() { - const int timerIntervalMs = timerBaseIntervalMs * speedSpinBox->value(); - timeSlider->setSliderPosition(timeSlider->sliderPosition() + timerIntervalMs); - }); -} + container->addWidget(m_viewport->container()); -Simulation::~Simulation() = default; + static const int timerBaseIntervalMs = 30; + m_timer.setInterval(timerBaseIntervalMs); + m_timer.callOnTimeout([this]() { + const int timerIntervalMs = timerBaseIntervalMs * speedSpinBox->value(); + timeSlider->setSliderPosition(timeSlider->sliderPosition() + timerIntervalMs); + }); + } -void Simulation::setSimulation(model::Simulation&& simulation) -{ - m_simulation = std::move(simulation); + ~Simulation() = default; - internal::Scene* newScene = new internal::Scene(m_simulation); - m_viewport->setScene(newScene); - m_scene.reset(newScene); + void setSimulation(model::Simulation&& simulation) + { + m_simulation = std::move(simulation); - const float secondDuration = m_simulation.duration(); - const int durationMs = secondDuration * 1e3; - timeSlider->setMaximum(durationMs); + internal::Scene* newScene = new internal::Scene(m_simulation); + m_viewport->setScene(newScene); + m_scene.reset(newScene); - setLabelTimeText(totalTimeLabel, durationMs); -} + const float secondDuration = m_simulation.duration(); + const int durationMs = secondDuration * 1e3; + timeSlider->setMaximum(durationMs); + + setLabelTimeText(totalTimeLabel, durationMs); + } +}; } diff --git a/src/view/simulation/simulation.h b/src/view/simulation/simulation.h deleted file mode 100644 index 6e562f9..0000000 --- a/src/view/simulation/simulation.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include - -#include "uic/simulation/ui_simulation.h" - -#include -#include - -namespace view::simulation -{ - -namespace internal -{ - - class Scene; - class Viewport; - -} - -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: - void moveToolAtTime(int ms); - void startStopToolAnimation(); - -public: - explicit Simulation(); - ~Simulation(); - - void setSimulation(model::Simulation&& simulation); -}; - -} diff --git a/src/view/task/CMakeLists.txt b/src/view/task/CMakeLists.txt index 672e627..2197017 100644 --- a/src/view/task/CMakeLists.txt +++ b/src/view/task/CMakeLists.txt @@ -3,13 +3,9 @@ set(SRC task.cpp pathlistmodel.cpp layertreemodel.cpp - - path.h - task.h - pathlistmodel.h - layertreemodel.h ) -add_library(view-task ${SRC}) -target_link_libraries(view-task PRIVATE config) +add_module_library(view-task ${SRC}) +target_link_libraries(view-task PUBLIC model model-application PRIVATE config geometry) add_dependencies(view-task uic) + diff --git a/src/view/task/layertreemodel.cpp b/src/view/task/layertreemodel.cpp index 30cd761..cadb2c2 100644 --- a/src/view/task/layertreemodel.cpp +++ b/src/view/task/layertreemodel.cpp @@ -1,158 +1,181 @@ -#include +module; +#include +#include +#include #include +#include -namespace view::task -{ +export module view.task.layertreemodel; + +import model.task; +import model.layer; +import model.path; +import model.renderable; -LayerTreeModel::LayerTreeModel(model::Task& task, QObject* parent) - : QAbstractItemModel(parent) - , m_task(task) - , m_ignoreSelectionChanged(false) +export namespace view::task { -} -QVariant LayerTreeModel::data(const QModelIndex& index, int role) const +class LayerTreeModel : public QAbstractItemModel { - if (!index.isValid()) { - return QVariant(); + Q_OBJECT + +private: + model::Task& m_task; + bool m_ignoreSelectionChanged; + +public: + explicit LayerTreeModel(model::Task& task, QObject* parent) + : QAbstractItemModel(parent) + , m_task(task) + , m_ignoreSelectionChanged(false) + { } - const model::Renderable& item = *static_cast(index.internalPointer()); + QVariant data(const QModelIndex& index, int role) const override + { + if (!index.isValid()) { + return QVariant(); + } + + const model::Renderable& item = *static_cast(index.internalPointer()); - switch (role) { - case Qt::DisplayRole: - case Qt::ToolTipRole: { - switch (index.column()) { - case 0: { - return QString::fromStdString(item.name()); - break; + switch (role) { + case Qt::DisplayRole: + case Qt::ToolTipRole: { + switch (index.column()) { + case 0: { + return QString::fromStdString(item.name()); + break; + } } + break; } - break; - } - case Qt::DecorationRole: { - switch (index.column()) { - case 1: { - if (item.visible()) { - return QIcon::fromTheme(":/icons/layer-visible-on.svg"); - } else { - return QIcon::fromTheme(":/icons/layer-visible-off.svg"); + case Qt::DecorationRole: { + switch (index.column()) { + case 1: { + if (item.visible()) { + return QIcon::fromTheme(":/icons/layer-visible-on.svg"); + } else { + return QIcon::fromTheme(":/icons/layer-visible-off.svg"); + } + break; } - break; } + break; } - break; } - } - return QVariant(); -} + return QVariant(); + } -QModelIndex LayerTreeModel::index(int row, int column, const QModelIndex& parent) const -{ - model::Renderable* parentItem = static_cast(parent.internalPointer()); - if (model::Layer* parentLayer = dynamic_cast(parentItem)) { - model::Path& path = parentLayer->childrenAt(row); - return createIndex(row, column, &path); - } else { - model::Layer& layer = m_task.layerAt(row); - return createIndex(row, column, &layer); + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override + { + model::Renderable* parentItem = static_cast(parent.internalPointer()); + if (model::Layer* parentLayer = dynamic_cast(parentItem)) { + model::Path& path = parentLayer->childrenAt(row); + return createIndex(row, column, &path); + } else { + model::Layer& layer = m_task.layerAt(row); + return createIndex(row, column, &layer); + } } -} -QModelIndex LayerTreeModel::parent(const QModelIndex& index) const -{ - if (!index.isValid()) { + QModelIndex parent(const QModelIndex& index) const override + { + if (!index.isValid()) { + return QModelIndex(); + } + + model::Renderable* item = static_cast(index.internalPointer()); + if (model::Path* path = dynamic_cast(item)) { + model::Layer& layer = static_cast(*path->parent()); + const int row = m_task.layerIndexFor(layer); + return createIndex(row, 0, &layer); + } + return QModelIndex(); } - model::Renderable* item = static_cast(index.internalPointer()); - if (model::Path* path = dynamic_cast(item)) { - model::Layer& layer = path->layer(); - const int row = m_task.layerIndexFor(layer); - return createIndex(row, 0, &layer); + int rowCount(const QModelIndex& parent = QModelIndex()) const override + { + model::Renderable* parentItem = static_cast(parent.internalPointer()); + if (model::Layer* parentLayer = dynamic_cast(parentItem)) { + return parentLayer->childrenCount(); + } else if ([[maybe_unused]] model::Path* parentPath = dynamic_cast(parentItem)) { + return 0; + } else { + return m_task.layerCount(); + } } - return QModelIndex(); -} + int columnCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override { return 2; } -int LayerTreeModel::rowCount(const QModelIndex& parent) const -{ - model::Renderable* parentItem = static_cast(parent.internalPointer()); - if (model::Layer* parentLayer = dynamic_cast(parentItem)) { - return parentLayer->childrenCount(); - } else if ([[maybe_unused]] model::Path* parentPath = dynamic_cast(parentItem)) { - return 0; - } else { - return m_task.layerCount(); + Qt::ItemFlags flags(const QModelIndex& index) const override + { + if (index.column() == 0) { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsAutoTristate; + } + return Qt::ItemIsEnabled; } -} -int LayerTreeModel::columnCount([[maybe_unused]] const QModelIndex& parent) const -{ - return 2; -} + void itemClicked(const QModelIndex& index) + { + switch (index.column()) { + case 1: { + model::Renderable* item = static_cast(index.internalPointer()); + item->toggleVisible(); -Qt::ItemFlags LayerTreeModel::flags(const QModelIndex& index) const -{ - if (index.column() == 0) { - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsAutoTristate; + emit documentVisibilityChanged(); + + emit dataChanged(index, index); + } + } } - return Qt::ItemIsEnabled; -} -void LayerTreeModel::itemClicked(const QModelIndex& index) -{ - switch (index.column()) { - case 1: { - model::Renderable* item = static_cast(index.internalPointer()); - item->toggleVisible(); + void clearSelection(QItemSelectionModel* selectionModel) + { + m_ignoreSelectionChanged = true; - emit documentVisibilityChanged(); + selectionModel->clear(); - emit dataChanged(index, index); - } + m_ignoreSelectionChanged = false; } -} - -void LayerTreeModel::clearSelection(QItemSelectionModel* selectionModel) -{ - m_ignoreSelectionChanged = true; - selectionModel->clear(); + void updateItemSelection(const model::Path& path, QItemSelectionModel::SelectionFlag flag, + QItemSelectionModel* selectionModel) + { + m_ignoreSelectionChanged = true; - m_ignoreSelectionChanged = false; -} + 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); -void LayerTreeModel::updateItemSelection(const model::Path& path, QItemSelectionModel::SelectionFlag flag, - QItemSelectionModel* selectionModel) -{ - m_ignoreSelectionChanged = true; + m_ignoreSelectionChanged = false; + } - 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); + void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) + { + if (m_ignoreSelectionChanged) { + return; + } - m_ignoreSelectionChanged = false; -} + for (const QModelIndex& index : selected.indexes()) { + model::Renderable& renderable = *static_cast(index.internalPointer()); + renderable.setSelected(true); + } -void LayerTreeModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) -{ - if (m_ignoreSelectionChanged) { - return; + for (const QModelIndex& index : deselected.indexes()) { + model::Renderable& renderable = *static_cast(index.internalPointer()); + renderable.setSelected(false); + } } - for (const QModelIndex& index : selected.indexes()) { - model::Renderable& renderable = *static_cast(index.internalPointer()); - renderable.setSelected(true); - } +signals: + void documentVisibilityChanged(); +}; - for (const QModelIndex& index : deselected.indexes()) { - model::Renderable& renderable = *static_cast(index.internalPointer()); - renderable.setSelected(false); - } } -} +#include "layertreemodel.moc" diff --git a/src/view/task/layertreemodel.h b/src/view/task/layertreemodel.h deleted file mode 100644 index fcbc7c5..0000000 --- a/src/view/task/layertreemodel.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -namespace view::task -{ - -class LayerTreeModel : public QAbstractItemModel -{ - Q_OBJECT - -private: - model::Task& m_task; - bool m_ignoreSelectionChanged; - -public: - explicit LayerTreeModel(model::Task& task, QObject* parent); - - QVariant data(const QModelIndex& index, int role) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& index) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - 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 6d98a5d..b6dd069 100644 --- a/src/view/task/path.cpp +++ b/src/view/task/path.cpp @@ -1,79 +1,121 @@ -#include "model/application.h" -#include +module; -namespace view::task -{ +#include +#include -void Path::setupModel() -{ - m_groupSettings.reset(new model::PathGroupSettings(task())); - - stackedWidget->setCurrentWidget(pageNoSelection); - - connect(&task(), &model::Task::selectionChanged, this, &Path::selectionChanged); - - connectOnFieldChanged(planeFeedRate, [this](double value) { - m_groupSettings->setPlaneFeedRate(value); - }); - connectOnFieldChanged(depthFeedRate, [this](double value) { - m_groupSettings->setDepthFeedRate(value); - }); - connectOnFieldChanged(intensity, [this](double value) { - m_groupSettings->setIntensity(value); - }); - connectOnFieldChanged(Ui::Path::depth, [this](double value) { - m_groupSettings->setDepth(value); - }); -} +export module view.task.path; +import model.application; -void Path::documentChanged() -{ - setupModel(); - updateFieldVisibility(document()->toolConfig()); -} +import model.documentmodelobserver; +import model.pathgroupsettings; +import model.task; +import config.config; -Path::Path(model::Application& app) - : DocumentModelObserver(app) - , m_app(app) +export namespace view::task { - setupUi(this); - connect(&m_app, &model::Application::toolChanged, this, &Path::toolChanged); - connect(&m_app, &model::Application::configChanged, this, &Path::configChanged); -} -void Path::selectionChanged(bool empty) +class Path : public model::DocumentModelObserver, private Ui::Path { - if (empty) { - stackedWidget->setCurrentWidget(pageNoSelection); - } else { - updateFieldValue(planeFeedRate, m_groupSettings->planeFeedRate()); - updateFieldValue(depthFeedRate, m_groupSettings->depthFeedRate()); - updateFieldValue(intensity, m_groupSettings->intensity()); - updateFieldValue(Ui::Path::depth, m_groupSettings->depth()); +private: + model::Application& m_app; + + std::unique_ptr m_groupSettings; + + void selectionChanged(bool empty) + { + if (empty) { + stackedWidget->setCurrentWidget(pageNoSelection); + } else { + 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); + stackedWidget->setCurrentWidget(pagePathSelected); + } } -} -void Path::toolChanged() -{ - if (document()) { - updateFieldVisibility(document()->toolConfig()); + void toolChanged() + { + if (document()) { + updateFieldVisibility(document()->toolConfig()); + } } -} -void Path::configChanged() -{ - if (document()) { + void configChanged() + { + if (document()) { + updateFieldVisibility(document()->toolConfig()); + } + } + + void updateFieldVisibility(const config::Tools::Tool& tool) + { + const bool toolHasDepth = !tool.general().laser(); + Ui::Path::depth->setVisible(toolHasDepth); + depthLabel->setVisible(toolHasDepth); + } + + template + void connectOnFieldChanged(Field* field, std::function&& 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 + void updateFieldValue(Field* field, std::optional&& valueOpt) + { + if (valueOpt) { + field->setValue(*valueOpt); + } else { + field->clear(); + } + } + + void setupModel() + { + m_groupSettings.reset(new model::PathGroupSettings(task())); + + stackedWidget->setCurrentWidget(pageNoSelection); + + connect(&task(), &model::Task::selectionChanged, this, &Path::selectionChanged); + + connectOnFieldChanged(planeFeedRate, [this](double value) { + m_groupSettings->setPlaneFeedRate(value); + }); + connectOnFieldChanged(depthFeedRate, [this](double value) { + m_groupSettings->setDepthFeedRate(value); + }); + connectOnFieldChanged(intensity, [this](double value) { + m_groupSettings->setIntensity(value); + }); + connectOnFieldChanged(Ui::Path::depth, [this](double value) { + m_groupSettings->setDepth(value); + }); + } + +protected: + void documentChanged() override + { + setupModel(); updateFieldVisibility(document()->toolConfig()); } -} -void Path::updateFieldVisibility(const config::Tools::Tool& tool) -{ - const bool toolHasDepth = !tool.general().laser(); - Ui::Path::depth->setVisible(toolHasDepth); - depthLabel->setVisible(toolHasDepth); -} +public: + explicit Path(model::Application& app) + : DocumentModelObserver(app) + , m_app(app) + { + setupUi(this); + connect(&m_app, &model::Application::toolChanged, this, &Path::toolChanged); + connect(&m_app, &model::Application::configChanged, this, &Path::configChanged); + } +}; } diff --git a/src/view/task/path.h b/src/view/task/path.h deleted file mode 100644 index 90ca1e5..0000000 --- a/src/view/task/path.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#include - -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); - void toolChanged(); - void configChanged(); - - void updateFieldVisibility(const config::Tools::Tool& tool); - - template - void connectOnFieldChanged(Field* field, std::function&& 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 - void updateFieldValue(Field* field, std::optional&& valueOpt) - { - if (valueOpt) { - field->setValue(*valueOpt); - } else { - field->clear(); - } - } - - void setupModel(); - -protected: - void documentChanged() override; - -public: - explicit Path(model::Application& app); -}; - -} diff --git a/src/view/task/pathlistmodel.cpp b/src/view/task/pathlistmodel.cpp index 50bcb99..d647970 100644 --- a/src/view/task/pathlistmodel.cpp +++ b/src/view/task/pathlistmodel.cpp @@ -1,168 +1,189 @@ -#include "qnamespace.h" -#include +module; +#include +#include +#include +#include "qnamespace.h" #include +#include -namespace view::task -{ +export module view.task.pathlistmodel; + +import model.task; +import model.path; -PathListModel::PathListModel(model::Task& task, QObject* parent) - : QAbstractListModel(parent) - , m_task(task) - , m_ignoreSelectionChanged(false) +export namespace view::task { - connect(&m_task, &model::Task::pathOrderChanged, [this] { - emit layoutChanged(); - }); -} -QVariant PathListModel::data(const QModelIndex& index, int role) const +class PathListModel : public QAbstractListModel { - if (!index.isValid()) { - return QVariant(); + Q_OBJECT + +private: + model::Task& m_task; + bool m_ignoreSelectionChanged; + +public: + explicit PathListModel(model::Task& task, QObject* parent) + : QAbstractListModel(parent) + , m_task(task) + , m_ignoreSelectionChanged(false) + { + connect(&m_task, &model::Task::pathOrderChanged, [this] { + emit layoutChanged(); + }); } - const model::Path& path = m_task.pathAt(index.row()); + QVariant data(const QModelIndex& index, int role) const override + { + if (!index.isValid()) { + return QVariant(); + } + + const model::Path& path = m_task.pathAt(index.row()); - switch (role) { - case Qt::DisplayRole: - case Qt::ToolTipRole: { - switch (index.column()) { - case 0: { - return QString::fromStdString(path.name()); - break; + switch (role) { + case Qt::DisplayRole: + case Qt::ToolTipRole: { + switch (index.column()) { + case 0: { + return QString::fromStdString(path.name()); + break; + } } + break; } - break; - } - case Qt::DecorationRole: { - switch (index.column()) { - case 1: { - if (path.globallyVisible()) { - return QIcon::fromTheme(":/icons/layer-visible-on.svg"); - } else { - return QIcon::fromTheme(":/icons/layer-visible-off.svg"); + case Qt::DecorationRole: { + switch (index.column()) { + case 1: { + if (path.globallyVisible()) { + return QIcon::fromTheme(":/icons/layer-visible-on.svg"); + } else { + return QIcon::fromTheme(":/icons/layer-visible-off.svg"); + } + break; } - break; } + break; } - break; } + + return QVariant(); } - return QVariant(); -} + int rowCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return m_task.pathCount(); + } -int PathListModel::rowCount([[maybe_unused]] const QModelIndex& parent) const -{ - return m_task.pathCount(); -} + int columnCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override { return 2; } -int PathListModel::columnCount([[maybe_unused]] const QModelIndex& parent) const -{ - return 2; -} + Qt::ItemFlags flags(const QModelIndex& index) const override + { + if (index.column() == 0) { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + } -Qt::ItemFlags PathListModel::flags(const QModelIndex& index) const -{ - if (index.column() == 0) { - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + const model::Path& path = m_task.pathAt(index.row()); + return (path.parent()->visible()) ? Qt::ItemIsEnabled : Qt::NoItemFlags; } - const model::Path& path = m_task.pathAt(index.row()); - return (path.layer().visible()) ? Qt::ItemIsEnabled : Qt::NoItemFlags; -} + QModelIndex movePathToDirection(const QModelIndex& index, model::Task::MoveDirection direction) + { + const int row = index.row(); + const int newRow = row + direction; -QModelIndex PathListModel::movePathToDirection(const QModelIndex& index, model::Task::MoveDirection direction) -{ - const int row = index.row(); - const int newRow = row + direction; - - if (index.isValid() && 0 <= newRow && newRow < rowCount(index)) { - // Mistic qt move indexing - const int newQtRow = (newRow > row) ? newRow + 1 : newRow; + if (index.isValid() && 0 <= newRow && newRow < rowCount(index)) { + // Mistic qt move indexing + const int newQtRow = (newRow > row) ? newRow + 1 : newRow; - if (beginMoveRows(index, row, row, index, newQtRow)) { + if (beginMoveRows(index, row, row, index, newQtRow)) { - m_task.movePath(row, direction); + m_task.movePath(row, direction); - endMoveRows(); + endMoveRows(); - return this->index(newRow); + return this->index(newRow); + } } + + return index; } - return index; -} + QModelIndex 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); -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); - if (index.isValid()) { - m_task.movePathToTip(row, tip); + const QModelIndex newIndex = this->index(newRow); + emit dataChanged(index, newIndex); - const QModelIndex newIndex = this->index(newRow); - emit dataChanged(index, newIndex); + return newIndex; + } - return newIndex; + return index; } - return index; -} - -void PathListModel::itemClicked(const QModelIndex& index) -{ - if ((index.flags() & Qt::ItemIsEnabled) == 0) { - return; - } + void itemClicked(const QModelIndex& index) + { + if ((index.flags() & Qt::ItemIsEnabled) == 0) { + return; + } - switch (index.column()) { - case 1: { - model::Path& path = m_task.pathAt(index.row()); - path.toggleVisible(); + switch (index.column()) { + case 1: { + model::Path& path = m_task.pathAt(index.row()); + path.toggleVisible(); - emit dataChanged(index, index); + emit dataChanged(index, index); + } } } -} - -void PathListModel::clearSelection(QItemSelectionModel* selectionModel) -{ - m_ignoreSelectionChanged = true; - selectionModel->clear(); + void clearSelection(QItemSelectionModel* selectionModel) + { + m_ignoreSelectionChanged = true; - m_ignoreSelectionChanged = false; -} + selectionModel->clear(); -void PathListModel::updateItemSelection(const model::Path& path, QItemSelectionModel::SelectionFlag flag, - QItemSelectionModel* selectionModel) -{ - m_ignoreSelectionChanged = true; + m_ignoreSelectionChanged = false; + } - const int row = m_task.pathIndexFor(path); - selectionModel->select(index(row, 0), flag); + void updateItemSelection(const model::Path& path, QItemSelectionModel::SelectionFlag flag, + QItemSelectionModel* selectionModel) + { + m_ignoreSelectionChanged = true; - m_ignoreSelectionChanged = false; -} + const int row = m_task.pathIndexFor(path); + selectionModel->select(index(row, 0), flag); -void PathListModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) -{ - if (m_ignoreSelectionChanged) { - return; + m_ignoreSelectionChanged = false; } - for (const QModelIndex& index : selected.indexes()) { - model::Path& path = m_task.pathAt(index.row()); - path.setSelected(true); - } + void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) + { + if (m_ignoreSelectionChanged) { + return; + } - for (const QModelIndex& index : deselected.indexes()) { - model::Path& path = m_task.pathAt(index.row()); - path.setSelected(false); + for (const QModelIndex& index : selected.indexes()) { + model::Path& path = m_task.pathAt(index.row()); + path.setSelected(true); + } + + for (const QModelIndex& index : deselected.indexes()) { + model::Path& path = m_task.pathAt(index.row()); + path.setSelected(false); + } } -} + +signals: + void documentVisibilityChanged(); +}; } + +#include "pathlistmodel.moc" diff --git a/src/view/task/pathlistmodel.h b/src/view/task/pathlistmodel.h deleted file mode 100644 index 2e9edce..0000000 --- a/src/view/task/pathlistmodel.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -namespace view::task -{ - -class PathListModel : public QAbstractListModel -{ - Q_OBJECT - -private: - model::Task& m_task; - bool m_ignoreSelectionChanged; - -public: - explicit PathListModel(model::Task& task, QObject* parent); - - QVariant data(const QModelIndex& index, int role) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - Qt::ItemFlags flags(const QModelIndex& index) const override; - - 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 f0b4443..2b0354a 100644 --- a/src/view/task/task.cpp +++ b/src/view/task/task.cpp @@ -1,101 +1,160 @@ -#include - -#include -#include +module; +#include +#include +#include #include #include -namespace view::task -{ - -Task::Task(model::Application& app) - : DocumentModelObserver(app) - , m_app(app) -{ - setupUi(this); -} - -void Task::setupModel() -{ - m_pathListModel = setupTreeViewModel(pathsTreeView); - m_layerTreeModel = setupTreeViewModel(layersTreeView); - - layersTreeView->expandAll(); -} - -void Task::setupController() -{ - // Track outside path selection, e.g from graphics view. - connect(&task(), &model::Task::pathSelectedChanged, this, &Task::pathSelectedChanged); - - setupTreeViewController(m_pathListModel, pathsTreeView); - setupTreeViewController(m_layerTreeModel, layersTreeView); - - 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); - }); -} +export module view.task.task; +import view.task.layertreemodel; +import view.task.pathlistmodel; -void Task::updateItemSelection(const model::Path& path, QItemSelectionModel::SelectionFlag flag) -{ - m_pathListModel->updateItemSelection(path, flag, pathsTreeView->selectionModel()); - m_layerTreeModel->updateItemSelection(path, flag, layersTreeView->selectionModel()); -} +import model.documentmodelobserver; +import model.task; +import model.application; +import model.path; -void Task::documentChanged() +export namespace view::task { - setupModel(); - setupController(); -} -void Task::pathSelectedChanged(model::Path& path, bool selected) +class Task : public model::DocumentModelObserver, private Ui::Task { - updateItemSelection(path, selected ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); -} - -void Task::moveCurrentPathToDirection(model::Task::MoveDirection direction) -{ - 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); - }); -} +private: + model::Application& m_app; + + std::unique_ptr m_pathListModel; + std::unique_ptr m_layerTreeModel; + + template + std::unique_ptr setupTreeViewModel(QTreeView* treeView) + { + std::unique_ptr model = std::make_unique(task(), this); + treeView->setModel(model.get()); + + QHeaderView* header = treeView->header(); + header->setStretchLastSection(false); + header->setSectionResizeMode(0, QHeaderView::Stretch); + header->setSectionResizeMode(1, QHeaderView::ResizeToContents); + + return model; + } + + template + void setupTreeViewController(std::unique_ptr& model, QTreeView* treeView) + { + // Synchronize selection in 2D view + QItemSelectionModel* selectionModel = treeView->selectionModel(); + 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() + { + m_pathListModel = setupTreeViewModel(pathsTreeView); + m_layerTreeModel = setupTreeViewModel(layersTreeView); + + layersTreeView->expandAll(); + } + + void setupController() + { + // Track outside path selection, e.g from graphics view. + connect(&task(), &model::Task::pathSelectedChanged, this, &Task::pathSelectedChanged); + + setupTreeViewController(m_pathListModel, pathsTreeView); + setupTreeViewController(m_layerTreeModel, layersTreeView); + + 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 updateItemSelection(const model::Path& path, QItemSelectionModel::SelectionFlag flag) + { + m_pathListModel->updateItemSelection(path, flag, pathsTreeView->selectionModel()); + m_layerTreeModel->updateItemSelection(path, flag, layersTreeView->selectionModel()); + } + + void moveCurrentPathToDirection(model::Task::MoveDirection direction) + { + moveCurrentPath([this, direction](const QModelIndex& index) { + m_pathListModel->movePathToDirection(index, direction); + }); + } + + void moveCurrentPathToTip(model::Task::MoveTip tip) + { + moveCurrentPath([this, tip](const QModelIndex& index) { + m_pathListModel->movePathToTip(index, 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() + { + 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); + }); + } + +public: + explicit Task(model::Application& app) + : DocumentModelObserver(app) + , m_app(app) + { + setupUi(this); + } + +protected: + void documentChanged() + { + setupModel(); + setupController(); + } + +protected Q_SLOTS: + void pathSelectedChanged(model::Path& path, bool selected) + { + updateItemSelection(path, selected ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); + } + + void documentVisibilityChanged() { m_app.takeDocumentSnapshot(); } +}; } diff --git a/src/view/task/task.h b/src/view/task/task.h deleted file mode 100644 index d221771..0000000 --- a/src/view/task/task.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include -#include - -namespace view::task -{ - -class PathListModel; -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; - - template - std::unique_ptr setupTreeViewModel(QTreeView* treeView) - { - std::unique_ptr model = std::make_unique(task(), this); - treeView->setModel(model.get()); - - QHeaderView* header = treeView->header(); - header->setStretchLastSection(false); - header->setSectionResizeMode(0, QHeaderView::Stretch); - header->setSectionResizeMode(1, QHeaderView::ResizeToContents); - - return model; - } - - template - void setupTreeViewController(std::unique_ptr& model, QTreeView* treeView) - { - // Synchronize selection in 2D view - QItemSelectionModel* selectionModel = treeView->selectionModel(); - 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(); - void setupController(); - - 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); - -protected: - void documentChanged(); - -protected Q_SLOTS: - void pathSelectedChanged(model::Path& path, bool selected); - void documentVisibilityChanged(); -}; - -} diff --git a/src/view/transform.cpp b/src/view/transform.cpp new file mode 100644 index 0000000..2fabf51 --- /dev/null +++ b/src/view/transform.cpp @@ -0,0 +1 @@ +export module view.transform; diff --git a/src/view/transform.h b/src/view/transform.h deleted file mode 100644 index e69de29..0000000 diff --git a/src/view/view2d/CMakeLists.txt b/src/view/view2d/CMakeLists.txt index 69a9b79..0d772ce 100644 --- a/src/view/view2d/CMakeLists.txt +++ b/src/view/view2d/CMakeLists.txt @@ -6,16 +6,8 @@ set(SRC polylinepathitem.cpp rubberband.cpp viewport.cpp - - basicpathitem.h - bulgepainter.h - offsettedpolylinepathitem.h - pointpathitem.h - polylinepathitem.h - rubberband.h - viewport.h ) -add_library(view-view2d ${SRC}) -target_link_libraries(view-view2d PRIVATE config) +add_module_library(view-view2d ${SRC}) +target_link_libraries(view-view2d PUBLIC model model-application PRIVATE config geometry) add_dependencies(view-view2d uic) diff --git a/src/view/view2d/basicpathitem.cpp b/src/view/view2d/basicpathitem.cpp index 85e2ac5..4ca0e83 100644 --- a/src/view/view2d/basicpathitem.cpp +++ b/src/view/view2d/basicpathitem.cpp @@ -1,75 +1,97 @@ -#include +module; +#include #include #include #include +#include -namespace view::view2d -{ +export module view.view2d.basicpathitem; -static const QBrush normalBrush(Qt::white); -static const QBrush selectBrush(QColor(80, 0, 255)); -static const QPen normalPen(normalBrush, 0.0f); -static const QPen selectPen(selectBrush, 0.0f); +import model.path; -BasicPathItem::BasicPathItem(model::Path& path) - : m_path(path) - , m_outsideSelectionBlocked(false) +namespace view::view2d { - setPen(normalPen); - setFlag(ItemIsSelectable); - setVisible(m_path.globallyVisible()); - connect(&m_path, &model::Path::selectedChanged, this, &BasicPathItem::selectedChanged); - connect(&m_path, &model::Path::globalVisibilityChanged, this, &BasicPathItem::visibilityChanged); - connect(&m_path, &model::Path::basePolylineTransformed, this, &BasicPathItem::basePolylineTransformed); -} - -void BasicPathItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, [[maybe_unused]] QWidget* widget) +namespace { - QStyleOptionGraphicsItem fixedOption(*option); - if (fixedOption.state & QStyle::State_Selected) { - fixedOption.state &= ~QStyle::State_Selected; - setPen(selectPen); - } else { - setPen(normalPen); - } + const QBrush normalBrush(Qt::white); + const QBrush selectBrush(QColor(80, 0, 255)); + const QPen normalPen(normalBrush, 0.0f); + const QPen selectPen(selectBrush, 0.0f); - painter->setPen(pen()); } -const model::Path& BasicPathItem::path() const -{ - return m_path; } -void BasicPathItem::setSelected(bool selected) +export namespace view::view2d { - QAbstractGraphicsShapeItem::setSelected(selected); -} -QVariant BasicPathItem::itemChange(GraphicsItemChange change, const QVariant& value) +class BasicPathItem : public QObject, public QGraphicsPathItem { - if (change & ItemSelectedChange) { - m_outsideSelectionBlocked = true; - m_path.setSelected(isSelected()); - m_outsideSelectionBlocked = false; + Q_OBJECT; + +private: + model::Path& m_path; + bool m_outsideSelectionBlocked; + +public: + explicit BasicPathItem(model::Path& path) + : m_path(path) + , m_outsideSelectionBlocked(false) + { + setPen(normalPen); + setFlag(ItemIsSelectable); + setVisible(m_path.globallyVisible()); + + connect(&m_path, &model::Path::selectedChanged, this, &BasicPathItem::selectedChanged); + connect(&m_path, &model::Path::globalVisibilityChanged, this, &BasicPathItem::visibilityChanged); + connect(&m_path, &model::Path::basePolylineTransformed, this, &BasicPathItem::basePolylineTransformed); } - return QAbstractGraphicsShapeItem::itemChange(change, value); -} + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, [[maybe_unused]] QWidget* widget) override + { + QStyleOptionGraphicsItem fixedOption(*option); -void BasicPathItem::selectedChanged(bool selected) -{ - if (!m_outsideSelectionBlocked) { - setSelected(selected); + if (fixedOption.state & QStyle::State_Selected) { + fixedOption.state &= ~QStyle::State_Selected; + setPen(selectPen); + } else { + setPen(normalPen); + } + + painter->setPen(pen()); } -} -void BasicPathItem::visibilityChanged(bool visible) -{ - setVisible(visible); -} + const model::Path& path() const { return m_path; } + + virtual void setSelected(bool selected) { QAbstractGraphicsShapeItem::setSelected(selected); } + +protected Q_SLOTS: + QVariant itemChange(GraphicsItemChange change, const QVariant& value) override + { + if (change & ItemSelectedChange) { + m_outsideSelectionBlocked = true; + m_path.setSelected(isSelected()); + m_outsideSelectionBlocked = false; + } + + return QAbstractGraphicsShapeItem::itemChange(change, value); + } + + void selectedChanged(bool selected) + { + if (!m_outsideSelectionBlocked) { + setSelected(selected); + } + } + + void visibilityChanged(bool visible) { setVisible(visible); } + + virtual void basePolylineTransformed() = 0; +}; } + +#include "basicpathitem.moc" diff --git a/src/view/view2d/basicpathitem.h b/src/view/view2d/basicpathitem.h deleted file mode 100644 index 35b515b..0000000 --- a/src/view/view2d/basicpathitem.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include - -#include - -namespace view::view2d -{ - -class BasicPathItem : public QObject, public QGraphicsPathItem -{ - Q_OBJECT; - -private: - model::Path& m_path; - bool m_outsideSelectionBlocked; - -public: - explicit BasicPathItem(model::Path& path); - - void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; - - const model::Path& path() const; - virtual void setSelected(bool selected); - -protected Q_SLOTS: - QVariant itemChange(GraphicsItemChange change, const QVariant& value) override; - void selectedChanged(bool selected); - void visibilityChanged(bool visible); - virtual void basePolylineTransformed() = 0; -}; - -} diff --git a/src/view/view2d/bulgepainter.cpp b/src/view/view2d/bulgepainter.cpp index 0eec0d4..c4f1945 100644 --- a/src/view/view2d/bulgepainter.cpp +++ b/src/view/view2d/bulgepainter.cpp @@ -1,28 +1,45 @@ -#include -#include +module; -namespace view::view2d -{ +#include +#include + +export module view.view2d.bulgepainter; + +import geometry.arc; +import geometry.bulge; +import model.path; -BulgePainter::BulgePainter(QPainterPath& painter) - : m_painter(painter) +export namespace view::view2d { -} -void BulgePainter::operator()(const geometry::Bulge& bulge) +class BulgePainter { - if (bulge.isLine()) { - const QVector2D& end = bulge.end(); - m_painter.lineTo(end.toPointF()); - } else { - const geometry::Arc arc = bulge.toArc(); +private: + QPainterPath& m_painter; - const float maxError = 0.0001; // TODO const + void lineToArcPoint(const QVector2D& center, float radius, float angle); - arc.approximateToLinesVisit(maxError, [this](const QVector2D& point) { - m_painter.lineTo(point.toPointF()); - }); +public: + explicit BulgePainter(QPainterPath& painter) + : m_painter(painter) + { } -} + + void operator()(const geometry::Bulge& bulge) + { + if (bulge.isLine()) { + const QVector2D& end = bulge.end(); + m_painter.lineTo(end.toPointF()); + } else { + const geometry::Arc arc = bulge.toArc(); + + const float maxError = 0.0001; // TODO const + + arc.approximateToLinesVisit(maxError, [this](const QVector2D& point) { + m_painter.lineTo(point.toPointF()); + }); + } + } +}; } diff --git a/src/view/view2d/bulgepainter.h b/src/view/view2d/bulgepainter.h deleted file mode 100644 index e559d5b..0000000 --- a/src/view/view2d/bulgepainter.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -#include - -namespace view::view2d -{ - -class BulgePainter -{ -private: - QPainterPath& m_painter; - - void lineToArcPoint(const QVector2D& center, float radius, float angle); - -public: - explicit BulgePainter(QPainterPath& painter); - - void operator()(const geometry::Bulge& bulge); -}; - -} diff --git a/src/view/view2d/offsettedpolylinepathitem.cpp b/src/view/view2d/offsettedpolylinepathitem.cpp index c54f095..f473ef3 100644 --- a/src/view/view2d/offsettedpolylinepathitem.cpp +++ b/src/view/view2d/offsettedpolylinepathitem.cpp @@ -1,75 +1,96 @@ -#include -#include - -#include +module; +#include #include #include #include +#include + +export module view.view2d.offsettedpolylinepathitem; +import view.view2d.bulgepainter; + +import geometry.arc; +import model.path; +import geometry.polyline; +import model.offsettedpath; namespace view::view2d { -static const QBrush normalBrush(Qt::magenta); -static const QBrush selectBrush(Qt::red); -static const QPen normalPen(normalBrush, 0.0f); -static const QPen selectPen(selectBrush, 0.0f); +namespace +{ + + const QBrush normalBrush(Qt::magenta); + const QBrush selectBrush(Qt::red); + const QPen normalPen(normalBrush, 0.0f); + const QPen selectPen(selectBrush, 0.0f); + +} -QPainterPath OffsettedPolylinePathItem::paintPath() const +} + +export namespace view::view2d +{ + +class OffsettedPolylinePathItem : public QObject, public QGraphicsPathItem { - const geometry::Polyline::List polylines = m_offsettedPath.polylines(); + Q_OBJECT; + +private: + const model::OffsettedPath& m_offsettedPath; + QPainterPath m_paintPath; - QPainterPath rootPainter; + QPainterPath paintPath() const + { + const geometry::Polyline::List polylines = m_offsettedPath.polylines(); - for (const geometry::Polyline& polyline : polylines) { - QPainterPath painter(polyline.start().toPointF()); + QPainterPath rootPainter; - BulgePainter functor(painter); - polyline.forEachBulge(functor); + for (const geometry::Polyline& polyline : polylines) { + QPainterPath painter(polyline.start().toPointF()); - rootPainter.addPath(painter); + BulgePainter functor(painter); + polyline.forEachBulge(functor); + + rootPainter.addPath(painter); + } + + return rootPainter; } - return rootPainter; -} + QPainterPath shape() const override { return QPainterPath(); } -QPainterPath OffsettedPolylinePathItem::shape() const -{ - return QPainterPath(); -} + void setupPaths() + { + m_paintPath = paintPath(); + setPath(m_paintPath); + } -void OffsettedPolylinePathItem::setupPaths() -{ - m_paintPath = paintPath(); - setPath(m_paintPath); -} +public: + explicit OffsettedPolylinePathItem(const model::OffsettedPath& offsettedPath) + : QGraphicsPathItem(QPainterPath()) + , m_offsettedPath(offsettedPath) + { + setupPaths(); + setPen(normalPen); -OffsettedPolylinePathItem::OffsettedPolylinePathItem(const model::OffsettedPath& offsettedPath) - : QGraphicsPathItem(QPainterPath()) - , m_offsettedPath(offsettedPath) -{ - setupPaths(); - setPen(normalPen); + connect(&offsettedPath, &model::OffsettedPath::polylinesTransformed, this, + &OffsettedPolylinePathItem::polylinesTransformed); + } - connect(&offsettedPath, &model::OffsettedPath::polylinesTransformed, this, - &OffsettedPolylinePathItem::polylinesTransformed); -} + void selected() { setPen(selectPen); } -void OffsettedPolylinePathItem::selected() -{ - setPen(selectPen); -} + void deselected() { setPen(normalPen); } -void OffsettedPolylinePathItem::deselected() -{ - setPen(normalPen); -} +protected Q_SLOTS: + void polylinesTransformed() + { + setupPaths(); -void OffsettedPolylinePathItem::polylinesTransformed() -{ - setupPaths(); + update(); + } +}; - update(); } -} +#include "offsettedpolylinepathitem.moc" diff --git a/src/view/view2d/offsettedpolylinepathitem.h b/src/view/view2d/offsettedpolylinepathitem.h deleted file mode 100644 index 4612e41..0000000 --- a/src/view/view2d/offsettedpolylinepathitem.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include - -#include - -namespace view::view2d -{ - -class OffsettedPolylinePathItem : public QObject, public QGraphicsPathItem -{ - Q_OBJECT; - -private: - const model::OffsettedPath& m_offsettedPath; - QPainterPath m_paintPath; - - QPainterPath paintPath() const; - - QPainterPath shape() const override; - - void setupPaths(); - -public: - explicit OffsettedPolylinePathItem(const model::OffsettedPath& offsettedPath); - - void selected(); - void deselected(); - -protected Q_SLOTS: - void polylinesTransformed(); -}; - -} diff --git a/src/view/view2d/pointpathitem.cpp b/src/view/view2d/pointpathitem.cpp index 8dd09e0..0a89b83 100644 --- a/src/view/view2d/pointpathitem.cpp +++ b/src/view/view2d/pointpathitem.cpp @@ -1,46 +1,62 @@ -#include +module; #include #include +#include -namespace view::view2d -{ - -QPainterPath PointPathItem::shapePath() const -{ - QPainterPath path; - constexpr float width = 0.05f; // TODO const or config - path.addEllipse(0.0f, 0.0f, width, width); +export module view.view2d.pointpathitem; - return path; -} +import view.view2d.basicpathitem; +import view.view2d.offsettedpolylinepathitem; +import model.path; -void PointPathItem::setupPosition() +export namespace view::view2d { - const QPointF point = path().basePolyline().start().toPointF(); - setPos(point); -} -PointPathItem::PointPathItem(model::Path& path) - : BasicPathItem(path) +/** @brief Graphics path item meant to display polylines with length. + */ +class PointPathItem : public BasicPathItem { - setPath(shapePath()); - setFlag(QGraphicsItem::ItemIgnoresTransformations); + Q_OBJECT; + +private: + QPainterPath shapePath() const + { + QPainterPath path; + constexpr float width = 0.05f; // TODO const or config + path.addEllipse(0.0f, 0.0f, width, width); + + return path; + } + + void setupPosition() + { + const QPointF point = path().basePolyline().start().toPointF(); + setPos(point); + } + +public: + explicit PointPathItem(model::Path& path) + : BasicPathItem(path) + { + setPath(shapePath()); + setFlag(QGraphicsItem::ItemIgnoresTransformations); + + setupPosition(); + } + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override + { + BasicPathItem::paint(painter, option, widget); + + painter->drawLine(-1.0f, 0.0f, 1.0f, 0.0f); + painter->drawLine(0.0f, -1.0f, 0.0f, 1.0f); + } + +protected: + void basePolylineTransformed() override { setupPosition(); } +}; - setupPosition(); } -void PointPathItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) -{ - BasicPathItem::paint(painter, option, widget); - - painter->drawLine(-1.0f, 0.0f, 1.0f, 0.0f); - painter->drawLine(0.0f, -1.0f, 0.0f, 1.0f); -} - -void PointPathItem::basePolylineTransformed() -{ - setupPosition(); -} - -} +#include "pointpathitem.moc" diff --git a/src/view/view2d/pointpathitem.h b/src/view/view2d/pointpathitem.h deleted file mode 100644 index e6f44f9..0000000 --- a/src/view/view2d/pointpathitem.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include - -namespace view::view2d -{ - -/** @brief Graphics path item meant to display polylines with length. - */ -class PointPathItem : public BasicPathItem -{ - Q_OBJECT; - -private: - QPainterPath shapePath() const; - - void setupPosition(); - -public: - explicit PointPathItem(model::Path& path); - - void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; - -protected: - void basePolylineTransformed() override; -}; - -} diff --git a/src/view/view2d/polylinepathitem.cpp b/src/view/view2d/polylinepathitem.cpp index 01da7d8..dd98fb4 100644 --- a/src/view/view2d/polylinepathitem.cpp +++ b/src/view/view2d/polylinepathitem.cpp @@ -1,88 +1,112 @@ -#include -#include +module; + +#include +#include #include +#include +#include + +export module view.view2d.polylinepathitem; +import view.view2d.bulgepainter; -#include // TODO +import view.view2d.basicpathitem; +import view.view2d.offsettedpolylinepathitem; +import geometry.polyline; +import model.offsettedpath; +import model.path; -namespace view::view2d +export namespace view::view2d { -QPainterPath PolylinePathItem::paintPath() const +/** @brief Graphics path item meant to display polylines with length. + */ +class PolylinePathItem : public BasicPathItem { - const geometry::Polyline& polyline = path().basePolyline(); + Q_OBJECT; - QPainterPath painter(polyline.start().toPointF()); +private: + QPainterPath m_paintPath; + QPainterPath m_shapePath; - BulgePainter functor(painter); - polyline.forEachBulge(functor); + // Item of offsetted polylines of the same path. + std::unique_ptr m_offsettedPath; - return painter; -} + QPainterPath paintPath() const + { + const geometry::Polyline& polyline = path().basePolyline(); -QPainterPath PolylinePathItem::shapePath(const QPainterPath& basePath) -{ - QPainterPathStroker stroker; - stroker.setWidth(0.05f); // TODO const or config - stroker.setCapStyle(Qt::RoundCap); - stroker.setJoinStyle(Qt::RoundJoin); + QPainterPath painter(polyline.start().toPointF()); - return stroker.createStroke(basePath); -} + BulgePainter functor(painter); + polyline.forEachBulge(functor); -void PolylinePathItem::setupPaths() -{ - m_paintPath = paintPath(); - setPath(m_paintPath); - m_shapePath = shapePath(m_paintPath); -} + return painter; + } -void PolylinePathItem::updateOffsetedPath() -{ - model::OffsettedPath* offsettedPath = path().offsettedPath(); - if (offsettedPath) { - m_offsettedPath = std::make_unique(*offsettedPath); - // Link our offsetted path item for drawing - m_offsettedPath->setParentItem(this); // TODO use QGraphicsItemGroup - } else { - m_offsettedPath.reset(); + static QPainterPath shapePath(const QPainterPath& basePath) + { + QPainterPathStroker stroker; + stroker.setWidth(0.05f); // TODO const or config + stroker.setCapStyle(Qt::RoundCap); + stroker.setJoinStyle(Qt::RoundJoin); + + return stroker.createStroke(basePath); } -} -void PolylinePathItem::setSelected(bool selected) -{ - BasicPathItem::setSelected(selected); + void setupPaths() + { + m_paintPath = paintPath(); + setPath(m_paintPath); + m_shapePath = shapePath(m_paintPath); + } - if (m_offsettedPath) { - m_offsettedPath->setSelected(selected); + void updateOffsetedPath() + { + model::OffsettedPath* offsettedPath = path().offsettedPath(); + if (offsettedPath) { + m_offsettedPath = std::make_unique(*offsettedPath); + // Link our offsetted path item for drawing + m_offsettedPath->setParentItem(this); // TODO use QGraphicsItemGroup + } else { + m_offsettedPath.reset(); + } } -} -PolylinePathItem::PolylinePathItem(model::Path& path) - : BasicPathItem(path) -{ - setupPaths(); + // Change selected state and propagate to sub paths (e.g offsetted path). + void setSelected(bool selected) override + { + BasicPathItem::setSelected(selected); - updateOffsetedPath(); + if (m_offsettedPath) { + m_offsettedPath->setSelected(selected); + } + } - connect(&path, &model::Path::offsettedPathChanged, this, &PolylinePathItem::updateOffsetedPath); -} +public: + explicit PolylinePathItem(model::Path& path) + : BasicPathItem(path) + { + setupPaths(); -void PolylinePathItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) -{ - BasicPathItem::paint(painter, option, widget); + updateOffsetedPath(); - painter->drawPath(m_paintPath); -} + connect(&path, &model::Path::offsettedPathChanged, this, &PolylinePathItem::updateOffsetedPath); + } -QPainterPath PolylinePathItem::shape() const -{ - return m_shapePath; -} + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override + { + BasicPathItem::paint(painter, option, widget); -void PolylinePathItem::basePolylineTransformed() -{ - setupPaths(); -} + painter->drawPath(m_paintPath); + } + + QPainterPath shape() const override { return m_shapePath; } + +protected: + void basePolylineTransformed() override { setupPaths(); } +}; } + +#include "polylinepathitem.moc" diff --git a/src/view/view2d/polylinepathitem.h b/src/view/view2d/polylinepathitem.h deleted file mode 100644 index 98cd863..0000000 --- a/src/view/view2d/polylinepathitem.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include - -namespace view::view2d -{ - -/** @brief Graphics path item meant to display polylines with length. - */ -class PolylinePathItem : public BasicPathItem -{ - Q_OBJECT; - -private: - QPainterPath m_paintPath; - QPainterPath m_shapePath; - - // Item of offsetted polylines of the same path. - std::unique_ptr m_offsettedPath; - - QPainterPath paintPath() const; - static QPainterPath shapePath(const QPainterPath& basePath); - - void setupPaths(); - - void updateOffsetedPath(); - - // Change selected state and propagate to sub paths (e.g offsetted path). - void setSelected(bool selected) override; - -public: - explicit PolylinePathItem(model::Path& path); - - void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; - - QPainterPath shape() const override; - -protected: - void basePolylineTransformed() override; -}; - -} diff --git a/src/view/view2d/rubberband.cpp b/src/view/view2d/rubberband.cpp index b12d66b..ae802fc 100644 --- a/src/view/view2d/rubberband.cpp +++ b/src/view/view2d/rubberband.cpp @@ -1,8 +1,11 @@ -#include +module; +#include #include #include +export module view.view2d.rubberband; + namespace view::view2d { @@ -10,52 +13,61 @@ static const QBrush borderBrush(QColor(0, 0, 255, 255)); static const QBrush fillBrush(QColor(0, 0, 255, 100)); static const QPen borderPen(borderBrush, 0.0f); -void RubberBand::paint(QPainter* painter, [[maybe_unused]] const QStyleOptionGraphicsItem* option, - [[maybe_unused]] QWidget* widget) -{ - painter->setPen(borderPen); - painter->drawRect(m_rectf); - painter->fillRect(m_rectf, fillBrush); } -QRectF RubberBand::boundingRect() const +export namespace view::view2d { - return rect(); -} -QRectF RubberBand::rect() const +class RubberBand : public QGraphicsItem { - return m_rectf.normalized(); -} +private: + QRect m_rect; + QRectF m_rectf; -bool RubberBand::empty(int tolerance) const -{ - const QRect normalizedRect = m_rect.normalized(); - return (normalizedRect.width() < tolerance) && (normalizedRect.height() < tolerance); -} +public: + explicit RubberBand() = default; -void RubberBand::start(const QPoint& screenStartCorner, const QPointF& sceneStartCorner) -{ - setVisible(true); - prepareGeometryChange(); + void paint(QPainter* painter, [[maybe_unused]] const QStyleOptionGraphicsItem* option, + [[maybe_unused]] QWidget* widget) override + { + painter->setPen(borderPen); + painter->drawRect(m_rectf); + painter->fillRect(m_rectf, fillBrush); + } - m_rect = QRect(screenStartCorner, screenStartCorner); - m_rectf = QRectF(sceneStartCorner, sceneStartCorner); -} + QRectF boundingRect() const override { return rect(); } -void RubberBand::update(const QPoint& screenEndCorner, const QPointF& sceneEndCorner) -{ - prepareGeometryChange(); + QRectF rect() const { return m_rectf.normalized(); } - m_rect.setBottomRight(screenEndCorner); - m_rectf.setBottomRight(sceneEndCorner); -} + bool empty(int tolerance) const + { + const QRect normalizedRect = m_rect.normalized(); + return (normalizedRect.width() < tolerance) && (normalizedRect.height() < tolerance); + } -void RubberBand::end(const QPoint& screenEndCorner, const QPointF& sceneEndCorner) -{ - update(screenEndCorner, sceneEndCorner); + void start(const QPoint& screenStartCorner, const QPointF& sceneStartCorner) + { + setVisible(true); + prepareGeometryChange(); - setVisible(false); -} + m_rect = QRect(screenStartCorner, screenStartCorner); + m_rectf = QRectF(sceneStartCorner, sceneStartCorner); + } + + void update(const QPoint& screenEndCorner, const QPointF& sceneEndCorner) + { + prepareGeometryChange(); + + m_rect.setBottomRight(screenEndCorner); + m_rectf.setBottomRight(sceneEndCorner); + } + + void end(const QPoint& screenEndCorner, const QPointF& sceneEndCorner) + { + update(screenEndCorner, sceneEndCorner); + + setVisible(false); + } +}; } diff --git a/src/view/view2d/rubberband.h b/src/view/view2d/rubberband.h deleted file mode 100644 index 8a4efd2..0000000 --- a/src/view/view2d/rubberband.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include - -namespace view::view2d -{ - -class RubberBand : public QGraphicsItem -{ -private: - QRect m_rect; - QRectF m_rectf; - -public: - explicit RubberBand() = default; - - void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; - QRectF boundingRect() const override; - - QRectF rect() const; - bool empty(int tolerance) const; - - void start(const QPoint& screenStartCorner, const QPointF& sceneStartCorner); - void update(const QPoint& screenEndCorner, const QPointF& sceneEndCorner); - void end(const QPoint& screenEndCorner, const QPointF& sceneEndCorner); -}; - -} diff --git a/src/view/view2d/viewport.cpp b/src/view/view2d/viewport.cpp index a8eda07..ac41910 100644 --- a/src/view/view2d/viewport.cpp +++ b/src/view/view2d/viewport.cpp @@ -1,135 +1,30 @@ -#include -#include -#include - -#include // TODO +module; + +#include +#include +#include +#include +#include +#include +#include + +export module view.view2d.viewport; +import view.view2d.pointpathitem; +import view.view2d.polylinepathitem; + +import model.documentmodelobserver; +import model.task; +import view.view2d.rubberband; +import model.application; +import model.path; +import view.view2d.basicpathitem; namespace view::view2d { constexpr int rubberBandTolerance = 2; -// Rectangle extend used when clicking with empty rubber band. constexpr QPoint pointSelectionRectExtend(10, 10); -void Viewport::setupPathItems() -{ - task().forEachPath([scene = scene()](model::Path& path) { - BasicPathItem* item; - if (path.isPoint()) { - item = new PointPathItem(path); - } else { - item = new PolylinePathItem(path); - } - scene->addItem(item); - }); -} - -void Viewport::startMovement(const QPoint& mousePos) -{ - m_lastMousePosition = mousePos; -} - -void Viewport::updateMovement(const QPoint& mousePos) -{ - const QPointF delta = mapToScene(mousePos) - mapToScene(m_lastMousePosition); - - // Disable anchor to avoid interferences - setTransformationAnchor(NoAnchor); - - translate(delta.x(), delta.y()); - - // Restore anchor - setTransformationAnchor(AnchorUnderMouse); - - m_lastMousePosition = mousePos; -} - -void Viewport::startRubberBand(const QPoint& mousePos) -{ - m_rubberBand.start(mousePos, mapToScene(mousePos)); -} - -void Viewport::updateRubberBand(const QPoint& mousePos) -{ - m_rubberBand.update(mousePos, mapToScene(mousePos)); -} - -void Viewport::endRubberBand(const QPoint& mousePos, bool addToSelection) -{ - m_rubberBand.end(mousePos, mapToScene(mousePos)); - - // Point selection - if (m_rubberBand.empty(rubberBandTolerance)) { - // Fake selection area - const QRect rect(mousePos - pointSelectionRectExtend, mousePos + pointSelectionRectExtend); - - // Find items in fake selection area - const QList items = QGraphicsView::items(rect); - if (!items.empty()) { - // Obtain only the first item. - QGraphicsItem* item = items.front(); - - if (!addToSelection) { - // Clear all and select one in replacive selection - scene()->clearSelection(); - item->setSelected(true); - } else { - // Toggle selection in additive selection - item->setSelected(!item->isSelected()); - } - } - } - // Area selection - else { - // Create a path with selection area - QPainterPath path; - path.addRect(m_rubberBand.rect()); - - scene()->setSelectionArea(path, addToSelection ? Qt::AddToSelection : Qt::ReplaceSelection); - } -} - -void Viewport::selectAllItems() -{ - for (QGraphicsItem* item : scene()->items()) { - item->setSelected(true); - } -} - -void Viewport::deselecteAllItems() -{ - for (QGraphicsItem* item : scene()->selectedItems()) { - item->setSelected(false); - } -} - -void Viewport::setupModel() -{ - setScene(new QGraphicsScene()); - - scene()->addItem(&m_rubberBand); - - setupPathItems(); - - // Expand scene rect by margin allowing moving out of bound - const QRectF originalRect = scene()->itemsBoundingRect(); - const QRectF expandedRect = originalRect.adjusted(-2000.0f, -2000.0f, 2000.0f, 2000.0f); - setSceneRect(expandedRect); -} - -void Viewport::fitItemsInView() -{ - setTransformationAnchor(NoAnchor); - - const QRectF boundingRect = scene()->itemsBoundingRect(); - centerOn(boundingRect.center()); - fitInView(boundingRect, Qt::KeepAspectRatio); - - setTransformationAnchor(AnchorUnderMouse); -} - -/** @brief Painter for grid and axis into background - */ class BackgroundPainter { private: @@ -243,133 +138,262 @@ class BackgroundPainter } }; -void Viewport::documentChanged() -{ - setupModel(); } -void Viewport::newDocumentOpened() +export namespace view::view2d { - fitItemsInView(); // TODO delay after UI update -} -void Viewport::wheelEvent(QWheelEvent* event) +class Viewport : public model::DocumentModelObserver { - constexpr float SCALE_STEP = 0.2f; + Q_OBJECT; - const float factor = 1.0f + ((event->angleDelta().y() > 0) ? SCALE_STEP : -SCALE_STEP); +private: + /// Last mouse position in scene. + QPoint m_lastMousePosition; - scale(factor, factor); + RubberBand m_rubberBand; - event->accept(); -} + void setupPathItems() + { + task().forEachPath([scene = scene()](model::Path& path) { + BasicPathItem* item; + if (path.isPoint()) { + item = new PointPathItem(path); + } else { + item = new PolylinePathItem(path); + } + scene->addItem(item); + }); + } -void Viewport::mousePressEvent(QMouseEvent* event) -{ - const QPoint& mousePos = event->pos(); + void startMovement(const QPoint& mousePos) { m_lastMousePosition = mousePos; } + + void updateMovement(const QPoint& mousePos) + { + const QPointF delta = mapToScene(mousePos) - mapToScene(m_lastMousePosition); - switch (event->button()) { - case Qt::MiddleButton: { - startMovement(mousePos); - break; + // Disable anchor to avoid interferences + setTransformationAnchor(NoAnchor); + + translate(delta.x(), delta.y()); + + // Restore anchor + setTransformationAnchor(AnchorUnderMouse); + + m_lastMousePosition = mousePos; + } + + void startRubberBand(const QPoint& mousePos) { m_rubberBand.start(mousePos, mapToScene(mousePos)); } + + void updateRubberBand(const QPoint& mousePos) { m_rubberBand.update(mousePos, mapToScene(mousePos)); } + + void endRubberBand(const QPoint& mousePos, bool addToSelection) + { + m_rubberBand.end(mousePos, mapToScene(mousePos)); + + // Point selection + if (m_rubberBand.empty(rubberBandTolerance)) { + // Fake selection area + const QRect rect(mousePos - pointSelectionRectExtend, mousePos + pointSelectionRectExtend); + + // Find items in fake selection area + const QList items = QGraphicsView::items(rect); + if (!items.empty()) { + // Obtain only the first item. + QGraphicsItem* item = items.front(); + + if (!addToSelection) { + // Clear all and select one in replacive selection + scene()->clearSelection(); + item->setSelected(true); + } else { + // Toggle selection in additive selection + item->setSelected(!item->isSelected()); + } + } } - case Qt::LeftButton: { - startRubberBand(mousePos); - break; + // Area selection + else { + // Create a path with selection area + QPainterPath path; + path.addRect(m_rubberBand.rect()); + + scene()->setSelectionArea(path, addToSelection ? Qt::AddToSelection : Qt::ReplaceSelection); } - default: { - break; + } + + void selectAllItems() + { + for (QGraphicsItem* item : scene()->items()) { + item->setSelected(true); } } - event->accept(); -} + void deselecteAllItems() + { + for (QGraphicsItem* item : scene()->selectedItems()) { + item->setSelected(false); + } + } -void Viewport::mouseReleaseEvent(QMouseEvent* event) -{ - const QPoint& mousePos = event->pos(); + void setupModel() + { + setScene(new QGraphicsScene()); - switch (event->button()) { - case Qt::LeftButton: { - const bool addToSelection = event->modifiers() & Qt::ControlModifier; - endRubberBand(mousePos, addToSelection); - break; - } - default: { - break; - } + scene()->addItem(&m_rubberBand); + + setupPathItems(); + + // Expand scene rect by margin allowing moving out of bound + const QRectF originalRect = scene()->itemsBoundingRect(); + const QRectF expandedRect = originalRect.adjusted(-2000.0f, -2000.0f, 2000.0f, 2000.0f); + setSceneRect(expandedRect); } - event->accept(); -} + void fitItemsInView() + { + setTransformationAnchor(NoAnchor); -void Viewport::mouseMoveEvent(QMouseEvent* event) -{ - const QPoint& mousePos = event->pos(); - const Qt::MouseButtons buttons = event->buttons(); + const QRectF boundingRect = scene()->itemsBoundingRect(); + centerOn(boundingRect.center()); + fitInView(boundingRect, Qt::KeepAspectRatio); - if (buttons & Qt::MiddleButton) { - updateMovement(mousePos); + setTransformationAnchor(AnchorUnderMouse); } - if (buttons & Qt::LeftButton) { - updateRubberBand(mousePos); + +protected: + void documentChanged() override { setupModel(); } + + void newDocumentOpened() override + { + fitItemsInView(); // TODO delay after UI update } - // Forward event used for anchors - QGraphicsView::mouseMoveEvent(event); + void wheelEvent(QWheelEvent* event) override + { + constexpr float SCALE_STEP = 0.2f; - emit cursorMoved(mapToScene(mousePos)); -} + const float factor = 1.0f + ((event->angleDelta().y() > 0) ? SCALE_STEP : -SCALE_STEP); -void Viewport::keyPressEvent(QKeyEvent* event) -{ - const int key = event->key(); - const int modifier = event->modifiers(); + scale(factor, factor); - if (key == Qt::Key_A && modifier & Qt::ControlModifier) { - selectAllItems(); - } else if (key == Qt::Key_Escape) { - deselecteAllItems(); + event->accept(); } -} -void Viewport::drawBackground(QPainter* painter, const QRectF& updatedRect) -{ + void mousePressEvent(QMouseEvent* event) override + { + const QPoint& mousePos = event->pos(); - const QRect screenRect = rect(); - const QRectF sceneRect = mapToScene(rect()).boundingRect(); + switch (event->button()) { + case Qt::MiddleButton: { + startMovement(mousePos); + break; + } + case Qt::LeftButton: { + startRubberBand(mousePos); + break; + } + default: { + break; + } + } - const float pixelRatio = std::max(sceneRect.width() / screenRect.width(), sceneRect.height() / screenRect.height()); + event->accept(); + } - // Basic background color - static const QBrush brush(QColor(0, 0, 0)); - painter->fillRect(updatedRect, brush); + void mouseReleaseEvent(QMouseEvent* event) override + { + const QPoint& mousePos = event->pos(); - BackgroundPainter backgroundPainter(painter, sceneRect, pixelRatio); -} + switch (event->button()) { + case Qt::LeftButton: { + const bool addToSelection = event->modifiers() & Qt::ControlModifier; + endRubberBand(mousePos, addToSelection); + break; + } + default: { + break; + } + } -Viewport::Viewport(model::Application& app) - : DocumentModelObserver(app) -{ - // Setup default empty scene - setScene(new QGraphicsScene()); + event->accept(); + } - // Invert Y axis - scale(1.0f, -1.0f); + void mouseMoveEvent(QMouseEvent* event) override + { + const QPoint& mousePos = event->pos(); + const Qt::MouseButtons buttons = event->buttons(); - // Disable dragging support - setDragMode(NoDrag); + if (buttons & Qt::MiddleButton) { + updateMovement(mousePos); + } + if (buttons & Qt::LeftButton) { + updateRubberBand(mousePos); + } - // Anchor under mouse for zooming - setResizeAnchor(AnchorUnderMouse); - setTransformationAnchor(AnchorUnderMouse); + // Forward event used for anchors + QGraphicsView::mouseMoveEvent(event); - setViewportUpdateMode(FullViewportUpdate); - setRenderHints(QPainter::Antialiasing); + emit cursorMoved(mapToScene(mousePos)); + } - // Hide scroll bars - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); -} + void keyPressEvent(QKeyEvent* event) override + { + const int key = event->key(); + const int modifier = event->modifiers(); + + if (key == Qt::Key_A && modifier & Qt::ControlModifier) { + selectAllItems(); + } else if (key == Qt::Key_Escape) { + deselecteAllItems(); + } + } + + void drawBackground(QPainter* painter, const QRectF& updatedRect) override + { + + const QRect screenRect = rect(); + const QRectF sceneRect = mapToScene(rect()).boundingRect(); + + const float pixelRatio + = std::max(sceneRect.width() / screenRect.width(), sceneRect.height() / screenRect.height()); + + // Basic background color + static const QBrush brush(QColor(0, 0, 0)); + painter->fillRect(updatedRect, brush); + + BackgroundPainter backgroundPainter(painter, sceneRect, pixelRatio); + } + +public: + explicit Viewport(model::Application& app) + : DocumentModelObserver(app) + { + // Setup default empty scene + setScene(new QGraphicsScene()); + + // Invert Y axis + scale(1.0f, -1.0f); + + // Disable dragging support + setDragMode(NoDrag); + + // Anchor under mouse for zooming + setResizeAnchor(AnchorUnderMouse); + setTransformationAnchor(AnchorUnderMouse); + + setViewportUpdateMode(FullViewportUpdate); + setRenderHints(QPainter::Antialiasing); + + // Hide scroll bars + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + +Q_SIGNALS: + void cursorMoved(const QPointF& position); +}; } + +#include "viewport.moc" diff --git a/src/view/view2d/viewport.h b/src/view/view2d/viewport.h deleted file mode 100644 index 7f910f6..0000000 --- a/src/view/view2d/viewport.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include -#include -#include -#include -#include - -namespace view::view2d -{ - -class Viewport : public model::DocumentModelObserver -{ - Q_OBJECT; - -private: - /// Last mouse position in scene. - QPoint m_lastMousePosition; - - RubberBand m_rubberBand; - - void setupPathItems(); - - void startMovement(const QPoint& mousePos); - void updateMovement(const QPoint& mousePos); - - void startRubberBand(const QPoint& mousePos); - void updateRubberBand(const QPoint& mousePos); - void endRubberBand(const QPoint& mousePos, bool addToSelection); - - void selectAllItems(); - void deselecteAllItems(); - - void setupModel(); - - void fitItemsInView(); - -protected: - void documentChanged() override; - void newDocumentOpened() override; - - void wheelEvent(QWheelEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - void keyPressEvent(QKeyEvent* event) override; - void drawBackground(QPainter* painter, const QRectF& updatedRect) override; - -public: - explicit Viewport(model::Application& app); - -Q_SIGNALS: - void cursorMoved(const QPointF& position); -}; - -} diff --git a/test/arc.cpp b/test/arc.cpp index 7815ece..2bc2bd8 100644 --- a/test/arc.cpp +++ b/test/arc.cpp @@ -1,5 +1,11 @@ #include -#include + +#include +#include + +import geometry.arc; +import geometry.bulge; +import geometry.utils; constexpr QVector2D point1(-1.0f, -0.5f); constexpr QVector2D point2(1.0f, 0.5f); diff --git a/test/assembler.cpp b/test/assembler.cpp index e671e96..d17645a 100644 --- a/test/assembler.cpp +++ b/test/assembler.cpp @@ -1,5 +1,10 @@ #include -#include + +#include + +import geometry.bulge; +import geometry.filter.assembler; +import geometry.polyline; constexpr QVector2D point1(1.2, 3.4); constexpr QVector2D point2(4.5, 6.7); diff --git a/test/bulge.cpp b/test/bulge.cpp index 2d18a80..e6f3a51 100644 --- a/test/bulge.cpp +++ b/test/bulge.cpp @@ -1,5 +1,10 @@ #include -#include + +#include +#include + +import geometry.bulge; +import geometry.rect; constexpr QVector2D point1(1.2, 3.4); constexpr QVector2D point2(4.5, 6.7); diff --git a/test/dxfplotexporter.cpp b/test/dxfplotexporter.cpp index a62d4dd..144cd09 100644 --- a/test/dxfplotexporter.cpp +++ b/test/dxfplotexporter.cpp @@ -1,9 +1,13 @@ -#include - -#include #include +#include +#include + +import exporter.dxfplot.exporter; +import geometry.bulge; +import geometry.polyline; + TEST_F(ExporterFixture, shouldExportNotEmpty) { diff --git a/test/dxfplotimporter.cpp b/test/dxfplotimporter.cpp index 9dc4ddd..56934b2 100644 --- a/test/dxfplotimporter.cpp +++ b/test/dxfplotimporter.cpp @@ -1,12 +1,18 @@ -#include -#include -#include +#include -#include +#include -#include +#include +#include +import common.exception; +import exporter.dxfplot.exporter; +import geometry.bulge; +import geometry.polyline; +import importer.dxfplot.importer; +import model.document; +import model.task; TEST_F(ExporterFixture, shouldThrowExceptionWhenToolNotFound) { diff --git a/test/exporterfixture.cpp b/test/exporterfixture.cpp index d202ff3..a98d771 100644 --- a/test/exporterfixture.cpp +++ b/test/exporterfixture.cpp @@ -1,5 +1,11 @@ #include +import geometry.polyline; +import model.document; +import model.layer; +import model.path; +import model.task; + void ExporterFixture::createTaskFromPolyline(geometry::Polyline &&polyline) { model::Path::UPtr path = std::make_unique(std::move(polyline), "", m_settings); diff --git a/test/exporterfixture.h b/test/exporterfixture.h index a9e61c6..a1310e9 100644 --- a/test/exporterfixture.h +++ b/test/exporterfixture.h @@ -2,7 +2,13 @@ #include -#include +#include + +import config.config; +import geometry.polyline; +import model.document; +import model.pathsettings; +import model.task; class ExporterFixture : public ::testing::Test { diff --git a/test/exporterrenderer.cpp b/test/exporterrenderer.cpp index b17ad0f..5214a56 100644 --- a/test/exporterrenderer.cpp +++ b/test/exporterrenderer.cpp @@ -1,6 +1,10 @@ + +#include #include -#include +import exporter.renderer.renderer; +import geometry.bulge; +import geometry.polyline; class DepthTrackerVisitor { @@ -101,4 +105,3 @@ TEST_F(ExporterRendererFixture, shouldCutPathSingleDepth) EXPECT_FLOAT_EQ(visitor.depths.front(), -0.1f); } - diff --git a/test/gcodeexporter.cpp b/test/gcodeexporter.cpp index b7b4660..adfbdf8 100644 --- a/test/gcodeexporter.cpp +++ b/test/gcodeexporter.cpp @@ -1,9 +1,14 @@ -#include - -#include #include +#include +#include + +import exporter.gcode.exporter; +import geometry.bulge; +import geometry.polyline; +import model.path; + TEST_F(ExporterFixture, shouldRenderAllPathsWhenAllVisible) { const geometry::Bulge bulge(QVector2D(0, 0), QVector2D(1, 1), 0); diff --git a/test/orderoptimizer.cpp b/test/orderoptimizer.cpp index df906e9..832e458 100644 --- a/test/orderoptimizer.cpp +++ b/test/orderoptimizer.cpp @@ -1,9 +1,9 @@ #include -#include - #include +import geometry.orderoptimizer; + TEST(OrderOptimizer, shouldRespectGroups) { geometry::OrderOptimizer::NodesPerGroup groups = { @@ -43,7 +43,6 @@ TEST(OrderOptimizer, shouldRespectGroups) } } - TEST(OrderOptimizer, shouldOrderSingleGroup) { geometry::OrderOptimizer::NodesPerGroup groups = { diff --git a/test/path.cpp b/test/path.cpp index de8a469..fa46866 100644 --- a/test/path.cpp +++ b/test/path.cpp @@ -1,8 +1,12 @@ #include -#include #include +import geometry.polyline; +import model.path; +import model.pathgroupsettings; +import model.pathsettings; + TEST(PathTest, ShouldEmitSignalsWhenSelected) { model::Path path(geometry::Polyline(), "1", model::PathSettings(1, 1, 1, 1)); diff --git a/test/pathsettings.cpp b/test/pathsettings.cpp index a44969a..6f3d285 100644 --- a/test/pathsettings.cpp +++ b/test/pathsettings.cpp @@ -1,5 +1,11 @@ #include -#include + +import geometry.polyline; +import model.layer; +import model.path; +import model.pathgroupsettings; +import model.pathsettings; +import model.task; TEST(PathGroupSettingsTest, ShouldShareValueAfterSet) { diff --git a/test/pocketer.cpp b/test/pocketer.cpp index 07e0162..bf5c2f0 100644 --- a/test/pocketer.cpp +++ b/test/pocketer.cpp @@ -1,7 +1,10 @@ #include -#include #include +import geometry.pocketer; +import geometry.polyline; +import geometry.utils; + TEST(PocketerTest, ShouldKeepBorderOrientationWhenBorderCcw) { const geometry::Polyline border = createStartPolyline(5.0f, 10.0f, 20); diff --git a/test/polyline.cpp b/test/polyline.cpp index 7bbe795..eca1a51 100644 --- a/test/polyline.cpp +++ b/test/polyline.cpp @@ -1,7 +1,12 @@ #include -#include + +#include #include +import geometry.bulge; +import geometry.polyline; +import geometry.utils; + constexpr QVector2D point1(1.2, 3.4); constexpr QVector2D point2(4.5, 6.7); constexpr QVector2D point3(7.8, 9.1); @@ -22,7 +27,6 @@ TEST(PolylineTest, WithEndAndStartEqualsAndOneBulgeIsPoint) EXPECT_FALSE(polyline2.isPoint()); } - TEST(PolylineTest, WithBulgeAndInvertBulgeIsClosed) { const geometry::Polyline polyline1({bulge1, bulge1invert}); diff --git a/test/polylineutils.cpp b/test/polylineutils.cpp index 66c61f5..db37fbd 100644 --- a/test/polylineutils.cpp +++ b/test/polylineutils.cpp @@ -1,5 +1,12 @@ + +#include +#include #include +import geometry.bulge; +import geometry.polyline; +import geometry.utils; + geometry::Polyline createStartPolyline(float innerRadius, float outterRadius, int nbBranches) { geometry::Point2DList points(nbBranches * 2); diff --git a/test/polylineutils.h b/test/polylineutils.h index 65196c9..92ab2a3 100644 --- a/test/polylineutils.h +++ b/test/polylineutils.h @@ -1,5 +1,5 @@ #pragma once -#include +import geometry.polyline; geometry::Polyline createStartPolyline(float innerRadius, float outterRadius, int nbBranches); diff --git a/test/serializer.cpp b/test/serializer.cpp index a906c6e..1ae7110 100644 --- a/test/serializer.cpp +++ b/test/serializer.cpp @@ -1,12 +1,16 @@ -#include #include #include -#include - #include +#include +#include +#include + +import geometry.bulge; +import serializer.bulge; + TEST(Serializer, shouldSerializeVectorWithNoDataLoose) { const QVector2D point(1, 42); diff --git a/test/simulation.cpp b/test/simulation.cpp index c18af09..88d2daa 100644 --- a/test/simulation.cpp +++ b/test/simulation.cpp @@ -1,9 +1,21 @@ #include -#include -#include #include +#include + +#include + +import config.config; +import geometry.bulge; +import geometry.polyline; +import model.document; +import model.layer; +import model.path; +import model.pathsettings; +import model.simulation; +import model.task; + config::Tools::Tool tool1mmPass() { config::Tools::Tool tool {"tool", YAML::Node()}; diff --git a/test/task.cpp b/test/task.cpp index f3750f0..f2e53f0 100644 --- a/test/task.cpp +++ b/test/task.cpp @@ -1,8 +1,14 @@ #include -#include #include +import geometry.polyline; +import model.layer; +import model.path; +import model.pathgroupsettings; +import model.pathsettings; +import model.task; + TEST(TaskTest, ShouldEmitSignalsWhenOnePathSelected) { model::Path::UPtr path1 = std::make_unique(geometry::Polyline(), "1", model::PathSettings(1, 1, 1, 1)); diff --git a/test/testarc.cpp b/test/testarc.cpp index 1960971..60599cb 100644 --- a/test/testarc.cpp +++ b/test/testarc.cpp @@ -1,9 +1,12 @@ -#include -#include #include #include +#include + +import geometry.bulge; +import geometry.utils; + int main() { const QVector2D s(0, 0); diff --git a/test/testbezier.cpp b/test/testbezier.cpp index 21f1bdd..0c740bf 100644 --- a/test/testbezier.cpp +++ b/test/testbezier.cpp @@ -1,8 +1,11 @@ -#include -#include #include +#include + +import geometry.bezier; +import geometry.utils; + void print(const geometry::Bezier &b) { qInfo() << "........................."; @@ -41,7 +44,6 @@ int main() qInfo() << ".........................."; bccw.toBiarc()->toPolyline(); - /*geometry::Bezier::Pair p1 = b.splitHalf(); geometry::Bezier::Pair p2 = b.split(0.5f); @@ -68,5 +70,4 @@ int main() // // qInfo() << "sin2" << s2; - } diff --git a/test/testexport.cpp b/test/testexport.cpp index f52f46b..59b04c9 100644 --- a/test/testexport.cpp +++ b/test/testexport.cpp @@ -1,10 +1,11 @@ -#include #include #include #include #include +import model.application; + int main(int argc, char *argv[]) { QApplication qapp(argc, argv); diff --git a/test/verticalspeed.cpp b/test/verticalspeed.cpp index d9df22d..f632872 100644 --- a/test/verticalspeed.cpp +++ b/test/verticalspeed.cpp @@ -1,5 +1,6 @@ #include -#include + +import exporter.gcode.exporter; TEST(testVerticalSpeed, TestVerticalMovementFeedRate) {