diff --git a/deps/CGAL/CGAL.cmake b/deps/CGAL/CGAL.cmake
index 39514e465..18fee7c15 100644
--- a/deps/CGAL/CGAL.cmake
+++ b/deps/CGAL/CGAL.cmake
@@ -1,31 +1,11 @@
orcaslicer_add_cmake_project(
CGAL
- GIT_REPOSITORY https://github.com/CGAL/cgal.git
- GIT_TAG caacd806dc55c61cc68adaad99f2240f00493b29 # releases/CGAL-5.3
+ # GIT_REPOSITORY https://github.com/CGAL/cgal.git
+ # GIT_TAG bec70a6d52d8aacb0b3d82a7b4edc3caa899184b # releases/CGAL-5.0
# For whatever reason, this keeps downloading forever (repeats downloads if finished)
- #URL https://github.com/CGAL/cgal/archive/releases/CGAL-5.0.zip
- #URL_HASH SHA256=c2b035bd078687b6d8c0fb6371a7443adcdb647856af9969532c4050cd5f48e5
+ URL https://github.com/CGAL/cgal/archive/refs/tags/v5.4.zip
+ URL_HASH SHA256=d7605e0a5a5ca17da7547592f6f6e4a59430a0bc861948974254d0de43eab4c0
DEPENDS dep_Boost dep_GMP dep_MPFR
)
include(GNUInstallDirs)
-
-# CGAL, for whatever reason, makes itself non-relocatable by writing the build directory into
-# CGALConfig-installation-dirs.cmake and including it in configure time.
-# If this file is not present, it will not consider the stored absolute path
-ExternalProject_Add_Step(dep_CGAL dep_CGAL_relocation_fix
- DEPENDEES install
-
- COMMAND ${CMAKE_COMMAND} -E remove CGALConfig-installation-dirs.cmake
- WORKING_DIRECTORY "${DESTDIR}/usr/local/${CMAKE_INSTALL_LIBDIR}/cmake/CGAL"
-)
-
-# Again, for whatever reason, CGAL thinks that its version is not relevant if
-# configured as a header only library. Fixing it by placing a cmake version file
-# besides the installed config file.
-ExternalProject_Add_Step(dep_CGAL dep_CGAL_version_fix
- DEPENDEES install
-
- COMMAND ${CMAKE_COMMAND} -E copy cgal/CGALConfigVersion.cmake "${DESTDIR}/usr/local/${CMAKE_INSTALL_LIBDIR}/cmake/CGAL/CGALConfigVersion.cmake"
- WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
-)
diff --git a/deps/Cereal/Cereal.cmake b/deps/Cereal/Cereal.cmake
index 6f4c9a1d9..7f87121f5 100644
--- a/deps/Cereal/Cereal.cmake
+++ b/deps/Cereal/Cereal.cmake
@@ -1,6 +1,12 @@
+#/|/ Copyright (c) Prusa Research 2021 - 2022 Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01
+#/|/
+#/|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+#/|/
orcaslicer_add_cmake_project(Cereal
- URL "https://github.com/USCiLab/cereal/archive/v1.2.2.tar.gz"
- URL_HASH SHA256=1921f26d2e1daf9132da3c432e2fd02093ecaedf846e65d7679ddf868c7289c4
+ URL "https://github.com/USCiLab/cereal/archive/refs/tags/v1.3.0.zip"
+ URL_HASH SHA256=71642cb54658e98c8f07a0f0d08bf9766f1c3771496936f6014169d3726d9657
CMAKE_ARGS
- -DJUST_INSTALL_CEREAL=on
+ -DJUST_INSTALL_CEREAL=ON
+ -DSKIP_PERFORMANCE_COMPARISON=ON
+ -DBUILD_TESTS=OFF
)
\ No newline at end of file
diff --git a/resources/images/add_copies.svg b/resources/images/add_copies.svg
index 45b1d27cf..7eb75471e 100644
--- a/resources/images/add_copies.svg
+++ b/resources/images/add_copies.svg
@@ -8,11 +8,11 @@
S11.87,1,8,1L8,1z"/>
-
-
diff --git a/resources/images/add_text_modifier.svg b/resources/images/add_text_modifier.svg
new file mode 100644
index 000000000..d79499eff
--- /dev/null
+++ b/resources/images/add_text_modifier.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/add_text_negative.svg b/resources/images/add_text_negative.svg
new file mode 100644
index 000000000..2cf445692
--- /dev/null
+++ b/resources/images/add_text_negative.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/add_text_part.svg b/resources/images/add_text_part.svg
new file mode 100644
index 000000000..c4972e6cb
--- /dev/null
+++ b/resources/images/add_text_part.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/align_horizontal_center.svg b/resources/images/align_horizontal_center.svg
new file mode 100644
index 000000000..a91ab70ac
--- /dev/null
+++ b/resources/images/align_horizontal_center.svg
@@ -0,0 +1,7 @@
+
diff --git a/resources/images/align_horizontal_left.svg b/resources/images/align_horizontal_left.svg
new file mode 100644
index 000000000..fc72e75cf
--- /dev/null
+++ b/resources/images/align_horizontal_left.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/images/align_horizontal_right.svg b/resources/images/align_horizontal_right.svg
new file mode 100644
index 000000000..2e83ee635
--- /dev/null
+++ b/resources/images/align_horizontal_right.svg
@@ -0,0 +1,7 @@
+
diff --git a/resources/images/align_vertical_bottom.svg b/resources/images/align_vertical_bottom.svg
new file mode 100644
index 000000000..9f65196ed
--- /dev/null
+++ b/resources/images/align_vertical_bottom.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/resources/images/align_vertical_center.svg b/resources/images/align_vertical_center.svg
new file mode 100644
index 000000000..348c08a98
--- /dev/null
+++ b/resources/images/align_vertical_center.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/resources/images/align_vertical_top.svg b/resources/images/align_vertical_top.svg
new file mode 100644
index 000000000..0bcb4b9e0
--- /dev/null
+++ b/resources/images/align_vertical_top.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/resources/images/burn.svg b/resources/images/burn.svg
new file mode 100644
index 000000000..685999cd1
--- /dev/null
+++ b/resources/images/burn.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/delete.svg b/resources/images/delete.svg
new file mode 100644
index 000000000..91d56e91d
--- /dev/null
+++ b/resources/images/delete.svg
@@ -0,0 +1,22 @@
+
+
+
diff --git a/resources/images/exclamation.svg b/resources/images/exclamation.svg
new file mode 100644
index 000000000..5a5b631a4
--- /dev/null
+++ b/resources/images/exclamation.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/resources/images/lock_closed.svg b/resources/images/lock_closed.svg
new file mode 100644
index 000000000..549cdba23
--- /dev/null
+++ b/resources/images/lock_closed.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/resources/images/lock_closed_f.svg b/resources/images/lock_closed_f.svg
new file mode 100644
index 000000000..2920ea0aa
--- /dev/null
+++ b/resources/images/lock_closed_f.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/resources/images/lock_open.svg b/resources/images/lock_open.svg
new file mode 100644
index 000000000..3f0da9ae0
--- /dev/null
+++ b/resources/images/lock_open.svg
@@ -0,0 +1,11 @@
+
+
+
diff --git a/resources/images/lock_open_f.svg b/resources/images/lock_open_f.svg
new file mode 100644
index 000000000..3d12d7874
--- /dev/null
+++ b/resources/images/lock_open_f.svg
@@ -0,0 +1,11 @@
+
+
+
diff --git a/resources/images/make_bold.svg b/resources/images/make_bold.svg
new file mode 100644
index 000000000..1c39a3d23
--- /dev/null
+++ b/resources/images/make_bold.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/make_italic.svg b/resources/images/make_italic.svg
new file mode 100644
index 000000000..5c128e630
--- /dev/null
+++ b/resources/images/make_italic.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/make_unbold.svg b/resources/images/make_unbold.svg
new file mode 100644
index 000000000..7a99cc227
--- /dev/null
+++ b/resources/images/make_unbold.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/make_unitalic.svg b/resources/images/make_unitalic.svg
new file mode 100644
index 000000000..f3c4d9068
--- /dev/null
+++ b/resources/images/make_unitalic.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/open.svg b/resources/images/open.svg
index 8fcddc2a6..5c7fa81be 100644
--- a/resources/images/open.svg
+++ b/resources/images/open.svg
@@ -5,7 +5,7 @@
-
diff --git a/resources/images/reflection_x.svg b/resources/images/reflection_x.svg
new file mode 100644
index 000000000..fa391e6cd
--- /dev/null
+++ b/resources/images/reflection_x.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/reflection_y.svg b/resources/images/reflection_y.svg
new file mode 100644
index 000000000..0de6a5971
--- /dev/null
+++ b/resources/images/reflection_y.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/refresh.svg b/resources/images/refresh.svg
new file mode 100644
index 000000000..af9b33685
--- /dev/null
+++ b/resources/images/refresh.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/svg_modifier.svg b/resources/images/svg_modifier.svg
new file mode 100644
index 000000000..8b1ff317b
--- /dev/null
+++ b/resources/images/svg_modifier.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/svg_negative.svg b/resources/images/svg_negative.svg
new file mode 100644
index 000000000..c47a8fe58
--- /dev/null
+++ b/resources/images/svg_negative.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/resources/images/svg_part.svg b/resources/images/svg_part.svg
new file mode 100644
index 000000000..40f907bd9
--- /dev/null
+++ b/resources/images/svg_part.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/resources/images/text_B.svg b/resources/images/text_B.svg
deleted file mode 100644
index c792b4be4..000000000
--- a/resources/images/text_B.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/resources/images/text_B_dark.svg b/resources/images/text_B_dark.svg
deleted file mode 100644
index 5fa0f1bbd..000000000
--- a/resources/images/text_B_dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/resources/images/text_T.svg b/resources/images/text_T.svg
deleted file mode 100644
index fa5cb783b..000000000
--- a/resources/images/text_T.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/resources/images/text_T_dark.svg b/resources/images/text_T_dark.svg
deleted file mode 100644
index af85448da..000000000
--- a/resources/images/text_T_dark.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/src/imgui/CMakeLists.txt b/src/imgui/CMakeLists.txt
index 2db32b306..da31d7695 100644
--- a/src/imgui/CMakeLists.txt
+++ b/src/imgui/CMakeLists.txt
@@ -3,16 +3,19 @@ project(imgui)
add_library(imgui STATIC
imconfig.h
+ imgui.cpp
imgui.h
+ imgui_demo.cpp
+ imgui_draw.cpp
imgui_internal.h
+ imgui_stdlib.cpp
+ imgui_stdlib.h
+ imgui_tables.cpp
+ imgui_widgets.cpp
+ # imgui STB
imstb_rectpack.h
imstb_textedit.h
imstb_truetype.h
- imgui_tables.cpp
- imgui.cpp
- imgui_demo.cpp
- imgui_draw.cpp
- imgui_widgets.cpp
)
if(Boost_FOUND)
diff --git a/src/imgui/imgui_stdlib.cpp b/src/imgui/imgui_stdlib.cpp
new file mode 100644
index 000000000..cb1fe1743
--- /dev/null
+++ b/src/imgui/imgui_stdlib.cpp
@@ -0,0 +1,76 @@
+// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
+// This is also an example of how you may wrap your own similar types.
+
+// Compatibility:
+// - std::string support is only guaranteed to work from C++11.
+// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture)
+
+// Changelog:
+// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
+
+#include "imgui.h"
+#include "imgui_stdlib.h"
+
+struct InputTextCallback_UserData
+{
+ std::string* Str;
+ ImGuiInputTextCallback ChainCallback;
+ void* ChainCallbackUserData;
+};
+
+static int InputTextCallback(ImGuiInputTextCallbackData* data)
+{
+ InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData;
+ if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
+ {
+ // Resize string callback
+ // If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want.
+ std::string* str = user_data->Str;
+ IM_ASSERT(data->Buf == str->c_str());
+ str->resize(data->BufTextLen);
+ data->Buf = (char*)str->c_str();
+ }
+ else if (user_data->ChainCallback)
+ {
+ // Forward to user callback, if any
+ data->UserData = user_data->ChainCallbackUserData;
+ return user_data->ChainCallback(data);
+ }
+ return 0;
+}
+
+bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
+ flags |= ImGuiInputTextFlags_CallbackResize;
+
+ InputTextCallback_UserData cb_user_data;
+ cb_user_data.Str = str;
+ cb_user_data.ChainCallback = callback;
+ cb_user_data.ChainCallbackUserData = user_data;
+ return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
+}
+
+bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
+ flags |= ImGuiInputTextFlags_CallbackResize;
+
+ InputTextCallback_UserData cb_user_data;
+ cb_user_data.Str = str;
+ cb_user_data.ChainCallback = callback;
+ cb_user_data.ChainCallbackUserData = user_data;
+ return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data);
+}
+
+bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
+ flags |= ImGuiInputTextFlags_CallbackResize;
+
+ InputTextCallback_UserData cb_user_data;
+ cb_user_data.Str = str;
+ cb_user_data.ChainCallback = callback;
+ cb_user_data.ChainCallbackUserData = user_data;
+ return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
+}
diff --git a/src/imgui/imgui_stdlib.h b/src/imgui/imgui_stdlib.h
new file mode 100644
index 000000000..f860b0c78
--- /dev/null
+++ b/src/imgui/imgui_stdlib.h
@@ -0,0 +1,22 @@
+// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
+// This is also an example of how you may wrap your own similar types.
+
+// Compatibility:
+// - std::string support is only guaranteed to work from C++11.
+// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture)
+
+// Changelog:
+// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
+
+#pragma once
+
+#include
+
+namespace ImGui
+{
+ // ImGui::InputText() with std::string
+ // Because text input needs dynamic resizing, we need to setup a callback to grow the capacity
+ IMGUI_API bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+ IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+ IMGUI_API bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+}
diff --git a/src/imgui/imgui_widgets.cpp b/src/imgui/imgui_widgets.cpp
index b09b09abc..ded72f417 100644
--- a/src/imgui/imgui_widgets.cpp
+++ b/src/imgui/imgui_widgets.cpp
@@ -4815,8 +4815,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
+ PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges
bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), true, ImGuiWindowFlags_NoMove);
- PopStyleVar(2);
+ PopStyleVar(3);
PopStyleColor();
if (!child_visible)
{
@@ -5454,7 +5455,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// Test if cursor is vertically visible
if (cursor_offset.y - g.FontSize < scroll_y)
scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
- else if (cursor_offset.y - inner_size.y >= scroll_y)
+ else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index 665fb0771..ba0d363f7 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -1,3 +1,7 @@
+///|/ Copyright (c) Prusa Research 2017 - 2023 Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak, David Kocík @kocikdav, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Vojtěch Král @vojtechkral
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "AppConfig.hpp"
@@ -43,6 +47,7 @@ static const std::string MODELS_STR = "models";
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
+const std::string AppConfig::SECTION_EMBOSS_STYLE = "font";
std::string AppConfig::get_language_code()
{
diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp
index dfcc24658..1219adec9 100644
--- a/src/libslic3r/AppConfig.hpp
+++ b/src/libslic3r/AppConfig.hpp
@@ -1,3 +1,7 @@
+///|/ Copyright (c) Prusa Research 2017 - 2023 Vojtěch Bubník @bubnikv, David Kocík @kocikdav, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Vojtěch Král @vojtechkral
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
#ifndef slic3r_AppConfig_hpp_
#define slic3r_AppConfig_hpp_
@@ -287,6 +291,7 @@ public:
static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS;
+ static const std::string SECTION_EMBOSS_STYLE;
private:
template
diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp
index a7d092ae2..8654669eb 100644
--- a/src/libslic3r/BoundingBox.hpp
+++ b/src/libslic3r/BoundingBox.hpp
@@ -1,3 +1,8 @@
+///|/ Copyright (c) Prusa Research 2016 - 2023 Tomáš Mészáros @tamasmeszaros, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01, Enrico Turri @enricoturri1966
+///|/ Copyright (c) Slic3r 2014 - 2015 Alessandro Ranellucci @alranel
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
#ifndef slic3r_BoundingBox_hpp_
#define slic3r_BoundingBox_hpp_
@@ -222,6 +227,8 @@ public:
friend BoundingBox get_extents_rotated(const Points &points, double angle);
};
+using BoundingBoxes = std::vector;
+
class BoundingBox3 : public BoundingBox3Base
{
public:
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index d5a020e05..37cb8ea0d 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -66,9 +66,15 @@ set(lisbslic3r_sources
EdgeGrid.hpp
ElephantFootCompensation.cpp
ElephantFootCompensation.hpp
+ Emboss.cpp
+ Emboss.hpp
+ EmbossShape.hpp
enum_bitmask.hpp
ExPolygon.cpp
ExPolygon.hpp
+ ExPolygonSerialize.hpp
+ ExPolygonsIndex.cpp
+ ExPolygonsIndex.hpp
Extruder.cpp
Extruder.hpp
ExtrusionEntity.cpp
@@ -224,6 +230,8 @@ set(lisbslic3r_sources
MultiPoint.cpp
MultiPoint.hpp
MutablePriorityQueue.hpp
+ NSVGUtils.cpp
+ NSVGUtils.hpp
ObjectID.cpp
ObjectID.hpp
PerimeterGenerator.cpp
@@ -318,6 +326,7 @@ set(lisbslic3r_sources
Technologies.hpp
Tesselate.cpp
Tesselate.hpp
+ TextConfiguration.hpp
TriangleMesh.cpp
TriangleMesh.hpp
TriangleMeshSlicer.cpp
@@ -329,6 +338,8 @@ set(lisbslic3r_sources
Utils.hpp
Time.cpp
Time.hpp
+ Timer.cpp
+ Timer.hpp
Thread.cpp
Thread.hpp
TriangleSelector.cpp
@@ -457,8 +468,13 @@ cmake_policy(SET CMP0011 NEW)
find_package(CGAL REQUIRED)
cmake_policy(POP)
-add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp TryCatchSignal.hpp
- TryCatchSignal.cpp)
+add_library(libslic3r_cgal STATIC
+ CutSurface.hpp CutSurface.cpp
+ IntersectionPoints.hpp IntersectionPoints.cpp
+ MeshBoolean.hpp MeshBoolean.cpp
+ TryCatchSignal.hpp TryCatchSignal.cpp
+ Triangulation.hpp Triangulation.cpp
+)
target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options
diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp
index ec16193fd..eeb43f6f0 100644
--- a/src/libslic3r/ClipperUtils.cpp
+++ b/src/libslic3r/ClipperUtils.cpp
@@ -272,8 +272,8 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
// Offset CCW contours outside, CW contours (holes) inside.
// Don't calculate union of the output paths.
-template
-static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
+template
+static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType endType = ClipperLib::etClosedPolygon)
{
ClipperLib::ClipperOffset co;
ClipperLib::Paths out;
@@ -354,16 +354,16 @@ TResult clipper_union(
// Perform union of input polygons using the positive rule, convert to ExPolygons.
//FIXME is there any benefit of not doing the boolean / using pftEvenOdd?
-ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union)
+inline ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union)
{
return PolyTreeToExPolygons(clipper_union(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd));
}
-template
-static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
+template
+static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type = ClipperLib::etOpenButt)
{
assert(offset > 0);
- return raw_offset(std::forward(paths), offset, joinType, miterLimit);
+ return raw_offset(std::forward(paths), offset, joinType, miterLimit, end_type);
}
template
@@ -418,10 +418,17 @@ Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, Cli
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return PolyTreeToExPolygons(offset_paths(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
-Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit)
- { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit))); }
-Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit)
- { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); }
+Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
+ { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit, end_type))); }
+Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
+ { assert(delta > 0); return to_polygons(clipper_union(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit, end_type))); }
+
+Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type, double miter_limit){
+ assert(line_width > 1.f); return to_polygons(clipper_union(
+ raw_offset(ClipperUtils::SinglePathProvider(polygon.points), line_width/2, join_type, miter_limit, ClipperLib::etClosedLine)));}
+Polygons contour_to_polygons(const Polygons &polygons, const float line_width, ClipperLib::JoinType join_type, double miter_limit){
+ assert(line_width > 1.f); return to_polygons(clipper_union(
+ raw_offset(ClipperUtils::PolygonsProvider(polygons), line_width/2, join_type, miter_limit, ClipperLib::etClosedLine)));}
// returns number of expolygons collected (0 or 1).
static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)
@@ -795,6 +802,8 @@ Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFil
{ return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject)
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
+Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2)
+ { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ClipperLib::pftNonZero)); }
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject)
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
// BBS
diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp
index 1428b6bd0..3607b0a69 100644
--- a/src/libslic3r/ClipperUtils.hpp
+++ b/src/libslic3r/ClipperUtils.hpp
@@ -22,6 +22,9 @@ namespace Slic3r {
static constexpr const float ClipperSafetyOffset = 10.f;
static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter;
+
+static constexpr const Slic3r::ClipperLib::EndType DefaultEndType = Slic3r::ClipperLib::etOpenButt;
+
//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2.
// Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill.
// However such a high limit causes issues with large positive or negative offsets, where a sharp corner
@@ -335,8 +338,8 @@ Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, Clipp
// offset Polylines
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
-Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
-Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
+Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
+Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
@@ -355,6 +358,10 @@ inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float
return offset_ex(temp, delta, joinType, miterLimit);
}
+// convert stroke to path by offsetting of contour
+Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit);
+Polygons contour_to_polygons(const Polygons &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit);
+
inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); }
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); }
@@ -539,6 +546,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero);
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject);
+Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2);
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject);
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPolygons& poly2, bool safety_offset_ = false);
diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp
index ea17328be..a11174639 100644
--- a/src/libslic3r/Color.hpp
+++ b/src/libslic3r/Color.hpp
@@ -61,6 +61,7 @@ public:
static const ColorRGB REDISH() { return { 1.0f, 0.5f, 0.5f }; }
static const ColorRGB YELLOW() { return { 1.0f, 1.0f, 0.0f }; }
static const ColorRGB WHITE() { return { 1.0f, 1.0f, 1.0f }; }
+ static const ColorRGB ORCA() { return {0.0f, 150.f / 255.0f, 136.0f / 255}; }
static const ColorRGB X() { return { 0.75f, 0.0f, 0.0f }; }
static const ColorRGB Y() { return { 0.0f, 0.75f, 0.0f }; }
diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp
new file mode 100644
index 000000000..b2cf3d965
--- /dev/null
+++ b/src/libslic3r/CutSurface.cpp
@@ -0,0 +1,4086 @@
+///|/ Copyright (c) Prusa Research 2022 - 2023 Vojtěch Bubník @bubnikv, Filip Sykala @Jony01
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
+#include "CutSurface.hpp"
+
+/// models_input.obj - Check transormation of model to each others
+/// projection_center.obj - circle representing center of projection with correct distance
+/// {M} .. model index
+/// model/model{M}.off - CGAL model created from index_triangle_set
+/// model_neg/model{M}.off - CGAL model created for differenciate (multi volume object)
+/// shape.off - CGAL model created from shapes
+/// constrained/model{M}.off - Visualization of inside and outside triangles
+/// Green - not along constrained edge
+/// Red - sure that are inside
+/// Purple - sure that are outside
+/// (only along constrained edge)
+/// filled/model{M}.off - flood fill green triangles inside of red area
+/// - Same meaning of color as constrained
+/// {N} .. Order of cutted Area of Interestmodel from model surface
+/// model_AOIs/{M}/cutAOI{N}.obj - Extracted Area of interest from corefined model
+/// model_AOIs/{M}/outline{N}.obj - Outline of Cutted Area
+/// {O} .. Order number of patch
+/// patches/patch{O}.off
+/// result.obj - Merged result its
+/// result_contours/{O}.obj - visualization of contours for result patches
+//#define DEBUG_OUTPUT_DIR std::string("C:/data/temp/cutSurface/")
+
+using namespace Slic3r;
+#include "ExPolygonsIndex.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+// libslic3r
+#include "TriangleMesh.hpp" // its_merge
+#include "Utils.hpp" // next_highest_power_of_2
+#include "ClipperUtils.hpp" // union_ex + offset_ex
+
+namespace priv {
+
+using Project = Emboss::IProjection;
+using Project3d = Emboss::IProject3d;
+
+///
+/// Set true for indices out of area of interest
+///
+/// Flag to convert triangle to cgal
+/// model
+/// Convert 2d point to pair of 3d points
+/// 2d bounding box define AOI
+void set_skip_for_out_of_aoi(std::vector &skip_indicies,
+ const indexed_triangle_set &its,
+ const Project &projection,
+ const BoundingBox &shapes_bb);
+
+///
+/// Set true for indicies outward and almost parallel together.
+/// Note: internally calculate normals
+///
+/// Flag to convert triangle to cgal
+/// model
+/// Direction to measure angle
+/// Maximal allowed angle between opposit normal and
+/// projection direction [in DEG]
+void set_skip_by_angle(std::vector &skip_indicies,
+ const indexed_triangle_set &its,
+ const Project3d &projection,
+ double max_angle = 89.);
+
+
+using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel;
+using CutMesh = CGAL::Surface_mesh;
+using CutMeshes = std::vector;
+
+using VI = CGAL::SM_Vertex_index;
+using HI = CGAL::SM_Halfedge_index;
+using EI = CGAL::SM_Edge_index;
+using FI = CGAL::SM_Face_index;
+using P3 = CGAL::Epick::Point_3;
+
+inline Vec3d to_vec3d(const P3 &p) { return Vec3d(p.x(),p.y(),p.z()); }
+
+///
+/// Convert triangle mesh model to CGAL Surface_mesh
+/// Filtrate out opposite triangles
+/// Add property map for source face index
+///
+/// Model
+/// Flags that triangle should be skiped
+/// When true triangle will flip normal
+/// CGAL mesh - half edge mesh
+CutMesh to_cgal(const indexed_triangle_set &its,
+ const std::vector &skip_indicies,
+ bool flip = false);
+
+///
+/// Covert 2d shape (e.g. Glyph) to CGAL model
+/// NOTE: internaly create
+/// edge_shape_map .. Property map to store conversion from edge to contour
+/// face_shape_map .. Property map to store conversion from face to contour
+///
+/// 2d shapes to project
+/// Define transformation 2d point into 3d
+/// CGAL model of extruded shape
+CutMesh to_cgal(const ExPolygons &shapes, const Project &projection);
+// function to check result of projection. 2d int32_t -> 3d double
+bool exist_duplicit_vertex(const CutMesh& mesh);
+
+
+///
+/// IntersectingElement
+///
+/// Adress polygon inside of ExPolygon
+/// Keep information about source of vertex:
+/// - from face (one of 2 possible)
+/// - from edge (one of 2 possible)
+///
+/// V1~~~~~V2
+/// | f1 /:
+/// | / :
+/// e1| /e2:
+/// | / :
+/// |/ f2 :
+/// V1'~~~~V2'
+///
+/// | .. edge
+/// / .. edge
+/// : .. foreign edge - neighbor
+/// ~ .. no care edge - idealy should not cross model
+/// V1,V1' .. projected 2d point to 3d
+/// V2,V2' .. projected 2d point to 3d
+///
+/// Vertex indexing
+/// V1 .. i (vertex_base + 2x index of point in polygon)
+/// V1' .. i + 1
+/// V2 .. j = i + 2 || 0 (for last i in polygon)
+/// V2' .. j + 1
+///
+/// f1 .. text_face_1 (triangle face made by side of shape contour)
+/// f2 .. text_face_2
+/// e1 .. text_edge_1 (edge on side of face made by side of shape contour)
+/// e2 .. text_edge_2
+///
+///
+struct IntersectingElement
+{
+ // identify source point in shapes
+ uint32_t shape_point_index{std::numeric_limits::max()};
+
+ // store together type, is_first, is_last
+ unsigned char attr{std::numeric_limits::max()};
+
+ // vertex or edge ID, where edge ID is the index of the source point.
+ // There are 4 consecutive indices generated for a single contour edge:
+ // 0th - 1st text edge (straight)
+ // 1th - 1st text face
+ // 2nd - 2nd text edge (diagonal)
+ // 3th - 2nd text face
+ // Type of intersecting element from extruded shape( 3d )
+ // NOTE: type must be storable to 3bit -> max value is 7
+ enum class Type: unsigned char {
+ edge_1 = 0,
+ face_1 = 1,
+ edge_2 = 2,
+ face_2 = 3,
+ undefined = 4
+ };
+
+ IntersectingElement &set_type(Type t)
+ {
+ attr = static_cast(
+ attr + (int) t - (int) get_type());
+ return *this;
+ }
+ void set_is_first(){ attr += 8; }
+ void set_is_last(){ attr += 16; }
+ Type get_type() const { return static_cast(attr % 8);}
+ bool is_first() const { return 8 <= attr && attr < 16; }
+ bool is_last() const { return attr >= 16; }
+};
+
+// stored in model made by shape
+using EdgeShapeMap = CutMesh::Property_map;
+using FaceShapeMap = CutMesh::Property_map;
+
+// stored in surface source - pointer to EdgeShapeMap | FaceShapeMap
+using VertexShapeMap = CutMesh::Property_map;
+
+// stored in model made by shape
+const std::string edge_shape_map_name = "e:IntersectingElement";
+const std::string face_shape_map_name = "f:IntersectingElement";
+
+// stored in surface source
+const std::string vert_shape_map_name = "v:IntersectingElement";
+
+///
+/// Flag for faces in CGAL mesh
+///
+enum class FaceType {
+ // face inside of the cutted shape
+ inside,
+ // face outside of the cutted shape
+ outside,
+ // face without constrained edge (In or Out)
+ not_constrained,
+
+ // Helper flag that inside was processed
+ inside_processed
+};
+using FaceTypeMap = CutMesh::Property_map;
+const std::string face_type_map_name = "f:side";
+
+// Conversion one vertex index to another
+using CvtVI2VI = CutMesh::Property_map;
+// Each Patch track outline vertex conversion to tource model
+const std::string patch_source_name = "v:patch_source";
+
+// For VI that should be reduced, contain VI to use instead of reduced
+// Other VI are invalid
+using ReductionMap = CvtVI2VI;
+const std::string vertex_reduction_map_name = "v:reduction";
+
+// A property map containing the constrained-or-not status of each edge
+using EdgeBoolMap = CutMesh::Property_map;
+const std::string is_constrained_edge_name = "e:is_constrained";
+
+///
+/// Create map to reduce unnecesary triangles,
+/// Triangles are made by divided quad to two triangles
+/// on side of cutting shape mesh
+/// Note: also use from mesh (have to be created)
+/// face_type_map .. Type of shape inside / outside
+/// vert_shape_map .. Source of outline vertex
+///
+/// Reduction map from vertex to vertex,
+/// when key == value than no reduction
+/// Faces of one
+/// Input object
+void create_reduce_map(ReductionMap &reduction_map, const CutMesh &meshes);
+
+// Patch made by Cut area of interest from model
+// connected faces(triangles) and outlines(halfEdges) for one surface cut
+using CutAOI = std::pair, std::vector>;
+// vector of Cutted Area of interest cutted from one CGAL model
+using CutAOIs = std::vector;
+// vector of CutAOIs for each model
+using VCutAOIs = std::vector;
+
+///
+/// Create AOIs(area of interest) on model surface
+///
+/// Input model converted to CGAL
+/// NOTE: will be extended by corefine edge
+/// 2d contours
+/// [const]Model made by shapes
+/// NOTE: Can't be definde as const because of corefine function input definition,
+/// but it is.
+/// Wanted projection distance
+/// Convert index to shape point from ExPolygons
+/// Patches from model surface
+CutAOIs cut_from_model(CutMesh &cgal_model,
+ const ExPolygons &shapes,
+ /*const*/ CutMesh &cgal_shape,
+ float projection_ratio,
+ const ExPolygonsIndices &s2i);
+
+using Loop = std::vector;
+using Loops = std::vector;
+
+///
+/// Create closed loops of contour vertices created from open half edges
+///
+/// Unsorted half edges
+/// Source mesh for half edges
+/// Closed loops
+Loops create_loops(const std::vector &outlines, const CutMesh &mesh);
+
+// To track during diff_models,
+// what was cutted off, from CutAOI
+struct SurfacePatch
+{
+ // converted cut to CGAL mesh
+ // Mesh is reduced.
+ // (do not contain divided triangles on contour - created by side Quad)
+ CutMesh mesh;
+ // CvtVI2VI cvt = mesh.property_map(patch_source_name);
+ // Conversion VI from this patch to source VI(model) is stored in mesh property
+
+ // Outlines - converted CutAOI.second (half edges)
+ // to loops (vertex indicies) by function create_loops
+ Loops loops;
+
+ // bounding box of mesh
+ BoundingBoxf3 bb;
+
+ //// Data needed to find best projection distances
+ // index of source model in models
+ size_t model_id;
+ // index of source CutAOI
+ size_t aoi_id;
+ // index of shape from ExPolygons
+ size_t shape_id = 0;
+
+ // flag that this patch contain whole CutAOI
+ bool is_whole_aoi = true;
+};
+using SurfacePatches = std::vector;
+
+struct ModelCutId
+{
+ // index of model
+ uint32_t model_index;
+ // index of cut inside model
+ uint32_t cut_index;
+};
+
+///
+/// Keep conversion from VCutAOIs to Index and vice versa
+/// Model_index .. contour(or hole) poin from ExPolygons
+/// Index .. continous number
+///
+class ModelCut2index
+{
+ std::vector m_offsets;
+ // for check range of index
+ uint32_t m_count;
+
+public:
+ ModelCut2index(const VCutAOIs &cuts);
+ uint32_t calc_index(const ModelCutId &id) const;
+ ModelCutId calc_id(uint32_t index) const;
+ uint32_t get_count() const { return m_count; };
+ const std::vector &get_offsets() const { return m_offsets; }
+};
+
+///
+/// Differenciate other models
+///
+/// Patches from meshes
+/// Source points for Cutted AOIs
+/// NOTE: Create Reduction map as mesh property - clean on end
+/// Original models without cut modifications
+/// used for differenciation
+/// NOTE: Clip function modify Mesh
+/// Define projection direction
+/// Cuts differenciate by models - Patch
+SurfacePatches diff_models(VCutAOIs &cuts,
+ /*const*/ CutMeshes &cut_models,
+ /*const*/ CutMeshes &models,
+ const Project3d &projection);
+
+///
+/// Checking whether patch is uninterrupted cover of whole expolygon it belongs.
+///
+/// Part of surface to check
+/// Source shape
+/// Source of cut
+/// True when cover whole expolygon otherwise false
+bool is_over_whole_expoly(const CutAOI &cutAOI,
+ const ExPolygon &shape,
+ const CutMesh &mesh);
+
+///
+/// Checking whether patch is uninterrupted cover of whole expolygon it belongs.
+///
+/// Part of surface to check
+/// Source shape
+/// True when cover whole expolygon otherwise false
+bool is_over_whole_expoly(const SurfacePatch &patch,
+ const ExPolygons &shapes,
+ const VCutAOIs &cutAOIs,
+ const CutMeshes &meshes);
+///
+/// Unptoject points from outline loops of patch
+///
+/// Contain loops and vertices
+/// Know how to project from 3d to 2d
+/// Range of unprojected points x .. min, y .. max value
+/// Unprojected points in loops
+Polygons unproject_loops(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range);
+
+///
+/// Unproject points from loops and create expolygons
+///
+/// Patch to convert on expolygon
+/// Convert 3d point to 2d
+/// Range of unprojected points x .. min, y .. max value
+/// Expolygon represent patch in 2d
+ExPolygon to_expoly(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range);
+
+///
+/// To select surface near projection distance
+///
+struct ProjectionDistance
+{
+ // index of source model
+ uint32_t model_index = std::numeric_limits::max();
+
+ // index of CutAOI
+ uint32_t aoi_index = std::numeric_limits::max();
+
+ // index of Patch
+ uint32_t patch_index = std::numeric_limits::max();
+
+ // signed distance to projection
+ float distance = std::numeric_limits::max();
+};
+// addresed by ExPolygonsIndices
+using ProjectionDistances = std::vector;
+
+// each point in shapes has its ProjectionDistances
+using VDistances = std::vector;
+
+///
+/// Calculate distances for SurfacePatches outline points
+/// NOTE:
+/// each model has to have "vert_shape_map" .. Know source of new vertices
+///
+/// Part of surface
+/// Vertices position
+/// Mesh created by shapes
+/// Count of contour points in shapes
+/// Define best distnace
+/// Projection distances of cutted shape points
+VDistances calc_distances(const SurfacePatches &patches,
+ const CutMeshes &models,
+ const CutMesh &shapes_mesh,
+ size_t count_shapes_points,
+ float projection_ratio);
+
+///
+/// Select distances in similar depth between expolygons
+///
+/// All distances - Vector distances for each shape point
+/// Vector of letters
+/// Pivot for start projection in 2d
+/// Convert index to addresss inside of shape
+/// Cutted parts from surface
+/// Closest distance projection indexed by points in shapes(see s2i)
+ProjectionDistances choose_best_distance(
+ const VDistances &distances,
+ const ExPolygons &shapes,
+ const Point &start,
+ const ExPolygonsIndices &s2i,
+ const SurfacePatches &patches);
+
+///
+/// Create mask for patches
+///
+/// For each point selected closest distance
+/// All patches
+/// Shape to cut
+/// Bound of shapes
+///
+///
+///
+///
+/// Mask of used patch
+std::vector select_patches(const ProjectionDistances &best_distances,
+ const SurfacePatches &patches,
+ const ExPolygons &shapes,
+ const BoundingBox &shapes_bb,
+ const ExPolygonsIndices &s2i,
+ const VCutAOIs &cutAOIs,
+ const CutMeshes &meshes,
+ const Project &projection);
+
+///
+/// Merge two surface cuts together
+/// Added surface cut will be consumed
+///
+/// Surface cut to extend
+/// Surface cut to consume
+void append(SurfaceCut &sc, SurfaceCut &&sc_add);
+
+///
+/// Convert patch to indexed_triangle_set
+///
+/// Part of surface
+/// Converted patch
+SurfaceCut patch2cut(SurfacePatch &patch);
+
+///
+/// Merge masked patches to one surface cut
+///
+/// All patches
+/// NOTE: Not const because One needs to add property for Convert indices
+/// Mash for using patch
+/// Result surface cut
+SurfaceCut merge_patches(/*const*/ SurfacePatches &patches,
+ const std::vector &mask);
+
+#ifdef DEBUG_OUTPUT_DIR
+void prepare_dir(const std::string &dir);
+void initialize_store(const std::string &dir_to_clear);
+///
+/// Debug purpose store of mesh with colored face by face type
+///
+/// Input mesh, could add property color
+/// NOTE: Not const because need to [optionaly] append color property map
+/// Color source
+/// File to store
+void store(const CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string &dir, bool is_filled = false);
+void store(const ExPolygons &shapes, const std::string &svg_file);
+void store(const CutMesh &mesh, const ReductionMap &reduction_map, const std::string &dir);
+void store(const CutAOIs &aois, const CutMesh &mesh, const std::string &dir);
+void store(const SurfacePatches &patches, const std::string &dir);
+void store(const Vec3f &vertex, const Vec3f &normal, const std::string &file, float size = 2.f);
+//void store(const ProjectionDistances &pds, const VCutAOIs &aois, const CutMeshes &meshes, const std::string &file, float width = 0.2f/* [in mm] */);
+using Connection = std::pair; using Connections = std::vector;
+void store(const ExPolygons &shapes, const std::vector &mask_distances, const Connections &connections, const std::string &file_svg);
+void store(const SurfaceCut &cut, const std::string &file, const std::string &contour_dir);
+void store(const std::vector &models, const std::string &obj_filename);
+void store(const std::vector&models, const std::string &dir);
+void store(const Emboss::IProjection &projection, const Point &point_to_project, float projection_ratio, const std::string &obj_filename);
+#endif // DEBUG_OUTPUT_DIR
+} // namespace privat
+
+#ifdef DEBUG_OUTPUT_DIR
+#include "libslic3r/SVG.hpp"
+#include
+#include
+#endif // DEBUG_OUTPUT_DIR
+
+SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes,
+ const std::vector &models,
+ const Emboss::IProjection &projection,
+ float projection_ratio)
+{
+ assert(!models.empty());
+ assert(!shapes.empty());
+ if (models.empty() || shapes.empty() ) return {};
+
+#ifdef DEBUG_OUTPUT_DIR
+ priv::initialize_store(DEBUG_OUTPUT_DIR);
+ priv::store(models, DEBUG_OUTPUT_DIR + "models_input.obj");
+ priv::store(shapes, DEBUG_OUTPUT_DIR + "shapes.svg");
+#endif // DEBUG_OUTPUT_DIR
+
+ // for filter out triangles out of bounding box
+ BoundingBox shapes_bb = get_extents(shapes);
+#ifdef DEBUG_OUTPUT_DIR
+ priv::store(projection, shapes_bb.center(), projection_ratio, DEBUG_OUTPUT_DIR + "projection_center.obj");
+#endif // DEBUG_OUTPUT_DIR
+
+ // for filttrate opposite triangles and a little more
+ const float max_angle = 89.9f;
+ priv::CutMeshes cgal_models; // source for patch
+ priv::CutMeshes cgal_neg_models; // model used for differenciate patches
+ cgal_models.reserve(models.size());
+ for (const indexed_triangle_set &its : models) {
+ std::vector skip_indicies(its.indices.size(), {false});
+ priv::set_skip_for_out_of_aoi(skip_indicies, its, projection, shapes_bb);
+
+ // create model for differenciate cutted patches
+ bool flip = true;
+ cgal_neg_models.push_back(priv::to_cgal(its, skip_indicies, flip));
+
+ // cut out more than only opposit triangles
+ priv::set_skip_by_angle(skip_indicies, its, projection, max_angle);
+ cgal_models.push_back(priv::to_cgal(its, skip_indicies));
+ }
+#ifdef DEBUG_OUTPUT_DIR
+ priv::store(cgal_models, DEBUG_OUTPUT_DIR + "model/");// model[0-N].off
+ priv::store(cgal_neg_models, DEBUG_OUTPUT_DIR + "model_neg/"); // model[0-N].off
+#endif // DEBUG_OUTPUT_DIR
+
+ priv::CutMesh cgal_shape = priv::to_cgal(shapes, projection);
+#ifdef DEBUG_OUTPUT_DIR
+ CGAL::IO::write_OFF(DEBUG_OUTPUT_DIR + "shape.off", cgal_shape); // only debug
+#endif // DEBUG_OUTPUT_DIR
+
+ // create tool for convert index to shape Point adress and vice versa
+ ExPolygonsIndices s2i(shapes);
+ priv::VCutAOIs model_cuts;
+ // cut shape from each cgal model
+ for (priv::CutMesh &cgal_model : cgal_models) {
+ priv::CutAOIs cutAOIs = priv::cut_from_model(
+ cgal_model, shapes, cgal_shape, projection_ratio, s2i);
+#ifdef DEBUG_OUTPUT_DIR
+ size_t index = &cgal_model - &cgal_models.front();
+ priv::store(cutAOIs, cgal_model, DEBUG_OUTPUT_DIR + "model_AOIs/" + std::to_string(index) + "/"); // only debug
+#endif // DEBUG_OUTPUT_DIR
+ model_cuts.push_back(std::move(cutAOIs));
+ }
+
+ priv::SurfacePatches patches = priv::diff_models(model_cuts, cgal_models, cgal_neg_models, projection);
+#ifdef DEBUG_OUTPUT_DIR
+ priv::store(patches, DEBUG_OUTPUT_DIR + "patches/");
+#endif // DEBUG_OUTPUT_DIR
+ if (patches.empty()) return {};
+
+ // fix - convert shape_point_id to expolygon index
+ // save 1 param(s2i) from diff_models call
+ for (priv::SurfacePatch &patch : patches)
+ patch.shape_id = s2i.cvt(patch.shape_id).expolygons_index;
+
+ // calc distance to projection for all outline points of cutAOI(shape)
+ // it is used for distiguish the top one
+ uint32_t shapes_points = s2i.get_count();
+ // for each point collect all projection distances
+ priv::VDistances distances = priv::calc_distances(patches, cgal_models, cgal_shape, shapes_points, projection_ratio);
+
+ Point start = shapes_bb.center(); // only align center
+
+ // Use only outline points
+ // for each point select best projection
+ priv::ProjectionDistances best_projection = priv::choose_best_distance(distances, shapes, start, s2i, patches);
+ std::vector use_patch = priv::select_patches(best_projection, patches, shapes, shapes_bb, s2i, model_cuts, cgal_models, projection);
+ SurfaceCut result = merge_patches(patches, use_patch);
+ //*/
+
+#ifdef DEBUG_OUTPUT_DIR
+ priv::store(result, DEBUG_OUTPUT_DIR + "result.obj", DEBUG_OUTPUT_DIR + "result_contours/");
+#endif // DEBUG_OUTPUT_DIR
+ return result;
+}
+
+indexed_triangle_set Slic3r::cut2model(const SurfaceCut &cut,
+ const Emboss::IProject3d &projection)
+{
+ assert(!cut.empty());
+ size_t count_vertices = cut.vertices.size() * 2;
+ size_t count_indices = cut.indices.size() * 2;
+
+ // indices from from zig zag
+ for (const auto &c : cut.contours) {
+ assert(!c.empty());
+ count_indices += c.size() * 2;
+ }
+
+ indexed_triangle_set result;
+ result.vertices.reserve(count_vertices);
+ result.indices.reserve(count_indices);
+
+ // front
+ result.vertices.insert(result.vertices.end(),
+ cut.vertices.begin(), cut.vertices.end());
+ result.indices.insert(result.indices.end(),
+ cut.indices.begin(), cut.indices.end());
+
+ // back
+ for (const Vec3f &v : cut.vertices) {
+ Vec3d vd = v.cast();
+ Vec3d vd2 = projection.project(vd);
+ result.vertices.push_back(vd2.cast());
+ }
+
+ size_t back_offset = cut.vertices.size();
+ for (const auto &i : cut.indices) {
+ // check range of indices in cut
+ assert(i.x() + back_offset < result.vertices.size());
+ assert(i.y() + back_offset < result.vertices.size());
+ assert(i.z() + back_offset < result.vertices.size());
+ assert(i.x() >= 0 && i.x() < cut.vertices.size());
+ assert(i.y() >= 0 && i.y() < cut.vertices.size());
+ assert(i.z() >= 0 && i.z() < cut.vertices.size());
+ // Y and Z is swapped CCW triangles for back side
+ result.indices.emplace_back(i.x() + back_offset,
+ i.z() + back_offset,
+ i.y() + back_offset);
+ }
+
+ // zig zag indices
+ for (const auto &contour : cut.contours) {
+ size_t prev_front_index = contour.back();
+ size_t prev_back_index = back_offset + prev_front_index;
+ for (size_t front_index : contour) {
+ assert(front_index < cut.vertices.size());
+ size_t back_index = back_offset + front_index;
+ result.indices.emplace_back(front_index, prev_front_index, back_index);
+ result.indices.emplace_back(prev_front_index, prev_back_index, back_index);
+ prev_front_index = front_index;
+ prev_back_index = back_index;
+ }
+ }
+
+ assert(count_vertices == result.vertices.size());
+ assert(count_indices == result.indices.size());
+ return result;
+}
+
+// set_skip_for_out_of_aoi helping functions
+namespace priv {
+// define plane
+using PointNormal = std::pair;
+using PointNormals = std::array;
+
+///
+/// Check
+///
+///
+///
+///
+///
+bool is_out_of(const Vec3d &v, const PointNormal &point_normal);
+
+using IsOnSides = std::vector>;
+///
+/// Check if triangle t has all vertices out of any plane
+///
+/// Triangle
+/// Flag is vertex index out of plane
+/// True when triangle is out of one of plane
+bool is_all_on_one_side(const Vec3i &t, const IsOnSides& is_on_sides);
+
+} // namespace priv
+
+bool priv::is_out_of(const Vec3d &v, const PointNormal &point_normal)
+{
+ const Vec3d& p = point_normal.first;
+ const Vec3d& n = point_normal.second;
+ double signed_distance = (v - p).dot(n);
+ return signed_distance > 1e-5;
+};
+
+bool priv::is_all_on_one_side(const Vec3i &t, const IsOnSides& is_on_sides) {
+ for (size_t side = 0; side < 4; side++) {
+ bool result = true;
+ for (auto vi : t) {
+ if (!is_on_sides[vi][side]) {
+ result = false;
+ break;
+ }
+ }
+ if (result) return true;
+ }
+ return false;
+}
+
+void priv::set_skip_for_out_of_aoi(std::vector &skip_indicies,
+ const indexed_triangle_set &its,
+ const Project &projection,
+ const BoundingBox &shapes_bb)
+{
+ assert(skip_indicies.size() == its.indices.size());
+ // 1`*----* 2`
+ // / 2 /|
+ // 1 *----* |
+ // | | * 3`
+ // | |/
+ // 0 *----* 3
+ //////////////////
+ std::array, 4> bb;
+ int index = 0;
+ for (Point v :
+ {shapes_bb.min, Point{shapes_bb.min.x(), shapes_bb.max.y()},
+ shapes_bb.max, Point{shapes_bb.max.x(), shapes_bb.min.y()}})
+ bb[index++] = projection.create_front_back(v);
+
+ // define planes to test
+ // 0 .. under
+ // 1 .. left
+ // 2 .. above
+ // 3 .. right
+ size_t prev_i = 3;
+ // plane is defined by point and normal
+ PointNormals point_normals;
+ for (size_t i = 0; i < 4; i++) {
+ const Vec3d &p1 = bb[i].first;
+ const Vec3d &p2 = bb[i].second;
+ const Vec3d &p3 = bb[prev_i].first;
+ prev_i = i;
+
+ Vec3d v1 = p2 - p1;
+ v1.normalize();
+ Vec3d v2 = p3 - p1;
+ v2.normalize();
+
+ Vec3d normal = v2.cross(v1);
+ normal.normalize();
+
+ point_normals[i] = {p1, normal};
+ }
+
+ // check that projection is not left handed
+ // Fix for reflected projection
+ if (is_out_of(point_normals[2].first, point_normals[0])) {
+ // projection is reflected so normals are reflected
+ for (auto &pn : point_normals)
+ pn.second *= -1;
+ }
+
+ // same meaning as point normal
+ IsOnSides is_on_sides(its.vertices.size(), {false,false,false,false});
+
+ // inspect all vertices when it is out of bounding box
+ tbb::parallel_for(tbb::blocked_range(0, its.vertices.size()),
+ [&its, &point_normals, &is_on_sides](const tbb::blocked_range &range) {
+ for (size_t i = range.begin(); i < range.end(); ++i) {
+ Vec3d v = its.vertices[i].cast();
+ // under + above
+ for (int side : {0, 2}) {
+ if (is_out_of(v, point_normals[side])) {
+ is_on_sides[i][side] = true;
+ // when it is under it can't be above
+ break;
+ }
+ }
+ // left + right
+ for (int side : {1, 3}) {
+ if (is_out_of(v, point_normals[side])) {
+ is_on_sides[i][side] = true;
+ // when it is on left side it can't be on right
+ break;
+ }
+ }
+ }
+ }); // END parallel for
+
+ // inspect all triangles, when it is out of bounding box
+ tbb::parallel_for(tbb::blocked_range(0, its.indices.size()),
+ [&its, &is_on_sides, &skip_indicies](const tbb::blocked_range &range) {
+ for (size_t i = range.begin(); i < range.end(); ++i) {
+ if (is_all_on_one_side(its.indices[i], is_on_sides))
+ skip_indicies[i] = true;
+ }
+ }); // END parallel for
+}
+
+indexed_triangle_set Slic3r::its_mask(const indexed_triangle_set &its,
+ const std::vector &mask)
+{
+ if (its.indices.size() != mask.size()) {
+ assert(false);
+ return {};
+ }
+
+ std::vector cvt_vetices(its.vertices.size(), {std::numeric_limits::max()});
+ size_t vertices_count = 0;
+ size_t faces_count = 0;
+ for (const auto &t : its.indices) {
+ size_t index = &t - &its.indices.front();
+ if (!mask[index]) continue;
+ ++faces_count;
+ for (const auto vi : t) {
+ uint32_t &cvt = cvt_vetices[vi];
+ if (cvt == std::numeric_limits::max())
+ cvt = vertices_count++;
+ }
+ }
+ if (faces_count == 0) return {};
+
+ indexed_triangle_set result;
+ result.indices.reserve(faces_count);
+ result.vertices = std::vector(vertices_count);
+ for (size_t i = 0; i < its.vertices.size(); ++i) {
+ uint32_t index = cvt_vetices[i];
+ if (index == std::numeric_limits::max()) continue;
+ result.vertices[index] = its.vertices[i];
+ }
+
+ for (const stl_triangle_vertex_indices &f : its.indices)
+ if (mask[&f - &its.indices.front()])
+ result.indices.push_back(stl_triangle_vertex_indices(
+ cvt_vetices[f[0]], cvt_vetices[f[1]], cvt_vetices[f[2]]));
+
+ return result;
+}
+
+indexed_triangle_set Slic3r::its_cut_AoI(const indexed_triangle_set &its,
+ const BoundingBox &bb,
+ const Emboss::IProjection &projection)
+{
+ std::vector skip_indicies(its.indices.size(), false);
+ priv::set_skip_for_out_of_aoi(skip_indicies, its, projection, bb);
+ // invert values in vector of bool
+ skip_indicies.flip();
+ return its_mask(its, skip_indicies);
+}
+
+void priv::set_skip_by_angle(std::vector &skip_indicies,
+ const indexed_triangle_set &its,
+ const Project3d &projection,
+ double max_angle)
+{
+ assert(max_angle < 90. && max_angle > 89.);
+ assert(skip_indicies.size() == its.indices.size());
+ float threshold = static_cast(cos(max_angle / 180. * M_PI));
+ for (const stl_triangle_vertex_indices& face : its.indices) {
+ size_t index = &face - &its.indices.front();
+ if (skip_indicies[index]) continue;
+ Vec3f n = its_face_normal(its, face);
+ const Vec3f& v = its.vertices[face[0]];
+ const Vec3d vd = v.cast();
+ // Improve: For Orthogonal Projection it is same for each vertex
+ Vec3d projectedd = projection.project(vd);
+ Vec3f projected = projectedd.cast();
+ Vec3f project_dir = projected - v;
+ project_dir.normalize();
+ float cos_alpha = project_dir.dot(n);
+ if (cos_alpha > threshold) continue;
+ skip_indicies[index] = true;
+ }
+}
+
+priv::CutMesh priv::to_cgal(const indexed_triangle_set &its,
+ const std::vector &skip_indicies,
+ bool flip)
+{
+ const std::vector &vertices = its.vertices;
+ const std::vector &indices = its.indices;
+
+ std::vector use_vetices(vertices.size(), {false});
+
+ size_t vertices_count = 0;
+ size_t faces_count = 0;
+ size_t edges_count = 0;
+
+ for (const auto &t : indices) {
+ size_t index = &t - &indices.front();
+ if (skip_indicies[index]) continue;
+ ++faces_count;
+ size_t count_used_vertices = 0;
+ for (const auto vi : t) {
+ if (!use_vetices[vi]) {
+ ++vertices_count;
+ use_vetices[vi] = true;
+ } else {
+ ++count_used_vertices;
+ }
+ }
+ switch (count_used_vertices) {
+ case 3: break; // all edges are already counted
+ case 2: edges_count += 2; break;
+ case 1:
+ case 0: edges_count += 3; break;
+ default: assert(false);
+ }
+ }
+ assert(vertices_count <= vertices.size());
+ assert(edges_count <= (indices.size() * 3));
+ assert(faces_count <= indices.size());
+
+ CutMesh result;
+ result.reserve(vertices_count, edges_count, faces_count);
+
+ std::vector to_filtrated_vertices_index(vertices.size());
+ size_t filtrated_vertices_index = 0;
+ for (size_t i = 0; i < vertices.size(); ++i)
+ if (use_vetices[i]) {
+ to_filtrated_vertices_index[i] = VI(filtrated_vertices_index);
+ ++filtrated_vertices_index;
+ }
+
+ for (const stl_vertex& v : vertices) {
+ if (!use_vetices[&v - &vertices.front()]) continue;
+ result.add_vertex(CutMesh::Point{v.x(), v.y(), v.z()});
+ }
+
+ if (!flip) {
+ for (const stl_triangle_vertex_indices &f : indices) {
+ if (skip_indicies[&f - &indices.front()]) continue;
+ result.add_face(to_filtrated_vertices_index[f[0]],
+ to_filtrated_vertices_index[f[1]],
+ to_filtrated_vertices_index[f[2]]);
+ }
+ } else {
+ for (const stl_triangle_vertex_indices &f : indices) {
+ if (skip_indicies[&f - &indices.front()]) continue;
+ result.add_face(to_filtrated_vertices_index[f[2]],
+ to_filtrated_vertices_index[f[1]],
+ to_filtrated_vertices_index[f[0]]);
+ }
+ }
+
+ return result;
+}
+
+bool priv::exist_duplicit_vertex(const CutMesh &mesh) {
+ std::vector points;
+ points.reserve(mesh.vertices().size());
+ // copy points
+ for (VI vi : mesh.vertices()) {
+ const P3 &p = mesh.point(vi);
+ points.emplace_back(p.x(), p.y(), p.z());
+ }
+ std::sort(points.begin(), points.end(), [](const Vec3d &v1, const Vec3d &v2) {
+ return v1.x() < v2.x() ||
+ (v1.x() == v2.x() &&
+ (v1.y() < v2.y() ||
+ (v1.y() == v2.y() &&
+ v1.z() < v2.z())));
+ });
+ // find first duplicit
+ auto it = std::adjacent_find(points.begin(), points.end());
+ return it != points.end();
+}
+
+priv::CutMesh priv::to_cgal(const ExPolygons &shapes,
+ const Project &projection)
+{
+ if (shapes.empty()) return {};
+
+ CutMesh result;
+ EdgeShapeMap edge_shape_map = result.add_property_map(edge_shape_map_name).first;
+ FaceShapeMap face_shape_map = result.add_property_map(face_shape_map_name).first;
+
+ std::vector indices;
+ auto insert_contour = [&projection, &indices, &result,
+ &edge_shape_map, &face_shape_map]
+ (const Polygon &polygon) {
+ indices.clear();
+ indices.reserve(polygon.points.size() * 2);
+ size_t num_vertices_old = result.number_of_vertices();
+ for (const Point &polygon_point : polygon.points) {
+ auto [front, back] = projection.create_front_back(polygon_point);
+ P3 v_front{front.x(), front.y(), front.z()};
+ VI vi1 = result.add_vertex(v_front);
+ assert(vi1.idx() == (indices.size() + num_vertices_old));
+ indices.push_back(vi1);
+
+ P3 v_back{back.x(), back.y(), back.z()};
+ VI vi2 = result.add_vertex(v_back);
+ assert(vi2.idx() == (indices.size() + num_vertices_old));
+ indices.push_back(vi2);
+ }
+
+ auto find_edge = [&result](FI fi, VI from, VI to) {
+ HI hi = result.halfedge(fi);
+ for (; result.target(hi) != to; hi = result.next(hi));
+ assert(result.source(hi) == from);
+ assert(result.target(hi) == to);
+ return result.edge(hi);
+ };
+
+ uint32_t contour_index = static_cast(num_vertices_old / 2);
+ for (int32_t i = 0; i < int32_t(indices.size()); i += 2) {
+ bool is_first = i == 0;
+ bool is_last = size_t(i + 2) >= indices.size();
+ int32_t j = is_last ? 0 : (i + 2);
+
+ FI fi1 = result.add_face(indices[i], indices[j], indices[i + 1]);
+ EI ei1 = find_edge(fi1, indices[i + 1], indices[i]);
+ EI ei2 = find_edge(fi1, indices[j], indices[i + 1]);
+ FI fi2 = result.add_face(indices[j], indices[j + 1], indices[i + 1]);
+ IntersectingElement element {contour_index, (unsigned char)IntersectingElement::Type::undefined};
+ if (is_first) element.set_is_first();
+ if (is_last) element.set_is_last();
+ edge_shape_map[ei1] = element.set_type(IntersectingElement::Type::edge_1);
+ face_shape_map[fi1] = element.set_type(IntersectingElement::Type::face_1);
+ edge_shape_map[ei2] = element.set_type(IntersectingElement::Type::edge_2);
+ face_shape_map[fi2] = element.set_type(IntersectingElement::Type::face_2);
+ ++contour_index;
+ }
+ };
+
+ size_t count_point = count_points(shapes);
+ result.reserve(result.number_of_vertices() + 2 * count_point,
+ result.number_of_edges() + 4 * count_point,
+ result.number_of_faces() + 2 * count_point);
+
+ // Identify polygon
+ for (const ExPolygon &shape : shapes) {
+ insert_contour(shape.contour);
+ for (const Polygon &hole : shape.holes)
+ insert_contour(hole);
+ }
+ assert(!exist_duplicit_vertex(result));
+ return result;
+}
+
+priv::ModelCut2index::ModelCut2index(const VCutAOIs &cuts)
+{
+ // prepare offsets
+ m_offsets.reserve(cuts.size());
+ uint32_t offset = 0;
+ for (const CutAOIs &model_cuts: cuts) {
+ m_offsets.push_back(offset);
+ offset += model_cuts.size();
+ }
+ m_count = offset;
+}
+
+uint32_t priv::ModelCut2index::calc_index(const ModelCutId &id) const
+{
+ assert(id.model_index < m_offsets.size());
+ uint32_t offset = m_offsets[id.model_index];
+ uint32_t res = offset + id.cut_index;
+ assert(((id.model_index+1) < m_offsets.size() && res < m_offsets[id.model_index+1]) ||
+ ((id.model_index+1) == m_offsets.size() && res < m_count));
+ return res;
+}
+
+priv::ModelCutId priv::ModelCut2index::calc_id(uint32_t index) const
+{
+ assert(index < m_count);
+ ModelCutId result{0,0};
+ // find shape index
+ for (size_t model_index = 1; model_index < m_offsets.size(); ++model_index) {
+ if (m_offsets[model_index] > index) break;
+ result.model_index = model_index;
+ }
+ result.cut_index = index - m_offsets[result.model_index];
+ return result;
+}
+
+// cut_from_model help functions
+namespace priv {
+
+///
+/// Track source of intersection
+/// Help for anotate inner and outer faces
+///
+struct Visitor : public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor {
+ Visitor(const CutMesh &object, const CutMesh &shape, EdgeShapeMap edge_shape_map,
+ FaceShapeMap face_shape_map, VertexShapeMap vert_shape_map, bool* is_valid) :
+ object(object), shape(shape), edge_shape_map(edge_shape_map), face_shape_map(face_shape_map),
+ vert_shape_map(vert_shape_map), is_valid(is_valid)
+ {}
+
+ const CutMesh &object;
+ const CutMesh &shape;
+
+ // Properties of the shape mesh:
+ EdgeShapeMap edge_shape_map;
+ FaceShapeMap face_shape_map;
+
+ // Properties of the object mesh.
+ VertexShapeMap vert_shape_map;
+
+ // check for anomalities
+ bool* is_valid;
+
+ // keep source of intersection for each intersection
+ // used to copy data into vert_shape_map
+ std::vector intersections;
+
+ ///
+ /// Called when a new intersection point is detected.
+ /// The intersection is detected using a face of tm_f and an edge of tm_e.
+ /// Intersecting an edge hh_edge from tm_f with a face h_e of tm_e.
+ /// https://doc.cgal.org/latest/Polygon_mesh_processing/classPMPCorefinementVisitor.html#a00ee0ca85db535a48726a92414acda7f
+ ///
+ /// The id of the intersection point, starting at 0. Ids are consecutive.
+ /// Dimension of a simplex part of face(h_e) that is intersected by edge(h_f):
+ /// 0 for vertex: target(h_e)
+ /// 1 for edge: h_e
+ /// 2 for the interior of face: face(h_e)
+ ///
+ /// A halfedge from tm_f indicating the simplex intersected:
+ /// if sdim==0 the target of h_f is the intersection point,
+ /// if sdim==1 the edge of h_f contains the intersection point in its interior,
+ /// if sdim==2 the face of h_f contains the intersection point in its interior.
+ /// @Vojta: Edge of tm_f, see is_target_coplanar & is_source_coplanar whether any vertex of h_f is coplanar with face(h_e).
+ ///
+ /// A halfedge from tm_e
+ /// @Vojta: Vertex, halfedge or face of tm_e intersected by h_f, see comment at sdim.
+ ///
+ /// Mesh containing h_f
+ /// Mesh containing h_e
+ /// True if the target of h_e is the intersection point
+ /// @Vojta: source(h_f) is coplanar with face(made by h_e).
+ /// True if the source of h_e is the intersection point
+ /// @Vojta: target(h_f) is coplanar with face(h_e).
+ void intersection_point_detected(std::size_t i_id,
+ int sdim,
+ HI h_f,
+ HI h_e,
+ const CutMesh &tm_f,
+ const CutMesh &tm_e,
+ bool is_target_coplanar,
+ bool is_source_coplanar);
+
+ ///
+ /// Called when a new vertex is added in tm (either an edge split or a vertex inserted in the interior of a face).
+ /// Fill vertex_shape_map by intersections
+ ///
+ /// Order number of intersection point
+ /// New added vertex
+ /// Affected mesh
+ void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm);
+};
+
+///
+/// Distiquish face type for half edge
+///
+/// Define face
+/// Mesh to process
+/// Vertices of mesh made by shapes
+/// Keep information about source of created vertex
+///
+/// Convert index to shape point from ExPolygons
+/// Face type defined by hi
+bool is_face_inside(HI hi,
+ const CutMesh &mesh,
+ const CutMesh &shape_mesh,
+ const VertexShapeMap &vertex_shape_map,
+ const ExPolygonsIndices &shape2index);
+
+///
+/// Face with constrained edge are inside/outside by type of intersection
+/// Other set to not_constrained(still it could be inside/outside)
+///
+/// [Output] property map with type of faces
+/// Mesh to process
+/// Keep information about source of created vertex
+/// Dynamic Edge Constrained Map of bool
+/// Vertices of mesh made by shapes
+/// Convert index to shape point from ExPolygons
+void set_face_type(FaceTypeMap &face_type_map,
+ const CutMesh &mesh,
+ const VertexShapeMap &vertex_shape_map,
+ const EdgeBoolMap &ecm,
+ const CutMesh &shape_mesh,
+ const ExPolygonsIndices &shape2index);
+
+///
+/// Change FaceType from not_constrained to inside
+/// For neighbor(or neighbor of neighbor of ...) of inside triangles.
+/// Process only not_constrained triangles
+///
+/// Corefined mesh
+/// In/Out map with faces type
+void flood_fill_inner(const CutMesh &mesh, FaceTypeMap &face_type_map);
+
+///
+/// Collect connected inside faces
+/// Collect outline half edges
+///
+/// Queue of face to process - find connected
+/// [Output] collected Face indices from mesh
+/// [Output] collected Halfedge indices from mesh
+/// Use flag inside / outside
+/// NOTE: Modify in function: inside -> inside_processed
+/// mesh to process
+void collect_surface_data(std::queue &process,
+ std::vector &faces,
+ std::vector &outlines,
+ FaceTypeMap &face_type_map,
+ const CutMesh &mesh);
+
+///
+/// Create areas from mesh surface
+///
+/// Model
+/// Cutted shapes
+/// Define Triangles of interest.
+/// Edge between inside / outside.
+/// NOTE: Not const because it need to flag proccessed faces
+/// Areas of interest from mesh
+CutAOIs create_cut_area_of_interests(const CutMesh &mesh,
+ const ExPolygons &shapes,
+ FaceTypeMap &face_type_map);
+
+} // namespace priv
+
+void priv::Visitor::intersection_point_detected(std::size_t i_id,
+ int sdim,
+ HI h_f,
+ HI h_e,
+ const CutMesh &tm_f,
+ const CutMesh &tm_e,
+ bool is_target_coplanar,
+ bool is_source_coplanar)
+{
+ if (i_id >= intersections.size()) {
+ size_t capacity = Slic3r::next_highest_power_of_2(i_id + 1);
+ intersections.reserve(capacity);
+ intersections.resize(capacity);
+ }
+
+ const IntersectingElement *intersection_ptr = nullptr;
+ if (&tm_e == &shape) {
+ assert(&tm_f == &object);
+ switch (sdim) {
+ case 1:
+ // edge x edge intersection
+ intersection_ptr = &edge_shape_map[shape.edge(h_e)];
+ break;
+ case 2:
+ // edge x face intersection
+ intersection_ptr = &face_shape_map[shape.face(h_e)];
+ break;
+ default: assert(false);
+ }
+ if (is_target_coplanar)
+ vert_shape_map[object.source(h_f)] = intersection_ptr;
+ if (is_source_coplanar)
+ vert_shape_map[object.target(h_f)] = intersection_ptr;
+ } else {
+ assert(&tm_f == &shape && &tm_e == &object);
+ assert(!is_target_coplanar);
+ assert(!is_source_coplanar);
+ if (is_target_coplanar || is_source_coplanar)
+ *is_valid = false;
+ intersection_ptr = &edge_shape_map[shape.edge(h_f)];
+ if (sdim == 0) vert_shape_map[object.target(h_e)] = intersection_ptr;
+ }
+
+ if (intersection_ptr->shape_point_index == std::numeric_limits::max()) {
+ // there is unexpected intersection
+ // Top (or Bottom) shape contour edge (or vertex) intersection
+ // Suggest to change projection min/max limits
+ *is_valid = false;
+ }
+ intersections[i_id] = intersection_ptr;
+}
+
+void priv::Visitor::new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm)
+{
+ assert(&tm == &object);
+ assert(i_id < intersections.size());
+ const IntersectingElement *intersection_ptr = intersections[i_id];
+ assert(intersection_ptr != nullptr);
+ // intersection was not filled in function intersection_point_detected
+ //assert(intersection_ptr->point_index != std::numeric_limits::max());
+ vert_shape_map[v] = intersection_ptr;
+}
+
+bool priv::is_face_inside(HI hi,
+ const CutMesh &mesh,
+ const CutMesh &shape_mesh,
+ const VertexShapeMap &vertex_shape_map,
+ const ExPolygonsIndices &shape2index)
+{
+ VI vi_from = mesh.source(hi);
+ VI vi_to = mesh.target(hi);
+ // This face has a constrained edge.
+ const IntersectingElement &shape_from = *vertex_shape_map[vi_from];
+ const IntersectingElement &shape_to = *vertex_shape_map[vi_to];
+ assert(shape_from.shape_point_index != std::numeric_limits::max());
+ assert(shape_from.attr != (unsigned char) IntersectingElement::Type::undefined);
+ assert(shape_to.shape_point_index != std::numeric_limits::max());
+ assert(shape_to.attr != (unsigned char) IntersectingElement::Type::undefined);
+
+ // index into contour
+ uint32_t i_from = shape_from.shape_point_index;
+ uint32_t i_to = shape_to.shape_point_index;
+ IntersectingElement::Type type_from = shape_from.get_type();
+ IntersectingElement::Type type_to = shape_to.get_type();
+ if (i_from == i_to && type_from == type_to) {
+ // intersecting element must be face
+ assert(type_from == IntersectingElement::Type::face_1 ||
+ type_from == IntersectingElement::Type::face_2);
+
+ // count of vertices is twice as count of point in the contour
+ uint32_t i = i_from * 2;
+ // j is next contour point in vertices
+ uint32_t j = i + 2;
+ if (shape_from.is_last()) {
+ ExPolygonsIndex point_id = shape2index.cvt(i_from);
+ point_id.point_index = 0;
+ j = shape2index.cvt(point_id)*2;
+ }
+
+ // opposit point(in triangle face) to edge
+ const P3 &p = mesh.point(mesh.target(mesh.next(hi)));
+
+ // abc is source triangle face
+ CGAL::Sign abcp = type_from == IntersectingElement::Type::face_1 ?
+ CGAL::orientation(shape_mesh.point(VI(i)),
+ shape_mesh.point(VI(i + 1)),
+ shape_mesh.point(VI(j)), p) :
+ // type_from == IntersectingElement::Type::face_2
+ CGAL::orientation(shape_mesh.point(VI(j)),
+ shape_mesh.point(VI(i + 1)),
+ shape_mesh.point(VI(j + 1)), p);
+ return abcp == CGAL::POSITIVE;
+ } else if (i_from < i_to || (i_from == i_to && type_from < type_to)) {
+ bool is_last = shape_to.is_last() && shape_from.is_first();
+ // check continuity of indicies
+ assert(i_from == i_to || is_last || (i_from + 1) == i_to);
+ return !is_last;
+ } else {
+ assert(i_from > i_to || (i_from == i_to && type_from > type_to));
+ bool is_last = shape_to.is_first() && shape_from.is_last();
+ // check continuity of indicies
+ assert(i_from == i_to || is_last || (i_to + 1) == i_from);
+ return is_last;
+ }
+
+ assert(false);
+ return false;
+}
+
+void priv::set_face_type(FaceTypeMap &face_type_map,
+ const CutMesh &mesh,
+ const VertexShapeMap &vertex_shape_map,
+ const EdgeBoolMap &ecm,
+ const CutMesh &shape_mesh,
+ const ExPolygonsIndices &shape2index)
+{
+ for (EI ei : mesh.edges()) {
+ if (!ecm[ei]) continue;
+ HI hi = mesh.halfedge(ei);
+ FI fi = mesh.face(hi);
+ bool is_inside = is_face_inside(hi, mesh, shape_mesh, vertex_shape_map, shape2index);
+ face_type_map[fi] = is_inside ? FaceType::inside : FaceType::outside;
+ HI hi_op = mesh.opposite(hi);
+ assert(hi_op.is_valid());
+ if (!hi_op.is_valid()) continue;
+ FI fi_op = mesh.face(hi_op);
+ assert(fi_op.is_valid());
+ if (!fi_op.is_valid()) continue;
+ face_type_map[fi_op] = (!is_inside) ? FaceType::inside : FaceType::outside;
+ }
+}
+
+priv::CutAOIs priv::cut_from_model(CutMesh &cgal_model,
+ const ExPolygons &shapes,
+ CutMesh &cgal_shape,
+ float projection_ratio,
+ const ExPolygonsIndices &s2i)
+{
+ // pointer to edge or face shape_map
+ VertexShapeMap vert_shape_map = cgal_model.add_property_map(vert_shape_map_name, nullptr).first;
+
+ // detect anomalities in visitor.
+ bool is_valid = true;
+ // NOTE: map are created when convert shapes to cgal model
+ const EdgeShapeMap& edge_shape_map = cgal_shape.property_map(edge_shape_map_name).first;
+ const FaceShapeMap& face_shape_map = cgal_shape.property_map(face_shape_map_name).first;
+ Visitor visitor{cgal_model, cgal_shape, edge_shape_map, face_shape_map, vert_shape_map, &is_valid};
+
+ // a property map containing the constrained-or-not status of each edge
+ EdgeBoolMap ecm = cgal_model.add_property_map(is_constrained_edge_name, false).first;
+ const auto &p = CGAL::parameters::visitor(visitor)
+ .edge_is_constrained_map(ecm)
+ .throw_on_self_intersection(false);
+ const auto& q = CGAL::parameters::do_not_modify(true);
+ CGAL::Polygon_mesh_processing::corefine(cgal_model, cgal_shape, p, q);
+
+ if (!is_valid) return {};
+
+ FaceTypeMap face_type_map = cgal_model.add_property_map(face_type_map_name, FaceType::not_constrained).first;
+
+ // Select inside and outside face in model
+ set_face_type(face_type_map, cgal_model, vert_shape_map, ecm, cgal_shape, s2i);
+#ifdef DEBUG_OUTPUT_DIR
+ store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "constrained/"); // only debug
+#endif // DEBUG_OUTPUT_DIR
+
+ // flood fill the other faces inside the region.
+ flood_fill_inner(cgal_model, face_type_map);
+
+#ifdef DEBUG_OUTPUT_DIR
+ store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "filled/", true); // only debug
+#endif // DEBUG_OUTPUT_DIR
+
+ // IMPROVE: AOIs area could be created during flood fill
+ return create_cut_area_of_interests(cgal_model, shapes, face_type_map);
+}
+
+void priv::flood_fill_inner(const CutMesh &mesh,
+ FaceTypeMap &face_type_map)
+{
+ std::vector process;
+ // guess count of connected not constrained triangles
+ size_t guess_size = 128;
+ process.reserve(guess_size);
+
+ // check if neighbor(one of three in triangle) has type inside
+ auto has_inside_neighbor = [&mesh, &face_type_map](FI fi) {
+ HI hi = mesh.halfedge(fi);
+ HI hi_end = hi;
+ auto exist_next = [&hi, &hi_end, &mesh]() -> bool {
+ hi = mesh.next(hi);
+ return hi != hi_end;
+ };
+ // loop over 3 half edges of face
+ do {
+ HI hi_opposite = mesh.opposite(hi);
+ // open edge doesn't have opposit half edge
+ if (!hi_opposite.is_valid()) continue;
+ FI fi_opposite = mesh.face(hi_opposite);
+ if (!fi_opposite.is_valid()) continue;
+ if (face_type_map[fi_opposite] == FaceType::inside) return true;
+ } while (exist_next());
+ return false;
+ };
+
+ for (FI fi : mesh.faces()) {
+ FaceType type = face_type_map[fi];
+ if (type != FaceType::not_constrained) continue;
+ if (!has_inside_neighbor(fi)) continue;
+ assert(process.empty());
+ process.push_back(fi);
+ //store(mesh, face_type_map, DEBUG_OUTPUT_DIR + "progress.off");
+
+ while (!process.empty()) {
+ FI process_fi = process.back();
+ process.pop_back();
+ // Do not fill twice
+ FaceType& process_type = face_type_map[process_fi];
+ if (process_type == FaceType::inside) continue;
+ process_type = FaceType::inside;
+
+ // check neighbor triangle
+ HI hi = mesh.halfedge(process_fi);
+ HI hi_end = hi;
+ auto exist_next = [&hi, &hi_end, &mesh]() -> bool {
+ hi = mesh.next(hi);
+ return hi != hi_end;
+ };
+ do {
+ HI hi_opposite = mesh.opposite(hi);
+ // open edge doesn't have opposit half edge
+ if (!hi_opposite.is_valid()) continue;
+ FI fi_opposite = mesh.face(hi_opposite);
+ if (!fi_opposite.is_valid()) continue;
+ FaceType type_opposite = face_type_map[fi_opposite];
+ if (type_opposite == FaceType::not_constrained)
+ process.push_back(fi_opposite);
+ } while (exist_next());
+ }
+ }
+}
+
+void priv::collect_surface_data(std::queue &process,
+ std::vector &faces,
+ std::vector &outlines,
+ FaceTypeMap &face_type_map,
+ const CutMesh &mesh)
+{
+ assert(!process.empty());
+ assert(faces.empty());
+ assert(outlines.empty());
+ while (!process.empty()) {
+ FI fi = process.front();
+ process.pop();
+
+ FaceType &fi_type = face_type_map[fi];
+ // Do not process twice
+ if (fi_type == FaceType::inside_processed) continue;
+ assert(fi_type == FaceType::inside);
+ // flag face as processed
+ fi_type = FaceType::inside_processed;
+ faces.push_back(fi);
+
+ // check neighbor triangle
+ HI hi = mesh.halfedge(fi);
+ HI hi_end = hi;
+ do {
+ HI hi_opposite = mesh.opposite(hi);
+ // open edge doesn't have opposit half edge
+ if (!hi_opposite.is_valid()) {
+ outlines.push_back(hi);
+ hi = mesh.next(hi);
+ continue;
+ }
+ FI fi_opposite = mesh.face(hi_opposite);
+ if (!fi_opposite.is_valid()) {
+ outlines.push_back(hi);
+ hi = mesh.next(hi);
+ continue;
+ }
+ FaceType side = face_type_map[fi_opposite];
+ if (side == FaceType::inside) {
+ process.emplace(fi_opposite);
+ } else if (side == FaceType::outside) {
+ // store outlines
+ outlines.push_back(hi);
+ }
+ hi = mesh.next(hi);
+ } while (hi != hi_end);
+ }
+}
+
+void priv::create_reduce_map(ReductionMap &reduction_map, const CutMesh &mesh)
+{
+ const VertexShapeMap &vert_shape_map = mesh.property_map(vert_shape_map_name).first;
+ const EdgeBoolMap &ecm = mesh.property_map(is_constrained_edge_name).first;
+
+ // check if vertex was made by edge_2 which is diagonal of quad
+ auto is_reducible_vertex = [&vert_shape_map](VI reduction_from) -> bool {
+ const IntersectingElement *ie = vert_shape_map[reduction_from];
+ if (ie == nullptr) return false;
+ IntersectingElement::Type type = ie->get_type();
+ return type == IntersectingElement::Type::edge_2;
+ };
+
+ ///
+ /// Append reduction or change existing one.
+ ///
+ /// HalEdge between outside and inside face.
+ /// Target vertex will be reduced, source vertex left
+ /// [[maybe_unused]] &face_type_map, &is_reducible_vertex are need only in debug
+ auto add_reduction = [&] //&reduction_map, &mesh, &face_type_map, &is_reducible_vertex
+ (HI hi) {
+ VI erase = mesh.target(hi);
+ VI left = mesh.source(hi);
+ assert(is_reducible_vertex(erase));
+ assert(!is_reducible_vertex(left));
+ VI &vi = reduction_map[erase];
+ // check if it is first add
+ if (vi.is_valid())
+ return;
+
+ // check that all triangles after reduction has 'erase' and 'left' vertex
+ // on same side of opposite line of vertex in triangle
+ Vec3d v_erase = to_vec3d(mesh.point(erase));
+ Vec3d v_left = to_vec3d(mesh.point(left));
+ for (FI fi : mesh.faces_around_target(hi)) {
+ if (!fi.is_valid())
+ continue;
+ // get vertices of rest
+ VI vi_a, vi_b;
+ for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
+ if (!vi.is_valid())
+ continue;
+ if (vi == erase)
+ continue;
+ if (!vi_a.is_valid())
+ vi_a = vi;
+ else {
+ assert(!vi_b.is_valid());
+ vi_b = vi;
+ }
+ }
+ assert(vi_b.is_valid());
+ // do not check triangle, which will be removed
+ if (vi_a == left || vi_b == left)
+ continue;
+
+ Vec3d v_a = to_vec3d(mesh.point(vi_a));
+ Vec3d v_b = to_vec3d(mesh.point(vi_b));
+ // Vectors of triangle edges
+ Vec3d v_ab = v_b - v_a;
+ Vec3d v_ae = v_erase - v_a;
+ Vec3d v_al = v_left - v_a;
+
+ Vec3d n1 = v_ab.cross(v_ae);
+ Vec3d n2 = v_ab.cross(v_al);
+ // check that normal has same direction
+ if (((n1.x() > 0) != (n2.x() > 0)) ||
+ ((n1.y() > 0) != (n2.y() > 0)) ||
+ ((n1.z() > 0) != (n2.z() > 0)))
+ return; // this reduction will create CCW triangle
+ }
+
+ reduction_map[erase] = left;
+ // I have no better rule than take the first
+ // for decide which reduction will be better
+ // But it could be use only one of them
+ };
+
+ for (EI ei : mesh.edges()) {
+ if (!ecm[ei]) continue;
+ HI hi = mesh.halfedge(ei);
+ VI vi = mesh.target(hi);
+ if (is_reducible_vertex(vi)) add_reduction(hi);
+
+ HI hi_op = mesh.opposite(hi);
+ VI vi_op = mesh.target(hi_op);
+ if (is_reducible_vertex(vi_op)) add_reduction(hi_op);
+ }
+#ifdef DEBUG_OUTPUT_DIR
+ store(mesh, reduction_map, DEBUG_OUTPUT_DIR + "reduces/");
+#endif // DEBUG_OUTPUT_DIR
+}
+
+priv::CutAOIs priv::create_cut_area_of_interests(const CutMesh &mesh,
+ const ExPolygons &shapes,
+ FaceTypeMap &face_type_map)
+{
+ // IMPROVE: Create better heuristic for count.
+ size_t faces_per_cut = mesh.faces().size() / shapes.size();
+ size_t outlines_per_cut = faces_per_cut / 2;
+ size_t cuts_per_model = shapes.size() * 2;
+
+ CutAOIs result;
+ result.reserve(cuts_per_model);
+
+ // It is faster to use one queue for all cuts
+ std::queue process;
+ for (FI fi : mesh.faces()) {
+ if (face_type_map[fi] != FaceType::inside) continue;
+
+ CutAOI cut;
+ std::vector &faces = cut.first;
+ std::vector &outlines = cut.second;
+
+ // faces for one surface cut
+ faces.reserve(faces_per_cut);
+ // outline for one surface cut
+ outlines.reserve(outlines_per_cut);
+
+ assert(process.empty());
+ // Process queue of faces to separate to surface_cut
+ process.push(fi);
+ collect_surface_data(process, faces, outlines, face_type_map, mesh);
+ assert(!faces.empty());
+ assert(!outlines.empty());
+ result.emplace_back(std::move(cut));
+ }
+ return result;
+}
+
+namespace priv {
+
+///
+/// Calculate projection distance of point [in mm]
+///
+/// Point to calc distance
+/// Index of point on contour
+/// Model of cutting shape
+/// Ratio for best projection distance
+/// Distance of point from best projection
+float calc_distance(const P3 &p,
+ uint32_t pi,
+ const CutMesh &shapes_mesh,
+ float projection_ratio);
+
+}
+
+float priv::calc_distance(const P3 &p,
+ uint32_t pi,
+ const CutMesh &shapes_mesh,
+ float projection_ratio)
+{
+ // It is known because shapes_mesh is created inside of private space
+ VI vi_start(2 * pi);
+ VI vi_end(2 * pi + 1);
+
+ // Get range for intersection
+ const P3 &start = shapes_mesh.point(vi_start);
+ const P3 &end = shapes_mesh.point(vi_end);
+
+ // find index in vector with biggest difference
+ size_t max_i = 0;
+ float max_val = 0.f;
+ for (size_t i = 0; i < 3; i++) {
+ float val = start[i] - end[i];
+ // abs value
+ if (val < 0.f) val *= -1.f;
+ if (max_val < val) {
+ max_val = val;
+ max_i = i;
+ }
+ }
+
+ float from_start = p[max_i] - start[max_i];
+ float best_distance = projection_ratio * (end[max_i] - start[max_i]);
+ return from_start - best_distance;
+}
+
+priv::VDistances priv::calc_distances(const SurfacePatches &patches,
+ const CutMeshes &models,
+ const CutMesh &shapes_mesh,
+ size_t count_shapes_points,
+ float projection_ratio)
+{
+ priv::VDistances result(count_shapes_points);
+ for (const SurfacePatch &patch : patches) {
+ // map is created during intersection by corefine visitor
+ const VertexShapeMap &vert_shape_map =
+ models[patch.model_id].property_map(vert_shape_map_name).first;
+ uint32_t patch_index = &patch - &patches.front();
+ // map is created during patch creation / dividing
+ const CvtVI2VI& cvt = patch.mesh.property_map(patch_source_name).first;
+ // for each point on outline
+ for (const Loop &loop : patch.loops)
+ for (const VI &vi_patch : loop) {
+ VI vi_model = cvt[vi_patch];
+ if (!vi_model.is_valid()) continue;
+ const IntersectingElement *ie = vert_shape_map[vi_model];
+ if (ie == nullptr) continue;
+ assert(ie->shape_point_index != std::numeric_limits::max());
+ assert(ie->attr != (unsigned char) IntersectingElement::Type::undefined);
+ uint32_t pi = ie->shape_point_index;
+ assert(pi <= count_shapes_points);
+ std::vector &pds = result[pi];
+ uint32_t model_index = patch.model_id;
+ uint32_t aoi_index = patch.aoi_id;
+ //uint32_t hi_index = &hi - &patch.outline.front();
+ const P3 &p = patch.mesh.point(vi_patch);
+ float distance = calc_distance(p, pi, shapes_mesh, projection_ratio);
+ pds.push_back({model_index, aoi_index, patch_index, distance});
+ }
+ }
+ return result;
+}
+
+
+#include "libslic3r/AABBTreeLines.hpp"
+#include "libslic3r/Line.hpp"
+// functions for choose_best_distance
+namespace priv {
+
+// euler square size of vector stored in Point
+float calc_size_sq(const Point &p);
+
+// structure to store index and distance together
+struct ClosePoint
+{
+ // index of closest point from another shape
+ uint32_t index = std::numeric_limits::max();
+ // squere distance to index
+ float dist_sq = std::numeric_limits::max();
+};
+
+struct SearchData{
+// IMPROVE: float lines are enough
+std::vector lines;
+// convert line index into Shape point index
+std::vector cvt;
+// contain lines from prev point to Point index
+AABBTreeIndirect::Tree<2, double> tree;
+};
+
+SearchData create_search_data(const ExPolygons &shapes, const std::vector& mask);
+uint32_t get_closest_point_index(const SearchData &sd, size_t line_idx, const Vec2d &hit_point, const ExPolygons &shapes, const ExPolygonsIndices &s2i);
+
+// use AABB Tree Lines to find closest point
+uint32_t find_closest_point_index(const Point &p, const ExPolygons &shapes, const ExPolygonsIndices &s2i, const std::vector &mask);
+
+std::pair find_closest_point_pair(const ExPolygons &shapes, const std::vector &done_shapes, const ExPolygonsIndices &s2i, const std::vector &mask);
+
+// Search for closest projection to wanted distance
+const ProjectionDistance *get_closest_projection(const ProjectionDistances &distance, float wanted_distance);
+
+// fill result around known index inside one polygon
+void fill_polygon_distances(const ProjectionDistance &pd, uint32_t index, const ExPolygonsIndex &id, ProjectionDistances & result, const ExPolygon &shape, const VDistances &distances);
+
+// search for closest projection for expolygon
+// choose correct cut by source point
+void fill_shape_distances(uint32_t start_index, const ProjectionDistance *start_pd, ProjectionDistances &result, const ExPolygonsIndices &s2i, const ExPolygon &shape, const VDistances &distances);
+
+// find close points between finished and unfinished ExPolygons
+ClosePoint find_close_point(const Point &p, ProjectionDistances &result, std::vector& finished_shapes,const ExPolygonsIndices &s2i, const ExPolygons &shapes);
+
+}
+
+float priv::calc_size_sq(const Point &p){
+ // NOTE: p.squaredNorm() can't be use due to overflow max int value
+ return (float) p.x() * p.x() + (float) p.y() * p.y();
+}
+
+priv::SearchData priv::create_search_data(const ExPolygons &shapes,
+ const std::vector &mask)
+{
+ // IMPROVE: Use float precission (it is enough)
+ SearchData sd;
+ sd.lines.reserve(mask.size());
+ sd.cvt.reserve(mask.size());
+ size_t index = 0;
+ auto add_lines = [&sd, &index, &mask]
+ (const Polygon &poly) {
+ Vec2d prev = poly.back().cast();
+ bool use_point = mask[index + poly.points.size() - 1];
+ for (const Point &p : poly.points) {
+ if (!use_point) {
+ use_point = mask[index];
+ if (use_point) prev = p.cast();
+ } else if (!mask[index]) {
+ use_point = false;
+ } else {
+ Vec2d p_d = p.cast();
+ sd.lines.emplace_back(prev, p_d);
+ sd.cvt.push_back(index);
+ prev = p_d;
+ }
+ ++index;
+ }
+ };
+
+ for (const ExPolygon &shape : shapes) {
+ add_lines(shape.contour);
+ for (const Polygon &hole : shape.holes) add_lines(hole);
+ }
+ sd.tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(sd.lines);
+ return sd;
+}
+
+uint32_t priv::get_closest_point_index(const SearchData &sd,
+ size_t line_idx,
+ const Vec2d &hit_point,
+ const ExPolygons &shapes,
+ const ExPolygonsIndices &s2i)
+{
+ const Linef &line = sd.lines[line_idx];
+ Vec2d dir = line.a - line.b;
+ Vec2d dir_abs = dir.cwiseAbs();
+ // use x coordinate
+ int i = (dir_abs.x() > dir_abs.y())? 0 :1;
+
+ bool use_index = abs(line.a[i] - hit_point[i]) >
+ abs(line.b[i] - hit_point[i]);
+ size_t point_index = sd.cvt[line_idx];
+
+ // Lambda used only for check result
+ [[maybe_unused]] auto is_same = [&s2i, &shapes]
+ (const Vec2d &p, size_t i) -> bool {
+ auto id = s2i.cvt(i);
+ const ExPolygon &shape = shapes[id.expolygons_index];
+ const Polygon &poly = (id.polygon_index == 0) ?
+ shape.contour :
+ shape.holes[id.polygon_index - 1];
+ Vec2i p_ = p.cast();
+ return p_ == poly[id.point_index];
+ };
+
+ if (use_index) {
+ assert(is_same(line.b, point_index));
+ return point_index;
+ }
+ auto id = s2i.cvt(point_index);
+ if (id.point_index != 0) {
+ assert(is_same(line.a, point_index - 1));
+ return point_index - 1;
+ }
+ const ExPolygon &shape = shapes[id.expolygons_index];
+ size_t count_polygon_points = (id.polygon_index == 0) ?
+ shape.contour.size() :
+ shape.holes[id.polygon_index - 1].size();
+ size_t prev_point_index = point_index + (count_polygon_points - 1);
+ assert(is_same(line.a, prev_point_index));
+ // return previous point index
+ return prev_point_index;
+}
+
+// use AABB Tree Lines
+uint32_t priv::find_closest_point_index(const Point &p,
+ const ExPolygons &shapes,
+ const ExPolygonsIndices &s2i,
+ const std::vector &mask)
+{
+ SearchData sd = create_search_data(shapes, mask);
+ if (sd.tree.nodes().size() == 0){
+ // no lines in expolygon, check whether exist point to start
+ double closest_square_distance = INFINITY;
+ uint32_t closest_id = -1;
+ for (uint32_t i = 0; i < mask.size(); i++)
+ if (mask[i]){
+ ExPolygonsIndex ei = s2i.cvt(i);
+ const Point& s_p = ei.is_contour()?
+ shapes[ei.expolygons_index].contour[ei.point_index]:
+ shapes[ei.expolygons_index].holes[ei.hole_index()][ei.point_index];
+ double square_distance = (p - s_p).cast().squaredNorm();
+ if (closest_id >= mask.size() ||
+ closest_square_distance > square_distance) {
+ closest_id = i;
+ closest_square_distance = square_distance;
+ }
+ }
+ assert(closest_id < mask.size());
+ return closest_id;
+ }
+ size_t line_idx = std::numeric_limits::max();
+ Vec2d hit_point;
+ Vec2d p_d = p.cast();
+ [[maybe_unused]] double distance_sq =
+ AABBTreeLines::squared_distance_to_indexed_lines(
+ sd.lines, sd.tree, p_d, line_idx, hit_point);
+ assert(distance_sq > 0);
+
+ // IMPROVE: one could use line ratio to find closest point
+ return get_closest_point_index(sd, line_idx, hit_point, shapes, s2i);
+}
+
+std::pair priv::find_closest_point_pair(
+ const ExPolygons &shapes,
+ const std::vector &done_shapes,
+ const ExPolygonsIndices &s2i,
+ const std::vector &mask)
+{
+ assert(mask.size() == s2i.get_count());
+ assert(done_shapes.size() == shapes.size());
+ std::vector unfinished_mask = mask; // copy
+
+ size_t index = 0;
+ for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) {
+ size_t count = count_points(shapes[shape_index]);
+ if (done_shapes[shape_index]) {
+ for (size_t i = 0; i < count; ++i, ++index)
+ unfinished_mask[index] = false;
+ } else {
+ index += count;
+ }
+ }
+ assert(index == s2i.get_count());
+ SearchData sd = create_search_data(shapes, unfinished_mask);
+
+ struct ClosestPair
+ {
+ size_t finish_idx = std::numeric_limits::max();
+ size_t unfinished_line_idx = std::numeric_limits::max();
+ Vec2d hit_point = Vec2d();
+ double distance_sq = std::numeric_limits::max();
+ } cp;
+
+ index = 0;
+ for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) {
+ const ExPolygon shape = shapes[shape_index];
+ if (!done_shapes[shape_index]) {
+ index += count_points(shape);
+ continue;
+ }
+
+ auto search_in_polygon = [&index, &cp, &sd, &mask](const Polygon& polygon) {
+ for (size_t i = 0; i < polygon.size(); ++i, ++index) {
+ if (mask[index] == false) continue;
+ Vec2d p_d = polygon[i].cast();
+ size_t line_idx = std::numeric_limits::max();
+ Vec2d hit_point;
+ double distance_sq = AABBTreeLines::squared_distance_to_indexed_lines(
+ sd.lines, sd.tree, p_d, line_idx, hit_point, cp.distance_sq);
+ if (distance_sq < 0 ||
+ distance_sq >= cp.distance_sq) continue;
+ assert(line_idx < sd.lines.size());
+ cp.distance_sq = distance_sq;
+ cp.unfinished_line_idx = line_idx;
+ cp.hit_point = hit_point;
+ cp.finish_idx = index;
+ }
+ };
+ search_in_polygon(shape.contour);
+ for (const Polygon& hole: shape.holes)
+ search_in_polygon(hole);
+ }
+ assert(index == s2i.get_count());
+ // check that exists result
+ if (cp.finish_idx == std::numeric_limits::max()) {
+ return std::make_pair(std::numeric_limits::max(),
+ std::numeric_limits::max());
+ }
+
+ size_t unfinished_idx = get_closest_point_index(sd, cp.unfinished_line_idx, cp.hit_point, shapes, s2i);
+ return std::make_pair(cp.finish_idx, unfinished_idx);
+}
+
+const priv::ProjectionDistance *priv::get_closest_projection(
+ const ProjectionDistances &distance, float wanted_distance)
+{
+ // minimal distance
+ float min_d = std::numeric_limits::max();
+ const ProjectionDistance *min_pd = nullptr;
+ for (const ProjectionDistance &pd : distance) {
+ float d = std::fabs(pd.distance - wanted_distance);
+ // There should be limit for maximal distance
+ if (min_d > d) {
+ min_d = d;
+ min_pd = &pd;
+ }
+ }
+ return min_pd;
+}
+
+void priv::fill_polygon_distances(const ProjectionDistance &pd,
+ uint32_t index,
+ const ExPolygonsIndex &id,
+ ProjectionDistances &result,
+ const ExPolygon &shape,
+ const VDistances &distances)
+{
+ const Points& points = (id.polygon_index == 0) ?
+ shape.contour.points :
+ shape.holes[id.polygon_index - 1].points;
+ // border of indexes for Polygon
+ uint32_t first_index = index - id.point_index;
+ uint32_t last_index = first_index + points.size();
+
+ uint32_t act_index = index;
+ const ProjectionDistance* act_pd = &pd;
+
+ // Copy starting pd to result
+ result[act_index] = pd;
+
+ auto exist_next = [&distances, &act_index, &act_pd, &result]
+ (uint32_t nxt_index) {
+ const ProjectionDistance *nxt_pd = get_closest_projection(distances[nxt_index] ,act_pd->distance);
+ // exist next projection distance ?
+ if (nxt_pd == nullptr) return false;
+
+ // check no rewrite result
+ assert(result[nxt_index].aoi_index == std::numeric_limits::max());
+ // copy founded projection to result
+ result[nxt_index] = *nxt_pd; // copy
+
+ // next
+ act_index = nxt_index;
+ act_pd = &result[nxt_index];
+ return true;
+ };
+
+ // last index in circle
+ uint32_t finish_index = (index == first_index) ? (last_index - 1) :
+ (index - 1);
+ // Positive iteration inside polygon
+ do {
+ uint32_t nxt_index = act_index + 1;
+ // close loop of indexes inside of contour
+ if (nxt_index == last_index) nxt_index = first_index;
+ // check that exist next
+ if (!exist_next(nxt_index)) break;
+ } while (act_index != finish_index);
+
+ // when all results for polygon are set no neccessary to iterate negative
+ if (act_index == finish_index) return;
+
+ act_index = index;
+ act_pd = &pd;
+ // Negative iteration inside polygon
+ do {
+ uint32_t nxt_index = (act_index == first_index) ?
+ (last_index-1) : (act_index - 1);
+ // When iterate negative it must be split to parts
+ // and can't iterate in circle
+ assert(nxt_index != index);
+ // check that exist next
+ if (!exist_next(nxt_index)) break;
+ } while (true);
+}
+
+// IMPROVE: when select distance fill in all distances from Patch
+void priv::fill_shape_distances(uint32_t start_index,
+ const ProjectionDistance *start_pd,
+ ProjectionDistances &result,
+ const ExPolygonsIndices &s2i,
+ const ExPolygon &shape,
+ const VDistances &distances)
+{
+ uint32_t expolygons_index = s2i.cvt(start_index).expolygons_index;
+ uint32_t first_shape_index = s2i.cvt({expolygons_index, 0, 0});
+ do {
+ fill_polygon_distances(*start_pd, start_index, s2i.cvt(start_index),result, shape, distances);
+ // seaching only inside shape, return index of closed finished point
+ auto find_close_finished_point = [&first_shape_index, &shape, &result]
+ (const Point &p) -> ClosePoint {
+ uint32_t index = first_shape_index;
+ ClosePoint cp;
+ auto check_finished_points = [&cp, &result, &index, &p]
+ (const Points& pts) {
+ for (const Point &p_ : pts) {
+ // finished point with some distances
+ if (result[index].aoi_index == std::numeric_limits::max()) {
+ ++index;
+ continue;
+ }
+ float distance = calc_size_sq(p_ - p);
+ if (cp.dist_sq > distance) {
+ cp.dist_sq = distance;
+ cp.index = index;
+ }
+ ++index;
+ }
+ };
+ check_finished_points(shape.contour.points);
+ for (const Polygon &h : shape.holes)
+ check_finished_points(h.points);
+ return cp;
+ };
+
+ // find next closest pair of points
+ // (finished + unfinished) in ExPolygon
+ start_index = std::numeric_limits::max(); // unfinished_index
+ uint32_t finished_index = std::numeric_limits::max();
+ float dist_sq = std::numeric_limits::max();
+
+ // first index in shape
+ uint32_t index = first_shape_index;
+ auto check_unfinished_points = [&index, &result, &distances, &find_close_finished_point, &dist_sq, &start_index, &finished_index]
+ (const Points& pts) {
+ for (const Point &p : pts) {
+ // try find unfinished
+ if (result[index].aoi_index !=
+ std::numeric_limits::max() ||
+ distances[index].empty()) {
+ ++index;
+ continue;
+ }
+ ClosePoint cp = find_close_finished_point(p);
+ if (dist_sq > cp.dist_sq) {
+ dist_sq = cp.dist_sq;
+ start_index = index;
+ finished_index = cp.index;
+ }
+ ++index;
+ }
+ };
+ // for each unfinished points
+ check_unfinished_points(shape.contour.points);
+ for (const Polygon &h : shape.holes)
+ check_unfinished_points(h.points);
+ } while (start_index != std::numeric_limits::max());
+}
+
+priv::ClosePoint priv::find_close_point(const Point &p,
+ ProjectionDistances &result,
+ std::vector &finished_shapes,
+ const ExPolygonsIndices &s2i,
+ const ExPolygons &shapes)
+{
+ // result
+ ClosePoint cp;
+ // for all finished points
+ for (uint32_t shape_index = 0; shape_index < shapes.size(); ++shape_index) {
+ if (!finished_shapes[shape_index]) continue;
+ const ExPolygon &shape = shapes[shape_index];
+ uint32_t index = s2i.cvt({shape_index, 0, 0});
+ auto find_close_point_in_points = [&p, &cp, &index, &result]
+ (const Points &pts){
+ for (const Point &p_ : pts) {
+ // Exist result (is finished) ?
+ if (result[index].aoi_index ==
+ std::numeric_limits::max()) {
+ ++index;
+ continue;
+ }
+ float distance_sq = calc_size_sq(p - p_);
+ if (cp.dist_sq > distance_sq) {
+ cp.dist_sq = distance_sq;
+ cp.index = index;
+ }
+ ++index;
+ }
+ };
+ find_close_point_in_points(shape.contour.points);
+ // shape could be inside of another shape's hole
+ for (const Polygon& h:shape.holes)
+ find_close_point_in_points(h.points);
+ }
+ return cp;
+}
+
+// IMPROVE: when select distance fill in all distances from Patch
+priv::ProjectionDistances priv::choose_best_distance(
+ const VDistances &distances, const ExPolygons &shapes, const Point &start, const ExPolygonsIndices &s2i, const SurfacePatches &patches)
+{
+ assert(distances.size() == count_points(shapes));
+
+ // vector of patches for shape
+ std::vector> shapes_patches(shapes.size());
+ for (const SurfacePatch &patch : patches)
+ shapes_patches[patch.shape_id].push_back(&patch-&patches.front());
+
+ // collect one closest projection for each outline point
+ ProjectionDistances result(distances.size());
+
+ // store info about finished shapes
+ std::vector finished_shapes(shapes.size(), {false});
+
+ // wanted distance from ideal projection
+ // Distances are relative to projection distance
+ // so first wanted distance is the closest one (ZERO)
+ float wanted_distance = 0.f;
+
+ std::vector mask_distances(s2i.get_count(), {true});
+ for (const auto &d : distances)
+ if (d.empty()) mask_distances[&d - &distances.front()] = false;
+
+ // Select point from shapes(text contour) which is closest to center (all in 2d)
+ uint32_t unfinished_index = find_closest_point_index(start, shapes, s2i, mask_distances);
+ assert(unfinished_index < s2i.get_count());
+ if (unfinished_index >= s2i.get_count())
+ // no point to select
+ return result;
+
+#ifdef DEBUG_OUTPUT_DIR
+ Connections connections;
+ connections.reserve(shapes.size());
+ connections.emplace_back(unfinished_index, unfinished_index);
+#endif // DEBUG_OUTPUT_DIR
+
+ do {
+ const ProjectionDistance* pd = get_closest_projection(distances[unfinished_index], wanted_distance);
+ // selection of closest_id should proove that pd has value
+ // (functions: get_closest_point_index and find_close_point_in_points)
+ assert(pd != nullptr);
+ uint32_t expolygons_index = s2i.cvt(unfinished_index).expolygons_index;
+ const ExPolygon &shape = shapes[expolygons_index];
+ std::vector &shape_patches = shapes_patches[expolygons_index];
+ if (shape_patches.size() == 1){
+ // Speed up, only one patch so copy distance from patch
+ uint32_t first_shape_index = s2i.cvt({expolygons_index, 0, 0});
+ uint32_t laset_shape_index = first_shape_index + count_points(shape);
+ for (uint32_t i = first_shape_index; i < laset_shape_index; ++i) {
+ const ProjectionDistances &pds = distances[i];
+ if (pds.empty()) continue;
+ // check that index belongs to patch
+ assert(pds.front().patch_index == shape_patches.front());
+ result[i] = pds.front();
+ if (pds.size() == 1) continue;
+
+ float relative_distance = fabs(result[i].distance - pd->distance);
+ // patch could contain multiple value for one outline point
+ // so choose closest to start point
+ for (uint32_t pds_index = 1; pds_index < pds.size(); ++pds_index) {
+ // check that index still belongs to same patch
+ assert(pds[pds_index].patch_index == shape_patches.front());
+ float relative_distance2 = fabs(pds[pds_index].distance - pd->distance);
+ if (relative_distance > relative_distance2) {
+ relative_distance = relative_distance2;
+ result[i] = pds[pds_index];
+ }
+ }
+ }
+ } else {
+ // multiple patches for expolygon
+ // check that exist patch to fill shape
+ assert(!shape_patches.empty());
+ fill_shape_distances(unfinished_index, pd, result, s2i, shape, distances);
+ }
+
+ finished_shapes[expolygons_index] = true;
+ // The most close points between finished and unfinished shapes
+ auto [finished, unfinished] = find_closest_point_pair(
+ shapes, finished_shapes, s2i, mask_distances);
+
+ // detection of end (best doesn't have value)
+ if (finished == std::numeric_limits::max()) break;
+
+ assert(unfinished != std::numeric_limits::max());
+ const ProjectionDistance &closest_pd = result[finished];
+ // check that best_cp is finished and has result
+ assert(closest_pd.aoi_index != std::numeric_limits::max());
+ wanted_distance = closest_pd.distance;
+ unfinished_index = unfinished;
+
+#ifdef DEBUG_OUTPUT_DIR
+ connections.emplace_back(finished, unfinished);
+#endif // DEBUG_OUTPUT_DIR
+ } while (true); //(unfinished_index != std::numeric_limits::max());
+#ifdef DEBUG_OUTPUT_DIR
+ store(shapes, mask_distances, connections, DEBUG_OUTPUT_DIR + "closest_points.svg");
+#endif // DEBUG_OUTPUT_DIR
+ return result;
+}
+
+// functions to help 'diff_model'
+namespace priv {
+const VI default_vi(std::numeric_limits::max());
+
+// Keep info about intersection source
+struct Source{ HI hi; int sdim=0;};
+using Sources = std::vector;
+const std::string vertex_source_map_name = "v:SourceIntersecting";
+using VertexSourceMap = CutMesh::Property_map;
+
+///
+/// Corefine visitor
+/// Store intersection source for vertices of constrained edge of tm1
+/// Must be used with corefine flag no modification of tm2
+///
+struct IntersectionSources
+{
+ const CutMesh *patch; // patch
+ const CutMesh *model; // const model
+
+ VertexSourceMap vmap;
+
+ // keep sources from call intersection_point_detected
+ // until call new_vertex_added
+ Sources* sources;
+
+ // count intersections
+ void intersection_point_detected(std::size_t i_id,
+ int sdim,
+ HI h_f,
+ HI h_e,
+ const CutMesh &tm_f,
+ const CutMesh &tm_e,
+ bool is_target_coplanar,
+ bool is_source_coplanar)
+ {
+ Source source;
+ if (&tm_e == model) {
+ source = {h_e, sdim};
+ // check other CGAL model that is patch
+ assert(&tm_f == patch);
+ if (is_target_coplanar) {
+ assert(sdim == 0);
+ vmap[tm_f.source(h_f)] = source;
+ }
+ if (is_source_coplanar) {
+ assert(sdim == 0);
+ vmap[tm_f.target(h_f)] = source;
+ }
+
+ // clear source to be able check that this intersection source is
+ // not used any more
+ if (is_source_coplanar || is_target_coplanar) source = {};
+ } else {
+ source = {h_f, sdim};
+ assert(&tm_f == model && &tm_e == patch);
+ assert(!is_target_coplanar);
+ assert(!is_source_coplanar);
+ // if (is_target_coplanar) vmap[tm_e.source(h_e)] = source;
+ // if (is_source_coplanar) vmap[tm_e.target(h_e)] = source;
+ // if (sdim == 0)
+ // vmap[tm_e.target(h_e)] = source;
+ }
+
+ // By documentation i_id is consecutive.
+ // check id goes in a row, without skips
+ assert(sources->size() == i_id);
+ // add source of intersection
+ sources->push_back(source);
+ }
+
+ ///
+ /// Store VI to intersections by i_id
+ ///
+ /// Order number of intersection point
+ /// New added vertex
+ /// Affected mesh
+ void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm)
+ {
+ // check that it is first insertation into item of vmap
+ assert(!vmap[v].hi.is_valid());
+ // check valid addresing into sources
+ assert(i_id < sources->size());
+ // check that source has value
+ assert(sources->at(i_id).hi.is_valid());
+ vmap[v] = sources->at(i_id);
+ }
+
+ // Not used visitor functions
+ void before_subface_creations(FI /* f_old */, CutMesh & /* mesh */) {}
+ void after_subface_created(FI /* f_new */, CutMesh & /* mesh */) {}
+ void after_subface_creations(CutMesh &) {}
+ void before_subface_created(CutMesh &) {}
+ void before_edge_split(HI /* h */, CutMesh & /* tm */) {}
+ void edge_split(HI /* hnew */, CutMesh & /* tm */) {}
+ void after_edge_split() {}
+ void add_retriangulation_edge(HI /* h */, CutMesh & /* tm */) {}
+};
+
+///
+/// Create map1 and map2
+///
+/// Convert tm1.face to type
+/// Corefined mesh
+/// Source of intersection
+/// Identify constrainde edge
+/// Convert tm1.face to type
+void create_face_types(FaceTypeMap &map,
+ const CutMesh &tm1,
+ const CutMesh &tm2,
+ const EdgeBoolMap &ecm,
+ const VertexSourceMap &sources);
+
+///
+/// Implement 'cut' Minus 'clipper', where clipper is reverse input Volume
+/// NOTE: clipper will be modified (corefined by cut) !!!
+///
+/// differ from
+/// differ what
+/// True on succes, otherwise FALSE
+bool clip_cut(SurfacePatch &cut, CutMesh clipper);
+
+BoundingBoxf3 bounding_box(const CutAOI &cut, const CutMesh &mesh);
+BoundingBoxf3 bounding_box(const CutMesh &mesh);
+BoundingBoxf3 bounding_box(const SurfacePatch &ecut);
+
+///
+/// Create patch
+///
+/// Define patch faces
+/// Source of fis
+/// NOTE: Need temporary add property map for convert vertices
+/// Options to reduce vertices from fis.
+/// NOTE: Used for skip vertices made by diagonal edge in rectangle of shape side
+/// Patch
+SurfacePatch create_surface_patch(const std::vector &fis,
+ /*const*/ CutMesh &mesh,
+ const ReductionMap *rmap = nullptr);
+
+} // namespace priv
+
+void priv::create_face_types(FaceTypeMap &map,
+ const CutMesh &tm1,
+ const CutMesh &tm2,
+ const EdgeBoolMap &ecm,
+ const VertexSourceMap &sources)
+{
+ auto get_intersection_source = [&tm2](const Source& s1, const Source& s2)->FI{
+ // when one of sources is face than return it
+ FI fi1 = tm2.face(s1.hi);
+ if (s1.sdim == 2) return fi1;
+ FI fi2 = tm2.face(s2.hi);
+ if (s2.sdim == 2) return fi2;
+ // both vertices are made by same source triangle
+ if (fi1 == fi2) return fi1;
+
+ // when one from sources is edge second one decide side of triangle triangle
+ HI hi1_opposit = tm2.opposite(s1.hi);
+ FI fi1_opposit;
+ if (hi1_opposit.is_valid())
+ fi1_opposit = tm2.face(hi1_opposit);
+ if (fi2 == fi1_opposit) return fi2;
+
+ HI hi2_opposit = tm2.opposite(s2.hi);
+ FI fi2_opposit;
+ if (hi2_opposit.is_valid())
+ fi2_opposit = tm2.face(hi2_opposit);
+ if (fi1 == fi2_opposit) return fi1;
+ if (fi1_opposit.is_valid() && fi1_opposit == fi2_opposit)
+ return fi1_opposit;
+
+ // when intersection is vertex need loop over neighbor
+ for (FI fi_around_hi1 : tm2.faces_around_target(s1.hi)) {
+ for (FI fi_around_hi2 : tm2.faces_around_target(s2.hi)) {
+ if (fi_around_hi1 == fi_around_hi2)
+ return fi_around_hi1;
+ }
+ }
+
+ // should never rich it
+ // Exist case when do not know source triangle for decide side of intersection
+ assert(false);
+ return FI();
+ };
+
+ for (FI fi : tm1.faces()) map[fi] = FaceType::not_constrained;
+ for (EI ei1 : tm1.edges()) {
+ if (!get(ecm, ei1)) continue;
+
+ // get faces from tm1 (f1a + f1b)
+ HI hi1 = tm1.halfedge(ei1);
+ assert(hi1.is_valid());
+ FI f1a = tm1.face(hi1);
+ assert(f1a.is_valid());
+ HI hi_op = tm1.opposite(hi1);
+ assert(hi_op.is_valid());
+ FI f1b = tm1.face(hi_op);
+ assert(f1b.is_valid());
+
+ // get faces from tm2 (f2a + f2b)
+ VI vi1_source = tm1.source(hi1);
+ assert(vi1_source.is_valid());
+ VI vi1_target = tm1.target(hi1);
+ assert(vi1_target.is_valid());
+
+ const Source &s_s = sources[vi1_source];
+ const Source &s_t = sources[vi1_target];
+ FI fi2 = get_intersection_source(s_s, s_t);
+
+ // in release solve situation that face was NOT deduced
+ if (!fi2.is_valid()) continue;
+
+ HI hi2 = tm2.halfedge(fi2);
+ std::array t;
+ size_t ti =0;
+ for (VI vi2 : tm2.vertices_around_face(hi2))
+ t[ti++] = &tm2.point(vi2);
+
+ // triangle tip from face f1a
+ VI vi1a_tip = tm1.target(tm1.next(hi1));
+ assert(vi1a_tip.is_valid());
+ const P3 &p = tm1.point(vi1a_tip);
+
+ // check if f1a is behinde f2a
+ // inside mean it will be used
+ // outside will be discarded
+ if (CGAL::orientation(*t[0], *t[1], *t[2], p) == CGAL::POSITIVE) {
+ map[f1a] = FaceType::inside;
+ map[f1b] = FaceType::outside;
+ } else {
+ map[f1a] = FaceType::outside;
+ map[f1b] = FaceType::inside;
+ }
+ }
+}
+
+#include
+#include
+bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper)
+{
+ CutMesh& tm = cut.mesh;
+ // create backup for case that there is no intersection
+ CutMesh backup_copy = tm;
+
+ class ExistIntersectionClipVisitor: public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor
+ {
+ bool* exist_intersection;
+ public:
+ ExistIntersectionClipVisitor(bool *exist_intersection): exist_intersection(exist_intersection){}
+ void intersection_point_detected(std::size_t, int , HI, HI, const CutMesh&, const CutMesh&, bool, bool)
+ { *exist_intersection = true;}
+ };
+ bool exist_intersection = false;
+ ExistIntersectionClipVisitor visitor{&exist_intersection};
+
+ // namep parameters for model tm and function clip
+ const auto &np_tm = CGAL::parameters::visitor(visitor)
+ .throw_on_self_intersection(false);
+
+ // name parameters for model clipper and function clip
+ const auto &np_c = CGAL::parameters::throw_on_self_intersection(false);
+ // Can't use 'do_not_modify', when Ture than clipper has to be closed !!
+ // .do_not_modify(true);
+ // .throw_on_self_intersection(false); is set automaticaly by param 'do_not_modify'
+ // .clip_volume(false); is set automaticaly by param 'do_not_modify'
+
+ bool suc = CGAL::Polygon_mesh_processing::clip(tm, clipper, np_tm, np_c);
+
+ // true if the output surface mesh is manifold.
+ // If false is returned tm and clipper are only corefined.
+ assert(suc);
+ // decide what TODO when can't clip source object !?!
+ if (!exist_intersection || !suc) {
+ // TODO: test if cut is fully in or fully out!!
+ cut.mesh = backup_copy;
+ return false;
+ }
+ return true;
+}
+
+BoundingBoxf3 priv::bounding_box(const CutAOI &cut, const CutMesh &mesh) {
+ const P3& p_from_cut = mesh.point(mesh.target(mesh.halfedge(cut.first.front())));
+ Vec3d min = to_vec3d(p_from_cut);
+ Vec3d max = min;
+ for (FI fi : cut.first) {
+ for(VI vi: mesh.vertices_around_face(mesh.halfedge(fi))){
+ const P3& p = mesh.point(vi);
+ for (size_t i = 0; i < 3; ++i) {
+ if (min[i] > p[i]) min[i] = p[i];
+ if (max[i] < p[i]) max[i] = p[i];
+ }
+ }
+ }
+ return BoundingBoxf3(min, max);
+}
+
+BoundingBoxf3 priv::bounding_box(const CutMesh &mesh)
+{
+ Vec3d min = to_vec3d(*mesh.points().begin());
+ Vec3d max = min;
+ for (VI vi : mesh.vertices()) {
+ const P3 &p = mesh.point(vi);
+ for (size_t i = 0; i < 3; ++i) {
+ if (min[i] > p[i]) min[i] = p[i];
+ if (max[i] < p[i]) max[i] = p[i];
+ }
+ }
+ return BoundingBoxf3(min, max);
+}
+
+BoundingBoxf3 priv::bounding_box(const SurfacePatch &ecut) {
+ return bounding_box(ecut.mesh);
+}
+
+priv::SurfacePatch priv::create_surface_patch(const std::vector &fis,
+ /* const */ CutMesh &mesh,
+ const ReductionMap *rmap)
+{
+ auto is_counted = mesh.add_property_map("v:is_counted").first;
+ uint32_t count_vertices = 0;
+ if (rmap == nullptr) {
+ for (FI fi : fis)
+ for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi)))
+ if (!is_counted[vi]) {
+ is_counted[vi] = true;
+ ++count_vertices;
+ }
+ } else {
+ for (FI fi : fis)
+ for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
+ // Will vertex be reduced?
+ if ((*rmap)[vi].is_valid()) continue;
+ if (!is_counted[vi]) {
+ is_counted[vi] = true;
+ ++count_vertices;
+ }
+ }
+ }
+ mesh.remove_property_map(is_counted);
+
+ uint32_t count_faces = fis.size();
+ // IMPROVE: Value is greater than neccessary, count edges used twice
+ uint32_t count_edges = count_faces*3;
+
+ CutMesh cm;
+ cm.reserve(count_vertices, count_edges, count_faces);
+
+ // vertex conversion function from mesh VI to result VI
+ CvtVI2VI mesh2result = mesh.add_property_map("v:mesh2result").first;
+
+ if (rmap == nullptr) {
+ for (FI fi : fis) {
+ std::array t;
+ int index = 0;
+ for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
+ VI &vi_cvt = mesh2result[vi];
+ if (!vi_cvt.is_valid()) {
+ vi_cvt = VI(cm.vertices().size());
+ cm.add_vertex(mesh.point(vi));
+ }
+ t[index++] = vi_cvt;
+ }
+ cm.add_face(t[0], t[1], t[2]);
+ }
+ } else {
+ for (FI fi :fis) {
+ std::array t;
+ int index = 0;
+ bool exist_reduction = false;
+ for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
+ VI vi_r = (*rmap)[vi];
+ if (vi_r.is_valid()) {
+ exist_reduction = true;
+ vi = vi_r;
+ }
+ VI &vi_cvt = mesh2result[vi];
+ if (!vi_cvt.is_valid()) {
+ vi_cvt = VI(cm.vertices().size());
+ cm.add_vertex(mesh.point(vi));
+ }
+ t[index++] = vi_cvt;
+ }
+
+ // prevent add reduced triangle
+ if (exist_reduction &&
+ (t[0] == t[1] ||
+ t[1] == t[2] ||
+ t[2] == t[0]))
+ continue;
+
+ cm.add_face(t[0], t[1], t[2]);
+ }
+ }
+
+ assert(count_vertices == cm.vertices().size());
+ assert((rmap == nullptr && count_faces == cm.faces().size()) ||
+ (rmap != nullptr && count_faces >= cm.faces().size()));
+ assert(count_edges >= cm.edges().size());
+
+ // convert VI from this patch to source VI, when exist
+ CvtVI2VI cvt = cm.add_property_map(patch_source_name).first;
+ // vi_s .. VertexIndex into mesh (source)
+ // vi_d .. new VertexIndex in cm (destination)
+ for (VI vi_s : mesh.vertices()) {
+ VI vi_d = mesh2result[vi_s];
+ if (!vi_d.is_valid()) continue;
+ cvt[vi_d] = vi_s;
+ }
+ mesh.remove_property_map(mesh2result);
+ return {std::move(cm)};
+}
+
+// diff_models help functions
+namespace priv {
+
+struct SurfacePatchEx
+{
+ SurfacePatch patch;
+
+ // flag that part will be deleted
+ bool full_inside = false;
+ // flag that Patch could contain more than one part
+ bool just_cliped = false;
+};
+using SurfacePatchesEx = std::vector;
+
+
+using BBS = std::vector;
+///
+/// Create bounding boxes for AOI
+///
+/// Cutted AOI from models
+/// Source points of cuts
+/// Bounding boxes
+BBS create_bbs(const VCutAOIs &cuts, const CutMeshes &cut_models);
+
+using Primitive = CGAL::AABB_face_graph_triangle_primitive;
+using Traits = CGAL::AABB_traits;
+using Ray = EpicKernel::Ray_3;
+using Tree = CGAL::AABB_tree;
+using Trees = std::vector;
+///
+/// Create AABB trees for check when patch is whole inside of model
+///
+/// Source for trees
+/// trees
+Trees create_trees(const CutMeshes &models);
+
+///
+/// Check whether bounding box has intersection with model
+///
+/// Bounding box to check
+/// Model to check with
+/// All bounding boxes from VCutAOIs
+/// Help index into VCutAOIs
+/// True when exist bounding boxes intersection
+bool has_bb_intersection(const BoundingBoxf3 &bb,
+ size_t model_index,
+ const BBS &bbs,
+ const ModelCut2index &m2i);
+
+///
+/// Only for model without intersection
+/// Use ray (in projection direction) from a point from patch
+/// and count intersections: pair .. outside | odd .. inside
+///
+/// Patch to check
+/// Model converted to AABB tree
+/// Define direction of projection
+/// True when patch point lay inside of model defined by tree,
+/// otherwise FALSE
+bool is_patch_inside_of_model(const SurfacePatch &patch,
+ const Tree &tree,
+ const Project3d &projection);
+
+///
+/// Return some shape point index which identify shape
+/// NOTE: Used to find expolygon index
+///
+/// Used to search source shapes poin
+///
+/// shape point index
+uint32_t get_shape_point_index(const CutAOI &cut, const CutMesh &model);
+
+using PatchNumber = CutMesh::Property_map;
+///
+/// Separate triangles singned with number n
+///
+/// Face indices owned by separate patch
+/// Original patch
+/// NOTE: Can't be const. For indexing vetices need temporary add property map
+/// conversion map
+/// Just separated patch
+SurfacePatch separate_patch(const std::vector &fis,
+ /* const*/ SurfacePatch &patch,
+ const CvtVI2VI &cvt_from);
+
+///
+/// Separate connected triangles into it's own patches
+/// new patches are added to back of input patches
+///
+/// index into patches
+/// In/Out Patches
+void divide_patch(size_t i, SurfacePatchesEx &patches);
+
+///
+/// Fill outline in patches by open edges
+///
+/// Input/Output meshes with open edges
+void collect_open_edges(SurfacePatches &patches);
+
+} // namespace priv
+
+std::vector priv::create_bbs(const VCutAOIs &cuts,
+ const CutMeshes &cut_models)
+{
+ size_t count = 0;
+ for (const CutAOIs &cut : cuts) count += cut.size();
+
+ std::vector bbs;
+ bbs.reserve(count);
+ for (size_t model_index = 0; model_index < cut_models.size(); ++model_index) {
+ const CutMesh &cut_model = cut_models[model_index];
+ const CutAOIs &cutAOIs = cuts[model_index];
+ for (size_t cut_index = 0; cut_index < cutAOIs.size(); ++cut_index) {
+ const CutAOI &cut = cutAOIs[cut_index];
+ bbs.push_back(bounding_box(cut, cut_model));
+ }
+ }
+ return bbs;
+}
+
+
+priv::Trees priv::create_trees(const CutMeshes &models) {
+ Trees result;
+ result.reserve(models.size());
+ for (const CutMesh &model : models) {
+ Tree tree;
+ tree.insert(faces(model).first, faces(model).second, model);
+ tree.build();
+ result.emplace_back(std::move(tree));
+ }
+ return result;
+}
+
+bool priv::has_bb_intersection(const BoundingBoxf3 &bb,
+ size_t model_index,
+ const BBS &bbs,
+ const ModelCut2index &m2i)
+{
+ const auto&offsets = m2i.get_offsets();
+ // for cut index with model_index2
+ size_t start = offsets[model_index];
+ size_t next = model_index + 1;
+ size_t end = (next < offsets.size()) ? offsets[next] : m2i.get_count();
+ for (size_t bb_index = start; bb_index < end; bb_index++)
+ if (bb.intersects(bbs[bb_index])) return true;
+ return false;
+}
+
+bool priv::is_patch_inside_of_model(const SurfacePatch &patch,
+ const Tree &tree,
+ const Project3d &projection)
+{
+ // TODO: Solve model with hole in projection direction !!!
+ const P3 &a = patch.mesh.point(VI(0));
+ Vec3d a_ = to_vec3d(a);
+ Vec3d b_ = projection.project(a_);
+ P3 b(b_.x(), b_.y(), b_.z());
+
+ Ray ray_query(a, b);
+ size_t count = tree.number_of_intersected_primitives(ray_query);
+ bool is_in = (count % 2) == 1;
+
+ // try opposit direction result should be same, otherwise open model is used
+ //Vec3f c_ = a_ - (b_ - a_); // opposit direction
+ //P3 c(c_.x(), c_.y(), c_.z());
+ //Ray ray_query2(a, b);
+ //size_t count2 = tree.number_of_intersected_primitives(ray_query2);
+ //bool is_in2 = (count2 % 2) == 1;
+ assert(((tree.number_of_intersected_primitives(
+ Ray(a, P3(2 * a.x() - b.x(),
+ 2 * a.y() - b.y(),
+ 2 * a.z() - b.z()))) %
+ 2) == 1) == is_in);
+ return is_in;
+}
+
+uint32_t priv::get_shape_point_index(const CutAOI &cut, const CutMesh &model)
+{
+ // map is created during intersection by corefine visitor
+ const VertexShapeMap &vert_shape_map = model.property_map(vert_shape_map_name).first;
+ // for each half edge of outline
+ for (HI hi : cut.second) {
+ VI vi = model.source(hi);
+ const IntersectingElement *ie = vert_shape_map[vi];
+ if (ie == nullptr) continue;
+ assert(ie->shape_point_index != std::numeric_limits::max());
+ return ie->shape_point_index;
+ }
+ // can't found any intersecting element in cut
+ assert(false);
+ return 0;
+}
+
+priv::SurfacePatch priv::separate_patch(const std::vector& fis,
+ SurfacePatch &patch,
+ const CvtVI2VI &cvt_from)
+{
+ assert(patch.mesh.is_valid());
+ SurfacePatch patch_new = create_surface_patch(fis, patch.mesh);
+ patch_new.bb = bounding_box(patch_new.mesh);
+ patch_new.aoi_id = patch.aoi_id;
+ patch_new.model_id = patch.model_id;
+ patch_new.shape_id = patch.shape_id;
+ // fix cvt
+ CvtVI2VI cvt = patch_new.mesh.property_map(patch_source_name).first;
+ for (VI &vi : cvt) {
+ if (!vi.is_valid()) continue;
+ vi = cvt_from[vi];
+ }
+ return patch_new;
+}
+
+void priv::divide_patch(size_t i, SurfacePatchesEx &patches)
+{
+ SurfacePatchEx &patch_ex = patches[i];
+ assert(patch_ex.just_cliped);
+ patch_ex.just_cliped = false;
+
+ SurfacePatch& patch = patch_ex.patch;
+ CutMesh& cm = patch.mesh;
+ assert(!cm.faces().empty());
+ std::string patch_number_name = "f:patch_number";
+ CutMesh::Property_map is_processed = cm.add_property_map(patch_number_name, false).first;
+
+ const CvtVI2VI& cvt_from = patch.mesh.property_map(patch_source_name).first;
+
+ std::vector fis;
+ fis.reserve(cm.faces().size());
+
+ SurfacePatchesEx new_patches;
+ std::vector queue;
+ // IMPROVE: create groups around triangles and than connect groups
+ for (FI fi_cm : cm.faces()) {
+ if (is_processed[fi_cm]) continue;
+ assert(queue.empty());
+ queue.push_back(fi_cm);
+ if (!fis.empty()) {
+ // Be carefull after push to patches,
+ // all ref on patch contain non valid values
+ SurfacePatchEx patch_ex_n;
+ patch_ex_n.patch = separate_patch(fis, patch, cvt_from);
+ patch_ex_n.patch.is_whole_aoi = false;
+ new_patches.push_back(std::move(patch_ex_n));
+ fis.clear();
+ }
+ // flood fill from triangle fi_cm to surrounding
+ do {
+ FI fi_q = queue.back();
+ queue.pop_back();
+ if (is_processed[fi_q]) continue;
+ is_processed[fi_q] = true;
+ fis.push_back(fi_q);
+ HI hi = cm.halfedge(fi_q);
+ for (FI fi : cm.faces_around_face(hi)) {
+ // by documentation The face descriptor may be the null face, and it may be several times the same face descriptor.
+ if (!fi.is_valid()) continue;
+ if (!is_processed[fi]) queue.push_back(fi);
+ }
+ } while (!queue.empty());
+ }
+ cm.remove_property_map(is_processed);
+ assert(!fis.empty());
+
+ // speed up for only one patch - no dividing (the most common)
+ if (new_patches.empty()) {
+ patch.bb = bounding_box(cm);
+ patch.is_whole_aoi = false;
+ } else {
+ patch = separate_patch(fis, patch, cvt_from);
+ patches.insert(patches.end(), new_patches.begin(), new_patches.end());
+ }
+}
+
+void priv::collect_open_edges(SurfacePatches &patches) {
+ std::vector open_half_edges;
+ for (SurfacePatch &patch : patches) {
+ open_half_edges.clear();
+ const CutMesh &mesh = patch.mesh;
+ for (FI fi : mesh.faces()) {
+ HI hi1 = mesh.halfedge(fi);
+ assert(hi1.is_valid());
+ HI hi2 = mesh.next(hi1);
+ assert(hi2.is_valid());
+ HI hi3 = mesh.next(hi2);
+ assert(hi3.is_valid());
+ // Is fi triangle?
+ assert(mesh.next(hi3) == hi1);
+ for (HI hi : {hi1, hi2, hi3}) {
+ HI hi_op = mesh.opposite(hi);
+ FI fi_op = mesh.face(hi_op);
+ if (!fi_op.is_valid())
+ open_half_edges.push_back(hi);
+ }
+ }
+ patch.loops = create_loops(open_half_edges, mesh);
+ }
+}
+
+priv::SurfacePatches priv::diff_models(VCutAOIs &cuts,
+ /*const*/ CutMeshes &cut_models,
+ /*const*/ CutMeshes &models,
+ const Project3d &projection)
+{
+ // IMPROVE: when models contain ONE mesh. It is only about convert cuts to patches
+ // and reduce unneccessary triangles on contour
+
+ //Convert model_index and cut_index into one index
+ priv::ModelCut2index m2i(cuts);
+
+ // create bounding boxes for cuts
+ std::vector bbs = create_bbs(cuts, cut_models);
+ Trees trees(models.size());
+
+ SurfacePatches patches;
+
+ // queue of patches for one AOI (permanent with respect to for loop)
+ SurfacePatchesEx aoi_patches;
+
+ //SurfacePatches aoi_patches;
+ patches.reserve(m2i.get_count()); // only approximation of count
+ size_t index = 0;
+ for (size_t model_index = 0; model_index < models.size(); ++model_index) {
+ CutAOIs &model_cuts = cuts[model_index];
+ CutMesh &cut_model_ = cut_models[model_index];
+ const CutMesh &cut_model = cut_model_;
+ ReductionMap vertex_reduction_map = cut_model_.add_property_map(vertex_reduction_map_name).first;
+ create_reduce_map(vertex_reduction_map, cut_model);
+
+ for (size_t cut_index = 0; cut_index < model_cuts.size(); ++cut_index, ++index) {
+ const CutAOI &cut = model_cuts[cut_index];
+ SurfacePatchEx patch_ex;
+ SurfacePatch &patch = patch_ex.patch;
+ patch = create_surface_patch(cut.first, cut_model_, &vertex_reduction_map);
+ patch.bb = bbs[index];
+ patch.aoi_id = cut_index;
+ patch.model_id = model_index;
+ patch.shape_id = get_shape_point_index(cut, cut_model);
+ patch.is_whole_aoi = true;
+
+ aoi_patches.clear();
+ aoi_patches.push_back(patch_ex);
+ for (size_t model_index2 = 0; model_index2 < models.size(); ++model_index2) {
+ // do not clip source model itself
+ if (model_index == model_index2) continue;
+ for (SurfacePatchEx &patch_ex : aoi_patches) {
+ SurfacePatch &patch = patch_ex.patch;
+ if (has_bb_intersection(patch.bb, model_index2, bbs, m2i) &&
+ clip_cut(patch, models[model_index2])){
+ patch_ex.just_cliped = true;
+ } else {
+ // build tree on demand
+ // NOTE: it is possible not neccessary: e.g. one model
+ Tree &tree = trees[model_index2];
+ if (tree.empty()) {
+ const CutMesh &model = models[model_index2];
+ auto f_range = faces(model);
+ tree.insert(f_range.first, f_range.second, model);
+ tree.build();
+ }
+ if (is_patch_inside_of_model(patch, tree, projection))
+ patch_ex.full_inside = true;
+ }
+ }
+ // erase full inside
+ for (size_t i = aoi_patches.size(); i != 0; --i) {
+ auto it = aoi_patches.begin() + (i - 1);
+ if (it->full_inside) aoi_patches.erase(it);
+ }
+
+ // detection of full AOI inside of model
+ if (aoi_patches.empty()) break;
+
+ // divide cliped into parts
+ size_t end = aoi_patches.size();
+ for (size_t i = 0; i < end; ++i)
+ if (aoi_patches[i].just_cliped)
+ divide_patch(i, aoi_patches);
+ }
+
+ if (!aoi_patches.empty()) {
+ patches.reserve(patches.size() + aoi_patches.size());
+ for (SurfacePatchEx &patch : aoi_patches)
+ patches.push_back(std::move(patch.patch));
+
+ }
+ }
+ cut_model_.remove_property_map(vertex_reduction_map);
+ }
+
+ // Also use outline inside of patches(made by non manifold models)
+ // IMPROVE: trace outline from AOIs
+ collect_open_edges(patches);
+ return patches;
+}
+
+bool priv::is_over_whole_expoly(const SurfacePatch &patch,
+ const ExPolygons &shapes,
+ const VCutAOIs &cutAOIs,
+ const CutMeshes &meshes)
+{
+ if (!patch.is_whole_aoi) return false;
+ return is_over_whole_expoly(cutAOIs[patch.model_id][patch.aoi_id],
+ shapes[patch.shape_id],
+ meshes[patch.model_id]);
+}
+
+bool priv::is_over_whole_expoly(const CutAOI &cutAOI,
+ const ExPolygon &shape,
+ const CutMesh &mesh)
+{
+ // NonInterupted contour is without other point and contain all from shape
+ const VertexShapeMap &vert_shape_map = mesh.property_map(vert_shape_map_name).first;
+ for (HI hi : cutAOI.second) {
+ const IntersectingElement *ie_s = vert_shape_map[mesh.source(hi)];
+ const IntersectingElement *ie_t = vert_shape_map[mesh.target(hi)];
+ if (ie_s == nullptr || ie_t == nullptr)
+ return false;
+
+ assert(ie_s->attr != (unsigned char) IntersectingElement::Type::undefined);
+ assert(ie_t->attr != (unsigned char) IntersectingElement::Type::undefined);
+
+ // check if it is neighbor indices
+ uint32_t i_s = ie_s->shape_point_index;
+ uint32_t i_t = ie_t->shape_point_index;
+ assert(i_s != std::numeric_limits::max());
+ assert(i_t != std::numeric_limits::max());
+ if (i_s == std::numeric_limits::max() ||
+ i_t == std::numeric_limits::max())
+ return false;
+
+ // made by same index
+ if (i_s == i_t) continue;
+
+ // order from source to target
+ if (i_s > i_t) {
+ std::swap(i_s, i_t);
+ std::swap(ie_s, ie_t);
+ }
+ // Must be after fix order !!
+ bool is_last_polygon_segment = ie_s->is_first() && ie_t->is_last();
+ if (is_last_polygon_segment) {
+ std::swap(i_s, i_t);
+ std::swap(ie_s, ie_t);
+ }
+
+ // Is continous indices
+ if (!is_last_polygon_segment &&
+ (ie_s->is_last() || (i_s + 1) != i_t))
+ return false;
+
+ IntersectingElement::Type t_s = ie_s->get_type();
+ IntersectingElement::Type t_t = ie_t->get_type();
+ if (t_s == IntersectingElement::Type::undefined ||
+ t_t == IntersectingElement::Type::undefined)
+ return false;
+
+ // next segment must start with edge intersection
+ if (t_t != IntersectingElement::Type::edge_1)
+ return false;
+
+ // After face1 must be edge2 or face2
+ if (t_s == IntersectingElement::Type::face_1)
+ return false;
+ }
+
+ // When all open edges are on contour than there is NO holes is shape
+ auto is_open = [&mesh](HI hi)->bool {
+ HI opposite = mesh.opposite(hi);
+ return !mesh.face(opposite).is_valid();
+ };
+
+ std::vector opens; // copy
+ opens.reserve(cutAOI.second.size());
+ for (HI hi : cutAOI.second) // from lower to bigger
+ if (is_open(hi)) opens.push_back(hi);
+ std::sort(opens.begin(), opens.end());
+
+ for (FI fi: cutAOI.first) {
+ HI face_hi = mesh.halfedge(fi);
+ for (HI hi : mesh.halfedges_around_face(face_hi)) {
+ if (!is_open(hi)) continue;
+ // open edge
+ auto lb = std::lower_bound(opens.begin(), opens.end(), hi);
+ if (lb == opens.end() || *lb != hi)
+ return false; // not in contour
+ }
+ }
+ return true;
+}
+
+std::vector priv::select_patches(const ProjectionDistances &best_distances,
+ const SurfacePatches &patches,
+ const ExPolygons &shapes,
+ const BoundingBox &shapes_bb,
+ const ExPolygonsIndices &s2i,
+ const VCutAOIs &cutAOIs,
+ const CutMeshes &meshes,
+ const Project &projection)
+{
+ // extension to cover numerical mistake made by back projection patch from 3d to 2d
+ // Calculated as one percent of average size(width and height)
+ Point s = shapes_bb.size();
+ const float extend_delta = (s.x() + s.y())/ float(2 * 100);
+
+ // vector of patches for shape
+ std::vector> used_shapes_patches(shapes.size());
+ std::vector in_distances(patches.size(), {false});
+ for (const ProjectionDistance &d : best_distances) {
+ // exist valid projection for shape point?
+ if (d.patch_index == std::numeric_limits::max()) continue;
+ if (in_distances[d.patch_index]) continue;
+ in_distances[d.patch_index] = true;
+
+ ExPolygonsIndex id = s2i.cvt(&d - &best_distances.front());
+ used_shapes_patches[id.expolygons_index].push_back(d.patch_index);
+ }
+
+ // vector of patches for shape
+ std::vector> shapes_patches(shapes.size());
+ for (const SurfacePatch &patch : patches)
+ shapes_patches[patch.shape_id].push_back(&patch - &patches.front());
+
+#ifdef DEBUG_OUTPUT_DIR
+ std::string store_dir = DEBUG_OUTPUT_DIR + "select_patches/";
+ prepare_dir(store_dir);
+#endif // DEBUG_OUTPUT_DIR
+
+ for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) {
+ const ExPolygon &shape = shapes[shape_index];
+ std::vector &used_shape_patches = used_shapes_patches[shape_index];
+ if (used_shape_patches.empty()) continue;
+ // is used all exist patches?
+ if (used_shapes_patches.size() == shapes_patches[shape_index].size()) continue;
+ if (used_shape_patches.size() == 1) {
+ uint32_t patch_index = used_shape_patches.front();
+ const SurfacePatch &patch = patches[patch_index];
+ if (is_over_whole_expoly(patch, shapes, cutAOIs, meshes)) continue;
+ }
+
+ // only shapes containing multiple patches
+ // or not full filled are back projected (hard processed)
+
+ // intersection of converted patches to 2d
+ ExPolygons fill;
+ fill.reserve(used_shape_patches.size());
+
+ // Heuristics to predict which patch to be used need average patch depth
+ Vec2d used_patches_depth(std::numeric_limits::max(), std::numeric_limits::min());
+ for (uint32_t patch_index : used_shape_patches) {
+ ExPolygon patch_area = to_expoly(patches[patch_index], projection, used_patches_depth);
+ //*/
+ ExPolygons patch_areas = offset_ex(patch_area, extend_delta);
+ fill.insert(fill.end(), patch_areas.begin(), patch_areas.end());
+ /*/
+ // without save extension
+ fill.push_back(patch_area);
+ //*/
+ }
+ fill = union_ex(fill);
+
+ // not cutted area of expolygon
+ ExPolygons rest = diff_ex(ExPolygons{shape}, fill, ApplySafetyOffset::Yes);
+#ifdef DEBUG_OUTPUT_DIR
+ BoundingBox shape_bb = get_extents(shape);
+ SVG svg(store_dir + "shape_" + std::to_string(shape_index) + ".svg", shape_bb);
+ svg.draw(fill, "darkgreen");
+ svg.draw(rest, "green");
+#endif // DEBUG_OUTPUT_DIR
+
+ // already filled by multiple patches
+ if (rest.empty()) continue;
+
+ // find patches overlaped rest area
+ struct PatchShape{
+ uint32_t patch_index;
+ ExPolygon shape;
+ ExPolygons intersection;
+ double depth_range_center_distance; // always positive
+ };
+ using PatchShapes = std::vector;
+ PatchShapes patch_shapes;
+
+ double used_patches_depth_center = (used_patches_depth[0] + used_patches_depth[1]) / 2;
+
+ // sort used_patches for faster search
+ std::sort(used_shape_patches.begin(), used_shape_patches.end());
+ for (uint32_t patch_index : shapes_patches[shape_index]) {
+ // check is patch already used
+ auto it = std::lower_bound(used_shape_patches.begin(), used_shape_patches.end(), patch_index);
+ if (it != used_shape_patches.end() && *it == patch_index) continue;
+
+ // Heuristics to predict which patch to be used need average patch depth
+ Vec2d patche_depth_range(std::numeric_limits::max(), std::numeric_limits::min());
+ ExPolygon patch_shape = to_expoly(patches[patch_index], projection, patche_depth_range);
+ double depth_center = (patche_depth_range[0] + patche_depth_range[1]) / 2;
+ double depth_range_center_distance = std::fabs(used_patches_depth_center - depth_center);
+
+ ExPolygons patch_intersection = intersection_ex(ExPolygons{patch_shape}, rest);
+ if (patch_intersection.empty()) continue;
+
+ patch_shapes.push_back({patch_index, patch_shape, patch_intersection, depth_range_center_distance});
+ }
+
+ // nothing to add
+ if (patch_shapes.empty()) continue;
+ // only one solution to add
+ if (patch_shapes.size() == 1) {
+ used_shape_patches.push_back(patch_shapes.front().patch_index);
+ continue;
+ }
+
+ // Idea: Get depth range of used patches and add patches in order by distance to used depth center
+ std::sort(patch_shapes.begin(), patch_shapes.end(), [](const PatchShape &a, const PatchShape &b)
+ { return a.depth_range_center_distance < b.depth_range_center_distance; });
+
+#ifdef DEBUG_OUTPUT_DIR
+ for (size_t i = patch_shapes.size(); i > 0; --i) {
+ const PatchShape &p = patch_shapes[i - 1];
+ int gray_level = (i * 200) / patch_shapes.size();
+ std::stringstream color;
+ color << "#" << std::hex << std::setfill('0') << std::setw(2) << gray_level << gray_level << gray_level;
+ svg.draw(p.shape, color.str());
+ Point text_pos = get_extents(p.shape).center().cast();
+ svg.draw_text(text_pos, std::to_string(i-1).c_str(), "orange", std::ceil(shape_bb.size().x() / 20 * 0.000001));
+ //svg.draw(p.intersection, color.str());
+ }
+#endif // DEBUG_OUTPUT_DIR
+
+ for (const PatchShape &patch : patch_shapes) {
+ // Check when exist some place to fill
+ ExPolygons patch_intersection = intersection_ex(patch.intersection, rest);
+ if (patch_intersection.empty()) continue;
+
+ // Extend for sure
+ ExPolygons intersection = offset_ex(patch.intersection, extend_delta);
+ rest = diff_ex(rest, intersection, ApplySafetyOffset::Yes);
+
+ used_shape_patches.push_back(patch.patch_index);
+ if (rest.empty()) break;
+ }
+
+ // QUESTION: How to select which patch to use? How to sort them?
+ // Now is used back projection distance from used patches
+ //
+ // Idealy by outline depth: (need ray cast into patches)
+ // how to calc wanted depth - idealy by depth of outline help to overlap
+ // how to calc patch depth - depth in place of outline position
+ // Which outline to use between
+
+ }
+
+ std::vector result(patches.size(), {false});
+ for (const std::vector &patches: used_shapes_patches)
+ for (uint32_t patch_index : patches) {
+ assert(patch_index < result.size());
+ // check only onece insertation of patch
+ assert(!result[patch_index]);
+ result[patch_index] = true;
+ }
+ return result;
+}
+
+priv::Loops priv::create_loops(const std::vector &outlines, const CutMesh& mesh)
+{
+ Loops loops;
+ Loops unclosed;
+ for (HI hi : outlines) {
+ VI vi_s = mesh.source(hi);
+ VI vi_t = mesh.target(hi);
+ Loop *loop_move = nullptr;
+ Loop *loop_connect = nullptr;
+ for (std::vector &cut : unclosed) {
+ if (cut.back() != vi_s) continue;
+ if (cut.front() == vi_t) {
+ // cut closing
+ loop_move = &cut;
+ } else {
+ loop_connect = &cut;
+ }
+ break;
+ }
+ if (loop_move != nullptr) {
+ // index of closed cut
+ size_t index = loop_move - &unclosed.front();
+ // move cut to result
+ loops.emplace_back(std::move(*loop_move));
+ // remove it from unclosed cut
+ unclosed.erase(unclosed.begin() + index);
+ } else if (loop_connect != nullptr) {
+ // try find tail to connect cut
+ Loop *loop_tail = nullptr;
+ for (Loop &cut : unclosed) {
+ if (cut.front() != vi_t) continue;
+ loop_tail = &cut;
+ break;
+ }
+ if (loop_tail != nullptr) {
+ // index of tail
+ size_t index = loop_tail - &unclosed.front();
+ // move to connect vector
+ loop_connect->insert(loop_connect->end(),
+ make_move_iterator(loop_tail->begin()),
+ make_move_iterator(loop_tail->end()));
+ // remove tail from unclosed cut
+ unclosed.erase(unclosed.begin() + index);
+ } else {
+ loop_connect->push_back(vi_t);
+ }
+ } else { // not found
+ bool create_cut = true;
+ // try to insert to front of cut
+ for (Loop &cut : unclosed) {
+ if (cut.front() != vi_t) continue;
+ cut.insert(cut.begin(), vi_s);
+ create_cut = false;
+ break;
+ }
+ if (create_cut)
+ unclosed.emplace_back(std::vector{vi_s, vi_t});
+ }
+ }
+ assert(unclosed.empty());
+ return loops;
+}
+
+Polygons priv::unproject_loops(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range)
+{
+ assert(!patch.loops.empty());
+ if (patch.loops.empty()) return {};
+
+ // NOTE: this method is working only when patch did not contain outward faces
+ Polygons polys;
+ polys.reserve(patch.loops.size());
+ // project conture into 2d space to fillconvert outlines to
+
+ size_t count = 0;
+ for (const Loop &l : patch.loops) count += l.size();
+ std::vector depths;
+ depths.reserve(count);
+
+ Points pts;
+ for (const Loop &l : patch.loops) {
+ pts.clear();
+ pts.reserve(l.size());
+ for (VI vi : l) {
+ const P3 &p3 = patch.mesh.point(vi);
+ Vec3d p = to_vec3d(p3);
+ double depth;
+ std::optional p2_opt = projection.unproject(p, &depth);
+ if (depth_range[0] > depth) depth_range[0] = depth; // min
+ if (depth_range[1] < depth) depth_range[1] = depth; // max
+ // Check when appear that skip is enough for poit which can't be unprojected
+ // - it could break contour
+ assert(p2_opt.has_value());
+ if (!p2_opt.has_value()) continue;
+
+ pts.push_back(p2_opt->cast());
+ depths.push_back(static_cast(depth));
+ }
+ // minimal is triangle
+ assert(pts.size() >= 3);
+ if (pts.size() < 3) continue;
+
+ polys.emplace_back(pts);
+ }
+
+ assert(!polys.empty());
+ return polys;
+}
+
+ExPolygon priv::to_expoly(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range)
+{
+ Polygons polys = unproject_loops(patch, projection, depth_range);
+ // should not be used when no opposit triangle are counted so should not create overlaps
+ ClipperLib::PolyFillType fill_type = ClipperLib::PolyFillType::pftEvenOdd;
+ ExPolygons expolys = Slic3r::union_ex(polys, fill_type);
+ if (expolys.size() == 1)
+ return expolys.front();
+
+ // It should be one expolygon
+ assert(false);
+
+ if (expolys.empty()) return {};
+ // find biggest
+ const ExPolygon *biggest = &expolys.front();
+ for (size_t index = 1; index < expolys.size(); ++index) {
+ const ExPolygon *current = &expolys[index];
+ if (biggest->contour.size() < current->contour.size())
+ biggest = current;
+ }
+ return *biggest;
+}
+
+SurfaceCut priv::patch2cut(SurfacePatch &patch)
+{
+ CutMesh &mesh = patch.mesh;
+
+ std::string convert_map_name = "v:convert";
+ CutMesh::Property_map convert_map =
+ mesh.add_property_map(convert_map_name).first;
+
+ size_t indices_size = mesh.faces().size();
+ size_t vertices_size = mesh.vertices().size();
+
+ SurfaceCut sc;
+ sc.indices.reserve(indices_size);
+ sc.vertices.reserve(vertices_size);
+ for (VI vi : mesh.vertices()) {
+ // vi order is is not sorted
+ // assert(vi.idx() == sc.vertices.size());
+ // vi is not continous
+ // assert(vi.idx() < vertices_size);
+ convert_map[vi] = sc.vertices.size();
+ const P3 &p = mesh.point(vi);
+ sc.vertices.emplace_back(p.x(), p.y(), p.z());
+ }
+
+ for (FI fi : mesh.faces()) {
+ HI hi = mesh.halfedge(fi);
+ assert(mesh.next(hi).is_valid());
+ assert(mesh.next(mesh.next(hi)).is_valid());
+ // Is fi triangle?
+ assert(mesh.next(mesh.next(mesh.next(hi))) == hi);
+
+ // triangle indicies
+ Vec3i ti;
+ size_t i = 0;
+ for (VI vi : { mesh.source(hi),
+ mesh.target(hi),
+ mesh.target(mesh.next(hi))})
+ ti[i++] = convert_map[vi];
+ sc.indices.push_back(ti);
+ }
+
+ sc.contours.reserve(patch.loops.size());
+ for (const Loop &loop : patch.loops) {
+ sc.contours.push_back({});
+ std::vector &contour = sc.contours.back();
+ contour.reserve(loop.size());
+ for (VI vi : loop) contour.push_back(convert_map[vi]);
+ }
+
+ // Not neccessary, clean and free memory
+ mesh.remove_property_map(convert_map);
+ return sc;
+}
+
+void priv::append(SurfaceCut &sc, SurfaceCut &&sc_add)
+{
+ if (sc.empty()) {
+ sc = std::move(sc_add);
+ return;
+ }
+
+ if (!sc_add.contours.empty()) {
+ SurfaceCut::Index offset = static_cast(
+ sc.vertices.size());
+ size_t require = sc.contours.size() + sc_add.contours.size();
+ if (sc.contours.capacity() < require) sc.contours.reserve(require);
+ for (std::vector &cut : sc_add.contours)
+ for (SurfaceCut::Index &i : cut) i += offset;
+ Slic3r::append(sc.contours, std::move(sc_add.contours));
+ }
+ its_merge(sc, std::move(sc_add));
+}
+
+SurfaceCut priv::merge_patches(SurfacePatches &patches, const std::vector& mask)
+{
+ SurfaceCut result;
+ for (SurfacePatch &patch : patches) {
+ size_t index = &patch - &patches.front();
+ if (!mask[index]) continue;
+ append(result, patch2cut(patch));
+ }
+ return result;
+}
+
+#ifdef DEBUG_OUTPUT_DIR
+void priv::prepare_dir(const std::string &dir){
+ namespace fs = std::filesystem;
+ if (fs::exists(dir)) {
+ for (auto &path : fs::directory_iterator(dir)) fs::remove_all(path);
+ } else {
+ fs::create_directories(dir);
+ }
+}
+
+namespace priv{
+int reduction_order = 0;
+int filled_order = 0;
+int constrained_order = 0;
+int diff_patch_order = 0;
+
+} // namespace priv
+
+void priv::initialize_store(const std::string& dir)
+{
+ // clear previous output
+ prepare_dir(dir);
+ reduction_order = 0;
+ filled_order = 0;
+ constrained_order = 0;
+ diff_patch_order = 0;
+}
+
+void priv::store(const Vec3f &vertex,
+ const Vec3f &normal,
+ const std::string &file,
+ float size)
+{
+ int flatten = 20;
+ size_t min_i = 0;
+ for (size_t i = 1; i < 3; i++)
+ if (normal[min_i] > normal[i]) min_i = i;
+ Vec3f up_ = Vec3f::Zero();
+ up_[min_i] = 1.f;
+ Vec3f side = normal.cross(up_).normalized() * size;
+ Vec3f up = side.cross(normal).normalized() * size;
+
+ indexed_triangle_set its;
+ its.vertices.reserve(flatten + 1);
+ its.indices.reserve(flatten);
+
+ its.vertices.push_back(vertex);
+ its.vertices.push_back(vertex + up);
+ size_t max_i = static_cast(flatten);
+ for (size_t i = 1; i < max_i; i++) {
+ float angle = i * 2 * M_PI / flatten;
+ Vec3f v = vertex + sin(angle) * side + cos(angle) * up;
+ its.vertices.push_back(v);
+ its.indices.emplace_back(0, i, i + 1);
+ }
+ its.indices.emplace_back(0, flatten, 1);
+ its_write_obj(its, file.c_str());
+}
+
+void priv::store(const CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string& dir, bool is_filled)
+{
+ std::string off_file;
+ if (is_filled) {
+ if (filled_order == 0) prepare_dir(dir);
+ off_file = dir + "model" + std::to_string(filled_order++) + ".off";
+ }else{
+ if (constrained_order == 0) prepare_dir(dir);
+ off_file = dir + "model" + std::to_string(constrained_order++) + ".off";
+ }
+
+ CutMesh &mesh_ = const_cast(mesh);
+ auto face_colors = mesh_.add_property_map("f:color").first;
+ for (FI fi : mesh.faces()) {
+ auto &color = face_colors[fi];
+ switch (face_type_map[fi]) {
+ case FaceType::inside: color = CGAL::Color{100, 250, 100}; break; // light green
+ case FaceType::inside_processed: color = CGAL::Color{170, 0, 0}; break; // dark red
+ case FaceType::outside: color = CGAL::Color{100, 0, 100}; break; // purple
+ case FaceType::not_constrained: color = CGAL::Color{127, 127, 127}; break; // gray
+ default: color = CGAL::Color{0, 0, 255}; // blue
+ }
+ }
+ CGAL::IO::write_OFF(off_file, mesh, CGAL::parameters::face_color_map(face_colors));
+ mesh_.remove_property_map(face_colors);
+}
+
+void priv::store(const ExPolygons &shapes, const std::string &svg_file) {
+ SVG svg(svg_file);
+ svg.draw(shapes);
+}
+
+void priv::store(const CutMesh &mesh, const ReductionMap &reduction_map, const std::string& dir)
+{
+ if (reduction_order == 0) prepare_dir(dir);
+ std::string off_file = dir + "model" + std::to_string(reduction_order++) + ".off";
+
+ CutMesh &mesh_ = const_cast(mesh);
+ auto vertex_colors = mesh_.add_property_map("v:color").first;
+ // initialize to gray color
+ for (VI vi: mesh.vertices())
+ vertex_colors[vi] = CGAL::Color{127, 127, 127};
+
+ for (VI reduction_from : mesh.vertices()) {
+ VI reduction_to = reduction_map[reduction_from];
+ if (!reduction_to.is_valid()) continue;
+ vertex_colors[reduction_from] = CGAL::Color{255, 0, 0};
+ vertex_colors[reduction_to] = CGAL::Color{0, 0, 255};
+ }
+
+ CGAL::IO::write_OFF(off_file, mesh, CGAL::parameters::vertex_color_map(vertex_colors));
+ mesh_.remove_property_map(vertex_colors);
+}
+
+namespace priv {
+indexed_triangle_set create_indexed_triangle_set(const std::vector &faces,
+ const CutMesh &mesh);
+} // namespace priv
+
+indexed_triangle_set priv::create_indexed_triangle_set(
+ const std::vector &faces, const CutMesh &mesh)
+{
+ std::vector