diff --git a/resources/images/add_text_modifier.svg b/resources/images/add_text_modifier.svg
new file mode 100644
index 000000000..efb21d925
--- /dev/null
+++ b/resources/images/add_text_modifier.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/add_text_negative.svg b/resources/images/add_text_negative.svg
new file mode 100644
index 000000000..a59209746
--- /dev/null
+++ b/resources/images/add_text_negative.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/add_text_part.svg b/resources/images/add_text_part.svg
new file mode 100644
index 000000000..d36ff970c
--- /dev/null
+++ b/resources/images/add_text_part.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/menu_obj_svg.svg b/resources/images/menu_obj_svg.svg
new file mode 100644
index 000000000..039ac65d9
--- /dev/null
+++ b/resources/images/menu_obj_svg.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/menu_obj_text.svg b/resources/images/menu_obj_text.svg
new file mode 100644
index 000000000..48661b30f
--- /dev/null
+++ b/resources/images/menu_obj_text.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/svg_modifier.svg b/resources/images/svg_modifier.svg
new file mode 100644
index 000000000..74548e964
--- /dev/null
+++ b/resources/images/svg_modifier.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/svg_negative.svg b/resources/images/svg_negative.svg
new file mode 100644
index 000000000..7845267e2
--- /dev/null
+++ b/resources/images/svg_negative.svg
@@ -0,0 +1 @@
+
\ 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..b75b85bd8
--- /dev/null
+++ b/resources/images/svg_part.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/text_bake.svg b/resources/images/text_bake.svg
new file mode 100644
index 000000000..4d89b3657
--- /dev/null
+++ b/resources/images/text_bake.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/text_lock_closed.svg b/resources/images/text_lock_closed.svg
new file mode 100644
index 000000000..ba2bf7b4a
--- /dev/null
+++ b/resources/images/text_lock_closed.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/text_lock_open.svg b/resources/images/text_lock_open.svg
new file mode 100644
index 000000000..12f420274
--- /dev/null
+++ b/resources/images/text_lock_open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/text_obj_warning.svg b/resources/images/text_obj_warning.svg
new file mode 100644
index 000000000..239e6b725
--- /dev/null
+++ b/resources/images/text_obj_warning.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/text_open.svg b/resources/images/text_open.svg
new file mode 100644
index 000000000..466eace4d
--- /dev/null
+++ b/resources/images/text_open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/text_reflection_x.svg b/resources/images/text_reflection_x.svg
new file mode 100644
index 000000000..4a9401389
--- /dev/null
+++ b/resources/images/text_reflection_x.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/text_reflection_y.svg b/resources/images/text_reflection_y.svg
new file mode 100644
index 000000000..55d357df6
--- /dev/null
+++ b/resources/images/text_reflection_y.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/text_refresh.svg b/resources/images/text_refresh.svg
new file mode 100644
index 000000000..b25d53312
--- /dev/null
+++ b/resources/images/text_refresh.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/text_save.svg b/resources/images/text_save.svg
new file mode 100644
index 000000000..ba542737f
--- /dev/null
+++ b/resources/images/text_save.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/text_undo.svg b/resources/images/text_undo.svg
new file mode 100644
index 000000000..763a86ff7
--- /dev/null
+++ b/resources/images/text_undo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/imgui/CMakeLists.txt b/src/imgui/CMakeLists.txt
index 213b3b830..592e78c8e 100644
--- a/src/imgui/CMakeLists.txt
+++ b/src/imgui/CMakeLists.txt
@@ -13,6 +13,8 @@ add_library(imgui STATIC
imgui_demo.cpp
imgui_draw.cpp
imgui_widgets.cpp
+ imgui_stdlib.cpp
+ imgui_stdlib.h
)
if(Boost_FOUND)
diff --git a/src/imgui/imgui_draw.cpp b/src/imgui/imgui_draw.cpp
index 913a551fa..619e41c69 100644
--- a/src/imgui/imgui_draw.cpp
+++ b/src/imgui/imgui_draw.cpp
@@ -2905,7 +2905,7 @@ static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short*
//-------------------------------------------------------------------------
// [SECTION] ImFontAtlas glyph ranges helpers
//-------------------------------------------------------------------------
-const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() // used in bold_font only
+const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() // used in bold_font only
{
// Store 2500 regularly used characters for Simplified Chinese.
// Sourced from https://zh.wiktionary.org/wiki/%E9%99%84%E5%BD%95:%E7%8E%B0%E4%BB%A3%E6%B1%89%E8%AF%AD%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8
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 be6b07664..4c418437c 100644
--- a/src/imgui/imgui_widgets.cpp
+++ b/src/imgui/imgui_widgets.cpp
@@ -3070,7 +3070,7 @@ bool ImGui::BBLDragFloat(const char *label, float *v, float v_speed, float v_min
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.00f, 0.68f, 0.26f, 0.00f));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.00f, 0.68f, 0.26f, 0.00f));
bool bbl_drag_scalar = BBLDragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);
- if (v_max > v_min + 0.001) {
+ if (v_max > v_min + 0.001) {
*v = std::clamp(*v, v_min, v_max);
}
ImGui::PopStyleColor(3);
@@ -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/imgui/imstb_truetype.h b/src/imgui/imstb_truetype.h
index c7a2d5a5e..c35d7456d 100644
--- a/src/imgui/imstb_truetype.h
+++ b/src/imgui/imstb_truetype.h
@@ -51,7 +51,7 @@
// Rob Loach Cort Stratton
// Kenney Phillis Jr. github:oyvindjam
// Brian Costabile github:vassvik
-//
+//
// VERSION HISTORY
//
// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics()
@@ -212,7 +212,7 @@
//
// Advancing for the next character:
// Call GlyphHMetrics, and compute 'current_point += SF * advance'.
-//
+//
//
// ADVANCED USAGE
//
@@ -257,7 +257,7 @@
// Curve tessellation 120 LOC \__ 550 LOC Bitmap creation
// Bitmap management 100 LOC /
// Baked bitmap interface 70 LOC /
-// Font name matching & access 150 LOC ---- 150
+// Font name matching & access 150 LOC ---- 150
// C runtime library abstraction 60 LOC ---- 60
//
//
@@ -350,7 +350,7 @@ int main(int argc, char **argv)
}
return 0;
}
-#endif
+#endif
//
// Output:
//
@@ -364,9 +364,9 @@ int main(int argc, char **argv)
// :@@. M@M
// @@@o@@@@
// :M@@V:@@.
-//
+//
//////////////////////////////////////////////////////////////////////////////
-//
+//
// Complete program: print "Hello World!" banner, with bugs
//
#if 0
@@ -500,11 +500,11 @@ int main(int arg, char **argv)
#ifndef __STB_INCLUDE_STB_TRUETYPE_H__
#define __STB_INCLUDE_STB_TRUETYPE_H__
-//#ifdef STBTT_STATIC
-//#define STBTT_DEF static
-//#else
+#ifdef STBTT_STATIC
+#define STBTT_DEF static
+#else
#define STBTT_DEF extern
-//#endif
+#endif
#ifdef __cplusplus
extern "C" {
@@ -667,7 +667,7 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, cons
// Calling these functions in sequence is roughly equivalent to calling
// stbtt_PackFontRanges(). If you more control over the packing of multiple
// fonts, or if you want to pack custom data into a font texture, take a look
-// at the source to of stbtt_PackFontRanges() and create a custom version
+// at the source to of stbtt_PackFontRanges() and create a custom version
// using these functions, e.g. call GatherRects multiple times,
// building up a single array of rects, then call PackRects once,
// then call RenderIntoRects repeatedly. This may result in a
@@ -975,7 +975,7 @@ STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, floa
// and computing from that can allow drop-out prevention).
//
// The algorithm has not been optimized at all, so expect it to be slow
-// if computing lots of characters or very large sizes.
+// if computing lots of characters or very large sizes.
@@ -1741,7 +1741,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s
if (i != 0)
num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
- // now start the new one
+ // now start the new one
start_off = !(flags & 1);
if (start_off) {
// if we start off with an off-curve point, then when we need to find a point on the curve
@@ -1794,7 +1794,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s
int comp_num_verts = 0, i;
stbtt_vertex *comp_verts = 0, *tmp = 0;
float mtx[6] = {1,0,0,1,0,0}, m, n;
-
+
flags = ttSHORT(comp); comp+=2;
gidx = ttSHORT(comp); comp+=2;
@@ -1824,7 +1824,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s
mtx[2] = ttSHORT(comp)/16384.0f; comp+=2;
mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;
}
-
+
// Find transformation scales.
m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]);
n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]);
@@ -2755,7 +2755,7 @@ static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, i
float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
STBTT_assert(z != NULL);
if (!z) return z;
-
+
// round dx down to avoid overshooting
if (dxdy < 0)
z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy);
@@ -2833,7 +2833,7 @@ static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__ac
}
}
}
-
+
e = e->next;
}
}
@@ -3563,7 +3563,7 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info
{
int ix0,iy0,ix1,iy1;
stbtt__bitmap gbm;
- stbtt_vertex *vertices;
+ stbtt_vertex *vertices;
int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
if (scale_x == 0) scale_x = scale_y;
@@ -3586,7 +3586,7 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info
if (height) *height = gbm.h;
if (xoff ) *xoff = ix0;
if (yoff ) *yoff = iy0;
-
+
if (gbm.w && gbm.h) {
gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata);
if (gbm.pixels) {
@@ -3597,7 +3597,7 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info
}
STBTT_free(vertices, info->userdata);
return gbm.pixels;
-}
+}
STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff)
{
@@ -3609,7 +3609,7 @@ STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigne
int ix0,iy0;
stbtt_vertex *vertices;
int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
- stbtt__bitmap gbm;
+ stbtt__bitmap gbm;
stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0);
gbm.pixels = output;
@@ -3631,7 +3631,7 @@ STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *
STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff)
{
return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff);
-}
+}
STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint)
{
@@ -3646,7 +3646,7 @@ STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, uns
STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff)
{
return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff);
-}
+}
STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint)
{
@@ -3771,7 +3771,7 @@ static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *no
con->y = 0;
con->bottom_y = 0;
STBTT__NOTUSED(nodes);
- STBTT__NOTUSED(num_nodes);
+ STBTT__NOTUSED(num_nodes);
}
static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects)
@@ -4156,7 +4156,7 @@ STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char
n = 0;
for (i=0; i < num_ranges; ++i)
n += ranges[i].num_chars;
-
+
rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context);
if (rects == NULL)
return 0;
@@ -4167,7 +4167,7 @@ STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char
n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects);
stbtt_PackFontRangesPackRects(spc, rects, n);
-
+
return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects);
STBTT_free(rects, spc->user_allocator_context);
@@ -4328,7 +4328,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;
- if (x_inter < x)
+ if (x_inter < x)
winding += (y0 < y1) ? 1 : -1;
}
}
@@ -4354,7 +4354,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
y1 = (int)verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;
- if (x_inter < x)
+ if (x_inter < x)
winding += (y0 < y1) ? 1 : -1;
}
} else {
@@ -4366,7 +4366,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
if (hits[1][0] < 0)
winding += (hits[1][1] < 0 ? -1 : 1);
}
- }
+ }
}
}
return winding;
@@ -4447,7 +4447,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
// invert for y-downwards bitmaps
scale_y = -scale_y;
-
+
{
int x,y,i,j;
float *precompute;
@@ -4596,7 +4596,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
STBTT_free(verts, info->userdata);
}
return data;
-}
+}
STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff)
{
@@ -4614,7 +4614,7 @@ STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata)
//
// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string
-static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2)
+static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2)
{
stbtt_int32 i=0;
@@ -4653,7 +4653,7 @@ static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, s
return i;
}
-static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2)
+static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2)
{
return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2);
}
@@ -4782,7 +4782,7 @@ STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset,
STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index)
{
- return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index);
+ return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index);
}
STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data)
@@ -4875,38 +4875,38 @@ This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
+The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
-Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
-software, either in source code form or as a compiled binary, for any purpose,
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
-In jurisdictions that recognize copyright laws, the author or authors of this
-software dedicate any and all copyright interest in the software to the public
-domain. We make this dedication for the benefit of the public at large and to
-the detriment of our heirs and successors. We intend this dedication to be an
-overt act of relinquishment in perpetuity of all present and future rights to
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp
index ae8eae2ca..d1dd4acba 100644
--- a/src/libslic3r/AppConfig.hpp
+++ b/src/libslic3r/AppConfig.hpp
@@ -80,6 +80,7 @@ public:
{ std::string value; this->get(section, key, value); return value; }
std::string get(const std::string &key) const
{ std::string value; this->get("app", key, value); return value; }
+ bool get_bool(const std::string &key) const { return this->get(key) == "true" || this->get(key) == "1"; }
void set(const std::string §ion, const std::string &key, const std::string &value)
{
#ifndef NDEBUG
diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp
index 58cbdd688..22767622f 100644
--- a/src/libslic3r/BoundingBox.hpp
+++ b/src/libslic3r/BoundingBox.hpp
@@ -16,9 +16,9 @@ public:
PointClass min;
PointClass max;
bool defined;
-
+
BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {}
- BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) :
+ BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) :
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
@@ -27,7 +27,7 @@ public:
template>
BoundingBoxBase(It from, It to)
{
- construct(*this, from, to);
+ construct(*this, from, to);
}
BoundingBoxBase(const std::vector &points)
@@ -107,8 +107,8 @@ class BoundingBox3Base : public BoundingBoxBase
{
public:
BoundingBox3Base() : BoundingBoxBase() {}
- BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
- BoundingBoxBase(pmin, pmax)
+ BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
+ BoundingBoxBase(pmin, pmax)
{ if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; }
BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
BoundingBoxBase(p1, p1) { merge(p2); merge(p3); }
@@ -206,7 +206,7 @@ public:
// Align the min corner to a grid of cell_size x cell_size cells,
// to encompass the original bounding box.
void align_to_grid(const coord_t cell_size);
-
+
BoundingBox() : BoundingBoxBase() {}
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {}
BoundingBox(const Points &points) : BoundingBoxBase(points) {}
@@ -216,7 +216,9 @@ public:
friend BoundingBox get_extents_rotated(const Points &points, double angle);
};
-class BoundingBox3 : public BoundingBox3Base
+using BoundingBoxes = std::vector;
+
+class BoundingBox3 : public BoundingBox3Base
{
public:
BoundingBox3() : BoundingBox3Base() {}
@@ -224,7 +226,7 @@ public:
BoundingBox3(const Points3& points) : BoundingBox3Base(points) {}
};
-class BoundingBoxf : public BoundingBoxBase
+class BoundingBoxf : public BoundingBoxBase
{
public:
BoundingBoxf() : BoundingBoxBase() {}
@@ -232,7 +234,7 @@ public:
BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {}
};
-class BoundingBoxf3 : public BoundingBox3Base
+class BoundingBoxf3 : public BoundingBox3Base
{
public:
using BoundingBox3Base::BoundingBox3Base;
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 82d011119..1751e25bd 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -60,9 +60,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
MutablePriorityQueue.hpp
ObjectID.cpp
ObjectID.hpp
+ NSVGUtils.cpp
+ NSVGUtils.hpp
ParameterUtils.cpp
ParameterUtils.hpp
PerimeterGenerator.cpp
@@ -316,6 +324,8 @@ set(lisbslic3r_sources
Utils.hpp
Time.cpp
Time.hpp
+ Timer.cpp
+ Timer.hpp
Thread.cpp
Thread.hpp
TriangleSelector.cpp
@@ -426,7 +436,7 @@ if (APPLE)
)
endif ()
-add_library(libslic3r STATIC ${lisbslic3r_sources}
+add_library(libslic3r STATIC ${lisbslic3r_sources}
"${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h"
${OpenVDBUtils_SOURCES})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${lisbslic3r_sources})
@@ -442,8 +452,12 @@ find_package(CGAL REQUIRED)
find_package(OpenCV REQUIRED core)
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 2845f934e..6644ede98 100644
--- a/src/libslic3r/ClipperUtils.cpp
+++ b/src/libslic3r/ClipperUtils.cpp
@@ -130,7 +130,7 @@ template [[nodiscard]] std::vector clip_clipper_p
[[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox) { return clip_clipper_polygon_with_subject_bbox_templ(src, bbox); }
[[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox) { return clip_clipper_polygon_with_subject_bbox_templ(src, bbox); }
-void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out) {
+void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out) {
clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points);
}
@@ -176,7 +176,7 @@ static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree)
{
struct Inner {
static void PolyTreeToExPolygonsRecursive(ClipperLib::PolyNode &&polynode, ExPolygons *expolygons)
- {
+ {
size_t cnt = expolygons->size();
expolygons->resize(cnt + 1);
(*expolygons)[cnt].contour.points = std::move(polynode.Contour);
@@ -331,7 +331,7 @@ TResult clipper_do(
{
// Safety offset only allowed on intersection and difference.
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
- return do_safety_offset == ApplySafetyOffset::Yes ?
+ return do_safety_offset == ApplySafetyOffset::Yes ?
clipper_do(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) :
clipper_do(clipType, std::forward(subject), std::forward(clip), fillType);
}
@@ -420,7 +420,20 @@ Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, Cli
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))); }
-// returns number of expolygons collected (0 or 1).
+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)
{
// 1) Offset the outer contour.
@@ -468,8 +481,8 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
// No hole remaining after an offset. Just copy the outer contour.
append(out, std::move(contours));
} else if (delta < 0) {
- // Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
- // Subtract the offsetted holes from the offsetted contours.
+ // Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
+ // Subtract the offsetted holes from the offsetted contours.
if (auto output = clipper_do(ClipperLib::ctDifference, contours, holes, ClipperLib::pftNonZero); ! output.empty()) {
append(out, std::move(output));
} else {
@@ -539,7 +552,7 @@ template
static ClipperLib::PolyTree expolygons_offset_pt(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{
auto [output, expolygons_collected] = expolygons_offset_raw(expolygons, delta, joinType, miterLimit);
- // Unite the offsetted expolygons for both the
+ // Unite the offsetted expolygons for both the
return clipper_union(output);
}
@@ -648,7 +661,7 @@ inline ClipperLib::PolyTree clipper_do_polytree(
const ApplySafetyOffset do_safety_offset)
{
assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion);
- return do_safety_offset == ApplySafetyOffset::Yes ?
+ return do_safety_offset == ApplySafetyOffset::Yes ?
clipper_do_polytree(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) :
clipper_do_polytree(clipType, std::forward(subject), std::forward(clip), fillType);
}
@@ -663,9 +676,9 @@ Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &cli
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
-Slic3r::Polygons diff_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
+Slic3r::Polygons diff_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return diff(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); }
-Slic3r::ExPolygons diff_clipped(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
+Slic3r::ExPolygons diff_clipped(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return diff_ex(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); }
Slic3r::ExPolygons diff_clipped(const Slic3r::ExPolygons & subject, const Slic3r::ExPolygons & clip, ApplySafetyOffset do_safety_offset)
{
@@ -681,7 +694,7 @@ Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &c
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
-Slic3r::Polygons intersection_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
+Slic3r::Polygons intersection_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return intersection(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); }
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); }
@@ -801,7 +814,12 @@ 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::Surfaces &subject)
+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
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPolygons& poly2, bool safety_offset_)
@@ -843,14 +861,14 @@ static void _clipper_pl_recombine(Polylines &polylines)
polylines.erase(polylines.begin() + j);
--j;
} else if (polylines[i].points.front() == polylines[j].points.front()) {
- /* Since Clipper does not preserve orientation of polylines,
+ /* Since Clipper does not preserve orientation of polylines,
also check the case when first point of i coincides with first point of j. */
polylines[j].reverse();
polylines[i].points.insert(polylines[i].points.begin(), polylines[j].points.begin(), polylines[j].points.end()-1);
polylines.erase(polylines.begin() + j);
--j;
} else if (polylines[i].points.back() == polylines[j].points.back()) {
- /* Since Clipper does not preserve orientation of polylines,
+ /* Since Clipper does not preserve orientation of polylines,
also check the case when last point of i coincides with last point of j. */
polylines[j].reverse();
polylines[i].points.insert(polylines[i].points.end(), polylines[j].points.begin()+1, polylines[j].points.end());
@@ -919,10 +937,10 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol
polylines.reserve(subject.size());
for (const Line &line : subject)
polylines.emplace_back(Polyline(line.a, line.b));
-
+
// perform operation
polylines = _clipper_pl_open(clipType, ClipperUtils::PolylinesProvider(polylines), ClipperUtils::PolygonsProvider(clip));
-
+
// convert Polylines to Lines
Lines retval;
for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline)
@@ -949,7 +967,7 @@ ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes)
// collect ordering points
Points ordering_points;
ordering_points.reserve(nodes.size());
-
+
for (const ClipperLib::PolyNode *node : nodes)
ordering_points.emplace_back(
Point(node->Contour.front().x(), node->Contour.front().y()));
@@ -963,7 +981,7 @@ ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes)
static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *out)
{
- foreach_node(nodes, [&out](const ClipperLib::PolyNode *node)
+ foreach_node(nodes, [&out](const ClipperLib::PolyNode *node)
{
traverse_pt_noholes(node->Childs, out);
out->emplace_back(node->Contour);
@@ -983,7 +1001,7 @@ static void traverse_pt_outside_in(ClipperLib::PolyNodes &&nodes, Polygons *retv
//FIXME pass the last point to chain_clipper_polynodes?
for (ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) {
retval->emplace_back(std::move(node->Contour));
- if (node->IsHole())
+ if (node->IsHole())
// Orient a hole, which is clockwise oriented, to CCW.
retval->back().reverse();
// traverse the next depth
@@ -1010,7 +1028,7 @@ Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear)
} else {
output = ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(subject), ClipperLib::pftNonZero);
}
-
+
// convert into Slic3r polygons
return to_polygons(std::move(output));
}
@@ -1020,13 +1038,13 @@ ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear
if (! preserve_collinear)
return union_ex(simplify_polygons(subject, false));
- ClipperLib::PolyTree polytree;
+ ClipperLib::PolyTree polytree;
ClipperLib::Clipper c;
c.PreserveCollinear(true);
c.StrictlySimple(true);
c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true);
c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
-
+
// convert into ExPolygons
return PolyTreeToExPolygons(std::move(polytree));
}
@@ -1039,7 +1057,7 @@ Polygons top_level_islands(const Slic3r::Polygons &polygons)
// perform union
clipper.AddPaths(ClipperUtils::PolygonsProvider(polygons), ClipperLib::ptSubject, true);
ClipperLib::PolyTree polytree;
- clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd);
+ clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd);
// Convert only the top level islands to the output.
Polygons out;
out.reserve(polytree.ChildCount());
@@ -1050,7 +1068,7 @@ Polygons top_level_islands(const Slic3r::Polygons &polygons)
// Outer offset shall not split the input contour into multiples. It is expected, that the solution will be non empty and it will contain just a single polygon.
ClipperLib::Paths fix_after_outer_offset(
- const ClipperLib::Path &input,
+ const ClipperLib::Path &input,
// combination of default prameters to correspond to void ClipperOffset::Execute(Paths& solution, double delta)
// to produce a CCW output contour from CCW input contour for a positive offset.
ClipperLib::PolyFillType filltype, // = ClipperLib::pftPositive
@@ -1068,7 +1086,7 @@ ClipperLib::Paths fix_after_outer_offset(
// Inner offset may split the source contour into multiple contours, but one resulting contour shall not lie inside the other.
ClipperLib::Paths fix_after_inner_offset(
- const ClipperLib::Path &input,
+ const ClipperLib::Path &input,
// combination of default prameters to correspond to void ClipperOffset::Execute(Paths& solution, double delta)
// to produce a CCW output contour from CCW input contour for a negative offset.
ClipperLib::PolyFillType filltype, // = ClipperLib::pftNegative
@@ -1116,7 +1134,7 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v
// Clamp miter limit to 2.
miter_limit = (miter_limit > 2.) ? 2. / (miter_limit * miter_limit) : 0.5;
-
+
// perpenduclar vector
auto perp = [](const Vec2d &v) -> Vec2d { return Vec2d(v.y(), - v.x()); };
@@ -1244,7 +1262,7 @@ Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector 0.);
#endif /* NDEBUG */
@@ -1254,7 +1272,7 @@ Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector 0.);
#endif /* NDEBUG */
@@ -1347,7 +1365,7 @@ for (const std::vector& ds : deltas)
ExPolygons output;
if (holes.empty()) {
output.reserve(contours.size());
- for (ClipperLib::Path &path : contours)
+ for (ClipperLib::Path &path : contours)
output.emplace_back(std::move(path));
} else {
ClipperLib::Clipper clipper;
@@ -1393,7 +1411,7 @@ ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vectorcontour.points : m_it_expolygon->holes[m_idx_contour - 1].points; }
bool operator==(const iterator &rhs) const { return m_it_expolygon == rhs.m_it_expolygon && m_idx_contour == rhs.m_idx_contour; }
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
- iterator& operator++() {
+ iterator& operator++() {
if (++ m_idx_contour == m_it_expolygon->holes.size() + 1) {
++ m_it_expolygon;
m_idx_contour = 0;
}
return *this;
}
- const Points& operator++(int) {
+ const Points& operator++(int) {
const Points &out = **this;
++ (*this);
return out;
@@ -244,14 +244,14 @@ namespace ClipperUtils {
const Points& operator*() const { return (m_idx_contour == 0) ? m_it_surface->expolygon.contour.points : m_it_surface->expolygon.holes[m_idx_contour - 1].points; }
bool operator==(const iterator &rhs) const { return m_it_surface == rhs.m_it_surface && m_idx_contour == rhs.m_idx_contour; }
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
- iterator& operator++() {
+ iterator& operator++() {
if (++ m_idx_contour == m_it_surface->expolygon.holes.size() + 1) {
++ m_it_surface;
m_idx_contour = 0;
}
return *this;
}
- const Points& operator++(int) {
+ const Points& operator++(int) {
const Points &out = **this;
++ (*this);
return out;
@@ -285,14 +285,14 @@ namespace ClipperUtils {
const Points& operator*() const { return (m_idx_contour == 0) ? (*m_it_surface)->expolygon.contour.points : (*m_it_surface)->expolygon.holes[m_idx_contour - 1].points; }
bool operator==(const iterator &rhs) const { return m_it_surface == rhs.m_it_surface && m_idx_contour == rhs.m_idx_contour; }
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
- iterator& operator++() {
+ iterator& operator++() {
if (++ m_idx_contour == (*m_it_surface)->expolygon.holes.size() + 1) {
++ m_it_surface;
m_idx_contour = 0;
}
return *this;
}
- const Points& operator++(int) {
+ const Points& operator++(int) {
const Points &out = **this;
++ (*this);
return out;
@@ -313,7 +313,7 @@ namespace ClipperUtils {
size_t m_size;
};
-
+
// For ClipperLib with Z coordinates.
using ZPoint = Vec3i32;
using ZPoints = std::vector;
@@ -363,6 +363,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); }
@@ -374,18 +378,18 @@ Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons);
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons);
// Aliases for the various offset(...) functions, conveying the purpose of the offset.
-inline Slic3r::Polygons expand(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::Polygons expand(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset(polygon, delta, joinType, miterLimit); }
-inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); }
-inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, delta, joinType, miterLimit); }
// Input polygons for shrinking shall be "normalized": There must be no overlap / intersections between the input polygons.
-inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); }
-inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
-inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
// Wherever applicable, please use the opening() / closing() 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.
@@ -400,14 +404,14 @@ Slic3r::ExPolygons _clipper_ex(ClipperLib::ClipType clipType,
// Offset outside, then inside produces morphological closing. All deltas should be positive.
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
-inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return closing(polygons, delta, delta, joinType, miterLimit); }
Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
-inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return closing_ex(polygons, delta, delta, joinType, miterLimit); }
-inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(polygons, delta, - delta, joinType, miterLimit); }
-inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(surfaces, delta, - delta, joinType, miterLimit); }
// Offset inside, then outside produces morphological opening. All deltas should be positive.
@@ -415,15 +419,15 @@ inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const flo
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
-inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return opening(polygons, delta, delta, joinType, miterLimit); }
-inline Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return opening(expolygons, delta, delta, joinType, miterLimit); }
-inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ return opening(surfaces, delta, delta, joinType, miterLimit); }
-inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(polygons, - delta, delta, joinType, miterLimit); }
-inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
+inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset2_ex(surfaces, - delta, delta, joinType, miterLimit); }
Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip);
@@ -549,6 +553,7 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon
// 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);
@@ -578,7 +583,7 @@ template struct _foreach_node {
template struct _foreach_node {
void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn)
{
- for (auto &n : nodes) fn(n);
+ for (auto &n : nodes) fn(n);
}
};
@@ -587,7 +592,7 @@ template struct _foreach_node {
void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn)
{
auto ordered_nodes = order_nodes(nodes);
- for (auto &n : nodes) fn(n);
+ for (auto &n : nodes) fn(n);
}
};
@@ -604,10 +609,10 @@ template
void traverse_pt(const ClipperLib::PolyNode *tree, Polygons *out)
{
if (!tree) return; // terminates recursion
-
+
// Push the contour of the current level
out->emplace_back(tree->Contour);
-
+
// Do the recursion for all the children.
traverse_pt(tree->Childs, out);
}
@@ -623,21 +628,21 @@ void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *out)
traverse_pt(tree->Childs, out);
return;
}
-
+
ExPolygon level;
level.contour.points = tree->Contour;
-
- foreach_node(tree->Childs,
+
+ foreach_node(tree->Childs,
[out, &level] (const ClipperLib::PolyNode *node) {
-
- // Holes are collected here.
+
+ // Holes are collected here.
level.holes.emplace_back(node->Contour);
-
+
// By doing a recursion, a new level expoly is created with the contour
// and holes of the lower level. Doing this for all the childs.
traverse_pt(node->Childs, out);
- });
-
+ });
+
out->emplace_back(level);
}
diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp
new file mode 100644
index 000000000..1d74fcfc7
--- /dev/null
+++ b/src/libslic3r/CutSurface.cpp
@@ -0,0 +1,4082 @@
+#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
+#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 Vec3i32 &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 Vec3i32 &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];
+ auto 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