NEW:add interactive function after importing SVG
jira:STUDIO-7406 most of code is from PrusaSlicer and OrcaSlicer,thanks for Filip Sykala - NTB T15p(PrusaSlicer) and Noisyfox(OrcaSlicer) Port Emboss & SVG gizmo from PrusaSlicer (#2819) * Rework UI jobs to make them more understandable and flexible. Change-Id: I765c7658b0881869754bdb161d720e4cbb180c92 (cherry picked from commit 3cef4611793899fa0ac39cb4d3a3abed7270a8e9)
1
resources/images/add_text_modifier.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M6.5,14.5a1,1,0,0,1-1-1v-9h-4a1,1,0,0,1-1-1v-2a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v2a1,1,0,0,1-1,1h-4v2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="8.5" y1="8.5" x2="14.5" y2="14.5" style="fill:none;stroke:#009688;stroke-linejoin:round"/><line x1="14.5" y1="8.5" x2="8.5" y2="14.5" style="fill:none;stroke:#009688;stroke-linejoin:round"/><line x1="11.5" y1="8.5" x2="11.5" y2="14.5" style="fill:none;stroke:#009688;stroke-linecap:square;stroke-linejoin:round"/><line x1="14.5" y1="11.5" x2="8.5" y2="11.5" style="fill:none;stroke:#009688;stroke-linecap:square;stroke-linejoin:round"/></svg>
|
After Width: | Height: | Size: 721 B |
1
resources/images/add_text_negative.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M6.5,14.5a1,1,0,0,1-1-1v-9h-4a1,1,0,0,1-1-1v-2a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v2a1,1,0,0,1-1,1h-4v2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M15,11.5A3.5,3.5,0,1,1,11.5,8,3.5,3.5,0,0,1,15,11.5ZM14,11H9v1h5Z" style="fill:#009688"/></svg>
|
After Width: | Height: | Size: 373 B |
1
resources/images/add_text_part.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M6.5,14.5a1,1,0,0,1-1-1v-9h-4a1,1,0,0,1-1-1v-2a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1v2a1,1,0,0,1-1,1h-4v2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M15,11.5A3.5,3.5,0,1,1,11.5,8,3.5,3.5,0,0,1,15,11.5ZM14,11H12V9H11v2H9v1h2v2h1V12h2Z" style="fill:#009688"/></svg>
|
After Width: | Height: | Size: 392 B |
1
resources/images/menu_obj_svg.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><line x1="2.5" y1="1.5" x2="12.5" y2="1.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round;opacity:0.5"/><path d="M1.541,12.5A12,12,0,0,1,12.5,1.541" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="12.5" y="0.5" width="2" height="2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="0.5" y="12.5" width="2" height="2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><circle cx="1.5" cy="1.5" r="1.5" style="fill:#949494"/></svg>
|
After Width: | Height: | Size: 641 B |
1
resources/images/menu_obj_text.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M6.5,14.5h2a1,1,0,0,0,1-1v-9h4a1,1,0,0,0,1-1v-2a1,1,0,0,0-1-1H1.5a1,1,0,0,0-1,1v2a1,1,0,0,0,1,1h4v9a1,1,0,0,0,1,1" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
After Width: | Height: | Size: 290 B |
1
resources/images/svg_modifier.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><line x1="2.5" y1="1.5" x2="12.5" y2="1.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round;opacity:0.5"/><line x1="8.5" y1="8.5" x2="14.5" y2="14.5" style="fill:none;stroke:#009688;stroke-linejoin:round"/><line x1="14.5" y1="8.5" x2="8.5" y2="14.5" style="fill:none;stroke:#009688;stroke-linejoin:round"/><line x1="11.5" y1="8.5" x2="11.5" y2="14.5" style="fill:none;stroke:#009688;stroke-linecap:square;stroke-linejoin:round"/><line x1="14.5" y1="11.5" x2="8.5" y2="11.5" style="fill:none;stroke:#009688;stroke-linecap:square;stroke-linejoin:round"/><path d="M1.541,12.5A12,12,0,0,1,12.5,1.541" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><rect x="12.5" y="0.5" width="2" height="2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="0.5" y="12.5" width="2" height="2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><circle cx="1.5" cy="1.5" r="1.5" style="fill:#949494"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
resources/images/svg_negative.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M15,11.5A3.5,3.5,0,1,1,11.5,8,3.5,3.5,0,0,1,15,11.5ZM14,11H9v1h5Z" style="fill:#009688"/><line x1="2.5" y1="1.5" x2="12.5" y2="1.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round;opacity:0.5"/><path d="M1.541,12.5A12,12,0,0,1,12.5,1.541" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><rect x="12.5" y="0.5" width="2" height="2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="0.5" y="12.5" width="2" height="2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><circle cx="1.5" cy="1.5" r="1.5" style="fill:#949494"/></svg>
|
After Width: | Height: | Size: 739 B |
1
resources/images/svg_part.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M15,11.5A3.5,3.5,0,1,1,11.5,8,3.5,3.5,0,0,1,15,11.5ZM14,11H12V9H11v2H9v1h2v2h1V12h2Z" style="fill:#009688"/><line x1="2.5" y1="1.5" x2="12.5" y2="1.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round;opacity:0.5"/><path d="M1.541,12.5A12,12,0,0,1,12.5,1.541" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><rect x="12.5" y="0.5" width="2" height="2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><rect x="0.5" y="12.5" width="2" height="2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><circle cx="1.5" cy="1.5" r="1.5" style="fill:#949494"/></svg>
|
After Width: | Height: | Size: 758 B |
1
resources/images/text_bake.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M6.5,14.5c-3,0-5-1-5-4,0-5,6-5,4-10,7,2,9,6,9,9,0,4-3,5-6,5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M6.5,12.5a2.151,2.151,0,0,1-2-2c0-2,1-2,2-3,0,1,0,2,1,2s3-2,1-5c3,2,3,3,3,5a2.652,2.652,0,0,1-3,3" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
After Width: | Height: | Size: 421 B |
1
resources/images/text_lock_closed.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="2.5" y="7.5" width="10" height="7" rx="1" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><path d="M3.5,7.5v-3a4,4,0,0,1,8,0v3" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
After Width: | Height: | Size: 332 B |
1
resources/images/text_lock_open.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="2.5" y="7.5" width="10" height="7" rx="1" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><path d="M11.5,7.5v-3a4,4,0,0,0-7.874-1" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
After Width: | Height: | Size: 335 B |
1
resources/images/text_obj_warning.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M7.159.511l-7.034,13A1,1,0,0,0,.966,15H15.034a1,1,0,0,0,.841-1.494L8.841.511A.948.948,0,0,0,7.159.511Z" style="fill:#ff6f00"/><rect x="7" y="4" width="2" height="6" rx="1" style="fill:#fff"/><rect x="7" y="11" width="2" height="2" rx="1" style="fill:#fff"/></svg>
|
After Width: | Height: | Size: 355 B |
1
resources/images/text_open.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M14.5,8.5v5a1,1,0,0,1-1,1H1.5a1,1,0,0,1-1-1V1.5a1,1,0,0,1,1-1h5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><line x1="14.5" y1="0.5" x2="4.5" y2="10.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><line x1="14.5" y1="5.5" x2="14.5" y2="0.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><line x1="9.5" y1="0.5" x2="14.5" y2="0.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
After Width: | Height: | Size: 602 B |
1
resources/images/text_reflection_x.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="7" width="1" height="3" style="fill:#009688"/><rect x="7" y="12" width="1" height="3" style="fill:#009688"/><rect x="7" y="10" width="1" height="1" style="fill:#009688"/><rect x="7" y="4" width="1" height="1" style="fill:#009688"/><rect x="7" y="6" width="1" height="3" style="fill:#009688"/><polygon points="0.5 14.5 1.5 14.5 5.5 7.5 1.5 0.5 0.5 0.5 0.5 14.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polygon points="9.5 7.5 13.5 14.5 14.5 14.5 14.5 0.5 13.5 0.5 9.5 7.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
After Width: | Height: | Size: 685 B |
1
resources/images/text_reflection_y.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect x="13" y="6" width="1" height="3" transform="translate(21 -6) rotate(90)" style="fill:#009688"/><rect x="1" y="6" width="1" height="3" transform="translate(9 6) rotate(90)" style="fill:#009688"/><rect x="4" y="7" width="1" height="1" transform="translate(12 3) rotate(90)" style="fill:#009688"/><rect x="10" y="7" width="1" height="1" transform="translate(18 -3) rotate(90)" style="fill:#009688"/><rect x="7" y="6" width="1" height="3" transform="translate(15 0) rotate(90)" style="fill:#009688"/><polygon points="0.5 0.5 0.5 1.5 7.5 5.5 14.5 1.5 14.5 0.5 0.5 0.5" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polygon points="7.5 9.5 0.5 13.5 0.5 14.5 14.5 14.5 14.5 13.5 7.5 9.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
After Width: | Height: | Size: 886 B |
1
resources/images/text_refresh.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polyline points="0.5 0.5 0.5 4.5 4.5 4.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><polyline points="14.5 14.5 14.5 10.5 10.5 10.5" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><path d="M14.5,7.5a7,7,0,0,0-13.326-3" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><path d="M.5,7.5a7,7,0,0,0,13.326,3" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
After Width: | Height: | Size: 565 B |
1
resources/images/text_save.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M.5,13.5V1.5a1,1,0,0,1,1-1h10l3,3v10a1,1,0,0,1-1,1H1.5A1,1,0,0,1,.5,13.5Zm4-12v1a1,1,0,0,0,1,1h4a1,1,0,0,0,1-1v-1m1,12v-3a1,1,0,0,0-1-1h-6a1,1,0,0,0-1,1v3" style="fill:none;stroke:#949494;stroke-linecap:square;stroke-linejoin:round;opacity:1"/></svg>
|
After Width: | Height: | Size: 342 B |
1
resources/images/text_undo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M13.914,6.45A6.5,6.5,0,0,0,2,4.044V1.5a.5.5,0,0,0-1,0v4a.5.5,0,0,0,.5.5h4a.5.5,0,0,0,0-1H2.612a5.477,5.477,0,1,1-.54,3.388.5.5,0,1,0-.987.162,6.5,6.5,0,0,0,11.693,2.743A6.459,6.459,0,0,0,13.914,6.45Z" style="fill:#ff6f00"/></svg>
|
After Width: | Height: | Size: 321 B |
@ -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)
|
||||
|
@ -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
|
||||
|
76
src/imgui/imgui_stdlib.cpp
Normal file
@ -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);
|
||||
}
|
22
src/imgui/imgui_stdlib.h
Normal file
@ -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 <string>
|
||||
|
||||
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);
|
||||
}
|
@ -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);
|
||||
|
@ -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.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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<class It, class = IteratorOnly<It>>
|
||||
BoundingBoxBase(It from, It to)
|
||||
{
|
||||
construct(*this, from, to);
|
||||
construct(*this, from, to);
|
||||
}
|
||||
|
||||
BoundingBoxBase(const std::vector<PointClass> &points)
|
||||
@ -107,8 +107,8 @@ class BoundingBox3Base : public BoundingBoxBase<PointClass>
|
||||
{
|
||||
public:
|
||||
BoundingBox3Base() : BoundingBoxBase<PointClass>() {}
|
||||
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
|
||||
BoundingBoxBase<PointClass>(pmin, pmax)
|
||||
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
|
||||
BoundingBoxBase<PointClass>(pmin, pmax)
|
||||
{ if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; }
|
||||
BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
|
||||
BoundingBoxBase<PointClass>(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<Point>() {}
|
||||
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {}
|
||||
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {}
|
||||
@ -216,7 +216,9 @@ public:
|
||||
friend BoundingBox get_extents_rotated(const Points &points, double angle);
|
||||
};
|
||||
|
||||
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
|
||||
using BoundingBoxes = std::vector<BoundingBox>;
|
||||
|
||||
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
|
||||
{
|
||||
public:
|
||||
BoundingBox3() : BoundingBox3Base<Vec3crd>() {}
|
||||
@ -224,7 +226,7 @@ public:
|
||||
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {}
|
||||
};
|
||||
|
||||
class BoundingBoxf : public BoundingBoxBase<Vec2d>
|
||||
class BoundingBoxf : public BoundingBoxBase<Vec2d>
|
||||
{
|
||||
public:
|
||||
BoundingBoxf() : BoundingBoxBase<Vec2d>() {}
|
||||
@ -232,7 +234,7 @@ public:
|
||||
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {}
|
||||
};
|
||||
|
||||
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
|
||||
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
|
||||
{
|
||||
public:
|
||||
using BoundingBox3Base::BoundingBox3Base;
|
||||
|
@ -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
|
||||
|
@ -130,7 +130,7 @@ template<typename PointType> [[nodiscard]] std::vector<PointType> 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<TResult>(clipType, std::forward<TSubj>(subject), safety_offset(std::forward<TClip>(clip)), fillType) :
|
||||
clipper_do<TResult>(clipType, std::forward<TSubj>(subject), std::forward<TClip>(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<ClipperLib::Paths>(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<ClipperLib::Paths>(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<ClipperLib::Paths>(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::Paths>(ClipperLib::ctDifference, contours, holes, ClipperLib::pftNonZero); ! output.empty()) {
|
||||
append(out, std::move(output));
|
||||
} else {
|
||||
@ -539,7 +552,7 @@ template<typename ExPolygonVector>
|
||||
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<ClipperLib::PolyTree>(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<PathProvider1>(subject), safety_offset(std::forward<PathProvider2>(clip)), fillType) :
|
||||
clipper_do_polytree(clipType, std::forward<PathProvider1>(subject), std::forward<PathProvider2>(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<e_ordering::ON>(nodes, [&out](const ClipperLib::PolyNode *node)
|
||||
foreach_node<e_ordering::ON>(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<std::v
|
||||
|
||||
// 1) Offset the outer contour.
|
||||
ClipperLib::Paths contours = fix_after_inner_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftNegative, true);
|
||||
#ifndef NDEBUG
|
||||
#ifndef NDEBUG
|
||||
for (auto &c : contours)
|
||||
assert(ClipperLib::Area(c) > 0.);
|
||||
#endif /* NDEBUG */
|
||||
@ -1254,7 +1272,7 @@ Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector<std::v
|
||||
holes.reserve(expoly.holes.size());
|
||||
for (const Polygon& hole : expoly.holes)
|
||||
append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole.points, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftNegative, false));
|
||||
#ifndef NDEBUG
|
||||
#ifndef NDEBUG
|
||||
for (auto &c : holes)
|
||||
assert(ClipperLib::Area(c) > 0.);
|
||||
#endif /* NDEBUG */
|
||||
@ -1347,7 +1365,7 @@ for (const std::vector<float>& 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::vector<s
|
||||
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;
|
||||
|
@ -203,14 +203,14 @@ namespace ClipperUtils {
|
||||
const Points& operator*() const { return (m_idx_contour == 0) ? m_it_expolygon->contour.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<Vec3i32>;
|
||||
@ -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<e_ordering o, class Fn> struct _foreach_node {
|
||||
template<class Fn> struct _foreach_node<e_ordering::OFF, Fn> {
|
||||
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<class Fn> struct _foreach_node<e_ordering::ON, Fn> {
|
||||
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<e_ordering ordering = e_ordering::OFF>
|
||||
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<ordering>(tree->Childs, out);
|
||||
}
|
||||
@ -623,21 +628,21 @@ void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *out)
|
||||
traverse_pt<ordering>(tree->Childs, out);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ExPolygon level;
|
||||
level.contour.points = tree->Contour;
|
||||
|
||||
foreach_node<ordering>(tree->Childs,
|
||||
|
||||
foreach_node<ordering>(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<ordering>(node->Childs, out);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
out->emplace_back(level);
|
||||
}
|
||||
|
||||
|
4082
src/libslic3r/CutSurface.cpp
Normal file
74
src/libslic3r/CutSurface.hpp
Normal file
@ -0,0 +1,74 @@
|
||||
#ifndef slic3r_CutSurface_hpp_
|
||||
#define slic3r_CutSurface_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <admesh/stl.h> // indexed_triangle_set
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Emboss.hpp" // IProjection
|
||||
|
||||
namespace Slic3r{
|
||||
|
||||
/// <summary>
|
||||
/// Represents cutted surface from object
|
||||
/// Extend index triangle set by outlines
|
||||
/// </summary>
|
||||
struct SurfaceCut : public indexed_triangle_set
|
||||
{
|
||||
// vertex indices(index to mesh vertices)
|
||||
using Index = unsigned int;
|
||||
using Contour = std::vector<Index>;
|
||||
using Contours = std::vector<Contour>;
|
||||
// list of circulated open surface
|
||||
Contours contours;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Cut surface shape from models.
|
||||
/// </summary>
|
||||
/// <param name="shapes">Multiple shape to cut from model</param>
|
||||
/// <param name="models">Multi mesh to cut, need to be in same coordinate system</param>
|
||||
/// <param name="projection">Define transformation 2d shape into 3d</param>
|
||||
/// <param name="projection_ratio">Define ideal ratio between front and back projection to cut
|
||||
/// 0 .. means use closest to front projection
|
||||
/// 1 .. means use closest to back projection
|
||||
/// value from <0, 1>
|
||||
/// </param>
|
||||
/// <returns>Cutted surface from model</returns>
|
||||
SurfaceCut cut_surface(const ExPolygons &shapes,
|
||||
const std::vector<indexed_triangle_set> &models,
|
||||
const Emboss::IProjection &projection,
|
||||
float projection_ratio);
|
||||
|
||||
/// <summary>
|
||||
/// Create model from surface cuts by projection
|
||||
/// </summary>
|
||||
/// <param name="cut">Surface from model with outlines</param>
|
||||
/// <param name="projection">Way of emboss</param>
|
||||
/// <returns>Mesh</returns>
|
||||
indexed_triangle_set cut2model(const SurfaceCut &cut,
|
||||
const Emboss::IProject3d &projection);
|
||||
|
||||
/// <summary>
|
||||
/// Separate (A)rea (o)f (I)nterest .. AoI from model
|
||||
/// NOTE: Only 2d filtration, do not filtrate by Z coordinate
|
||||
/// </summary>
|
||||
/// <param name="its">Input model</param>
|
||||
/// <param name="bb">Bounding box to project into space</param>
|
||||
/// <param name="projection">Define tranformation of BB into space</param>
|
||||
/// <returns>Triangles lay at least partialy inside of projected Bounding box</returns>
|
||||
indexed_triangle_set its_cut_AoI(const indexed_triangle_set &its,
|
||||
const BoundingBox &bb,
|
||||
const Emboss::IProjection &projection);
|
||||
|
||||
/// <summary>
|
||||
/// Separate triangles by mask
|
||||
/// </summary>
|
||||
/// <param name="its">Input model</param>
|
||||
/// <param name="mask">Mask - same size as its::indices</param>
|
||||
/// <returns>Copy of indices by mask(with their vertices)</returns>
|
||||
indexed_triangle_set its_mask(const indexed_triangle_set &its, const std::vector<bool> &mask);
|
||||
|
||||
bool corefine_test(const std::string &model_path, const std::string &shape_path);
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_CutSurface_hpp_
|
2181
src/libslic3r/Emboss.cpp
Normal file
473
src/libslic3r/Emboss.hpp
Normal file
@ -0,0 +1,473 @@
|
||||
#ifndef slic3r_Emboss_hpp_
|
||||
#define slic3r_Emboss_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <admesh/stl.h> // indexed_triangle_set
|
||||
#include "Polygon.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "EmbossShape.hpp" // ExPolygonsWithIds
|
||||
#include "BoundingBox.hpp"
|
||||
#include "TextConfiguration.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/// <summary>
|
||||
/// class with only static function add ability to engraved OR raised
|
||||
/// text OR polygons onto model surface
|
||||
/// </summary>
|
||||
namespace Emboss
|
||||
{
|
||||
static const float UNION_DELTA = 50.0f; // [approx in nano meters depends on volume scale]
|
||||
static const unsigned UNION_MAX_ITERATIN = 10; // [count]
|
||||
|
||||
/// <summary>
|
||||
/// Collect fonts registred inside OS
|
||||
/// </summary>
|
||||
/// <returns>OS registred TTF font files(full path) with names</returns>
|
||||
EmbossStyles get_font_list();
|
||||
#ifdef _WIN32
|
||||
EmbossStyles get_font_list_by_register();
|
||||
EmbossStyles get_font_list_by_enumeration();
|
||||
EmbossStyles get_font_list_by_folder();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// OS dependent function to get location of font by its name descriptor
|
||||
/// </summary>
|
||||
/// <param name="font_face_name">Unique identificator for font</param>
|
||||
/// <returns>File path to font when found</returns>
|
||||
std::optional<std::wstring> get_font_path(const std::wstring &font_face_name);
|
||||
|
||||
// description of one letter
|
||||
struct Glyph
|
||||
{
|
||||
// NOTE: shape is scaled by SHAPE_SCALE
|
||||
// to be able store points without floating points
|
||||
ExPolygons shape;
|
||||
|
||||
// values are in font points
|
||||
int advance_width=0, left_side_bearing=0;
|
||||
};
|
||||
// cache for glyph by unicode
|
||||
using Glyphs = std::map<int, Glyph>;
|
||||
|
||||
/// <summary>
|
||||
/// keep information from file about font
|
||||
/// (store file data itself)
|
||||
/// + cache data readed from buffer
|
||||
/// </summary>
|
||||
struct FontFile
|
||||
{
|
||||
// loaded data from font file
|
||||
// must store data size for imgui rasterization
|
||||
// To not store data on heap and To prevent unneccesary copy
|
||||
// data are stored inside unique_ptr
|
||||
std::unique_ptr<std::vector<unsigned char>> data;
|
||||
|
||||
struct Info
|
||||
{
|
||||
// vertical position is "scale*(ascent - descent + lineGap)"
|
||||
int ascent, descent, linegap;
|
||||
|
||||
// for convert font units to pixel
|
||||
int unit_per_em;
|
||||
};
|
||||
// info for each font in data
|
||||
std::vector<Info> infos;
|
||||
|
||||
FontFile(std::unique_ptr<std::vector<unsigned char>> data,
|
||||
std::vector<Info> &&infos)
|
||||
: data(std::move(data)), infos(std::move(infos))
|
||||
{
|
||||
assert(this->data != nullptr);
|
||||
assert(!this->data->empty());
|
||||
}
|
||||
|
||||
bool operator==(const FontFile &other) const {
|
||||
if (data->size() != other.data->size())
|
||||
return false;
|
||||
//if(*data != *other.data) return false;
|
||||
for (size_t i = 0; i < infos.size(); i++)
|
||||
if (infos[i].ascent != other.infos[i].ascent ||
|
||||
infos[i].descent == other.infos[i].descent ||
|
||||
infos[i].linegap == other.infos[i].linegap)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Add caching for shape of glyphs
|
||||
/// </summary>
|
||||
struct FontFileWithCache
|
||||
{
|
||||
// Pointer on data of the font file
|
||||
std::shared_ptr<const FontFile> font_file;
|
||||
|
||||
// Cache for glyph shape
|
||||
// IMPORTANT: accessible only in plater job thread !!!
|
||||
// main thread only clear cache by set to another shared_ptr
|
||||
std::shared_ptr<Emboss::Glyphs> cache;
|
||||
|
||||
FontFileWithCache() : font_file(nullptr), cache(nullptr) {}
|
||||
explicit FontFileWithCache(std::unique_ptr<FontFile> font_file)
|
||||
: font_file(std::move(font_file))
|
||||
, cache(std::make_shared<Emboss::Glyphs>())
|
||||
{}
|
||||
bool has_value() const { return font_file != nullptr && cache != nullptr; }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Load font file into buffer
|
||||
/// </summary>
|
||||
/// <param name="file_path">Location of .ttf or .ttc font file</param>
|
||||
/// <returns>Font object when loaded.</returns>
|
||||
std::unique_ptr<FontFile> create_font_file(const char *file_path);
|
||||
// data = raw file data
|
||||
std::unique_ptr<FontFile> create_font_file(std::unique_ptr<std::vector<unsigned char>> data);
|
||||
#ifdef _WIN32
|
||||
// fix for unknown pointer HFONT is replaced with "void *"
|
||||
void * can_load(void* hfont);
|
||||
std::unique_ptr<FontFile> create_font_file(void * hfont);
|
||||
#endif // _WIN32
|
||||
|
||||
/// <summary>
|
||||
/// convert letter into polygons
|
||||
/// </summary>
|
||||
/// <param name="font">Define fonts</param>
|
||||
/// <param name="font_index">Index of font in collection</param>
|
||||
/// <param name="letter">One character defined by unicode codepoint</param>
|
||||
/// <param name="flatness">Precision of lettter outline curve in conversion to lines</param>
|
||||
/// <returns>inner polygon cw(outer ccw)</returns>
|
||||
std::optional<Glyph> letter2glyph(const FontFile &font, unsigned int font_index, int letter, float flatness);
|
||||
|
||||
/// <summary>
|
||||
/// Convert text into polygons
|
||||
/// </summary>
|
||||
/// <param name="font">Define fonts + cache, which could extend</param>
|
||||
/// <param name="text">Characters to convert</param>
|
||||
/// <param name="font_prop">User defined property of the font</param>
|
||||
/// <param name="was_canceled">Way to interupt processing</param>
|
||||
/// <returns>Inner polygon cw(outer ccw)</returns>
|
||||
HealedExPolygons text2shapes (FontFileWithCache &font, const char *text, const FontProp &font_prop, const std::function<bool()> &was_canceled = []() {return false;});
|
||||
ExPolygonsWithIds text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function<bool()>& was_canceled = []() {return false;});
|
||||
|
||||
const unsigned ENTER_UNICODE = static_cast<unsigned>('\n');
|
||||
/// Sum of character '\n'
|
||||
unsigned get_count_lines(const std::wstring &ws);
|
||||
unsigned get_count_lines(const std::string &text);
|
||||
unsigned get_count_lines(const ExPolygonsWithIds &shape);
|
||||
|
||||
/// <summary>
|
||||
/// Fix duplicit points and self intersections in polygons.
|
||||
/// Also try to reduce amount of points and remove useless polygon parts
|
||||
/// </summary>
|
||||
/// <param name="is_non_zero">Fill type ClipperLib::pftNonZero for overlapping otherwise </param>
|
||||
/// <param name="max_iteration">Look at heal_expolygon()::max_iteration</param>
|
||||
/// <returns>Healed shapes with flag is fully healed</returns>
|
||||
HealedExPolygons heal_polygons(const Polygons &shape, bool is_non_zero = true, unsigned max_iteration = 10);
|
||||
|
||||
/// <summary>
|
||||
/// NOTE: call Slic3r::union_ex before this call
|
||||
///
|
||||
/// Heal (read: Fix) issues in expolygons:
|
||||
/// - self intersections
|
||||
/// - duplicit points
|
||||
/// - points close to line segments
|
||||
/// </summary>
|
||||
/// <param name="shape">In/Out shape to heal</param>
|
||||
/// <param name="max_iteration">Heal could create another issue,
|
||||
/// After healing it is checked again until shape is good or maximal count of iteration</param>
|
||||
/// <returns>True when shapes is good otherwise False</returns>
|
||||
bool heal_expolygons(ExPolygons &shape, unsigned max_iteration = 10);
|
||||
|
||||
/// <summary>
|
||||
/// Divide line segments in place near to point
|
||||
/// (which could lead to self intersection due to preccision)
|
||||
/// Remove same neighbors
|
||||
/// Note: Possible part of heal shape
|
||||
/// </summary>
|
||||
/// <param name="expolygons">Expolygon to edit</param>
|
||||
/// <param name="distance">(epsilon)Euclidean distance from point to line which divide line</param>
|
||||
/// <returns>True when some division was made otherwise false</returns>
|
||||
bool divide_segments_for_close_point(ExPolygons &expolygons, double distance);
|
||||
|
||||
/// <summary>
|
||||
/// Use data from font property to modify transformation
|
||||
/// </summary>
|
||||
/// <param name="angle">Z-rotation as angle to Y axis</param>
|
||||
/// <param name="distance">Z-move as surface distance</param>
|
||||
/// <param name="transformation">In / Out transformation to modify by property</param>
|
||||
void apply_transformation(const std::optional<float> &angle, const std::optional<float> &distance, Transform3d &transformation);
|
||||
|
||||
/// <summary>
|
||||
/// Read information from naming table of font file
|
||||
/// search for italic (or oblique), bold italic (or bold oblique)
|
||||
/// </summary>
|
||||
/// <param name="font">Selector of font</param>
|
||||
/// <param name="font_index">Index of font in collection</param>
|
||||
/// <returns>True when the font description contains italic/obligue otherwise False</returns>
|
||||
bool is_italic(const FontFile &font, unsigned int font_index);
|
||||
|
||||
/// <summary>
|
||||
/// Create unique character set from string with filtered from text with only character from font
|
||||
/// </summary>
|
||||
/// <param name="text">Source vector of glyphs</param>
|
||||
/// <param name="font">Font descriptor</param>
|
||||
/// <param name="font_index">Define font in collection</param>
|
||||
/// <param name="exist_unknown">True when text contain glyph unknown in font</param>
|
||||
/// <returns>Unique set of character from text contained in font</returns>
|
||||
std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate scale for glyph shape convert from shape points to mm
|
||||
/// </summary>
|
||||
/// <param name="fp">Property of font</param>
|
||||
/// <param name="ff">Font data</param>
|
||||
/// <returns>Conversion to mm</returns>
|
||||
double get_text_shape_scale(const FontProp &fp, const FontFile &ff);
|
||||
|
||||
/// <summary>
|
||||
/// getter of font info by collection defined in prop
|
||||
/// </summary>
|
||||
/// <param name="font">Contain infos about all fonts(collections) in file</param>
|
||||
/// <param name="prop">Index of collection</param>
|
||||
/// <returns>Ascent, descent, line gap</returns>
|
||||
const FontFile::Info &get_font_info(const FontFile &font, const FontProp &prop);
|
||||
|
||||
/// <summary>
|
||||
/// Read from font file and properties height of line with spacing
|
||||
/// </summary>
|
||||
/// <param name="font">Infos for collections</param>
|
||||
/// <param name="prop">Collection index + Additional line gap</param>
|
||||
/// <returns>Line height with spacing in scaled font points (same as ExPolygons)</returns>
|
||||
int get_line_height(const FontFile &font, const FontProp &prop);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate Vertical align
|
||||
/// </summary>
|
||||
/// <param name="align">Top | Center | Bottom</param>
|
||||
/// <param name="count_lines"></param>
|
||||
/// <returns>Return align Y offset in mm</returns>
|
||||
double get_align_y_offset_in_mm(FontProp::VerticalAlign align, unsigned count_lines, const FontFile &ff, const FontProp &fp);
|
||||
|
||||
/// <summary>
|
||||
/// Project spatial point
|
||||
/// </summary>
|
||||
class IProject3d
|
||||
{
|
||||
public:
|
||||
virtual ~IProject3d() = default;
|
||||
/// <summary>
|
||||
/// Move point with respect to projection direction
|
||||
/// e.g. Orthogonal projection will move with point by direction
|
||||
/// e.g. Spherical projection need to use center of projection
|
||||
/// </summary>
|
||||
/// <param name="point">Spatial point coordinate</param>
|
||||
/// <returns>Projected spatial point</returns>
|
||||
virtual Vec3d project(const Vec3d &point) const = 0;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Project 2d point into space
|
||||
/// Could be plane, sphere, cylindric, ...
|
||||
/// </summary>
|
||||
class IProjection : public IProject3d
|
||||
{
|
||||
public:
|
||||
virtual ~IProjection() = default;
|
||||
|
||||
/// <summary>
|
||||
/// convert 2d point to 3d points
|
||||
/// </summary>
|
||||
/// <param name="p">2d coordinate</param>
|
||||
/// <returns>
|
||||
/// first - front spatial point
|
||||
/// second - back spatial point
|
||||
/// </returns>
|
||||
virtual std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Back projection
|
||||
/// </summary>
|
||||
/// <param name="p">Point to project</param>
|
||||
/// <param name="depth">[optional] Depth of 2d projected point. Be careful number is in 2d scale</param>
|
||||
/// <returns>Uprojected point when it is possible</returns>
|
||||
virtual std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const = 0;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create triangle model for text
|
||||
/// </summary>
|
||||
/// <param name="shape2d">text or image</param>
|
||||
/// <param name="projection">Define transformation from 2d to 3d(orientation, position, scale, ...)</param>
|
||||
/// <returns>Projected shape into space</returns>
|
||||
indexed_triangle_set polygons2model(const ExPolygons &shape2d, const IProjection& projection);
|
||||
|
||||
/// <summary>
|
||||
/// Suggest wanted up vector of embossed text by emboss direction
|
||||
/// </summary>
|
||||
/// <param name="normal">Normalized vector of emboss direction in world</param>
|
||||
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
|
||||
/// <returns>Wanted up vector</returns>
|
||||
Vec3d suggest_up(const Vec3d normal, double up_limit = 0.9);
|
||||
|
||||
/// <summary>
|
||||
/// By transformation calculate angle between suggested and actual up vector
|
||||
/// </summary>
|
||||
/// <param name="tr">Transformation of embossed volume in world</param>
|
||||
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
|
||||
/// <returns>Rotation of suggested up-vector[in rad] in the range [-Pi, Pi], When rotation is not zero</returns>
|
||||
std::optional<float> calc_up(const Transform3d &tr, double up_limit = 0.9);
|
||||
|
||||
/// <summary>
|
||||
/// Create transformation for emboss text object to lay on surface point
|
||||
/// </summary>
|
||||
/// <param name="position">Position of surface point</param>
|
||||
/// <param name="normal">Normal of surface point</param>
|
||||
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
|
||||
/// <returns>Transformation onto surface point</returns>
|
||||
Transform3d create_transformation_onto_surface(
|
||||
const Vec3d &position, const Vec3d &normal, double up_limit = 0.9);
|
||||
|
||||
class ProjectZ : public IProjection
|
||||
{
|
||||
public:
|
||||
explicit ProjectZ(double depth) : m_depth(depth) {}
|
||||
// Inherited via IProject
|
||||
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override;
|
||||
Vec3d project(const Vec3d &point) const override;
|
||||
std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const override;
|
||||
double m_depth;
|
||||
};
|
||||
|
||||
class ProjectScale : public IProjection
|
||||
{
|
||||
std::unique_ptr<IProjection> core;
|
||||
double m_scale;
|
||||
public:
|
||||
ProjectScale(std::unique_ptr<IProjection> core, double scale)
|
||||
: core(std::move(core)), m_scale(scale)
|
||||
{}
|
||||
|
||||
// Inherited via IProject
|
||||
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override
|
||||
{
|
||||
auto res = core->create_front_back(p);
|
||||
return std::make_pair(res.first * m_scale, res.second * m_scale);
|
||||
}
|
||||
Vec3d project(const Vec3d &point) const override{
|
||||
return core->project(point);
|
||||
}
|
||||
std::optional<Vec2d> unproject(const Vec3d &p, double *depth = nullptr) const override {
|
||||
auto res = core->unproject(p / m_scale, depth);
|
||||
if (depth != nullptr) *depth *= m_scale;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
class ProjectTransform : public IProjection
|
||||
{
|
||||
std::unique_ptr<IProjection> m_core;
|
||||
Transform3d m_tr;
|
||||
Transform3d m_tr_inv;
|
||||
double z_scale;
|
||||
public:
|
||||
ProjectTransform(std::unique_ptr<IProjection> core, const Transform3d &tr) : m_core(std::move(core)), m_tr(tr)
|
||||
{
|
||||
m_tr_inv = m_tr.inverse();
|
||||
z_scale = (m_tr.linear() * Vec3d::UnitZ()).norm();
|
||||
}
|
||||
|
||||
// Inherited via IProject
|
||||
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override
|
||||
{
|
||||
auto [front, back] = m_core->create_front_back(p);
|
||||
return std::make_pair(m_tr * front, m_tr * back);
|
||||
}
|
||||
Vec3d project(const Vec3d &point) const override{
|
||||
return m_core->project(point);
|
||||
}
|
||||
std::optional<Vec2d> unproject(const Vec3d &p, double *depth = nullptr) const override {
|
||||
auto res = m_core->unproject(m_tr_inv * p, depth);
|
||||
if (depth != nullptr)
|
||||
*depth *= z_scale;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
class OrthoProject3d : public Emboss::IProject3d
|
||||
{
|
||||
// size and direction of emboss for ortho projection
|
||||
Vec3d m_direction;
|
||||
public:
|
||||
OrthoProject3d(Vec3d direction) : m_direction(direction) {}
|
||||
Vec3d project(const Vec3d &point) const override{ return point + m_direction;}
|
||||
};
|
||||
|
||||
class OrthoProject: public Emboss::IProjection {
|
||||
Transform3d m_matrix;
|
||||
// size and direction of emboss for ortho projection
|
||||
Vec3d m_direction;
|
||||
Transform3d m_matrix_inv;
|
||||
public:
|
||||
OrthoProject(Transform3d matrix, Vec3d direction)
|
||||
: m_matrix(matrix), m_direction(direction), m_matrix_inv(matrix.inverse())
|
||||
{}
|
||||
// Inherited via IProject
|
||||
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override;
|
||||
Vec3d project(const Vec3d &point) const override;
|
||||
std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Define polygon for draw letters
|
||||
/// </summary>
|
||||
struct TextLine
|
||||
{
|
||||
// slice of object
|
||||
Polygon polygon;
|
||||
|
||||
// point laying on polygon closest to zero
|
||||
PolygonPoint start;
|
||||
|
||||
// offset of text line in volume mm
|
||||
float y;
|
||||
};
|
||||
using TextLines = std::vector<TextLine>;
|
||||
|
||||
/// <summary>
|
||||
/// Sample slice polygon by bounding boxes centers
|
||||
/// slice start point has shape_center_x coor
|
||||
/// </summary>
|
||||
/// <param name="slice">Polygon and start point[Slic3r scaled milimeters]</param>
|
||||
/// <param name="bbs">Bounding boxes of letter on one line[in font scales]</param>
|
||||
/// <param name="scale">Scale for bbs (after multiply bb is in milimeters)</param>
|
||||
/// <returns>Sampled polygon by bounding boxes</returns>
|
||||
PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate angle for polygon point
|
||||
/// </summary>
|
||||
/// <param name="distance">Distance for found normal in point</param>
|
||||
/// <param name="polygon_point">Select point on polygon</param>
|
||||
/// <param name="polygon">Polygon know neighbor of point</param>
|
||||
/// <returns>angle(atan2) of normal in polygon point</returns>
|
||||
double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon);
|
||||
std::vector<double> calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon);
|
||||
|
||||
} // namespace Emboss
|
||||
|
||||
///////////////////////
|
||||
// Move to ExPolygonsWithIds Utils
|
||||
void translate(ExPolygonsWithIds &e, const Point &p);
|
||||
BoundingBox get_extents(const ExPolygonsWithIds &e);
|
||||
void center(ExPolygonsWithIds &e);
|
||||
// delta .. safe offset before union (use as boolean close)
|
||||
// NOTE: remove unprintable spaces between neighbor curves (made by linearization of curve)
|
||||
ExPolygons union_with_delta(EmbossShape &shape, float delta, unsigned max_heal_iteration);
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_Emboss_hpp_
|
143
src/libslic3r/EmbossShape.hpp
Normal file
@ -0,0 +1,143 @@
|
||||
#ifndef slic3r_EmbossShape_hpp_
|
||||
#define slic3r_EmbossShape_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <memory> // unique_ptr
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
#include <cereal/types/optional.hpp>
|
||||
#include <cereal/archives/binary.hpp>
|
||||
#include "Point.hpp" // Transform3d
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ExPolygonSerialize.hpp"
|
||||
#include "nanosvg/nanosvg.h" // NSVGimage
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct EmbossProjection{
|
||||
// Emboss depth, Size in local Z direction
|
||||
double depth = 1.; // [in loacal mm]
|
||||
// NOTE: User should see and modify mainly world size not local
|
||||
|
||||
// Flag that result volume use surface cutted from source objects
|
||||
bool use_surface = false;
|
||||
|
||||
bool operator==(const EmbossProjection &other) const {
|
||||
return depth == other.depth && use_surface == other.use_surface;
|
||||
}
|
||||
|
||||
// undo / redo stack recovery
|
||||
template<class Archive> void serialize(Archive &ar) { ar(depth, use_surface); }
|
||||
};
|
||||
|
||||
// Extend expolygons with information whether it was successfull healed
|
||||
struct HealedExPolygons{
|
||||
ExPolygons expolygons;
|
||||
bool is_healed;
|
||||
operator ExPolygons&() { return expolygons; }
|
||||
};
|
||||
|
||||
// Help structure to identify expolygons grups
|
||||
// e.g. emboss -> per glyph -> identify character
|
||||
struct ExPolygonsWithId
|
||||
{
|
||||
// Identificator for shape
|
||||
// In text it separate letters and the name is unicode value of letter
|
||||
// Is svg it is id of path
|
||||
unsigned id;
|
||||
|
||||
// shape defined by integer point contain only lines
|
||||
// Curves are converted to sequence of lines
|
||||
ExPolygons expoly;
|
||||
|
||||
// flag whether expolygons are fully healed(without duplication)
|
||||
bool is_healed = true;
|
||||
};
|
||||
using ExPolygonsWithIds = std::vector<ExPolygonsWithId>;
|
||||
|
||||
/// <summary>
|
||||
/// Contain plane shape information to be able emboss it and edit it
|
||||
/// </summary>
|
||||
struct EmbossShape
|
||||
{
|
||||
// shapes to to emboss separately over surface
|
||||
ExPolygonsWithIds shapes_with_ids;
|
||||
|
||||
// Only cache for final shape
|
||||
// It is calculated from ExPolygonsWithIds
|
||||
// Flag is_healed --> whether union of shapes is healed
|
||||
// Healed mean without selfintersection and point duplication
|
||||
HealedExPolygons final_shape;
|
||||
|
||||
// scale of shape, multiplier to get 3d point in mm from integer shape
|
||||
double scale = SCALING_FACTOR;
|
||||
|
||||
// Define how to emboss shape
|
||||
EmbossProjection projection;
|
||||
|
||||
// !!! Volume stored in .3mf has transformed vertices.
|
||||
// (baked transformation into vertices position)
|
||||
// Only place for fill this is when load from .3mf
|
||||
// This is correction for volume transformation
|
||||
// Stored_Transform3d * fix_3mf_tr = Transform3d_before_store_to_3mf
|
||||
std::optional<Slic3r::Transform3d> fix_3mf_tr;
|
||||
|
||||
struct SvgFile {
|
||||
// File(.svg) path on local computer
|
||||
// When empty can't reload from disk
|
||||
std::string path;
|
||||
|
||||
// File path into .3mf(.zip)
|
||||
// When empty svg is not stored into .3mf file yet.
|
||||
// and will create dialog to delete private data on save.
|
||||
std::string path_in_3mf;
|
||||
|
||||
// Loaded svg file data.
|
||||
// !!! It is not serialized on undo/redo stack
|
||||
std::shared_ptr<NSVGimage> image = nullptr;
|
||||
|
||||
// Loaded string data from file
|
||||
std::shared_ptr<std::string> file_data = nullptr;
|
||||
|
||||
template<class Archive> void save(Archive &ar) const {
|
||||
// Note: image is only cache it is not neccessary to store
|
||||
|
||||
// Store file data as plain string
|
||||
// For Embossed text file_data are nullptr
|
||||
ar(path, path_in_3mf, (file_data != nullptr) ? *file_data : std::string(""));
|
||||
}
|
||||
template<class Archive> void load(Archive &ar) {
|
||||
// for restore shared pointer on file data
|
||||
std::string file_data_str;
|
||||
ar(path, path_in_3mf, file_data_str);
|
||||
if (!file_data_str.empty())
|
||||
file_data = std::make_unique<std::string>(file_data_str);
|
||||
}
|
||||
};
|
||||
// When embossing shape is made by svg file this is source data
|
||||
std::optional<SvgFile> svg_file;
|
||||
|
||||
// undo / redo stack recovery
|
||||
template<class Archive> void save(Archive &ar) const
|
||||
{
|
||||
// final_shape is not neccessary to store - it is only cache
|
||||
ar(shapes_with_ids, final_shape, scale, projection, svg_file);
|
||||
cereal::save(ar, fix_3mf_tr);
|
||||
}
|
||||
template<class Archive> void load(Archive &ar)
|
||||
{
|
||||
ar(shapes_with_ids, final_shape, scale, projection, svg_file);
|
||||
cereal::load(ar, fix_3mf_tr);
|
||||
}
|
||||
};
|
||||
} // namespace Slic3r
|
||||
|
||||
// Serialization through the Cereal library
|
||||
namespace cereal {
|
||||
template<class Archive> void serialize(Archive &ar, Slic3r::ExPolygonsWithId &o) { ar(o.id, o.expoly, o.is_healed); }
|
||||
template<class Archive> void serialize(Archive &ar, Slic3r::HealedExPolygons &o) { ar(o.expolygons, o.is_healed); }
|
||||
}; // namespace cereal
|
||||
|
||||
#endif // slic3r_EmbossShape_hpp_
|
@ -72,7 +72,7 @@ public:
|
||||
void simplify(double tolerance, ExPolygons* expolygons) const;
|
||||
void medial_axis(double min_width, double max_width, ThickPolylines* polylines) const;
|
||||
void medial_axis(double min_width, double max_width, Polylines* polylines) const;
|
||||
Polylines medial_axis(double min_width, double max_width) const
|
||||
Polylines medial_axis(double min_width, double max_width) const
|
||||
{ Polylines out; this->medial_axis(min_width, max_width, &out); return out; }
|
||||
Lines lines() const;
|
||||
|
||||
@ -88,9 +88,9 @@ inline bool operator!=(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.
|
||||
inline size_t count_points(const ExPolygons &expolys)
|
||||
{
|
||||
size_t n_points = 0;
|
||||
for (const auto &expoly : expolys) {
|
||||
for (const auto &expoly : expolys) {
|
||||
n_points += expoly.contour.points.size();
|
||||
for (const auto &hole : expoly.holes)
|
||||
for (const auto &hole : expoly.holes)
|
||||
n_points += hole.points.size();
|
||||
}
|
||||
return n_points;
|
||||
@ -99,8 +99,8 @@ inline size_t count_points(const ExPolygons &expolys)
|
||||
inline size_t count_points(const ExPolygon &expoly)
|
||||
{
|
||||
size_t n_points = expoly.contour.points.size();
|
||||
for (const auto &hole : expoly.holes)
|
||||
n_points += hole.points.size();
|
||||
for (const auto &hole : expoly.holes)
|
||||
n_points += hole.points.size();
|
||||
return n_points;
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ inline size_t number_polygons(const ExPolygons &expolys)
|
||||
return n_polygons;
|
||||
}
|
||||
|
||||
inline Lines to_lines(const ExPolygon &src)
|
||||
inline Lines to_lines(const ExPolygon &src)
|
||||
{
|
||||
Lines lines;
|
||||
lines.reserve(count_points(src));
|
||||
@ -127,7 +127,7 @@ inline Lines to_lines(const ExPolygon &src)
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline Lines to_lines(const ExPolygons &src)
|
||||
inline Lines to_lines(const ExPolygons &src)
|
||||
{
|
||||
Lines lines;
|
||||
lines.reserve(count_points(src));
|
||||
@ -155,7 +155,7 @@ inline Linesf to_linesf(const ExPolygons &src, uint32_t count_lines = 0)
|
||||
assert(pts.size() >= 3);
|
||||
if (pts.size() < 2) return;
|
||||
bool is_first = true;
|
||||
for (const Point &p : pts) {
|
||||
for (const Point &p : pts) {
|
||||
Vec2d pd = p.cast<double>();
|
||||
if (is_first) is_first = false;
|
||||
else lines.emplace_back(prev_pd, pd);
|
||||
@ -165,7 +165,7 @@ inline Linesf to_linesf(const ExPolygons &src, uint32_t count_lines = 0)
|
||||
};
|
||||
for (const ExPolygon& expoly: src) {
|
||||
to_lines(expoly.contour.points);
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
to_lines(hole.points);
|
||||
}
|
||||
assert(lines.size() == count_lines);
|
||||
@ -372,15 +372,20 @@ inline Points to_points(const ExPolygon &expoly)
|
||||
return out;
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, const ExPolygon &src)
|
||||
{
|
||||
inline void translate(ExPolygons &expolys, const Point &p)
|
||||
{
|
||||
for (ExPolygon &expoly : expolys) expoly.translate(p);
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, const ExPolygon &src)
|
||||
{
|
||||
dst.reserve(dst.size() + src.holes.size() + 1);
|
||||
dst.push_back(src.contour);
|
||||
dst.insert(dst.end(), src.holes.begin(), src.holes.end());
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, const ExPolygons &src)
|
||||
{
|
||||
inline void polygons_append(Polygons &dst, const ExPolygons &src)
|
||||
{
|
||||
dst.reserve(dst.size() + number_polygons(src));
|
||||
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) {
|
||||
dst.push_back(it->contour);
|
||||
@ -389,36 +394,36 @@ inline void polygons_append(Polygons &dst, const ExPolygons &src)
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, ExPolygon &&src)
|
||||
{
|
||||
{
|
||||
dst.reserve(dst.size() + src.holes.size() + 1);
|
||||
dst.push_back(std::move(src.contour));
|
||||
dst.insert(dst.end(),
|
||||
dst.push_back(std::move(src.contour));
|
||||
dst.insert(dst.end(),
|
||||
std::make_move_iterator(src.holes.begin()),
|
||||
std::make_move_iterator(src.holes.end()));
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, ExPolygons &&src)
|
||||
{
|
||||
{
|
||||
dst.reserve(dst.size() + number_polygons(src));
|
||||
for (ExPolygon& expoly: src) {
|
||||
dst.push_back(std::move(expoly.contour));
|
||||
dst.insert(dst.end(),
|
||||
dst.insert(dst.end(),
|
||||
std::make_move_iterator(expoly.holes.begin()),
|
||||
std::make_move_iterator(expoly.holes.end()));
|
||||
}
|
||||
}
|
||||
|
||||
inline void expolygons_append(ExPolygons &dst, const ExPolygons &src)
|
||||
{
|
||||
inline void expolygons_append(ExPolygons &dst, const ExPolygons &src)
|
||||
{
|
||||
dst.insert(dst.end(), src.begin(), src.end());
|
||||
}
|
||||
|
||||
inline void expolygons_append(ExPolygons &dst, ExPolygons &&src)
|
||||
{
|
||||
{
|
||||
if (dst.empty()) {
|
||||
dst = std::move(src);
|
||||
} else {
|
||||
dst.insert(dst.end(),
|
||||
dst.insert(dst.end(),
|
||||
std::make_move_iterator(src.begin()),
|
||||
std::make_move_iterator(src.end()));
|
||||
}
|
||||
@ -464,6 +469,8 @@ std::vector<BoundingBox> get_extents_vector(const ExPolygons &polygons);
|
||||
// Test for duplicate points. The points are copied, sorted and checked for duplicates globally.
|
||||
bool has_duplicate_points(const ExPolygon &expoly);
|
||||
bool has_duplicate_points(const ExPolygons &expolys);
|
||||
// Return True when erase some otherwise False.
|
||||
bool remove_same_neighbor(ExPolygons &expolys);
|
||||
|
||||
bool remove_sticks(ExPolygon &poly);
|
||||
void keep_largest_contour_only(ExPolygons &polygons);
|
||||
@ -517,8 +524,8 @@ namespace boost { namespace polygon {
|
||||
return expolygon;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
template <>
|
||||
struct geometry_concept<Slic3r::ExPolygon> { typedef polygon_with_holes_concept type; };
|
||||
|
||||
@ -545,7 +552,7 @@ namespace boost { namespace polygon {
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//first we register CPolygonSet as a polygon set
|
||||
template <>
|
||||
struct geometry_concept<Slic3r::ExPolygons> { typedef polygon_set_concept type; };
|
||||
|
28
src/libslic3r/ExPolygonSerialize.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef slic3r_ExPolygonSerialize_hpp_
|
||||
#define slic3r_ExPolygonSerialize_hpp_
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Point.hpp" // Cereal serialization of Point
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
/// <summary>
|
||||
/// External Cereal serialization of ExPolygons
|
||||
/// </summary>
|
||||
|
||||
// Serialization through the Cereal library
|
||||
#include <cereal/access.hpp>
|
||||
namespace cereal {
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive, Slic3r::Polygon &polygon) {
|
||||
archive(polygon.points);
|
||||
}
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive, Slic3r::ExPolygon &expoly) {
|
||||
archive(expoly.contour, expoly.holes);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_ExPolygonSerialize_hpp_
|
82
src/libslic3r/ExPolygonsIndex.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include "ExPolygonsIndex.hpp"
|
||||
using namespace Slic3r;
|
||||
|
||||
// IMPROVE: use one dimensional vector for polygons offset with searching by std::lower_bound
|
||||
ExPolygonsIndices::ExPolygonsIndices(const ExPolygons &shapes)
|
||||
{
|
||||
// prepare offsets
|
||||
m_offsets.reserve(shapes.size());
|
||||
uint32_t offset = 0;
|
||||
for (const ExPolygon &shape : shapes) {
|
||||
assert(!shape.contour.points.empty());
|
||||
std::vector<uint32_t> shape_offsets;
|
||||
shape_offsets.reserve(shape.holes.size() + 1);
|
||||
shape_offsets.push_back(offset);
|
||||
offset += shape.contour.points.size();
|
||||
for (const Polygon &hole: shape.holes) {
|
||||
shape_offsets.push_back(offset);
|
||||
offset += hole.points.size();
|
||||
}
|
||||
m_offsets.push_back(std::move(shape_offsets));
|
||||
}
|
||||
m_count = offset;
|
||||
}
|
||||
|
||||
uint32_t ExPolygonsIndices::cvt(const ExPolygonsIndex &id) const
|
||||
{
|
||||
assert(id.expolygons_index < m_offsets.size());
|
||||
const std::vector<uint32_t> &shape_offset = m_offsets[id.expolygons_index];
|
||||
assert(id.polygon_index < shape_offset.size());
|
||||
uint32_t res = shape_offset[id.polygon_index] + id.point_index;
|
||||
assert(res < m_count);
|
||||
return res;
|
||||
}
|
||||
|
||||
ExPolygonsIndex ExPolygonsIndices::cvt(uint32_t index) const
|
||||
{
|
||||
assert(index < m_count);
|
||||
ExPolygonsIndex result{0, 0, 0};
|
||||
// find expolygon index
|
||||
auto fn = [](const std::vector<uint32_t> &offsets, uint32_t index) { return offsets[0] < index; };
|
||||
auto it = std::lower_bound(m_offsets.begin() + 1, m_offsets.end(), index, fn);
|
||||
result.expolygons_index = it - m_offsets.begin();
|
||||
if (it == m_offsets.end() || it->at(0) != index) --result.expolygons_index;
|
||||
|
||||
// find polygon index
|
||||
const std::vector<uint32_t> &shape_offset = m_offsets[result.expolygons_index];
|
||||
auto it2 = std::lower_bound(shape_offset.begin() + 1, shape_offset.end(), index);
|
||||
result.polygon_index = it2 - shape_offset.begin();
|
||||
if (it2 == shape_offset.end() || *it2 != index) --result.polygon_index;
|
||||
|
||||
// calculate point index
|
||||
uint32_t polygon_offset = shape_offset[result.polygon_index];
|
||||
assert(index >= polygon_offset);
|
||||
result.point_index = index - polygon_offset;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ExPolygonsIndices::is_last_point(const ExPolygonsIndex &id) const {
|
||||
assert(id.expolygons_index < m_offsets.size());
|
||||
const std::vector<uint32_t> &shape_offset = m_offsets[id.expolygons_index];
|
||||
assert(id.polygon_index < shape_offset.size());
|
||||
uint32_t index = shape_offset[id.polygon_index] + id.point_index;
|
||||
assert(index < m_count);
|
||||
// next index
|
||||
uint32_t next_point_index = index + 1;
|
||||
uint32_t next_poly_index = id.polygon_index + 1;
|
||||
uint32_t next_expoly_index = id.expolygons_index + 1;
|
||||
// is last expoly?
|
||||
if (next_expoly_index == m_offsets.size()) {
|
||||
// is last expoly last poly?
|
||||
if (next_poly_index == shape_offset.size())
|
||||
return next_point_index == m_count;
|
||||
} else {
|
||||
// (not last expoly) is expoly last poly?
|
||||
if (next_poly_index == shape_offset.size())
|
||||
return next_point_index == m_offsets[next_expoly_index][0];
|
||||
}
|
||||
// Not last polygon in expolygon
|
||||
return next_point_index == shape_offset[next_poly_index];
|
||||
}
|
||||
|
||||
uint32_t ExPolygonsIndices::get_count() const { return m_count; }
|
74
src/libslic3r/ExPolygonsIndex.hpp
Normal file
@ -0,0 +1,74 @@
|
||||
#ifndef slic3r_ExPolygonsIndex_hpp_
|
||||
#define slic3r_ExPolygonsIndex_hpp_
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
namespace Slic3r {
|
||||
|
||||
/// <summary>
|
||||
/// Index into ExPolygons
|
||||
/// Identify expolygon, its contour (or hole) and point
|
||||
/// </summary>
|
||||
struct ExPolygonsIndex
|
||||
{
|
||||
// index of ExPolygons
|
||||
uint32_t expolygons_index;
|
||||
|
||||
// index of Polygon
|
||||
// 0 .. contour
|
||||
// N .. hole[N-1]
|
||||
uint32_t polygon_index;
|
||||
|
||||
// index of point in polygon
|
||||
uint32_t point_index;
|
||||
|
||||
bool is_contour() const { return polygon_index == 0; }
|
||||
bool is_hole() const { return polygon_index != 0; }
|
||||
uint32_t hole_index() const { return polygon_index - 1; }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Keep conversion from ExPolygonsIndex to Index and vice versa
|
||||
/// ExPolygonsIndex .. contour(or hole) point from ExPolygons
|
||||
/// Index .. continous number
|
||||
///
|
||||
/// index is used to address lines and points as result from function
|
||||
/// Slic3r::to_lines, Slic3r::to_points
|
||||
/// </summary>
|
||||
class ExPolygonsIndices
|
||||
{
|
||||
std::vector<std::vector<uint32_t>> m_offsets;
|
||||
// for check range of index
|
||||
uint32_t m_count; // count of points
|
||||
public:
|
||||
ExPolygonsIndices(const ExPolygons &shapes);
|
||||
|
||||
/// <summary>
|
||||
/// Convert to one index number
|
||||
/// </summary>
|
||||
/// <param name="id">Compose of adress into expolygons</param>
|
||||
/// <returns>Index</returns>
|
||||
uint32_t cvt(const ExPolygonsIndex &id) const;
|
||||
|
||||
/// <summary>
|
||||
/// Separate to multi index
|
||||
/// </summary>
|
||||
/// <param name="index">adress into expolygons</param>
|
||||
/// <returns></returns>
|
||||
ExPolygonsIndex cvt(uint32_t index) const;
|
||||
|
||||
/// <summary>
|
||||
/// Check whether id is last point in polygon
|
||||
/// </summary>
|
||||
/// <param name="id">Identify point in expolygon</param>
|
||||
/// <returns>True when id is last point in polygon otherwise false</returns>
|
||||
bool is_last_point(const ExPolygonsIndex &id) const;
|
||||
|
||||
/// <summary>
|
||||
/// Count of points in expolygons
|
||||
/// </summary>
|
||||
/// <returns>Count of points in expolygons</returns>
|
||||
uint32_t get_count() const;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_ExPolygonsIndex_hpp_
|
@ -18,6 +18,8 @@
|
||||
#include <stdexcept>
|
||||
#include <iomanip>
|
||||
|
||||
#include <boost/assign.hpp>
|
||||
#include <boost/bimap.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
@ -46,6 +48,11 @@ namespace pt = boost::property_tree;
|
||||
#include <Eigen/Dense>
|
||||
#include "miniz_extension.hpp"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include "EmbossShape.hpp"
|
||||
#include "ExPolygonSerialize.hpp"
|
||||
#include "NSVGUtils.hpp"
|
||||
|
||||
#include <fast_float/fast_float.h>
|
||||
|
||||
// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter,
|
||||
@ -332,6 +339,40 @@ static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed";
|
||||
static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed";
|
||||
static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges";
|
||||
|
||||
// Store / load of TextConfiguration
|
||||
static constexpr const char *TEXT_DATA_ATTR = "text";
|
||||
// TextConfiguration::EmbossStyle
|
||||
static constexpr const char *STYLE_NAME_ATTR = "style_name";
|
||||
static constexpr const char *FONT_DESCRIPTOR_ATTR = "font_descriptor";
|
||||
static constexpr const char *FONT_DESCRIPTOR_TYPE_ATTR = "font_descriptor_type";
|
||||
|
||||
// TextConfiguration::FontProperty
|
||||
static constexpr const char *CHAR_GAP_ATTR = "char_gap";
|
||||
static constexpr const char *LINE_GAP_ATTR = "line_gap";
|
||||
static constexpr const char *LINE_HEIGHT_ATTR = "line_height";
|
||||
static constexpr const char *BOLDNESS_ATTR = "boldness";
|
||||
static constexpr const char *SKEW_ATTR = "skew";
|
||||
static constexpr const char *PER_GLYPH_ATTR = "per_glyph";
|
||||
static constexpr const char *HORIZONTAL_ALIGN_ATTR = "horizontal";
|
||||
static constexpr const char *VERTICAL_ALIGN_ATTR = "vertical";
|
||||
static constexpr const char *COLLECTION_NUMBER_ATTR = "collection";
|
||||
|
||||
static constexpr const char *FONT_FAMILY_ATTR = "family";
|
||||
static constexpr const char *FONT_FACE_NAME_ATTR = "face_name";
|
||||
static constexpr const char *FONT_STYLE_ATTR = "style";
|
||||
static constexpr const char *FONT_WEIGHT_ATTR = "weight";
|
||||
|
||||
// Store / load of EmbossShape
|
||||
static constexpr const char *SHAPE_TAG = "slic3rpe:shape";
|
||||
static constexpr const char *SHAPE_SCALE_ATTR = "scale";
|
||||
static constexpr const char *UNHEALED_ATTR = "unhealed";
|
||||
static constexpr const char *SVG_FILE_PATH_ATTR = "filepath";
|
||||
static constexpr const char *SVG_FILE_PATH_IN_3MF_ATTR = "filepath3mf";
|
||||
|
||||
// EmbossProjection
|
||||
static constexpr const char *DEPTH_ATTR = "depth";
|
||||
static constexpr const char *USE_SURFACE_ATTR = "use_surface";
|
||||
// static constexpr const char *FIX_TRANSFORMATION_ATTR = "transform";
|
||||
|
||||
const unsigned int BBS_VALID_OBJECT_TYPES_COUNT = 2;
|
||||
const char* BBS_VALID_OBJECT_TYPES[] =
|
||||
@ -719,7 +760,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
RepairedMeshErrors mesh_stats;
|
||||
ModelVolumeType part_type;
|
||||
TextInfo text_info;
|
||||
|
||||
std::optional<EmbossShape> shape_configuration;
|
||||
VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||
: first_triangle_id(first_triangle_id)
|
||||
, last_triangle_id(last_triangle_id)
|
||||
@ -772,6 +813,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
|
||||
/*typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
|
||||
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;*/
|
||||
using PathToEmbossShapeFileMap = std::map<std::string, std::shared_ptr<std::string>>;
|
||||
|
||||
struct ObjectImporter
|
||||
{
|
||||
@ -966,6 +1008,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
IdToLayerConfigRangesMap m_layer_config_ranges;
|
||||
/*IdToSlaSupportPointsMap m_sla_support_points;
|
||||
IdToSlaDrainHolesMap m_sla_drain_holes;*/
|
||||
PathToEmbossShapeFileMap m_path_to_emboss_shape_files;
|
||||
std::string m_curr_metadata_name;
|
||||
std::string m_curr_characters;
|
||||
std::string m_name;
|
||||
@ -1019,6 +1062,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
// add backup & restore logic
|
||||
bool _load_model_from_file(std::string filename, Model& model, PlateDataPtrs& plate_data_list, std::vector<Preset*>& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Import3mfProgressFn proFn = nullptr,
|
||||
BBLProject* project = nullptr, int plate_id = 0);
|
||||
bool _is_svg_shape_file(const std::string &filename) const;
|
||||
bool _extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function<bool (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)>, bool restore = false);
|
||||
bool _extract_xml_from_archive(mz_zip_archive& archive, std::string const & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
|
||||
bool _extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
|
||||
@ -1039,6 +1083,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
|
||||
void _extract_auxiliary_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
|
||||
void _extract_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat);
|
||||
|
||||
// handlers to parse the .model file
|
||||
void _handle_start_model_xml_element(const char* name, const char** attributes);
|
||||
@ -1094,6 +1139,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
bool _handle_start_metadata(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_metadata();
|
||||
|
||||
bool _handle_start_shape_configuration(const char **attributes, unsigned int num_attributes);
|
||||
|
||||
bool _create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
|
||||
|
||||
void _apply_transform(ModelInstance& instance, const Transform3d& transform);
|
||||
@ -1401,6 +1448,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
add_error("Archive does not contain a valid model config");
|
||||
return false;
|
||||
}
|
||||
} else if (_is_svg_shape_file(name)) {
|
||||
_extract_embossed_svg_shape_file(name, archive, stat);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) {
|
||||
m_parsing_slice_info = true;
|
||||
@ -2165,6 +2214,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_is_svg_shape_file(const std::string &name) const {
|
||||
return boost::starts_with(name, MODEL_FOLDER) && boost::ends_with(name, ".svg");
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function<bool (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)> extract, bool restore)
|
||||
{
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
||||
@ -2876,6 +2929,30 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
}
|
||||
}*/
|
||||
|
||||
void _BBS_3MF_Importer::_extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat)
|
||||
{
|
||||
assert(m_path_to_emboss_shape_files.find(filename) == m_path_to_emboss_shape_files.end());
|
||||
auto file = std::make_unique<std::string>(stat.m_uncomp_size, '\0');
|
||||
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void *) file->data(), stat.m_uncomp_size, 0);
|
||||
if (res == 0) {
|
||||
add_error("Error while reading svg shape for emboss");
|
||||
return;
|
||||
}
|
||||
|
||||
// store for case svg is loaded before volume
|
||||
m_path_to_emboss_shape_files[filename] = std::move(file);
|
||||
|
||||
// find embossed volume, for case svg is loaded after volume
|
||||
for (const ModelObject *object : m_model->objects)
|
||||
for (ModelVolume *volume : object->volumes) {
|
||||
std::optional<EmbossShape> &es = volume->emboss_shape;
|
||||
if (!es.has_value()) continue;
|
||||
std::optional<EmbossShape::SvgFile> &svg = es->svg_file;
|
||||
if (!svg.has_value()) continue;
|
||||
if (filename.compare(svg->path_in_3mf) == 0) svg->file_data = m_path_to_emboss_shape_files[filename];
|
||||
}
|
||||
}
|
||||
|
||||
void _BBS_3MF_Importer::_extract_custom_gcode_per_print_z_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat)
|
||||
{
|
||||
//BBS: add plate tree related logic
|
||||
@ -3086,7 +3163,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
res = _handle_start_assemble_item(attributes, num_attributes);
|
||||
else if (::strcmp(TEXT_INFO_TAG, name) == 0)
|
||||
res = _handle_start_text_info_item(attributes, num_attributes);
|
||||
|
||||
else if (::strcmp(SHAPE_TAG, name) == 0)
|
||||
res = _handle_start_shape_configuration(attributes, num_attributes);
|
||||
if (!res)
|
||||
_stop_xml_parser();
|
||||
}
|
||||
@ -3639,6 +3717,40 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Definition of read/write method for EmbossShape
|
||||
static void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive);
|
||||
static std::optional<EmbossShape> read_emboss_shape(const char **attributes, unsigned int num_attributes);
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_shape_configuration(const char **attributes, unsigned int num_attributes)
|
||||
{
|
||||
IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id);
|
||||
if (object == m_objects_metadata.end()) {
|
||||
add_error("Can not assign volume mesh to a valid object");
|
||||
return false;
|
||||
}
|
||||
auto &volumes = object->second.volumes;
|
||||
if (volumes.empty()) {
|
||||
add_error("Can not assign mesh to a valid volume");
|
||||
return false;
|
||||
}
|
||||
ObjectMetadata::VolumeMetadata &volume = volumes.back();
|
||||
volume.shape_configuration = read_emboss_shape(attributes, num_attributes);
|
||||
if (!volume.shape_configuration.has_value()) return false;
|
||||
|
||||
// Fill svg file content into shape_configuration
|
||||
std::optional<EmbossShape::SvgFile> &svg = volume.shape_configuration->svg_file;
|
||||
if (!svg.has_value()) return true; // do not contain svg file
|
||||
|
||||
const std::string &path = svg->path_in_3mf;
|
||||
if (path.empty()) return true; // do not contain svg file
|
||||
|
||||
auto it = m_path_to_emboss_shape_files.find(path);
|
||||
if (it == m_path_to_emboss_shape_files.end()) return true; // svg file is not loaded yet
|
||||
|
||||
svg->file_data = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
|
||||
{
|
||||
static const unsigned int MAX_RECURSIONS = 10;
|
||||
@ -4203,7 +4315,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
}
|
||||
|
||||
ObjectMetadata::VolumeMetadata &volume = object->second.volumes[m_curr_config.volume_id];
|
||||
|
||||
TextInfo text_info;
|
||||
text_info.m_text = xml_unescape(bbs_get_attribute_value_string(attributes, num_attributes, TEXT_ATTR));
|
||||
text_info.m_font_name = bbs_get_attribute_value_string(attributes, num_attributes, FONT_NAME_ATTR);
|
||||
@ -4508,7 +4619,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
}
|
||||
|
||||
volume->set_type(volume_data->part_type);
|
||||
|
||||
if (auto &es = volume_data->shape_configuration; es.has_value())
|
||||
volume->emboss_shape = std::move(es);
|
||||
if (!volume_data->text_info.m_text.empty())
|
||||
volume->set_text_info(volume_data->text_info);
|
||||
|
||||
@ -5277,7 +5389,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
bool save_model_to_file(StoreParams& store_params);
|
||||
// add backup logic
|
||||
bool save_object_mesh(const std::string& temp_path, ModelObject const & object, int obj_id);
|
||||
|
||||
static void add_transformation(std::stringstream &stream, const Transform3d &tr);
|
||||
private:
|
||||
//BBS: add plate data related logic
|
||||
bool _save_model_to_file(const std::string& filename,
|
||||
@ -6766,6 +6878,17 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return flush(output_buffer, true);
|
||||
}
|
||||
|
||||
void _BBS_3MF_Exporter::add_transformation(std::stringstream &stream, const Transform3d &tr)
|
||||
{
|
||||
for (unsigned c = 0; c < 4; ++c) {
|
||||
for (unsigned r = 0; r < 3; ++r) {
|
||||
stream << tr(r, c);
|
||||
if (r != 2 || c != 3)
|
||||
stream << " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const
|
||||
{
|
||||
// This happens for empty projects
|
||||
@ -6786,13 +6909,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (!item.path.empty())
|
||||
stream << "\" " << PPATH_ATTR << "=\"" << xml_escape(item.path);
|
||||
stream << "\" " << TRANSFORM_ATTR << "=\"";
|
||||
for (unsigned c = 0; c < 4; ++c) {
|
||||
for (unsigned r = 0; r < 3; ++r) {
|
||||
stream << item.transform(r, c);
|
||||
if (r != 2 || c != 3)
|
||||
stream << " ";
|
||||
}
|
||||
}
|
||||
add_transformation(stream, item.transform);
|
||||
stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n";
|
||||
}
|
||||
|
||||
@ -7186,6 +7303,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << " <" << METADATA_TAG << " "<< KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n";
|
||||
}
|
||||
|
||||
if (const std::optional<EmbossShape> &es = volume->emboss_shape; es.has_value())
|
||||
to_xml(stream, *es, *volume, archive);
|
||||
|
||||
const TextInfo &text_info = volume->get_text_info();
|
||||
if (!text_info.m_text.empty())
|
||||
_add_text_info_to_archive(stream, text_info);
|
||||
@ -8283,4 +8403,148 @@ SaveObjectGaurd::~SaveObjectGaurd()
|
||||
_BBS_Backup_Manager::get().pop_object_gaurd();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Conversion with bidirectional map
|
||||
// F .. first, S .. second
|
||||
template<typename F, typename S> F bimap_cvt(const boost::bimap<F, S> &bmap, S s, const F &def_value)
|
||||
{
|
||||
const auto &map = bmap.right;
|
||||
auto found_item = map.find(s);
|
||||
|
||||
// only for back and forward compatibility
|
||||
assert(found_item != map.end());
|
||||
if (found_item == map.end()) return def_value;
|
||||
|
||||
return found_item->second;
|
||||
}
|
||||
|
||||
template<typename F, typename S> S bimap_cvt(const boost::bimap<F, S> &bmap, F f, const S &def_value)
|
||||
{
|
||||
const auto &map = bmap.left;
|
||||
auto found_item = map.find(f);
|
||||
|
||||
// only for back and forward compatibility
|
||||
assert(found_item != map.end());
|
||||
if (found_item == map.end()) return def_value;
|
||||
|
||||
return found_item->second;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
Transform3d create_fix(const std::optional<Transform3d> &prev, const ModelVolume &volume)
|
||||
{
|
||||
// IMPROVE: check if volume was modified (translated, rotated OR scaled)
|
||||
// when no change do not calculate transformation only store original fix matrix
|
||||
|
||||
// Create transformation used after load actual stored volume
|
||||
// Orca: do not bake volume transformation into meshes
|
||||
// const Transform3d &actual_trmat = volume.get_matrix();
|
||||
const Transform3d &actual_trmat = Transform3d::Identity();
|
||||
|
||||
const auto &vertices = volume.mesh().its.vertices;
|
||||
Vec3d min = actual_trmat * vertices.front().cast<double>();
|
||||
Vec3d max = min;
|
||||
for (const Vec3f &v : vertices) {
|
||||
Vec3d vd = actual_trmat * v.cast<double>();
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
if (min[i] > vd[i]) min[i] = vd[i];
|
||||
if (max[i] < vd[i]) max[i] = vd[i];
|
||||
}
|
||||
}
|
||||
Vec3d center = (max + min) / 2;
|
||||
Transform3d post_trmat = Transform3d::Identity();
|
||||
post_trmat.translate(center);
|
||||
|
||||
Transform3d fix_trmat = actual_trmat.inverse() * post_trmat;
|
||||
if (!prev.has_value()) return fix_trmat;
|
||||
|
||||
// check whether fix somehow differ previous
|
||||
if (fix_trmat.isApprox(Transform3d::Identity(), 1e-5)) return *prev;
|
||||
|
||||
return *prev * fix_trmat;
|
||||
}
|
||||
|
||||
bool to_xml(std::stringstream &stream, const EmbossShape::SvgFile &svg, const ModelVolume &volume, mz_zip_archive &archive)
|
||||
{
|
||||
if (svg.path_in_3mf.empty())
|
||||
return true; // EmbossedText OR unwanted store .svg file into .3mf (protection of copyRight)
|
||||
|
||||
if (!svg.path.empty())
|
||||
stream << SVG_FILE_PATH_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path) << "\" ";
|
||||
stream << SVG_FILE_PATH_IN_3MF_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path_in_3mf) << "\" ";
|
||||
|
||||
std::shared_ptr<std::string> file_data = svg.file_data;
|
||||
assert(file_data != nullptr);
|
||||
if (file_data == nullptr && !svg.path.empty()) file_data = read_from_disk(svg.path);
|
||||
if (file_data == nullptr) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Can't write svg file no filedata";
|
||||
return false;
|
||||
}
|
||||
const std::string &file_data_str = *file_data;
|
||||
|
||||
return mz_zip_writer_add_mem(&archive, svg.path_in_3mf.c_str(), (const void *) file_data_str.c_str(), file_data_str.size(), MZ_DEFAULT_COMPRESSION);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive)
|
||||
{
|
||||
stream << " <" << SHAPE_TAG << " ";
|
||||
if (es.svg_file.has_value())
|
||||
if (!to_xml(stream, *es.svg_file, volume, archive)) BOOST_LOG_TRIVIAL(warning) << "Can't write svg file defiden embossed shape into 3mf";
|
||||
|
||||
stream << SHAPE_SCALE_ATTR << "=\"" << es.scale << "\" ";
|
||||
|
||||
if (!es.final_shape.is_healed) stream << UNHEALED_ATTR << "=\"" << 1 << "\" ";
|
||||
|
||||
// projection
|
||||
const EmbossProjection &p = es.projection;
|
||||
stream << DEPTH_ATTR << "=\"" << p.depth << "\" ";
|
||||
if (p.use_surface) stream << USE_SURFACE_ATTR << "=\"" << 1 << "\" ";
|
||||
|
||||
// FIX of baked transformation
|
||||
Transform3d fix = create_fix(es.fix_3mf_tr, volume);
|
||||
stream << TRANSFORM_ATTR << "=\"";
|
||||
_BBS_3MF_Exporter::add_transformation(stream, fix);
|
||||
stream << "\" ";
|
||||
|
||||
stream << "/>\n"; // end SHAPE_TAG
|
||||
}
|
||||
|
||||
std::optional<EmbossShape> read_emboss_shape(const char **attributes, unsigned int num_attributes)
|
||||
{
|
||||
double scale = bbs_get_attribute_value_float(attributes, num_attributes, SHAPE_SCALE_ATTR);
|
||||
int unhealed = bbs_get_attribute_value_int(attributes, num_attributes, UNHEALED_ATTR);
|
||||
bool is_healed = unhealed != 1;
|
||||
|
||||
EmbossProjection projection;
|
||||
projection.depth = bbs_get_attribute_value_float(attributes, num_attributes, DEPTH_ATTR);
|
||||
if (is_approx(projection.depth, 0.)) projection.depth = 10.;
|
||||
|
||||
int use_surface = bbs_get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR);
|
||||
if (use_surface == 1) projection.use_surface = true;
|
||||
|
||||
std::optional<Transform3d> fix_tr_mat;
|
||||
std::string fix_tr_mat_str = bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR);
|
||||
if (!fix_tr_mat_str.empty()) { fix_tr_mat = bbs_get_transform_from_3mf_specs_string(fix_tr_mat_str); }
|
||||
|
||||
std::string file_path = bbs_get_attribute_value_string(attributes, num_attributes, SVG_FILE_PATH_ATTR);
|
||||
std::string file_path_3mf = bbs_get_attribute_value_string(attributes, num_attributes, SVG_FILE_PATH_IN_3MF_ATTR);
|
||||
|
||||
// MayBe: store also shapes to not store svg
|
||||
// But be carefull curve will be lost -> scale will not change sampling
|
||||
// shapes could be loaded from SVG
|
||||
ExPolygonsWithIds shapes;
|
||||
// final shape could be calculated from shapes
|
||||
HealedExPolygons final_shape;
|
||||
final_shape.is_healed = is_healed;
|
||||
|
||||
EmbossShape::SvgFile svg{file_path, file_path_3mf};
|
||||
return EmbossShape{std::move(shapes), std::move(final_shape), scale, std::move(projection), std::move(fix_tr_mat), std::move(svg)};
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
@ -308,6 +308,16 @@ template<typename T> T angle_to_0_2PI(T angle)
|
||||
return angle;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void to_range_pi_pi(T &angle)
|
||||
{
|
||||
if (angle > T(PI) || angle <= -T(PI)) {
|
||||
int count = static_cast<int>(std::round(angle / (2 * PI)));
|
||||
angle -= static_cast<T>(count * 2 * PI);
|
||||
assert(angle <= T(PI) && angle > -T(PI));
|
||||
}
|
||||
}
|
||||
|
||||
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval);
|
||||
|
||||
double linint(double value, double oldmin, double oldmax, double newmin, double newmax);
|
||||
|
45
src/libslic3r/IntersectionPoints.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include "IntersectionPoints.hpp"
|
||||
#include <libslic3r/AABBTreeLines.hpp>
|
||||
|
||||
//NOTE: using CGAL SweepLines is slower !!! (example in git history)
|
||||
|
||||
namespace {
|
||||
using namespace Slic3r;
|
||||
IntersectionsLines compute_intersections(const Lines &lines)
|
||||
{
|
||||
if (lines.size() < 3)
|
||||
return {};
|
||||
|
||||
auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
|
||||
IntersectionsLines result;
|
||||
for (uint32_t li = 0; li < lines.size()-1; ++li) {
|
||||
const Line &l = lines[li];
|
||||
auto intersections = AABBTreeLines::get_intersections_with_line<false, Point, Line>(lines, tree, l);
|
||||
for (const auto &[p, node_index] : intersections) {
|
||||
if (node_index - 1 <= li)
|
||||
continue;
|
||||
if (const Line &l_ = lines[node_index];
|
||||
l_.a == l.a ||
|
||||
l_.a == l.b ||
|
||||
l_.b == l.a ||
|
||||
l_.b == l.b )
|
||||
// it is duplicit point not intersection
|
||||
continue;
|
||||
|
||||
// NOTE: fix AABBTree to compute intersection with double preccission!!
|
||||
Vec2d intersection_point = p.cast<double>();
|
||||
|
||||
result.push_back(IntersectionLines{li, static_cast<uint32_t>(node_index), intersection_point});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace Slic3r {
|
||||
IntersectionsLines get_intersections(const Lines &lines) { return compute_intersections(lines); }
|
||||
IntersectionsLines get_intersections(const Polygon &polygon) { return compute_intersections(to_lines(polygon)); }
|
||||
IntersectionsLines get_intersections(const Polygons &polygons) { return compute_intersections(to_lines(polygons)); }
|
||||
IntersectionsLines get_intersections(const ExPolygon &expolygon) { return compute_intersections(to_lines(expolygon)); }
|
||||
IntersectionsLines get_intersections(const ExPolygons &expolygons) { return compute_intersections(to_lines(expolygons)); }
|
||||
}
|
23
src/libslic3r/IntersectionPoints.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef slic3r_IntersectionPoints_hpp_
|
||||
#define slic3r_IntersectionPoints_hpp_
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct IntersectionLines {
|
||||
uint32_t line_index1;
|
||||
uint32_t line_index2;
|
||||
Vec2d intersection;
|
||||
};
|
||||
using IntersectionsLines = std::vector<IntersectionLines>;
|
||||
|
||||
// collect all intersecting points
|
||||
IntersectionsLines get_intersections(const Lines &lines);
|
||||
IntersectionsLines get_intersections(const Polygon &polygon);
|
||||
IntersectionsLines get_intersections(const Polygons &polygons);
|
||||
IntersectionsLines get_intersections(const ExPolygon &expolygon);
|
||||
IntersectionsLines get_intersections(const ExPolygons &expolygons);
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_IntersectionPoints_hpp_
|
@ -1932,6 +1932,21 @@ void ModelObject::clone_for_cut(ModelObject **obj)
|
||||
(*obj)->input_file.clear();
|
||||
}
|
||||
|
||||
bool ModelVolume::is_the_only_one_part() const
|
||||
{
|
||||
if (m_type != ModelVolumeType::MODEL_PART)
|
||||
return false;
|
||||
if (object == nullptr) return false;
|
||||
for (const ModelVolume *v : object->volumes) {
|
||||
if (v == nullptr) continue;
|
||||
// is this volume?
|
||||
if (v->id() == this->id()) continue;
|
||||
// exist another model part in object?
|
||||
if (v->type() == ModelVolumeType::MODEL_PART) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Transform3d ModelObject::calculate_cut_plane_inverse_matrix(const std::array<Vec3d, 4>& plane_points)
|
||||
{
|
||||
Vec3d mid_point = {0.0, 0.0, 0.0};
|
||||
@ -2425,6 +2440,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
|
||||
if (volume->type() != ModelVolumeType::MODEL_PART)
|
||||
continue;
|
||||
|
||||
// splited volume should not be text object
|
||||
if (!is_multi_volume_object) {
|
||||
//BBS: not multi volume object, then split mesh.
|
||||
std::vector<TriangleMesh> volume_meshes = volume->mesh().split();
|
||||
@ -3183,7 +3199,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
|
||||
std::vector<TriangleMesh> meshes = this->mesh().split();
|
||||
if (meshes.size() <= 1)
|
||||
return 1;
|
||||
|
||||
// splited volume should not be text object
|
||||
size_t idx = 0;
|
||||
size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin();
|
||||
const std::string name = this->name;
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "CustomGCode.hpp"
|
||||
#include "enum_bitmask.hpp"
|
||||
|
||||
#include "EmbossShape.hpp"
|
||||
//BBS: add bbs 3mf
|
||||
#include "Format/bbs_3mf.hpp"
|
||||
//BBS: add step
|
||||
@ -32,7 +32,7 @@
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <optional>
|
||||
namespace cereal {
|
||||
class BinaryInputArchive;
|
||||
class BinaryOutputArchive;
|
||||
@ -901,6 +901,7 @@ public:
|
||||
void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
|
||||
void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
|
||||
void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
|
||||
const std::shared_ptr<const TriangleMesh> &get_mesh_shared_ptr() const { return m_mesh; }
|
||||
// Configuration parameters specific to an object model geometry or a modifier volume,
|
||||
// overriding the global Slic3r settings and the ModelObject settings.
|
||||
ModelConfigObject config;
|
||||
@ -921,6 +922,10 @@ public:
|
||||
// List of exterior faces
|
||||
FacetsAnnotation exterior_facets;
|
||||
|
||||
|
||||
// Is set only when volume is Embossed Shape
|
||||
// Contain 2d information about embossed shape to be editabled
|
||||
std::optional<EmbossShape> emboss_shape;
|
||||
// A parent object owning this modifier volume.
|
||||
ModelObject* get_object() const { return this->object; }
|
||||
ModelVolumeType type() const { return m_type; }
|
||||
@ -931,6 +936,8 @@ public:
|
||||
bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; }
|
||||
bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; }
|
||||
bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; }
|
||||
bool is_svg() const { return emboss_shape.has_value(); }
|
||||
bool is_the_only_one_part() const; // behave like an object
|
||||
t_model_material_id material_id() const { return m_material_id; }
|
||||
void set_material_id(t_model_material_id material_id);
|
||||
void reset_extra_facets();
|
||||
@ -1119,7 +1126,7 @@ private:
|
||||
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
|
||||
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
|
||||
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets),
|
||||
m_text_info(other.m_text_info)
|
||||
m_text_info(other.m_text_info), emboss_shape(other.emboss_shape)
|
||||
{
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
@ -1139,7 +1146,8 @@ private:
|
||||
}
|
||||
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
|
||||
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
|
||||
name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
|
||||
name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
|
||||
emboss_shape(other.emboss_shape)
|
||||
{
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
@ -1186,7 +1194,6 @@ private:
|
||||
auto tr = m_transformation;
|
||||
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info, cut_info);
|
||||
mesh_changed |= !(tr == m_transformation);
|
||||
if (mesh_changed) m_transformation.get_matrix(true, true, true, true); // force dirty
|
||||
auto t = supported_facets.timestamp();
|
||||
cereal::load_by_value(ar, supported_facets);
|
||||
mesh_changed |= t != supported_facets.timestamp();
|
||||
@ -1197,6 +1204,7 @@ private:
|
||||
cereal::load_by_value(ar, mmu_segmentation_facets);
|
||||
mesh_changed |= t != mmu_segmentation_facets.timestamp();
|
||||
cereal::load_by_value(ar, config);
|
||||
cereal::load(ar, emboss_shape);
|
||||
assert(m_mesh);
|
||||
if (has_convex_hull) {
|
||||
cereal::load_optional(ar, m_convex_hull);
|
||||
@ -1215,6 +1223,7 @@ private:
|
||||
cereal::save_by_value(ar, seam_facets);
|
||||
cereal::save_by_value(ar, mmu_segmentation_facets);
|
||||
cereal::save_by_value(ar, config);
|
||||
cereal::save(ar, emboss_shape);
|
||||
if (has_convex_hull)
|
||||
cereal::save_optional(ar, m_convex_hull);
|
||||
}
|
||||
@ -1306,7 +1315,7 @@ public:
|
||||
// BBS
|
||||
void rotate(Matrix3d rotation_matrix) {
|
||||
// note: must remove scaling from transformation, otherwise auto-orientation with scaled objects will have problem
|
||||
auto R = get_matrix(true,false,true).matrix().block<3, 3>(0, 0);
|
||||
auto R = m_transformation.get_rotation_matrix().matrix().block<3, 3>(0, 0);
|
||||
auto R_new = rotation_matrix * R;
|
||||
auto euler_angles = Geometry::extract_euler_angles(R_new);
|
||||
set_rotation(euler_angles);
|
||||
|
543
src/libslic3r/NSVGUtils.cpp
Normal file
@ -0,0 +1,543 @@
|
||||
#include "NSVGUtils.hpp"
|
||||
#include <array>
|
||||
#include <charconv> // to_chars
|
||||
|
||||
#include <boost/nowide/iostream.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Emboss.hpp" // heal for shape
|
||||
|
||||
namespace {
|
||||
using namespace Slic3r; // Polygon
|
||||
// see function nsvg__lineTo(NSVGparser* p, float x, float y)
|
||||
bool is_line(const float *p, float precision = 1e-4f);
|
||||
// convert curve in path to lines
|
||||
struct LinesPath{
|
||||
Polygons polygons;
|
||||
Polylines polylines; };
|
||||
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams ¶m);
|
||||
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m);
|
||||
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m);
|
||||
} // namespace
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams ¶m)
|
||||
{
|
||||
ExPolygonsWithIds result;
|
||||
size_t shape_id = 0;
|
||||
for (NSVGshape *shape_ptr = image.shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next, ++shape_id) {
|
||||
const NSVGshape &shape = *shape_ptr;
|
||||
if (!(shape.flags & NSVG_FLAGS_VISIBLE))
|
||||
continue;
|
||||
|
||||
bool is_fill_used = shape.fill.type != NSVG_PAINT_NONE;
|
||||
bool is_stroke_used =
|
||||
shape.stroke.type != NSVG_PAINT_NONE &&
|
||||
shape.strokeWidth > 1e-5f;
|
||||
|
||||
if (!is_fill_used && !is_stroke_used)
|
||||
continue;
|
||||
|
||||
const LinesPath lines_path = linearize_path(shape.paths, param);
|
||||
|
||||
if (is_fill_used) {
|
||||
unsigned unique_id = static_cast<unsigned>(2 * shape_id);
|
||||
HealedExPolygons expoly = fill_to_expolygons(lines_path, shape, param);
|
||||
result.push_back({unique_id, expoly.expolygons, expoly.is_healed});
|
||||
}
|
||||
if (is_stroke_used) {
|
||||
unsigned unique_id = static_cast<unsigned>(2 * shape_id + 1);
|
||||
HealedExPolygons expoly = stroke_to_expolygons(lines_path, shape, param);
|
||||
result.push_back({unique_id, expoly.expolygons, expoly.is_healed});
|
||||
}
|
||||
}
|
||||
|
||||
// SVG is used as centered
|
||||
// Do not disturb user by settings of pivot position
|
||||
center(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
Polygons to_polygons(const NSVGimage &image, const NSVGLineParams ¶m)
|
||||
{
|
||||
Polygons result;
|
||||
for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
|
||||
if (!(shape->flags & NSVG_FLAGS_VISIBLE))
|
||||
continue;
|
||||
if (shape->fill.type == NSVG_PAINT_NONE)
|
||||
continue;
|
||||
const LinesPath lines_path = linearize_path(shape->paths, param);
|
||||
polygons_append(result, lines_path.polygons);
|
||||
// close polyline to create polygon
|
||||
polygons_append(result, to_polygons(lines_path.polylines));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void bounds(const NSVGimage &image, Vec2f& min, Vec2f &max)
|
||||
{
|
||||
for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next)
|
||||
for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
|
||||
if (min.x() > path->bounds[0])
|
||||
min.x() = path->bounds[0];
|
||||
if (min.y() > path->bounds[1])
|
||||
min.y() = path->bounds[1];
|
||||
if (max.x() < path->bounds[2])
|
||||
max.x() = path->bounds[2];
|
||||
if (max.y() < path->bounds[3])
|
||||
max.y() = path->bounds[3];
|
||||
}
|
||||
}
|
||||
|
||||
NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, float dpi)
|
||||
{
|
||||
NSVGimage *image = ::nsvgParseFromFile(filename.c_str(), units, dpi);
|
||||
return {image, &nsvgDelete};
|
||||
}
|
||||
|
||||
std::unique_ptr<std::string> read_from_disk(const std::string &path)
|
||||
{
|
||||
boost::nowide::ifstream fs{path};
|
||||
if (!fs.is_open())
|
||||
return nullptr;
|
||||
std::stringstream ss;
|
||||
ss << fs.rdbuf();
|
||||
return std::make_unique<std::string>(ss.str());
|
||||
}
|
||||
|
||||
NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units, float dpi){
|
||||
// NOTE: nsvg parser consume data from input(char *)
|
||||
size_t size = file_data.size();
|
||||
// file data could be big, so it is allocated on heap
|
||||
std::unique_ptr<char[]> data_copy(new char[size+1]);
|
||||
memcpy(data_copy.get(), file_data.c_str(), size);
|
||||
data_copy[size] = '\0'; // data for nsvg must be null terminated
|
||||
NSVGimage *image = ::nsvgParse(data_copy.get(), units, dpi);
|
||||
return {image, &nsvgDelete};
|
||||
}
|
||||
|
||||
NSVGimage *init_image(EmbossShape::SvgFile &svg_file){
|
||||
// is already initialized?
|
||||
if (svg_file.image.get() != nullptr)
|
||||
return svg_file.image.get();
|
||||
|
||||
if (svg_file.file_data == nullptr) {
|
||||
// chech if path is known
|
||||
if (svg_file.path.empty())
|
||||
return nullptr;
|
||||
svg_file.file_data = read_from_disk(svg_file.path);
|
||||
if (svg_file.file_data == nullptr)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// init svg image
|
||||
svg_file.image = nsvgParse(*svg_file.file_data);
|
||||
if (svg_file.image.get() == NULL)
|
||||
return nullptr;
|
||||
|
||||
return svg_file.image.get();
|
||||
}
|
||||
|
||||
size_t get_shapes_count(const NSVGimage &image)
|
||||
{
|
||||
size_t count = 0;
|
||||
for (NSVGshape * s = image.shapes; s != NULL; s = s->next)
|
||||
++count;
|
||||
return count;
|
||||
}
|
||||
|
||||
//void save(const NSVGimage &image, std::ostream &data)
|
||||
//{
|
||||
// data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>";
|
||||
//
|
||||
// // tl .. top left
|
||||
// Vec2f tl(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
|
||||
// // br .. bottom right
|
||||
// Vec2f br(std::numeric_limits<float>::min(), std::numeric_limits<float>::min());
|
||||
// bounds(image, tl, br);
|
||||
//
|
||||
// tl.x() = std::floor(tl.x());
|
||||
// tl.y() = std::floor(tl.y());
|
||||
//
|
||||
// br.x() = std::ceil(br.x());
|
||||
// br.y() = std::ceil(br.y());
|
||||
// Vec2f s = br - tl;
|
||||
// Point size = s.cast<Point::coord_type>();
|
||||
//
|
||||
// data << "<svg xmlns=\"http://www.w3.org/2000/svg\" "
|
||||
// << "width=\"" << size.x() << "mm\" "
|
||||
// << "height=\"" << size.y() << "mm\" "
|
||||
// << "viewBox=\"0 0 " << size.x() << " " << size.y() << "\" >\n";
|
||||
// data << "<!-- Created with PrusaSlicer (https://www.prusa3d.com/prusaslicer/) -->\n";
|
||||
//
|
||||
// std::array<char, 128> buffer;
|
||||
// auto write_point = [&tl, &buffer](std::string &d, const float *p) {
|
||||
// float x = p[0] - tl.x();
|
||||
// float y = p[1] - tl.y();
|
||||
// auto to_string = [&buffer](float f) -> std::string {
|
||||
// auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), f);
|
||||
// if (ec != std::errc{})
|
||||
// return "0";
|
||||
// return std::string(buffer.data(), ptr);
|
||||
// };
|
||||
// d += to_string(x) + "," + to_string(y) + " ";
|
||||
// };
|
||||
//
|
||||
// for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
|
||||
// enum struct Type { move, line, curve, close }; // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
|
||||
// Type type = Type::move;
|
||||
// std::string d = "M "; // move on start point
|
||||
// for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
|
||||
// if (path->npts <= 1)
|
||||
// continue;
|
||||
//
|
||||
// if (type == Type::close) {
|
||||
// type = Type::move;
|
||||
// // NOTE: After close must be a space
|
||||
// d += " M "; // move on start point
|
||||
// }
|
||||
// write_point(d, path->pts);
|
||||
// size_t path_size = static_cast<size_t>(path->npts - 1);
|
||||
//
|
||||
// if (path->closed) {
|
||||
// // Do not use last point in path it is duplicit
|
||||
// if (path->npts <= 4)
|
||||
// continue;
|
||||
// path_size = static_cast<size_t>(path->npts - 4);
|
||||
// }
|
||||
//
|
||||
// for (size_t i = 0; i < path_size; i += 3) {
|
||||
// const float *p = &path->pts[i * 2];
|
||||
// if (!::is_line(p)) {
|
||||
// if (type != Type::curve) {
|
||||
// type = Type::curve;
|
||||
// d += "C "; // start sequence of triplets defining curves
|
||||
// }
|
||||
// write_point(d, &p[2]);
|
||||
// write_point(d, &p[4]);
|
||||
// } else {
|
||||
//
|
||||
// if (type != Type::line) {
|
||||
// type = Type::line;
|
||||
// d += "L "; // start sequence of line points
|
||||
// }
|
||||
// }
|
||||
// write_point(d, &p[6]);
|
||||
// }
|
||||
// if (path->closed) {
|
||||
// type = Type::close;
|
||||
// d += "Z"; // start sequence of line points
|
||||
// }
|
||||
// }
|
||||
// if (type != Type::close) {
|
||||
// //type = Type::close;
|
||||
// d += "Z"; // closed path
|
||||
// }
|
||||
// data << "<path fill=\"#D2D2D2\" d=\"" << d << "\" />\n";
|
||||
// }
|
||||
// data << "</svg>\n";
|
||||
//}
|
||||
//
|
||||
//bool save(const NSVGimage &image, const std::string &svg_file_path)
|
||||
//{
|
||||
// std::ofstream file{svg_file_path};
|
||||
// if (!file.is_open())
|
||||
// return false;
|
||||
// save(image, file);
|
||||
// return true;
|
||||
//}
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace {
|
||||
using namespace Slic3r; // Polygon + Vec2f
|
||||
|
||||
Point::coord_type to_coor(float val, double scale) { return static_cast<Point::coord_type>(std::round(val * scale)); }
|
||||
|
||||
bool need_flattening(float tessTol, const Vec2f &p1, const Vec2f &p2, const Vec2f &p3, const Vec2f &p4) {
|
||||
// f .. first
|
||||
// s .. second
|
||||
auto det = [](const Vec2f &f, const Vec2f &s) {
|
||||
return std::fabs(f.x() * s.y() - f.y() * s.x());
|
||||
};
|
||||
|
||||
Vec2f pd = (p4 - p1);
|
||||
Vec2f pd2 = (p2 - p4);
|
||||
float d2 = det(pd2, pd);
|
||||
Vec2f pd3 = (p3 - p4);
|
||||
float d3 = det(pd3, pd);
|
||||
float d23 = d2 + d3;
|
||||
|
||||
return (d23 * d23) >= tessTol * pd.squaredNorm();
|
||||
}
|
||||
|
||||
// see function nsvg__lineTo(NSVGparser* p, float x, float y)
|
||||
bool is_line(const float *p, float precision){
|
||||
//Vec2f p1(p[0], p[1]);
|
||||
//Vec2f p2(p[2], p[3]);
|
||||
//Vec2f p3(p[4], p[5]);
|
||||
//Vec2f p4(p[6], p[7]);
|
||||
float dx_3 = (p[6] - p[0]) / 3.f;
|
||||
float dy_3 = (p[7] - p[1]) / 3.f;
|
||||
|
||||
return
|
||||
is_approx(p[2], p[0] + dx_3, precision) &&
|
||||
is_approx(p[4], p[6] - dx_3, precision) &&
|
||||
is_approx(p[3], p[1] + dy_3, precision) &&
|
||||
is_approx(p[5], p[7] - dy_3, precision);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert cubic curve to lines
|
||||
/// Inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez
|
||||
/// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335
|
||||
/// </summary>
|
||||
/// <param name="polygon">Result points</param>
|
||||
/// <param name="tessTol">Tesselation tolerance</param>
|
||||
/// <param name="p1">Curve point</param>
|
||||
/// <param name="p2">Curve point</param>
|
||||
/// <param name="p3">Curve point</param>
|
||||
/// <param name="p4">Curve point</param>
|
||||
/// <param name="level">Actual depth of recursion</param>
|
||||
void flatten_cubic_bez(Points &points, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level)
|
||||
{
|
||||
if (!need_flattening(tessTol, p1, p2, p3, p4)) {
|
||||
Point::coord_type x = static_cast<Point::coord_type>(std::round(p4.x()));
|
||||
Point::coord_type y = static_cast<Point::coord_type>(std::round(p4.y()));
|
||||
points.emplace_back(x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
--level;
|
||||
if (level == 0)
|
||||
return;
|
||||
|
||||
Vec2f p12 = (p1 + p2) * 0.5f;
|
||||
Vec2f p23 = (p2 + p3) * 0.5f;
|
||||
Vec2f p34 = (p3 + p4) * 0.5f;
|
||||
Vec2f p123 = (p12 + p23) * 0.5f;
|
||||
Vec2f p234 = (p23 + p34) * 0.5f;
|
||||
Vec2f p1234 = (p123 + p234) * 0.5f;
|
||||
flatten_cubic_bez(points, tessTol, p1, p12, p123, p1234, level);
|
||||
flatten_cubic_bez(points, tessTol, p1234, p234, p34, p4, level);
|
||||
}
|
||||
|
||||
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams ¶m)
|
||||
{
|
||||
LinesPath result;
|
||||
Polygons &polygons = result.polygons;
|
||||
Polylines &polylines = result.polylines;
|
||||
|
||||
// multiple use of allocated memmory for points between paths
|
||||
Points points;
|
||||
for (NSVGpath *path = first_path; path != NULL; path = path->next) {
|
||||
// Flatten path
|
||||
Point::coord_type x = to_coor(path->pts[0], param.scale);
|
||||
Point::coord_type y = to_coor(path->pts[1], param.scale);
|
||||
points.emplace_back(x, y);
|
||||
size_t path_size = (path->npts > 1) ? static_cast<size_t>(path->npts - 1) : 0;
|
||||
for (size_t i = 0; i < path_size; i += 3) {
|
||||
const float *p = &path->pts[i * 2];
|
||||
if (is_line(p)) {
|
||||
// point p4
|
||||
Point::coord_type xx = to_coor(p[6], param.scale);
|
||||
Point::coord_type yy = to_coor(p[7], param.scale);
|
||||
points.emplace_back(xx, yy);
|
||||
continue;
|
||||
}
|
||||
Vec2f p1(p[0], p[1]);
|
||||
Vec2f p2(p[2], p[3]);
|
||||
Vec2f p3(p[4], p[5]);
|
||||
Vec2f p4(p[6], p[7]);
|
||||
flatten_cubic_bez(points, param.tesselation_tolerance,
|
||||
p1 * param.scale, p2 * param.scale, p3 * param.scale, p4 * param.scale,
|
||||
param.max_level);
|
||||
}
|
||||
assert(!points.empty());
|
||||
if (points.empty())
|
||||
continue;
|
||||
|
||||
if (param.is_y_negative)
|
||||
for (Point &p : points)
|
||||
p.y() = -p.y();
|
||||
|
||||
if (path->closed) {
|
||||
polygons.emplace_back(points);
|
||||
} else {
|
||||
polylines.emplace_back(points);
|
||||
}
|
||||
// prepare for new path - recycle alocated memory
|
||||
points.clear();
|
||||
}
|
||||
remove_same_neighbor(polygons);
|
||||
remove_same_neighbor(polylines);
|
||||
return result;
|
||||
}
|
||||
|
||||
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m)
|
||||
{
|
||||
Polygons fill = lines_path.polygons; // copy
|
||||
|
||||
// close polyline to create polygon
|
||||
polygons_append(fill, to_polygons(lines_path.polylines));
|
||||
if (fill.empty())
|
||||
return {};
|
||||
|
||||
// if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_NONZERO)
|
||||
bool is_non_zero = true;
|
||||
if (shape.fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD)
|
||||
is_non_zero = false;
|
||||
|
||||
return Emboss::heal_polygons(fill, is_non_zero, param.max_heal_iteration);
|
||||
}
|
||||
|
||||
struct DashesParam{
|
||||
// first dash length
|
||||
float dash_length = 1.f; // scaled
|
||||
|
||||
// is current dash .. true
|
||||
// is current space .. false
|
||||
bool is_line = true;
|
||||
|
||||
// current index to array
|
||||
unsigned char dash_index = 0;
|
||||
static constexpr size_t max_dash_array_size = 8; // limitation of nanosvg strokeDashArray
|
||||
std::array<float, max_dash_array_size> dash_array; // scaled
|
||||
unsigned char dash_count = 0; // count of values in array
|
||||
|
||||
explicit DashesParam(const NSVGshape &shape, double scale) :
|
||||
dash_count(shape.strokeDashCount)
|
||||
{
|
||||
assert(dash_count > 0);
|
||||
assert(dash_count <= max_dash_array_size); // limitation of nanosvg strokeDashArray
|
||||
for (size_t i = 0; i < dash_count; ++i)
|
||||
dash_array[i] = static_cast<float>(shape.strokeDashArray[i] * scale);
|
||||
|
||||
// Figure out dash offset.
|
||||
float all_dash_length = 0;
|
||||
for (unsigned char j = 0; j < dash_count; ++j)
|
||||
all_dash_length += dash_array[j];
|
||||
|
||||
if (dash_count%2 == 1) // (shape.strokeDashCount & 1)
|
||||
all_dash_length *= 2.0f;
|
||||
|
||||
// Find location inside pattern
|
||||
float dash_offset = fmodf(static_cast<float>(shape.strokeDashOffset * scale), all_dash_length);
|
||||
if (dash_offset < 0.0f)
|
||||
dash_offset += all_dash_length;
|
||||
|
||||
while (dash_offset > dash_array[dash_index]) {
|
||||
dash_offset -= dash_array[dash_index];
|
||||
dash_index = (dash_index + 1) % shape.strokeDashCount;
|
||||
is_line = !is_line;
|
||||
}
|
||||
|
||||
dash_length = dash_array[dash_index] - dash_offset;
|
||||
}
|
||||
};
|
||||
|
||||
Polylines to_dashes(const Polyline &polyline, const DashesParam& param)
|
||||
{
|
||||
Polylines dashes;
|
||||
Polyline dash; // cache for one dash in dashed line
|
||||
Point prev_point;
|
||||
|
||||
bool is_line = param.is_line;
|
||||
unsigned char dash_index = param.dash_index;
|
||||
float dash_length = param.dash_length; // current rest of dash distance
|
||||
for (const Point &point : polyline.points) {
|
||||
if (&point == &polyline.points.front()) {
|
||||
// is first point
|
||||
prev_point = point; // copy
|
||||
continue;
|
||||
}
|
||||
|
||||
Point diff = point - prev_point;
|
||||
float line_segment_length = diff.cast<float>().norm();
|
||||
while (dash_length < line_segment_length) {
|
||||
// Calculate intermediate point
|
||||
float d = dash_length / line_segment_length;
|
||||
Point move_point = diff * d;
|
||||
Point intermediate = prev_point + move_point;
|
||||
|
||||
// add Dash in stroke
|
||||
if (is_line) {
|
||||
if (dash.empty()) {
|
||||
dashes.emplace_back(Points{prev_point, intermediate});
|
||||
} else {
|
||||
dash.append(prev_point);
|
||||
dash.append(intermediate);
|
||||
dashes.push_back(dash);
|
||||
dash.clear();
|
||||
}
|
||||
}
|
||||
|
||||
diff -= move_point;
|
||||
line_segment_length -= dash_length;
|
||||
prev_point = intermediate;
|
||||
|
||||
// Advance dash pattern
|
||||
is_line = !is_line;
|
||||
dash_index = (dash_index + 1) % param.dash_count;
|
||||
dash_length = param.dash_array[dash_index];
|
||||
}
|
||||
|
||||
if (is_line)
|
||||
dash.append(prev_point);
|
||||
dash_length -= line_segment_length;
|
||||
prev_point = point; // copy
|
||||
}
|
||||
|
||||
// add last dash
|
||||
if (is_line){
|
||||
assert(!dash.empty());
|
||||
dash.append(prev_point); // prev_point == polyline.points.back()
|
||||
dashes.push_back(dash);
|
||||
}
|
||||
return dashes;
|
||||
}
|
||||
|
||||
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m)
|
||||
{
|
||||
// convert stroke to polygon
|
||||
ClipperLib::JoinType join_type = ClipperLib::JoinType::jtSquare;
|
||||
switch (static_cast<NSVGlineJoin>(shape.strokeLineJoin)) {
|
||||
case NSVGlineJoin::NSVG_JOIN_BEVEL: join_type = ClipperLib::JoinType::jtSquare; break;
|
||||
case NSVGlineJoin::NSVG_JOIN_MITER: join_type = ClipperLib::JoinType::jtMiter; break;
|
||||
case NSVGlineJoin::NSVG_JOIN_ROUND: join_type = ClipperLib::JoinType::jtRound; break;
|
||||
}
|
||||
|
||||
double mitter = shape.miterLimit * param.scale;
|
||||
if (join_type == ClipperLib::JoinType::jtRound) {
|
||||
// mitter is used as ArcTolerance
|
||||
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
|
||||
mitter = std::pow(param.tesselation_tolerance, 1/3.);
|
||||
}
|
||||
float stroke_width = static_cast<float>(shape.strokeWidth * param.scale);
|
||||
|
||||
ClipperLib::EndType end_type = ClipperLib::EndType::etOpenButt;
|
||||
switch (static_cast<NSVGlineCap>(shape.strokeLineCap)) {
|
||||
case NSVGlineCap::NSVG_CAP_BUTT: end_type = ClipperLib::EndType::etOpenButt; break;
|
||||
case NSVGlineCap::NSVG_CAP_ROUND: end_type = ClipperLib::EndType::etOpenRound; break;
|
||||
case NSVGlineCap::NSVG_CAP_SQUARE: end_type = ClipperLib::EndType::etOpenSquare; break;
|
||||
}
|
||||
|
||||
Polygons result;
|
||||
if (shape.strokeDashCount > 0) {
|
||||
DashesParam params(shape, param.scale);
|
||||
Polylines dashes;
|
||||
for (const Polyline &polyline : lines_path.polylines)
|
||||
polylines_append(dashes, to_dashes(polyline, params));
|
||||
for (const Polygon &polygon : lines_path.polygons)
|
||||
polylines_append(dashes, to_dashes(to_polyline(polygon), params));
|
||||
result = offset(dashes, stroke_width / 2, join_type, mitter, end_type);
|
||||
} else {
|
||||
result = contour_to_polygons(lines_path.polygons, stroke_width, join_type, mitter);
|
||||
polygons_append(result, offset(lines_path.polylines, stroke_width / 2, join_type, mitter, end_type));
|
||||
}
|
||||
|
||||
bool is_non_zero = true;
|
||||
return Emboss::heal_polygons(result, is_non_zero, param.max_heal_iteration);
|
||||
}
|
||||
|
||||
} // namespace
|
82
src/libslic3r/NSVGUtils.hpp
Normal file
@ -0,0 +1,82 @@
|
||||
#ifndef slic3r_NSVGUtils_hpp_
|
||||
#define slic3r_NSVGUtils_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include "Polygon.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "EmbossShape.hpp" // ExPolygonsWithIds
|
||||
#include "nanosvg/nanosvg.h" // load SVG file
|
||||
|
||||
// Helper function to work with nano svg
|
||||
namespace Slic3r {
|
||||
|
||||
/// <summary>
|
||||
/// Paramreters for conversion curve from SVG to lines in Polygon
|
||||
/// </summary>
|
||||
struct NSVGLineParams
|
||||
{
|
||||
// Smaller will divide curve to more lines
|
||||
// NOTE: Value is in image scale
|
||||
double tesselation_tolerance = 10.f;
|
||||
|
||||
// Maximal depth of recursion for conversion curve to lines
|
||||
int max_level = 10;
|
||||
|
||||
// Multiplicator of point coors
|
||||
// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer --> Slic3r::Point
|
||||
double scale = 1. / SCALING_FACTOR;
|
||||
|
||||
// Flag wether y is negative, when true than y coor is multiplied by -1
|
||||
bool is_y_negative = true;
|
||||
|
||||
// Is used only with rounded Stroke
|
||||
double arc_tolerance = 1.;
|
||||
|
||||
// Maximal count of heal iteration
|
||||
unsigned max_heal_iteration = 10;
|
||||
|
||||
explicit NSVGLineParams(double tesselation_tolerance):
|
||||
tesselation_tolerance(tesselation_tolerance),
|
||||
arc_tolerance(std::pow(tesselation_tolerance, 1/3.))
|
||||
{}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Convert .svg opened by nanoSvg to shapes stored in expolygons with ids
|
||||
/// </summary>
|
||||
/// <param name="image">Parsed svg file by NanoSvg</param>
|
||||
/// <param name="tesselation_tolerance">Smaller will divide curve to more lines
|
||||
/// NOTE: Value is in image scale</param>
|
||||
/// <param name="max_level">Maximal depth for conversion curve to lines</param>
|
||||
/// <param name="scale">Multiplicator of point coors
|
||||
/// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer</param>
|
||||
/// <returns>Shapes from svg image - fill + stroke</returns>
|
||||
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams ¶m);
|
||||
|
||||
// help functions - prepare to be tested
|
||||
/// <param name="is_y_negative">Flag is y negative, when true than y coor is multiplied by -1</param>
|
||||
Polygons to_polygons(const NSVGimage &image, const NSVGLineParams ¶m);
|
||||
|
||||
void bounds(const NSVGimage &image, Vec2f &min, Vec2f &max);
|
||||
|
||||
// read text data from file
|
||||
std::unique_ptr<std::string> read_from_disk(const std::string &path);
|
||||
|
||||
using NSVGimage_ptr = std::unique_ptr<NSVGimage, void (*)(NSVGimage*)>;
|
||||
NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, const char *units = "mm", float dpi = 96.0f);
|
||||
NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units = "mm", float dpi = 96.0f);
|
||||
NSVGimage *init_image(EmbossShape::SvgFile &svg_file);
|
||||
|
||||
/// <summary>
|
||||
/// Iterate over shapes and calculate count
|
||||
/// </summary>
|
||||
/// <param name="image">Contain pointer to first shape</param>
|
||||
/// <returns>Count of shapes</returns>
|
||||
size_t get_shapes_count(const NSVGimage &image);
|
||||
|
||||
//void save(const NSVGimage &image, std::ostream &data);
|
||||
//bool save(const NSVGimage &image, const std::string &svg_file_path);
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_NSVGUtils_hpp_
|
@ -70,24 +70,24 @@ int Point::nearest_point_index(const PointConstPtrs &points) const
|
||||
{
|
||||
int idx = -1;
|
||||
double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough
|
||||
|
||||
|
||||
for (PointConstPtrs::const_iterator it = points.begin(); it != points.end(); ++it) {
|
||||
/* If the X distance of the candidate is > than the total distance of the
|
||||
best previous candidate, we know we don't want it */
|
||||
double d = sqr<double>((*this)(0) - (*it)->x());
|
||||
if (distance != -1 && d > distance) continue;
|
||||
|
||||
|
||||
/* If the Y distance of the candidate is > than the total distance of the
|
||||
best previous candidate, we know we don't want it */
|
||||
d += sqr<double>((*this)(1) - (*it)->y());
|
||||
if (distance != -1 && d > distance) continue;
|
||||
|
||||
|
||||
idx = it - points.begin();
|
||||
distance = d;
|
||||
|
||||
|
||||
if (distance < EPSILON) break;
|
||||
}
|
||||
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
@ -142,7 +142,7 @@ Point Point::projection_onto(const MultiPoint &poly) const
|
||||
{
|
||||
Point running_projection = poly.first_point();
|
||||
double running_min = (running_projection - *this).cast<double>().norm();
|
||||
|
||||
|
||||
Lines lines = poly.lines();
|
||||
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) {
|
||||
Point point_temp = this->projection_onto(*line);
|
||||
@ -157,7 +157,7 @@ Point Point::projection_onto(const MultiPoint &poly) const
|
||||
Point Point::projection_onto(const Line &line) const
|
||||
{
|
||||
if (line.a == line.b) return line.a;
|
||||
|
||||
|
||||
/*
|
||||
(Ported from VisiLibity by Karl J. Obermeyer)
|
||||
The projection of point_temp onto the line determined by
|
||||
@ -169,12 +169,12 @@ Point Point::projection_onto(const Line &line) const
|
||||
*/
|
||||
double lx = (double)(line.b(0) - line.a(0));
|
||||
double ly = (double)(line.b(1) - line.a(1));
|
||||
double theta = ( (double)(line.b(0) - (*this)(0))*lx + (double)(line.b(1)- (*this)(1))*ly )
|
||||
double theta = ( (double)(line.b(0) - (*this)(0))*lx + (double)(line.b(1)- (*this)(1))*ly )
|
||||
/ ( sqr<double>(lx) + sqr<double>(ly) );
|
||||
|
||||
|
||||
if (0.0 <= theta && theta <= 1.0)
|
||||
return (theta * line.a.cast<coordf_t>() + (1.0-theta) * line.b.cast<coordf_t>()).cast<coord_t>();
|
||||
|
||||
|
||||
// Else pick closest endpoint.
|
||||
return ((line.a - *this).cast<double>().squaredNorm() < (line.b - *this).cast<double>().squaredNorm()) ? line.a : line.b;
|
||||
}
|
||||
@ -188,9 +188,26 @@ bool has_duplicate_points(std::vector<Point> &&pts)
|
||||
return false;
|
||||
}
|
||||
|
||||
Points collect_duplicates(Points pts /* Copy */)
|
||||
{
|
||||
std::sort(pts.begin(), pts.end());
|
||||
Points duplicits;
|
||||
const Point *prev = &pts.front();
|
||||
for (size_t i = 1; i < pts.size(); ++i) {
|
||||
const Point *act = &pts[i];
|
||||
if (*prev == *act) {
|
||||
// duplicit point
|
||||
if (!duplicits.empty() && duplicits.back() == *act) continue; // only unique duplicits
|
||||
duplicits.push_back(*act);
|
||||
}
|
||||
prev = act;
|
||||
}
|
||||
return duplicits;
|
||||
}
|
||||
|
||||
template<bool IncludeBoundary>
|
||||
BoundingBox get_extents(const Points &pts)
|
||||
{
|
||||
{
|
||||
BoundingBox out;
|
||||
BoundingBox::construct<IncludeBoundary>(out, pts.begin(), pts.end());
|
||||
return out;
|
||||
|
@ -143,6 +143,29 @@ inline std::string to_string(const Vec3d &pt) { return std::string("[") + floa
|
||||
std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t);
|
||||
Pointf3s transform(const Pointf3s& points, const Transform3d& t);
|
||||
|
||||
/// <summary>
|
||||
/// Check whether transformation matrix contains odd number of mirroring.
|
||||
/// NOTE: In code is sometime function named is_left_handed
|
||||
/// </summary>
|
||||
/// <param name="transform">Transformation to check</param>
|
||||
/// <returns>Is positive determinant</returns>
|
||||
inline bool has_reflection(const Transform3d &transform) { return transform.matrix().determinant() < 0; }
|
||||
|
||||
/// <summary>
|
||||
/// Getter on base of transformation matrix
|
||||
/// </summary>
|
||||
/// <param name="index">column index</param>
|
||||
/// <param name="transform">source transformation</param>
|
||||
/// <returns>Base of transformation matrix</returns>
|
||||
inline const Vec3d get_base(unsigned index, const Transform3d &transform) { return transform.linear().col(index); }
|
||||
inline const Vec3d get_x_base(const Transform3d &transform) { return get_base(0, transform); }
|
||||
inline const Vec3d get_y_base(const Transform3d &transform) { return get_base(1, transform); }
|
||||
inline const Vec3d get_z_base(const Transform3d &transform) { return get_base(2, transform); }
|
||||
inline const Vec3d get_base(unsigned index, const Transform3d::LinearPart &transform) { return transform.col(index); }
|
||||
inline const Vec3d get_x_base(const Transform3d::LinearPart &transform) { return get_base(0, transform); }
|
||||
inline const Vec3d get_y_base(const Transform3d::LinearPart &transform) { return get_base(1, transform); }
|
||||
inline const Vec3d get_z_base(const Transform3d::LinearPart &transform) { return get_base(2, transform); }
|
||||
|
||||
template<int N, class T> using Vec = Eigen::Matrix<T, N, 1, Eigen::DontAlign, N, 1>;
|
||||
|
||||
class Point : public Vec2crd
|
||||
@ -175,7 +198,7 @@ public:
|
||||
Point& operator-=(const Point& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); return *this; }
|
||||
Point& operator*=(const double &rhs) { this->x() = coord_t(this->x() * rhs); this->y() = coord_t(this->y() * rhs); return *this; }
|
||||
Point operator*(const double &rhs) { return Point(this->x() * rhs, this->y() * rhs); }
|
||||
bool both_comp(const Point &rhs, const std::string& op) {
|
||||
bool both_comp(const Point &rhs, const std::string& op) {
|
||||
if (op == ">")
|
||||
return this->x() > rhs.x() && this->y() > rhs.y();
|
||||
else if (op == "<")
|
||||
@ -215,8 +238,8 @@ public:
|
||||
Point projection_onto(const Line &line) const;
|
||||
};
|
||||
|
||||
inline bool operator<(const Point &l, const Point &r)
|
||||
{
|
||||
inline bool operator<(const Point &l, const Point &r)
|
||||
{
|
||||
return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y());
|
||||
}
|
||||
|
||||
@ -311,6 +334,9 @@ inline bool has_duplicate_successive_points_closed(const std::vector<Point> &pts
|
||||
return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back());
|
||||
}
|
||||
|
||||
// Collect adjecent(duplicit points)
|
||||
Points collect_duplicates(Points pts /* Copy */);
|
||||
|
||||
inline bool shorter_then(const Point& p0, const coord_t len)
|
||||
{
|
||||
if (p0.x() > len || p0.x() < -len)
|
||||
@ -343,7 +369,7 @@ struct PointHash {
|
||||
template<typename ValueType, typename PointAccessor> class ClosestPointInRadiusLookup
|
||||
{
|
||||
public:
|
||||
ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) :
|
||||
ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) :
|
||||
m_search_radius(search_radius), m_point_accessor(point_accessor), m_grid_log2(0)
|
||||
{
|
||||
// Resolution of a grid, twice the search radius + some epsilon.
|
||||
@ -432,8 +458,8 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ?
|
||||
std::make_pair(value_min, dist_min) :
|
||||
return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ?
|
||||
std::make_pair(value_min, dist_min) :
|
||||
std::make_pair(nullptr, std::numeric_limits<double>::max());
|
||||
}
|
||||
|
||||
@ -553,13 +579,35 @@ inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) {
|
||||
assert(aligned <= coord);
|
||||
return aligned;
|
||||
}
|
||||
inline Point align_to_grid(Point coord, Point spacing)
|
||||
inline Point align_to_grid(Point coord, Point spacing)
|
||||
{ return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); }
|
||||
inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base)
|
||||
inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base)
|
||||
{ return base + align_to_grid(coord - base, spacing); }
|
||||
inline Point align_to_grid(Point coord, Point spacing, Point base)
|
||||
{ return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); }
|
||||
|
||||
// MinMaxLimits
|
||||
template<typename T> struct MinMax
|
||||
{
|
||||
T min;
|
||||
T max;
|
||||
};
|
||||
template<typename T> static bool apply(std::optional<T> &val, const MinMax<T> &limit)
|
||||
{
|
||||
if (!val.has_value()) return false;
|
||||
return apply<T>(*val, limit);
|
||||
}
|
||||
template<typename T> static bool apply(T &val, const MinMax<T> &limit)
|
||||
{
|
||||
if (val > limit.max) {
|
||||
val = limit.max;
|
||||
return true;
|
||||
}
|
||||
if (val < limit.min) {
|
||||
val = limit.min;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
||||
// start Boost
|
||||
@ -568,16 +616,16 @@ inline Point align_to_grid(Point coord, Point spacing, Point base)
|
||||
namespace boost { namespace polygon {
|
||||
template <>
|
||||
struct geometry_concept<Slic3r::Point> { using type = point_concept; };
|
||||
|
||||
|
||||
template <>
|
||||
struct point_traits<Slic3r::Point> {
|
||||
using coordinate_type = coord_t;
|
||||
|
||||
|
||||
static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) {
|
||||
return static_cast<coordinate_type>(point((orient == HORIZONTAL) ? 0 : 1));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <>
|
||||
struct point_mutable_traits<Slic3r::Point> {
|
||||
using coordinate_type = coord_t;
|
||||
|
@ -110,7 +110,7 @@ Polygons Polygon::simplify(double tolerance) const
|
||||
points.push_back(points.front());
|
||||
Polygon p(MultiPoint::_douglas_peucker(points, tolerance));
|
||||
p.points.pop_back();
|
||||
|
||||
|
||||
Polygons pp;
|
||||
pp.push_back(p);
|
||||
return simplify_polygons(pp);
|
||||
@ -125,7 +125,7 @@ void Polygon::triangulate_convex(Polygons* polygons) const
|
||||
p.points.push_back(this->points.front());
|
||||
p.points.push_back(*(it-1));
|
||||
p.points.push_back(*it);
|
||||
|
||||
|
||||
// this should be replaced with a more efficient call to a merge_collinear_segments() method
|
||||
if (p.area() > 0) polygons->push_back(p);
|
||||
}
|
||||
@ -241,7 +241,7 @@ Points filter_points_by_vectors(const Points &poly, FilterFn filter)
|
||||
v1 = v2;
|
||||
p1 = p2;
|
||||
}
|
||||
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -373,8 +373,8 @@ Polygon Polygon::transform(const Transform3d& trafo) const
|
||||
return dstpoly;
|
||||
}
|
||||
|
||||
BoundingBox get_extents(const Polygon &poly)
|
||||
{
|
||||
BoundingBox get_extents(const Polygon &poly)
|
||||
{
|
||||
return poly.bounding_box();
|
||||
}
|
||||
|
||||
@ -389,8 +389,8 @@ BoundingBox get_extents(const Polygons &polygons)
|
||||
return bb;
|
||||
}
|
||||
|
||||
BoundingBox get_extents_rotated(const Polygon &poly, double angle)
|
||||
{
|
||||
BoundingBox get_extents_rotated(const Polygon &poly, double angle)
|
||||
{
|
||||
return get_extents_rotated(poly.points, angle);
|
||||
}
|
||||
|
||||
@ -454,6 +454,32 @@ bool has_duplicate_points(const Polygons &polys)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool remove_same_neighbor(Polygon &polygon)
|
||||
{
|
||||
Points &points = polygon.points;
|
||||
if (points.empty()) return false;
|
||||
auto last = std::unique(points.begin(), points.end());
|
||||
|
||||
// remove first and last neighbor duplication
|
||||
if (const Point &last_point = *(last - 1); last_point == points.front()) { --last; }
|
||||
|
||||
// no duplicits
|
||||
if (last == points.end()) return false;
|
||||
|
||||
points.erase(last, points.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool remove_same_neighbor(Polygons &polygons)
|
||||
{
|
||||
if (polygons.empty()) return false;
|
||||
bool exist = false;
|
||||
for (Polygon &polygon : polygons) exist |= remove_same_neighbor(polygon);
|
||||
// remove empty polygons
|
||||
polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const Polygon &p) { return p.points.size() <= 2; }), polygons.end());
|
||||
return exist;
|
||||
}
|
||||
|
||||
static inline bool is_stick(const Point &p1, const Point &p2, const Point &p3)
|
||||
{
|
||||
Point v1 = p2 - p1;
|
||||
@ -509,7 +535,7 @@ bool remove_sticks(Polygons &polys)
|
||||
for (size_t i = 0; i < polys.size(); ++ i) {
|
||||
modified |= remove_sticks(polys[i]);
|
||||
if (polys[i].points.size() >= 3) {
|
||||
if (j < i)
|
||||
if (j < i)
|
||||
std::swap(polys[i].points, polys[j].points);
|
||||
++ j;
|
||||
}
|
||||
@ -525,7 +551,7 @@ bool remove_degenerate(Polygons &polys)
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < polys.size(); ++ i) {
|
||||
if (polys[i].points.size() >= 3) {
|
||||
if (j < i)
|
||||
if (j < i)
|
||||
std::swap(polys[i].points, polys[j].points);
|
||||
++ j;
|
||||
} else
|
||||
@ -542,7 +568,7 @@ bool remove_small(Polygons &polys, double min_area)
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < polys.size(); ++ i) {
|
||||
if (std::abs(polys[i].area()) >= min_area) {
|
||||
if (j < i)
|
||||
if (j < i)
|
||||
std::swap(polys[i].points, polys[j].points);
|
||||
++ j;
|
||||
} else
|
||||
@ -646,7 +672,7 @@ bool overlaps(const Polygons& polys1, const Polygons& polys2)
|
||||
|
||||
bool contains(const Polygon &polygon, const Point &p, bool border_result)
|
||||
{
|
||||
if (const int poly_count_inside = ClipperLib::PointInPolygon(p, polygon.points);
|
||||
if (const int poly_count_inside = ClipperLib::PointInPolygon(p, polygon.points);
|
||||
poly_count_inside == -1)
|
||||
return border_result;
|
||||
else
|
||||
|
@ -27,7 +27,7 @@ public:
|
||||
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
|
||||
Polygon(const Polygon &other) : MultiPoint(other.points) {}
|
||||
Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {}
|
||||
static Polygon new_scale(const std::vector<Vec2d> &points) {
|
||||
static Polygon new_scale(const std::vector<Vec2d> &points) {
|
||||
Polygon pgn;
|
||||
pgn.points.reserve(points.size());
|
||||
for (const Vec2d &pt : points)
|
||||
@ -51,7 +51,7 @@ public:
|
||||
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
|
||||
Polyline split_at_first_point() const { return this->split_at_index(0); }
|
||||
Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); }
|
||||
|
||||
|
||||
static double area(const Points &pts);
|
||||
double area() const;
|
||||
bool is_counter_clockwise() const;
|
||||
@ -86,7 +86,7 @@ public:
|
||||
// Projection of a point onto the polygon.
|
||||
Point point_projection(const Point &point) const;
|
||||
std::vector<float> parameter_by_length() const;
|
||||
|
||||
|
||||
//BBS
|
||||
Polygon transform(const Transform3d& trafo) const;
|
||||
|
||||
@ -112,6 +112,10 @@ inline bool has_duplicate_points(Polygon &&poly) { return has_duplicate_poi
|
||||
inline bool has_duplicate_points(const Polygon &poly) { return has_duplicate_points(poly.points); }
|
||||
bool has_duplicate_points(const Polygons &polys);
|
||||
|
||||
// Return True when erase some otherwise False.
|
||||
bool remove_same_neighbor(Polygon &polygon);
|
||||
bool remove_same_neighbor(Polygons &polygons);
|
||||
|
||||
inline double total_length(const Polygons &polylines) {
|
||||
double total = 0;
|
||||
for (Polygons::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
|
||||
@ -142,7 +146,7 @@ void remove_collinear(Polygons &polys);
|
||||
// Append a vector of polygons at the end of another vector of polygons.
|
||||
inline void polygons_append(Polygons &dst, const Polygons &src) { dst.insert(dst.end(), src.begin(), src.end()); }
|
||||
|
||||
inline void polygons_append(Polygons &dst, Polygons &&src)
|
||||
inline void polygons_append(Polygons &dst, Polygons &&src)
|
||||
{
|
||||
if (dst.empty()) {
|
||||
dst = std::move(src);
|
||||
@ -179,7 +183,7 @@ inline size_t count_points(const Polygons &polys) {
|
||||
return n_points;
|
||||
}
|
||||
|
||||
inline Points to_points(const Polygons &polys)
|
||||
inline Points to_points(const Polygons &polys)
|
||||
{
|
||||
Points points;
|
||||
points.reserve(count_points(polys));
|
||||
@ -188,7 +192,7 @@ inline Points to_points(const Polygons &polys)
|
||||
return points;
|
||||
}
|
||||
|
||||
inline Lines to_lines(const Polygon &poly)
|
||||
inline Lines to_lines(const Polygon &poly)
|
||||
{
|
||||
Lines lines;
|
||||
lines.reserve(poly.points.size());
|
||||
@ -200,7 +204,7 @@ inline Lines to_lines(const Polygon &poly)
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline Lines to_lines(const Polygons &polys)
|
||||
inline Lines to_lines(const Polygons &polys)
|
||||
{
|
||||
Lines lines;
|
||||
lines.reserve(count_points(polys));
|
||||
@ -245,6 +249,17 @@ inline Polylines to_polylines(Polygons &&polys)
|
||||
return polylines;
|
||||
}
|
||||
|
||||
// close polyline to polygon (connect first and last point in polyline)
|
||||
inline Polygons to_polygons(const Polylines &polylines)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(polylines.size());
|
||||
for (const Polyline &polyline : polylines) {
|
||||
if (polyline.size()) out.emplace_back(polyline.points);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline Polygons to_polygons(const std::vector<Points> &paths)
|
||||
{
|
||||
Polygons out;
|
||||
@ -270,6 +285,21 @@ bool polygons_match(const Polygon &l, const Polygon &r);
|
||||
Polygon make_circle(double radius, double error);
|
||||
Polygon make_circle_num_segments(double radius, size_t num_segments);
|
||||
|
||||
/// <summary>
|
||||
/// Define point laying on polygon
|
||||
/// keep index of polygon line and point coordinate
|
||||
/// </summary>
|
||||
struct PolygonPoint
|
||||
{
|
||||
// index of line inside of polygon
|
||||
// 0 .. from point polygon[0] to polygon[1]
|
||||
size_t index;
|
||||
|
||||
// Point, which lay on line defined by index
|
||||
Point point;
|
||||
};
|
||||
using PolygonPoints = std::vector<PolygonPoint>;
|
||||
|
||||
bool overlaps(const Polygons& polys1, const Polygons& polys2);
|
||||
} // Slic3r
|
||||
|
||||
@ -322,7 +352,7 @@ namespace boost { namespace polygon {
|
||||
return polygon;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <>
|
||||
struct geometry_concept<Slic3r::Polygons> { typedef polygon_set_concept type; };
|
||||
|
||||
|
@ -13,7 +13,7 @@ const Point& Polyline::leftmost_point() const
|
||||
{
|
||||
const Point *p = &this->points.front();
|
||||
for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++ it) {
|
||||
if (it->x() < p->x())
|
||||
if (it->x() < p->x())
|
||||
p = &(*it);
|
||||
}
|
||||
return *p;
|
||||
@ -122,7 +122,7 @@ Points Polyline::equally_spaced_points(double distance) const
|
||||
Points points;
|
||||
points.emplace_back(this->first_point());
|
||||
double len = 0;
|
||||
|
||||
|
||||
for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) {
|
||||
Vec2d p1 = (it-1)->cast<double>();
|
||||
Vec2d v = it->cast<double>() - p1;
|
||||
@ -172,7 +172,7 @@ Polylines Polyline::equally_spaced_lines(double distance) const
|
||||
if (len == distance) {
|
||||
line.append(*it);
|
||||
lines.emplace_back(line);
|
||||
|
||||
|
||||
line.clear();
|
||||
line.append(*it);
|
||||
len = 0;
|
||||
@ -202,7 +202,7 @@ template <class T>
|
||||
void Polyline::simplify_by_visibility(const T &area)
|
||||
{
|
||||
Points &pp = this->points;
|
||||
|
||||
|
||||
size_t s = 0;
|
||||
bool did_erase = false;
|
||||
for (size_t i = s+2; i < pp.size(); i = s + 2) {
|
||||
@ -232,7 +232,7 @@ void Polyline::split_at(Point &point, Polyline* p1, Polyline* p2) const
|
||||
point = p1->is_valid()? p1->last_point(): p2->first_point();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//1 find the line to split at
|
||||
size_t line_idx = 0;
|
||||
Point p = this->first_point();
|
||||
@ -530,6 +530,30 @@ BoundingBox get_extents(const Polylines &polylines)
|
||||
return bb;
|
||||
}
|
||||
|
||||
// Return True when erase some otherwise False.
|
||||
bool remove_same_neighbor(Polyline &polyline)
|
||||
{
|
||||
Points &points = polyline.points;
|
||||
if (points.empty()) return false;
|
||||
auto last = std::unique(points.begin(), points.end());
|
||||
|
||||
// no duplicits
|
||||
if (last == points.end()) return false;
|
||||
|
||||
points.erase(last, points.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool remove_same_neighbor(Polylines &polylines)
|
||||
{
|
||||
if (polylines.empty()) return false;
|
||||
bool exist = false;
|
||||
for (Polyline &polyline : polylines) exist |= remove_same_neighbor(polyline);
|
||||
// remove empty polylines
|
||||
polylines.erase(std::remove_if(polylines.begin(), polylines.end(), [](const Polyline &p) { return p.points.size() <= 1; }), polylines.end());
|
||||
return exist;
|
||||
}
|
||||
|
||||
const Point& leftmost_point(const Polylines &polylines)
|
||||
{
|
||||
if (polylines.empty())
|
||||
@ -550,7 +574,7 @@ bool remove_degenerate(Polylines &polylines)
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < polylines.size(); ++ i) {
|
||||
if (polylines[i].points.size() >= 2) {
|
||||
if (j < i)
|
||||
if (j < i)
|
||||
std::swap(polylines[i].points, polylines[j].points);
|
||||
++ j;
|
||||
} else
|
||||
|
@ -21,7 +21,7 @@ public:
|
||||
Polyline() {};
|
||||
Polyline(const Polyline& other) : MultiPoint(other.points), fitting_result(other.fitting_result) {}
|
||||
Polyline(Polyline &&other) : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {}
|
||||
Polyline(std::initializer_list<Point> list) : MultiPoint(list) {
|
||||
Polyline(std::initializer_list<Point> list) : MultiPoint(list) {
|
||||
fitting_result.clear();
|
||||
}
|
||||
explicit Polyline(const Point &p1, const Point &p2) {
|
||||
@ -55,7 +55,7 @@ public:
|
||||
pl.fitting_result.clear();
|
||||
return pl;
|
||||
}
|
||||
|
||||
|
||||
void append(const Point &point) {
|
||||
//BBS: don't need to append same point
|
||||
if (!this->empty() && this->last_point() == point)
|
||||
@ -128,7 +128,7 @@ public:
|
||||
std::vector<PathFittingData> fitting_result;
|
||||
//BBS: simplify points by arc fitting
|
||||
void simplify_by_fitting_arc(double tolerance);
|
||||
//BBS:
|
||||
//BBS:
|
||||
Polylines equally_spaced_lines(double distance) const;
|
||||
|
||||
private:
|
||||
@ -154,6 +154,10 @@ public:
|
||||
extern BoundingBox get_extents(const Polyline &polyline);
|
||||
extern BoundingBox get_extents(const Polylines &polylines);
|
||||
|
||||
// Return True when erase some otherwise False.
|
||||
bool remove_same_neighbor(Polyline &polyline);
|
||||
bool remove_same_neighbor(Polylines &polylines);
|
||||
|
||||
inline double total_length(const Polylines &polylines) {
|
||||
double total = 0;
|
||||
for (const Polyline &pl : polylines)
|
||||
@ -161,7 +165,7 @@ inline double total_length(const Polylines &polylines) {
|
||||
return total;
|
||||
}
|
||||
|
||||
inline Lines to_lines(const Polyline &poly)
|
||||
inline Lines to_lines(const Polyline &poly)
|
||||
{
|
||||
Lines lines;
|
||||
if (poly.points.size() >= 2) {
|
||||
@ -172,7 +176,7 @@ inline Lines to_lines(const Polyline &poly)
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline Lines to_lines(const Polylines &polys)
|
||||
inline Lines to_lines(const Polylines &polys)
|
||||
{
|
||||
size_t n_lines = 0;
|
||||
for (size_t i = 0; i < polys.size(); ++ i)
|
||||
@ -206,12 +210,12 @@ inline Polylines to_polylines(std::vector<Points> &&paths)
|
||||
return out;
|
||||
}
|
||||
|
||||
inline void polylines_append(Polylines &dst, const Polylines &src)
|
||||
{
|
||||
inline void polylines_append(Polylines &dst, const Polylines &src)
|
||||
{
|
||||
dst.insert(dst.end(), src.begin(), src.end());
|
||||
}
|
||||
|
||||
inline void polylines_append(Polylines &dst, Polylines &&src)
|
||||
inline void polylines_append(Polylines &dst, Polylines &&src)
|
||||
{
|
||||
if (dst.empty()) {
|
||||
dst = std::move(src);
|
||||
|
187
src/libslic3r/TextConfiguration.hpp
Normal file
@ -0,0 +1,187 @@
|
||||
#ifndef slic3r_TextConfiguration_hpp_
|
||||
#define slic3r_TextConfiguration_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/types/optional.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/archives/binary.hpp>
|
||||
#include "Point.hpp" // Transform3d
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/// <summary>
|
||||
/// User modifiable property of text style
|
||||
/// NOTE: OnEdit fix serializations: EmbossStylesSerializable, TextConfigurationSerialization
|
||||
/// </summary>
|
||||
struct FontProp
|
||||
{
|
||||
// define extra space between letters, negative mean closer letter
|
||||
// When not set value is zero and is not stored
|
||||
std::optional<int> char_gap; // [in font point]
|
||||
|
||||
// define extra space between lines, negative mean closer lines
|
||||
// When not set value is zero and is not stored
|
||||
std::optional<int> line_gap; // [in font point]
|
||||
|
||||
// positive value mean wider character shape
|
||||
// negative value mean tiner character shape
|
||||
// When not set value is zero and is not stored
|
||||
std::optional<float> boldness; // [in mm]
|
||||
|
||||
// positive value mean italic of character (CW)
|
||||
// negative value mean CCW skew (unItalic)
|
||||
// When not set value is zero and is not stored
|
||||
std::optional<float> skew; // [ration x:y]
|
||||
|
||||
// Parameter for True Type Font collections
|
||||
// Select index of font in collection
|
||||
std::optional<unsigned int> collection_number;
|
||||
|
||||
// Distiguish projection per glyph
|
||||
bool per_glyph;
|
||||
|
||||
// NOTE: way of serialize to 3mf force that zero must be default value
|
||||
enum class HorizontalAlign { left = 0, center, right };
|
||||
enum class VerticalAlign { top = 0, center, bottom };
|
||||
using Align = std::pair<HorizontalAlign, VerticalAlign>;
|
||||
// change pivot of text
|
||||
// When not set, center is used and is not stored
|
||||
Align align = Align(HorizontalAlign::center, VerticalAlign::center);
|
||||
|
||||
//////
|
||||
// Duplicit data to wxFontDescriptor
|
||||
// used for store/load .3mf file
|
||||
//////
|
||||
|
||||
// Height of text line (letters)
|
||||
// duplicit to wxFont::PointSize
|
||||
float size_in_mm; // [in mm]
|
||||
|
||||
// Additional data about font to be able to find substitution,
|
||||
// when same font is not installed
|
||||
std::optional<std::string> family;
|
||||
std::optional<std::string> face_name;
|
||||
std::optional<std::string> style;
|
||||
std::optional<std::string> weight;
|
||||
|
||||
/// <summary>
|
||||
/// Only constructor with restricted values
|
||||
/// </summary>
|
||||
/// <param name="line_height">Y size of text [in mm]</param>
|
||||
/// <param name="depth">Z size of text [in mm]</param>
|
||||
FontProp(float line_height = 10.f) : size_in_mm(line_height), per_glyph(false)
|
||||
{}
|
||||
|
||||
bool operator==(const FontProp& other) const {
|
||||
return
|
||||
char_gap == other.char_gap &&
|
||||
line_gap == other.line_gap &&
|
||||
per_glyph == other.per_glyph &&
|
||||
align == other.align &&
|
||||
is_approx(size_in_mm, other.size_in_mm) &&
|
||||
is_approx(boldness, other.boldness) &&
|
||||
is_approx(skew, other.skew);
|
||||
}
|
||||
|
||||
// undo / redo stack recovery
|
||||
template<class Archive> void save(Archive &ar) const
|
||||
{
|
||||
ar(size_in_mm, per_glyph, align.first, align.second);
|
||||
cereal::save(ar, char_gap);
|
||||
cereal::save(ar, line_gap);
|
||||
cereal::save(ar, boldness);
|
||||
cereal::save(ar, skew);
|
||||
cereal::save(ar, collection_number);
|
||||
}
|
||||
template<class Archive> void load(Archive &ar)
|
||||
{
|
||||
ar(size_in_mm, per_glyph, align.first, align.second);
|
||||
cereal::load(ar, char_gap);
|
||||
cereal::load(ar, line_gap);
|
||||
cereal::load(ar, boldness);
|
||||
cereal::load(ar, skew);
|
||||
cereal::load(ar, collection_number);
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Style of embossed text
|
||||
/// (Path + Type) must define how to open font for using on different OS
|
||||
/// NOTE: OnEdit fix serializations: EmbossStylesSerializable, TextConfigurationSerialization
|
||||
/// </summary>
|
||||
struct EmbossStyle
|
||||
{
|
||||
// Human readable name of style it is shown in GUI
|
||||
std::string name;
|
||||
|
||||
// Define how to open font
|
||||
// Meaning depend on type
|
||||
std::string path;
|
||||
|
||||
enum class Type;
|
||||
// Define what is stored in path
|
||||
Type type { Type::undefined };
|
||||
|
||||
// User modification of font style
|
||||
FontProp prop;
|
||||
|
||||
// when name is empty than Font item was loaded from .3mf file
|
||||
// and potentionaly it is not reproducable
|
||||
// define data stored in path
|
||||
// when wx change way of storing add new descriptor Type
|
||||
enum class Type {
|
||||
undefined = 0,
|
||||
|
||||
// wx font descriptors are platform dependent
|
||||
// path is font descriptor generated by wxWidgets
|
||||
wx_win_font_descr, // on Windows
|
||||
wx_lin_font_descr, // on Linux
|
||||
wx_mac_font_descr, // on Max OS
|
||||
|
||||
// TrueTypeFont file loacation on computer
|
||||
// for privacy: only filename is stored into .3mf
|
||||
file_path
|
||||
};
|
||||
|
||||
bool operator==(const EmbossStyle &other) const
|
||||
{
|
||||
return
|
||||
type == other.type &&
|
||||
prop == other.prop &&
|
||||
name == other.name &&
|
||||
path == other.path
|
||||
;
|
||||
}
|
||||
|
||||
// undo / redo stack recovery
|
||||
template<class Archive> void serialize(Archive &ar){ ar(name, path, type, prop); }
|
||||
};
|
||||
|
||||
// Emboss style name inside vector is unique
|
||||
// It is not map beacuse items has own order (view inside of slect)
|
||||
// It is stored into AppConfig by EmbossStylesSerializable
|
||||
using EmbossStyles = std::vector<EmbossStyle>;
|
||||
|
||||
/// <summary>
|
||||
/// Define how to create 'Text volume'
|
||||
/// It is stored into .3mf by TextConfigurationSerialization
|
||||
/// It is part of ModelVolume optional data
|
||||
/// </summary>
|
||||
struct TextConfiguration
|
||||
{
|
||||
// Style of embossed text
|
||||
EmbossStyle style;
|
||||
|
||||
// Embossed text value
|
||||
std::string text = "None";
|
||||
|
||||
// undo / redo stack recovery
|
||||
template<class Archive> void serialize(Archive &ar) { ar(style, text); }
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_TextConfiguration_hpp_
|
21
src/libslic3r/Timer.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "Timer.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
Slic3r::Timer::Timer(const std::string &name) : m_name(name), m_start(steady_clock::now()) {}
|
||||
|
||||
Slic3r::Timer::~Timer()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "Timer '" << m_name << "' spend " <<
|
||||
duration_cast<milliseconds>(steady_clock::now() - m_start).count() << "ms";
|
||||
}
|
||||
|
||||
|
||||
namespace Slic3r::Timing {
|
||||
|
||||
void TimeLimitAlarm::report_time_exceeded() const {
|
||||
BOOST_LOG_TRIVIAL(error) << "Time limit exceeded for " << m_limit_exceeded_message << ": " << m_timer.elapsed_seconds() << "s";
|
||||
}
|
||||
|
||||
}
|
92
src/libslic3r/Timer.hpp
Normal file
@ -0,0 +1,92 @@
|
||||
#ifndef libslic3r_Timer_hpp_
|
||||
#define libslic3r_Timer_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/// <summary>
|
||||
/// Instance of this class is used for measure time consumtion
|
||||
/// of block code until instance is alive and write result to debug output
|
||||
/// </summary>
|
||||
class Timer
|
||||
{
|
||||
std::string m_name;
|
||||
std::chrono::steady_clock::time_point m_start;
|
||||
public:
|
||||
/// <summary>
|
||||
/// name describe timer
|
||||
/// </summary>
|
||||
/// <param name="name">Describe timer in consol log</param>
|
||||
Timer(const std::string& name);
|
||||
|
||||
/// <summary>
|
||||
/// name describe timer
|
||||
/// </summary>
|
||||
~Timer();
|
||||
};
|
||||
|
||||
namespace Timing {
|
||||
|
||||
// Timing code from Catch2 unit testing library
|
||||
static inline uint64_t nanoseconds_since_epoch() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
// Timing code from Catch2 unit testing library
|
||||
class Timer {
|
||||
public:
|
||||
void start() {
|
||||
m_nanoseconds = nanoseconds_since_epoch();
|
||||
}
|
||||
uint64_t elapsed_nanoseconds() const {
|
||||
return nanoseconds_since_epoch() - m_nanoseconds;
|
||||
}
|
||||
uint64_t elapsed_microseconds() const {
|
||||
return elapsed_nanoseconds() / 1000;
|
||||
}
|
||||
unsigned int elapsed_milliseconds() const {
|
||||
return static_cast<unsigned int>(elapsed_microseconds()/1000);
|
||||
}
|
||||
double elapsed_seconds() const {
|
||||
return elapsed_microseconds() / 1000000.0;
|
||||
}
|
||||
private:
|
||||
uint64_t m_nanoseconds = 0;
|
||||
};
|
||||
|
||||
// Emits a Boost::log error if the life time of this timing object exceeds a limit.
|
||||
class TimeLimitAlarm {
|
||||
public:
|
||||
TimeLimitAlarm(uint64_t time_limit_nanoseconds, std::string_view limit_exceeded_message) :
|
||||
m_time_limit_nanoseconds(time_limit_nanoseconds), m_limit_exceeded_message(limit_exceeded_message) {
|
||||
m_timer.start();
|
||||
}
|
||||
~TimeLimitAlarm() {
|
||||
auto elapsed = m_timer.elapsed_nanoseconds();
|
||||
if (elapsed > m_time_limit_nanoseconds)
|
||||
this->report_time_exceeded();
|
||||
}
|
||||
static TimeLimitAlarm new_nanos(uint64_t time_limit_nanoseconds, std::string_view limit_exceeded_message) {
|
||||
return TimeLimitAlarm(time_limit_nanoseconds, limit_exceeded_message);
|
||||
}
|
||||
static TimeLimitAlarm new_milis(uint64_t time_limit_milis, std::string_view limit_exceeded_message) {
|
||||
return TimeLimitAlarm(uint64_t(time_limit_milis) * 1000000l, limit_exceeded_message);
|
||||
}
|
||||
static TimeLimitAlarm new_seconds(uint64_t time_limit_seconds, std::string_view limit_exceeded_message) {
|
||||
return TimeLimitAlarm(uint64_t(time_limit_seconds) * 1000000000l, limit_exceeded_message);
|
||||
}
|
||||
private:
|
||||
void report_time_exceeded() const;
|
||||
|
||||
Timer m_timer;
|
||||
uint64_t m_time_limit_nanoseconds;
|
||||
std::string_view m_limit_exceeded_message;
|
||||
};
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // libslic3r_Timer_hpp_
|
329
src/libslic3r/Triangulation.cpp
Normal file
@ -0,0 +1,329 @@
|
||||
#include "Triangulation.hpp"
|
||||
#include "IntersectionPoints.hpp"
|
||||
#include <boost/next_prior.hpp>
|
||||
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
|
||||
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
|
||||
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
|
||||
#include <CGAL/spatial_sort.h>
|
||||
|
||||
using namespace Slic3r;
|
||||
namespace priv{
|
||||
inline void insert_edges(Triangulation::HalfEdges &edges, uint32_t &offset, const Polygon &polygon, const Triangulation::Changes& changes) {
|
||||
const Points &pts = polygon.points;
|
||||
uint32_t size = static_cast<uint32_t>(pts.size());
|
||||
uint32_t last_index = offset + size - 1;
|
||||
uint32_t prev_index = changes[last_index];
|
||||
for (uint32_t i = 0; i < size; ++i) {
|
||||
uint32_t index = changes[offset + i];
|
||||
// when duplicit points are neighbor
|
||||
if (prev_index == index) continue;
|
||||
edges.push_back({prev_index, index});
|
||||
prev_index = index;
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
|
||||
inline void insert_edges(Triangulation::HalfEdges &edges, uint32_t &offset, const Polygon &polygon) {
|
||||
const Points &pts = polygon.points;
|
||||
uint32_t size = static_cast<uint32_t>(pts.size());
|
||||
uint32_t prev_index = offset + size - 1;
|
||||
for (uint32_t i = 0; i < size; ++i) {
|
||||
uint32_t index = offset + i;
|
||||
edges.push_back({prev_index, index});
|
||||
prev_index = index;
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
|
||||
inline bool has_bidirectional_constrained(
|
||||
const Triangulation::HalfEdges &constrained)
|
||||
{
|
||||
for (const auto &c : constrained) {
|
||||
auto key = std::make_pair(c.second, c.first);
|
||||
auto it = std::lower_bound(constrained.begin(), constrained.end(),
|
||||
key);
|
||||
if (it != constrained.end() && *it == key) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool is_unique(const Points &points) {
|
||||
Points pts = points; // copy
|
||||
std::sort(pts.begin(), pts.end());
|
||||
auto it = std::adjacent_find(pts.begin(), pts.end());
|
||||
return it == pts.end();
|
||||
}
|
||||
|
||||
inline bool has_self_intersection(
|
||||
const Points &points,
|
||||
const Triangulation::HalfEdges &constrained_half_edges)
|
||||
{
|
||||
Lines lines;
|
||||
lines.reserve(constrained_half_edges.size());
|
||||
for (const auto &he : constrained_half_edges)
|
||||
lines.emplace_back(points[he.first], points[he.second]);
|
||||
return !get_intersections(lines).empty();
|
||||
}
|
||||
|
||||
} // namespace priv
|
||||
|
||||
//#define VISUALIZE_TRIANGULATION
|
||||
#ifdef VISUALIZE_TRIANGULATION
|
||||
#include "admesh/stl.h" // indexed triangle set
|
||||
static void visualize(const Points &points,
|
||||
const Triangulation::Indices &indices,
|
||||
const char *filename)
|
||||
{
|
||||
// visualize
|
||||
indexed_triangle_set its;
|
||||
its.vertices.reserve(points.size());
|
||||
for (const Point &p : points) its.vertices.emplace_back(p.x(), p.y(), 0.);
|
||||
its.indices = indices;
|
||||
its_write_obj(its, filename);
|
||||
}
|
||||
#endif // VISUALIZE_TRIANGULATION
|
||||
|
||||
Triangulation::Indices Triangulation::triangulate(const Points &points,
|
||||
const HalfEdges &constrained_half_edges)
|
||||
{
|
||||
assert(!points.empty());
|
||||
assert(!constrained_half_edges.empty());
|
||||
// constrained must be sorted
|
||||
assert(std::is_sorted(constrained_half_edges.begin(),
|
||||
constrained_half_edges.end()));
|
||||
// check that there is no duplicit constrained edge
|
||||
assert(std::adjacent_find(constrained_half_edges.begin(), constrained_half_edges.end()) == constrained_half_edges.end());
|
||||
// edges can NOT contain bidirectional constrained
|
||||
assert(!priv::has_bidirectional_constrained(constrained_half_edges));
|
||||
// check that there is only unique poistion of points
|
||||
assert(priv::is_unique(points));
|
||||
assert(!priv::has_self_intersection(points, constrained_half_edges));
|
||||
// use cgal triangulation
|
||||
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
|
||||
using Vb = CGAL::Triangulation_vertex_base_with_info_2<uint32_t, K>;
|
||||
using Fb = CGAL::Constrained_triangulation_face_base_2<K>;
|
||||
using Tds = CGAL::Triangulation_data_structure_2<Vb, Fb>;
|
||||
using CDT = CGAL::Constrained_Delaunay_triangulation_2<K, Tds, CGAL::Exact_predicates_tag>;
|
||||
|
||||
// construct a constrained triangulation
|
||||
CDT cdt;
|
||||
{
|
||||
std::vector<CDT::Vertex_handle> vertices_handle(points.size()); // for constriants
|
||||
using Point_with_ord = std::pair<CDT::Point, size_t>;
|
||||
using SearchTrait = CGAL::Spatial_sort_traits_adapter_2
|
||||
<K, CGAL::First_of_pair_property_map<Point_with_ord> >;
|
||||
|
||||
std::vector<Point_with_ord> cdt_points;
|
||||
cdt_points.reserve(points.size());
|
||||
size_t ord = 0;
|
||||
for (const auto &p : points)
|
||||
cdt_points.emplace_back(std::make_pair(CDT::Point{p.x(), p.y()}, ord++));
|
||||
|
||||
SearchTrait st;
|
||||
CGAL::spatial_sort(cdt_points.begin(), cdt_points.end(), st);
|
||||
CDT::Face_handle f;
|
||||
for (const auto& p : cdt_points) {
|
||||
auto handle = cdt.insert(p.first, f);
|
||||
handle->info() = p.second;
|
||||
vertices_handle[p.second] = handle;
|
||||
f = handle->face();
|
||||
}
|
||||
|
||||
// Constrain the triangulation.
|
||||
for (const HalfEdge &edge : constrained_half_edges)
|
||||
cdt.insert_constraint(vertices_handle[edge.first], vertices_handle[edge.second]);
|
||||
}
|
||||
|
||||
auto faces = cdt.finite_face_handles();
|
||||
|
||||
// Unmark constrained edges of outside faces.
|
||||
size_t num_faces = 0;
|
||||
for (CDT::Face_handle fh : faces) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!fh->is_constrained(i)) continue;
|
||||
auto key = std::make_pair(fh->vertex((i + 2) % 3)->info(), fh->vertex((i + 1) % 3)->info());
|
||||
auto it = std::lower_bound(constrained_half_edges.begin(), constrained_half_edges.end(), key);
|
||||
if (it == constrained_half_edges.end() || *it != key) continue;
|
||||
// This face contains a constrained edge and it is outside.
|
||||
for (int j = 0; j < 3; ++ j)
|
||||
fh->set_constraint(j, false);
|
||||
--num_faces;
|
||||
break;
|
||||
}
|
||||
++num_faces;
|
||||
}
|
||||
|
||||
auto inside = [](CDT::Face_handle &fh) {
|
||||
return fh->neighbor(0) != fh &&
|
||||
(fh->is_constrained(0) ||
|
||||
fh->is_constrained(1) ||
|
||||
fh->is_constrained(2));
|
||||
};
|
||||
|
||||
#ifdef VISUALIZE_TRIANGULATION
|
||||
std::vector<Vec3i32> indices2;
|
||||
indices2.reserve(num_faces);
|
||||
for (CDT::Face_handle fh : faces)
|
||||
if (inside(fh)) indices2.emplace_back(fh->vertex(0)->info(), fh->vertex(1)->info(), fh->vertex(2)->info());
|
||||
visualize(points, indices2, "C:/data/temp/triangulation_without_floodfill.obj");
|
||||
#endif // VISUALIZE_TRIANGULATION
|
||||
|
||||
// Propagate inside the constrained regions.
|
||||
std::vector<CDT::Face_handle> queue;
|
||||
queue.reserve(num_faces);
|
||||
for (CDT::Face_handle seed : faces){
|
||||
if (!inside(seed)) continue;
|
||||
// Seed fill to neighbor faces.
|
||||
queue.emplace_back(seed);
|
||||
while (! queue.empty()) {
|
||||
CDT::Face_handle fh = queue.back();
|
||||
queue.pop_back();
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (fh->is_constrained(i)) continue;
|
||||
// Propagate along this edge.
|
||||
fh->set_constraint(i, true);
|
||||
CDT::Face_handle nh = fh->neighbor(i);
|
||||
bool was_inside = inside(nh);
|
||||
// Mark the other side of this edge.
|
||||
nh->set_constraint(nh->index(fh), true);
|
||||
if (! was_inside)
|
||||
queue.push_back(nh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vec3i32> indices;
|
||||
indices.reserve(num_faces);
|
||||
for (CDT::Face_handle fh : faces)
|
||||
if (inside(fh))
|
||||
indices.emplace_back(fh->vertex(0)->info(), fh->vertex(1)->info(), fh->vertex(2)->info());
|
||||
|
||||
#ifdef VISUALIZE_TRIANGULATION
|
||||
visualize(points, indices, "C:/data/temp/triangulation.obj");
|
||||
#endif // VISUALIZE_TRIANGULATION
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
Triangulation::Indices Triangulation::triangulate(const Polygon &polygon)
|
||||
{
|
||||
const Points &pts = polygon.points;
|
||||
HalfEdges edges;
|
||||
edges.reserve(pts.size());
|
||||
uint32_t offset = 0;
|
||||
priv::insert_edges(edges, offset, polygon);
|
||||
std::sort(edges.begin(), edges.end());
|
||||
return triangulate(pts, edges);
|
||||
}
|
||||
|
||||
Triangulation::Indices Triangulation::triangulate(const Polygons &polygons)
|
||||
{
|
||||
size_t count = count_points(polygons);
|
||||
Points points;
|
||||
points.reserve(count);
|
||||
|
||||
HalfEdges edges;
|
||||
edges.reserve(count);
|
||||
uint32_t offset = 0;
|
||||
|
||||
for (const Polygon &polygon : polygons) {
|
||||
Slic3r::append(points, polygon.points);
|
||||
priv::insert_edges(edges, offset, polygon);
|
||||
}
|
||||
|
||||
std::sort(edges.begin(), edges.end());
|
||||
return triangulate(points, edges);
|
||||
}
|
||||
|
||||
Triangulation::Indices Triangulation::triangulate(const ExPolygon &expolygon){
|
||||
ExPolygons expolys({expolygon});
|
||||
return triangulate(expolys);
|
||||
}
|
||||
|
||||
Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons){
|
||||
Points pts = to_points(expolygons);
|
||||
Points d_pts = collect_duplicates(pts);
|
||||
if (d_pts.empty()) return triangulate(expolygons, pts);
|
||||
|
||||
Changes changes = create_changes(pts, d_pts);
|
||||
Indices indices = triangulate(expolygons, pts, changes);
|
||||
// reverse map for changes
|
||||
Changes changes2(changes.size(), std::numeric_limits<uint32_t>::max());
|
||||
for (size_t i = 0; i < changes.size(); ++i)
|
||||
changes2[changes[i]] = i;
|
||||
|
||||
// convert indices into expolygons indicies
|
||||
for (Vec3i32 &t : indices)
|
||||
for (size_t ti = 0; ti < 3; ti++) t[ti] = changes2[t[ti]];
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons, const Points &points)
|
||||
{
|
||||
assert(count_points(expolygons) == points.size());
|
||||
// when contain duplicit coordinate in points will not work properly
|
||||
assert(collect_duplicates(points).empty());
|
||||
|
||||
HalfEdges edges;
|
||||
edges.reserve(points.size());
|
||||
uint32_t offset = 0;
|
||||
for (const ExPolygon &expolygon : expolygons) {
|
||||
priv::insert_edges(edges, offset, expolygon.contour);
|
||||
for (const Polygon &hole : expolygon.holes)
|
||||
priv::insert_edges(edges, offset, hole);
|
||||
}
|
||||
std::sort(edges.begin(), edges.end());
|
||||
return triangulate(points, edges);
|
||||
}
|
||||
|
||||
Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons, const Points& points, const Changes& changes)
|
||||
{
|
||||
assert(!points.empty());
|
||||
assert(count_points(expolygons) == points.size());
|
||||
assert(changes.size() == points.size());
|
||||
// IMPROVE: search from end and somehow distiquish that value is not a change
|
||||
uint32_t count_points = *std::max_element(changes.begin(), changes.end())+1;
|
||||
Points pts(count_points);
|
||||
for (size_t i = 0; i < changes.size(); i++)
|
||||
pts[changes[i]] = points[i];
|
||||
|
||||
HalfEdges edges;
|
||||
edges.reserve(points.size());
|
||||
uint32_t offset = 0;
|
||||
for (const ExPolygon &expolygon : expolygons) {
|
||||
priv::insert_edges(edges, offset, expolygon.contour, changes);
|
||||
for (const Polygon &hole : expolygon.holes)
|
||||
priv::insert_edges(edges, offset, hole, changes);
|
||||
}
|
||||
|
||||
std::sort(edges.begin(), edges.end());
|
||||
return triangulate(pts, edges);
|
||||
}
|
||||
|
||||
Triangulation::Changes Triangulation::create_changes(const Points &points, const Points &duplicits)
|
||||
{
|
||||
assert(!duplicits.empty());
|
||||
assert(duplicits.size() < points.size()/2);
|
||||
std::vector<uint32_t> duplicit_indices(duplicits.size(), std::numeric_limits<uint32_t>::max());
|
||||
Changes changes;
|
||||
changes.reserve(points.size());
|
||||
uint32_t index = 0;
|
||||
for (const Point &p: points) {
|
||||
auto it = std::lower_bound(duplicits.begin(), duplicits.end(), p);
|
||||
if (it == duplicits.end() || *it != p) {
|
||||
changes.push_back(index);
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
uint32_t &d_index = duplicit_indices[it - duplicits.begin()];
|
||||
if (d_index == std::numeric_limits<uint32_t>::max()) {
|
||||
d_index = index;
|
||||
changes.push_back(index);
|
||||
++index;
|
||||
} else {
|
||||
changes.push_back(d_index);
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
72
src/libslic3r/Triangulation.hpp
Normal file
@ -0,0 +1,72 @@
|
||||
#ifndef libslic3r_Triangulation_hpp_
|
||||
#define libslic3r_Triangulation_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Triangulation
|
||||
{
|
||||
public:
|
||||
Triangulation() = delete;
|
||||
|
||||
// define oriented connection of 2 vertices(defined by its index)
|
||||
using HalfEdge = std::pair<uint32_t, uint32_t>;
|
||||
using HalfEdges = std::vector<HalfEdge>;
|
||||
using Indices = std::vector<Vec3i32>;
|
||||
|
||||
/// <summary>
|
||||
/// Connect points by triangulation to create filled surface by triangles
|
||||
/// Input points have to be unique
|
||||
/// Inspiration for make unique points is Emboss::dilate_to_unique_points
|
||||
/// </summary>
|
||||
/// <param name="points">Points to connect</param>
|
||||
/// <param name="edges">Constraint for edges, pair is from point(first) to
|
||||
/// point(second), sorted lexicographically</param>
|
||||
/// <returns>Triangles</returns>
|
||||
static Indices triangulate(const Points &points,
|
||||
const HalfEdges &half_edges);
|
||||
static Indices triangulate(const Polygon &polygon);
|
||||
static Indices triangulate(const Polygons &polygons);
|
||||
static Indices triangulate(const ExPolygon &expolygon);
|
||||
static Indices triangulate(const ExPolygons &expolygons);
|
||||
|
||||
// Map for convert original index to set without duplication
|
||||
// from_index<to_index>
|
||||
using Changes = std::vector<uint32_t>;
|
||||
|
||||
/// <summary>
|
||||
/// Create conversion map from original index into new
|
||||
/// with respect of duplicit point
|
||||
/// </summary>
|
||||
/// <param name="points">input set of points</param>
|
||||
/// <param name="duplicits">duplicit points collected from points</param>
|
||||
/// <returns>Conversion map for point index</returns>
|
||||
static Changes create_changes(const Points &points, const Points &duplicits);
|
||||
|
||||
/// <summary>
|
||||
/// Triangulation for expolygons, speed up when points are already collected
|
||||
/// NOTE: Not working properly for ExPolygons with multiple point on same coordinate
|
||||
/// You should check it by "collect_changes"
|
||||
/// </summary>
|
||||
/// <param name="expolygons">Input shape to triangulation - define edges</param>
|
||||
/// <param name="points">Points from expolygons</param>
|
||||
/// <returns>Triangle indices</returns>
|
||||
static Indices triangulate(const ExPolygons &expolygons, const Points& points);
|
||||
|
||||
/// <summary>
|
||||
/// Triangulation for expolygons containing multiple points with same coordinate
|
||||
/// </summary>
|
||||
/// <param name="expolygons">Input shape to triangulation - define edge</param>
|
||||
/// <param name="points">Points from expolygons</param>
|
||||
/// <param name="changes">Changes swap for indicies into points</param>
|
||||
/// <returns>Triangle indices</returns>
|
||||
static Indices triangulate(const ExPolygons &expolygons, const Points& points, const Changes& changes);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // libslic3r_Triangulation_hpp_
|
@ -354,6 +354,7 @@ inline typename CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER
|
||||
}
|
||||
|
||||
extern std::string xml_escape(std::string text, bool is_marked = false);
|
||||
extern std::string xml_escape_double_quotes_attribute_value(std::string text);
|
||||
extern std::string xml_unescape(std::string text);
|
||||
|
||||
|
||||
|
@ -1210,6 +1210,34 @@ std::string xml_escape(std::string text, bool is_marked/* = false*/)
|
||||
return text;
|
||||
}
|
||||
|
||||
// Definition of escape symbols https://www.w3.org/TR/REC-xml/#AVNormalize
|
||||
// During the read of xml attribute normalization of white spaces is applied
|
||||
// Soo for not lose white space character it is escaped before store
|
||||
std::string xml_escape_double_quotes_attribute_value(std::string text)
|
||||
{
|
||||
std::string::size_type pos = 0;
|
||||
for (;;) {
|
||||
pos = text.find_first_of("\"&<\r\n\t", pos);
|
||||
if (pos == std::string::npos) break;
|
||||
|
||||
std::string replacement;
|
||||
switch (text[pos]) {
|
||||
case '\"': replacement = """; break;
|
||||
case '&': replacement = "&"; break;
|
||||
case '<': replacement = "<"; break;
|
||||
case '\r': replacement = "
"; break;
|
||||
case '\n': replacement = "
"; break;
|
||||
case '\t': replacement = "	"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
text.replace(pos, 1, replacement);
|
||||
pos += replacement.size();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string xml_unescape(std::string s)
|
||||
{
|
||||
std::string ret;
|
||||
|
@ -72,6 +72,7 @@ extern "C" {
|
||||
*/
|
||||
|
||||
enum NSVGpaintType {
|
||||
NSVG_PAINT_UNDEF = -1,
|
||||
NSVG_PAINT_NONE = 0,
|
||||
NSVG_PAINT_COLOR = 1,
|
||||
NSVG_PAINT_LINEAR_GRADIENT = 2,
|
||||
@ -119,7 +120,7 @@ typedef struct NSVGgradient {
|
||||
} NSVGgradient;
|
||||
|
||||
typedef struct NSVGpaint {
|
||||
char type;
|
||||
signed char type;
|
||||
union {
|
||||
unsigned int color;
|
||||
NSVGgradient* gradient;
|
||||
@ -143,14 +144,17 @@ typedef struct NSVGshape
|
||||
float opacity; // Opacity of the shape.
|
||||
float strokeWidth; // Stroke width (scaled).
|
||||
float strokeDashOffset; // Stroke dash offset (scaled).
|
||||
float strokeDashArray[8]; // Stroke dash array (scaled).
|
||||
char strokeDashCount; // Number of dash values in dash array.
|
||||
float strokeDashArray[8]; // Stroke dash array (scaled).
|
||||
char strokeDashCount; // Number of dash values in dash array.
|
||||
char strokeLineJoin; // Stroke join type.
|
||||
char strokeLineCap; // Stroke cap type.
|
||||
float miterLimit; // Miter limit
|
||||
char fillRule; // Fill rule, see NSVGfillRule.
|
||||
unsigned char flags; // Logical or of NSVG_FLAGS_* flags
|
||||
float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy].
|
||||
char fillGradient[64]; // Optional 'id' of fill gradient
|
||||
char strokeGradient[64]; // Optional 'id' of stroke gradient
|
||||
float xform[6]; // Root transformation for fill/stroke gradient
|
||||
NSVGpath* paths; // Linked list of paths in the image.
|
||||
struct NSVGshape* next; // Pointer to next shape, or NULL if last element.
|
||||
} NSVGshape;
|
||||
@ -181,16 +185,13 @@ void nsvgDelete(NSVGimage* image);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // NANOSVG_H
|
||||
|
||||
#ifdef NANOSVG_IMPLEMENTATION
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
#define NSVG_PI (3.14159265358979323846264338327f)
|
||||
#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs.
|
||||
|
||||
@ -227,11 +228,6 @@ static int nsvg__isdigit(char c)
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
static int nsvg__isnum(char c)
|
||||
{
|
||||
return strchr("0123456789+-.eE", c) != 0;
|
||||
}
|
||||
|
||||
static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; }
|
||||
static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; }
|
||||
|
||||
@ -402,7 +398,7 @@ typedef struct NSVGgradientData
|
||||
{
|
||||
char id[64];
|
||||
char ref[64];
|
||||
char type;
|
||||
signed char type;
|
||||
union {
|
||||
NSVGlinearData linear;
|
||||
NSVGradialData radial;
|
||||
@ -618,7 +614,7 @@ static void nsvg__curveBounds(float* bounds, float* curve)
|
||||
}
|
||||
}
|
||||
|
||||
static NSVGparser* nsvg__createParser()
|
||||
static NSVGparser* nsvg__createParser(void)
|
||||
{
|
||||
NSVGparser* p;
|
||||
p = (NSVGparser*)malloc(sizeof(NSVGparser));
|
||||
@ -738,9 +734,11 @@ static void nsvg__lineTo(NSVGparser* p, float x, float y)
|
||||
|
||||
static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y)
|
||||
{
|
||||
nsvg__addPoint(p, cpx1, cpy1);
|
||||
nsvg__addPoint(p, cpx2, cpy2);
|
||||
nsvg__addPoint(p, x, y);
|
||||
if (p->npts > 0) {
|
||||
nsvg__addPoint(p, cpx1, cpy1);
|
||||
nsvg__addPoint(p, cpx2, cpy2);
|
||||
nsvg__addPoint(p, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
static NSVGattrib* nsvg__getAttr(NSVGparser* p)
|
||||
@ -810,7 +808,9 @@ static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig,
|
||||
static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id)
|
||||
{
|
||||
NSVGgradientData* grad = p->gradients;
|
||||
while (grad) {
|
||||
if (id == NULL || *id == '\0')
|
||||
return NULL;
|
||||
while (grad != NULL) {
|
||||
if (strcmp(grad->id, id) == 0)
|
||||
return grad;
|
||||
grad = grad->next;
|
||||
@ -818,28 +818,34 @@ static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType)
|
||||
static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, float *xform, signed char* paintType)
|
||||
{
|
||||
NSVGattrib* attr = nsvg__getAttr(p);
|
||||
NSVGgradientData* data = NULL;
|
||||
NSVGgradientData* ref = NULL;
|
||||
NSVGgradientStop* stops = NULL;
|
||||
NSVGgradient* grad;
|
||||
float ox, oy, sw, sh, sl;
|
||||
int nstops = 0;
|
||||
int refIter;
|
||||
|
||||
data = nsvg__findGradientData(p, id);
|
||||
if (data == NULL) return NULL;
|
||||
|
||||
// TODO: use ref to fill in all unset values too.
|
||||
ref = data;
|
||||
refIter = 0;
|
||||
while (ref != NULL) {
|
||||
NSVGgradientData* nextRef = NULL;
|
||||
if (stops == NULL && ref->stops != NULL) {
|
||||
stops = ref->stops;
|
||||
nstops = ref->nstops;
|
||||
break;
|
||||
}
|
||||
ref = nsvg__findGradientData(p, ref->ref);
|
||||
nextRef = nsvg__findGradientData(p, ref->ref);
|
||||
if (nextRef == ref) break; // prevent infite loops on malformed data
|
||||
ref = nextRef;
|
||||
refIter++;
|
||||
if (refIter > 32) break; // prevent infite loops on malformed data
|
||||
}
|
||||
if (stops == NULL) return NULL;
|
||||
|
||||
@ -888,7 +894,7 @@ static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const f
|
||||
}
|
||||
|
||||
nsvg__xformMultiply(grad->xform, data->xform);
|
||||
nsvg__xformMultiply(grad->xform, attr->xform);
|
||||
nsvg__xformMultiply(grad->xform, xform);
|
||||
|
||||
grad->spread = data->spread;
|
||||
memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop));
|
||||
@ -952,6 +958,9 @@ static void nsvg__addShape(NSVGparser* p)
|
||||
memset(shape, 0, sizeof(NSVGshape));
|
||||
|
||||
memcpy(shape->id, attr->id, sizeof shape->id);
|
||||
memcpy(shape->fillGradient, attr->fillGradient, sizeof shape->fillGradient);
|
||||
memcpy(shape->strokeGradient, attr->strokeGradient, sizeof shape->strokeGradient);
|
||||
memcpy(shape->xform, attr->xform, sizeof shape->xform);
|
||||
scale = nsvg__getAverageScale(attr->xform);
|
||||
shape->strokeWidth = attr->strokeWidth * scale;
|
||||
shape->strokeDashOffset = attr->strokeDashOffset * scale;
|
||||
@ -987,13 +996,7 @@ static void nsvg__addShape(NSVGparser* p)
|
||||
shape->fill.color = attr->fillColor;
|
||||
shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24;
|
||||
} else if (attr->hasFill == 2) {
|
||||
float inv[6], localBounds[4];
|
||||
nsvg__xformInverse(inv, attr->xform);
|
||||
nsvg__getLocalBounds(localBounds, shape, inv);
|
||||
shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type);
|
||||
if (shape->fill.gradient == NULL) {
|
||||
shape->fill.type = NSVG_PAINT_NONE;
|
||||
}
|
||||
shape->fill.type = NSVG_PAINT_UNDEF;
|
||||
}
|
||||
|
||||
// Set stroke
|
||||
@ -1004,12 +1007,7 @@ static void nsvg__addShape(NSVGparser* p)
|
||||
shape->stroke.color = attr->strokeColor;
|
||||
shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24;
|
||||
} else if (attr->hasStroke == 2) {
|
||||
float inv[6], localBounds[4];
|
||||
nsvg__xformInverse(inv, attr->xform);
|
||||
nsvg__getLocalBounds(localBounds, shape, inv);
|
||||
shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type);
|
||||
if (shape->stroke.gradient == NULL)
|
||||
shape->stroke.type = NSVG_PAINT_NONE;
|
||||
shape->stroke.type = NSVG_PAINT_UNDEF;
|
||||
}
|
||||
|
||||
// Set flags
|
||||
@ -1042,6 +1040,10 @@ static void nsvg__addPath(NSVGparser* p, char closed)
|
||||
if (closed)
|
||||
nsvg__lineTo(p, p->pts[0], p->pts[1]);
|
||||
|
||||
// Expect 1 + N*3 points (N = number of cubic bezier segments).
|
||||
if ((p->npts % 3) != 1)
|
||||
return;
|
||||
|
||||
path = (NSVGpath*)malloc(sizeof(NSVGpath));
|
||||
if (path == NULL) goto error;
|
||||
memset(path, 0, sizeof(NSVGpath));
|
||||
@ -1090,7 +1092,7 @@ static double nsvg__atof(const char* s)
|
||||
char* cur = (char*)s;
|
||||
char* end = NULL;
|
||||
double res = 0.0, sign = 1.0;
|
||||
long long intPart = 0, fracPart = 0;
|
||||
double intPart = 0.0, fracPart = 0.0;
|
||||
char hasIntPart = 0, hasFracPart = 0;
|
||||
|
||||
// Parse optional sign
|
||||
@ -1104,9 +1106,13 @@ static double nsvg__atof(const char* s)
|
||||
// Parse integer part
|
||||
if (nsvg__isdigit(*cur)) {
|
||||
// Parse digit sequence
|
||||
#ifdef _MSC_VER
|
||||
intPart = (double)_strtoi64(cur, &end, 10);
|
||||
#else
|
||||
intPart = (double)strtoll(cur, &end, 10);
|
||||
#endif
|
||||
if (cur != end) {
|
||||
res = (double)intPart;
|
||||
res = intPart;
|
||||
hasIntPart = 1;
|
||||
cur = end;
|
||||
}
|
||||
@ -1117,9 +1123,13 @@ static double nsvg__atof(const char* s)
|
||||
cur++; // Skip '.'
|
||||
if (nsvg__isdigit(*cur)) {
|
||||
// Parse digit sequence
|
||||
fracPart = strtoll(cur, &end, 10);
|
||||
#ifdef _MSC_VER
|
||||
fracPart = (double)_strtoi64(cur, &end, 10);
|
||||
#else
|
||||
fracPart = (double)strtoll(cur, &end, 10);
|
||||
#endif
|
||||
if (cur != end) {
|
||||
res += (double)fracPart / pow(10.0, (double)(end - cur));
|
||||
res += fracPart / pow(10.0, (double)(end - cur));
|
||||
hasFracPart = 1;
|
||||
cur = end;
|
||||
}
|
||||
@ -1132,11 +1142,11 @@ static double nsvg__atof(const char* s)
|
||||
|
||||
// Parse optional exponent
|
||||
if (*cur == 'e' || *cur == 'E') {
|
||||
int expPart = 0;
|
||||
double expPart = 0.0;
|
||||
cur++; // skip 'E'
|
||||
expPart = strtol(cur, &end, 10); // Parse digit sequence with sign
|
||||
expPart = (double)strtol(cur, &end, 10); // Parse digit sequence with sign
|
||||
if (cur != end) {
|
||||
res *= pow(10.0, (double)expPart);
|
||||
res *= pow(10.0, expPart);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1170,7 +1180,7 @@ static const char* nsvg__parseNumber(const char* s, char* it, const int size)
|
||||
}
|
||||
}
|
||||
// exponent
|
||||
if (*s == 'e' || *s == 'E') {
|
||||
if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) {
|
||||
if (i < last) it[i++] = *s;
|
||||
s++;
|
||||
if (*s == '-' || *s == '+') {
|
||||
@ -1187,6 +1197,19 @@ static const char* nsvg__parseNumber(const char* s, char* it, const int size)
|
||||
return s;
|
||||
}
|
||||
|
||||
static const char* nsvg__getNextPathItemWhenArcFlag(const char* s, char* it)
|
||||
{
|
||||
it[0] = '\0';
|
||||
while (*s && (nsvg__isspace(*s) || *s == ',')) s++;
|
||||
if (!*s) return s;
|
||||
if (*s == '0' || *s == '1') {
|
||||
it[0] = *s++;
|
||||
it[1] = '\0';
|
||||
return s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static const char* nsvg__getNextPathItem(const char* s, char* it)
|
||||
{
|
||||
it[0] = '\0';
|
||||
@ -1207,35 +1230,66 @@ static const char* nsvg__getNextPathItem(const char* s, char* it)
|
||||
|
||||
static unsigned int nsvg__parseColorHex(const char* str)
|
||||
{
|
||||
unsigned int c = 0, r = 0, g = 0, b = 0;
|
||||
int n = 0;
|
||||
str++; // skip #
|
||||
// Calculate number of characters.
|
||||
while(str[n] && !nsvg__isspace(str[n]))
|
||||
n++;
|
||||
if (n == 6) {
|
||||
sscanf(str, "%x", &c);
|
||||
} else if (n == 3) {
|
||||
sscanf(str, "%x", &c);
|
||||
c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8);
|
||||
c |= c<<4;
|
||||
}
|
||||
r = (c >> 16) & 0xff;
|
||||
g = (c >> 8) & 0xff;
|
||||
b = c & 0xff;
|
||||
return NSVG_RGB(r,g,b);
|
||||
unsigned int r=0, g=0, b=0;
|
||||
if (sscanf(str, "#%2x%2x%2x", &r, &g, &b) == 3 ) // 2 digit hex
|
||||
return NSVG_RGB(r, g, b);
|
||||
if (sscanf(str, "#%1x%1x%1x", &r, &g, &b) == 3 ) // 1 digit hex, e.g. #abc -> 0xccbbaa
|
||||
return NSVG_RGB(r*17, g*17, b*17); // same effect as (r<<4|r), (g<<4|g), ..
|
||||
return NSVG_RGB(128, 128, 128);
|
||||
}
|
||||
|
||||
// Parse rgb color. The pointer 'str' must point at "rgb(" (4+ characters).
|
||||
// This function returns gray (rgb(128, 128, 128) == '#808080') on parse errors
|
||||
// for backwards compatibility. Note: other image viewers return black instead.
|
||||
|
||||
static unsigned int nsvg__parseColorRGB(const char* str)
|
||||
{
|
||||
int r = -1, g = -1, b = -1;
|
||||
char s1[32]="", s2[32]="";
|
||||
sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b);
|
||||
if (strchr(s1, '%')) {
|
||||
return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100);
|
||||
} else {
|
||||
return NSVG_RGB(r,g,b);
|
||||
int i;
|
||||
unsigned int rgbi[3];
|
||||
float rgbf[3];
|
||||
// try decimal integers first
|
||||
if (sscanf(str, "rgb(%u, %u, %u)", &rgbi[0], &rgbi[1], &rgbi[2]) != 3) {
|
||||
// integers failed, try percent values (float, locale independent)
|
||||
const char delimiter[3] = {',', ',', ')'};
|
||||
str += 4; // skip "rgb("
|
||||
for (i = 0; i < 3; i++) {
|
||||
while (*str && (nsvg__isspace(*str))) str++; // skip leading spaces
|
||||
if (*str == '+') str++; // skip '+' (don't allow '-')
|
||||
if (!*str) break;
|
||||
rgbf[i] = nsvg__atof(str);
|
||||
|
||||
// Note 1: it would be great if nsvg__atof() returned how many
|
||||
// bytes it consumed but it doesn't. We need to skip the number,
|
||||
// the '%' character, spaces, and the delimiter ',' or ')'.
|
||||
|
||||
// Note 2: The following code does not allow values like "33.%",
|
||||
// i.e. a decimal point w/o fractional part, but this is consistent
|
||||
// with other image viewers, e.g. firefox, chrome, eog, gimp.
|
||||
|
||||
while (*str && nsvg__isdigit(*str)) str++; // skip integer part
|
||||
if (*str == '.') {
|
||||
str++;
|
||||
if (!nsvg__isdigit(*str)) break; // error: no digit after '.'
|
||||
while (*str && nsvg__isdigit(*str)) str++; // skip fractional part
|
||||
}
|
||||
if (*str == '%') str++; else break;
|
||||
while (nsvg__isspace(*str)) str++;
|
||||
if (*str == delimiter[i]) str++;
|
||||
else break;
|
||||
}
|
||||
if (i == 3) {
|
||||
rgbi[0] = roundf(rgbf[0] * 2.55f);
|
||||
rgbi[1] = roundf(rgbf[1] * 2.55f);
|
||||
rgbi[2] = roundf(rgbf[2] * 2.55f);
|
||||
} else {
|
||||
rgbi[0] = rgbi[1] = rgbi[2] = 128;
|
||||
}
|
||||
}
|
||||
// clip values as the CSS spec requires
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (rgbi[i] > 255) rgbi[i] = 255;
|
||||
}
|
||||
return NSVG_RGB(rgbi[0], rgbi[1], rgbi[2]);
|
||||
}
|
||||
|
||||
typedef struct NSVGNamedColor {
|
||||
@ -1460,6 +1514,15 @@ static int nsvg__parseUnits(const char* units)
|
||||
return NSVG_UNITS_USER;
|
||||
}
|
||||
|
||||
static int nsvg__isCoordinate(const char* s)
|
||||
{
|
||||
// optional sign
|
||||
if (*s == '-' || *s == '+')
|
||||
s++;
|
||||
// must have at least one digit, or start by a dot
|
||||
return (nsvg__isdigit(*s) || *s == '.');
|
||||
}
|
||||
|
||||
static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str)
|
||||
{
|
||||
NSVGcoordinate coord = {0, NSVG_UNITS_USER};
|
||||
@ -1599,25 +1662,32 @@ static int nsvg__parseRotate(float* xform, const char* str)
|
||||
static void nsvg__parseTransform(float* xform, const char* str)
|
||||
{
|
||||
float t[6];
|
||||
int len;
|
||||
nsvg__xformIdentity(xform);
|
||||
while (*str)
|
||||
{
|
||||
if (strncmp(str, "matrix", 6) == 0)
|
||||
str += nsvg__parseMatrix(t, str);
|
||||
len = nsvg__parseMatrix(t, str);
|
||||
else if (strncmp(str, "translate", 9) == 0)
|
||||
str += nsvg__parseTranslate(t, str);
|
||||
len = nsvg__parseTranslate(t, str);
|
||||
else if (strncmp(str, "scale", 5) == 0)
|
||||
str += nsvg__parseScale(t, str);
|
||||
len = nsvg__parseScale(t, str);
|
||||
else if (strncmp(str, "rotate", 6) == 0)
|
||||
str += nsvg__parseRotate(t, str);
|
||||
len = nsvg__parseRotate(t, str);
|
||||
else if (strncmp(str, "skewX", 5) == 0)
|
||||
str += nsvg__parseSkewX(t, str);
|
||||
len = nsvg__parseSkewX(t, str);
|
||||
else if (strncmp(str, "skewY", 5) == 0)
|
||||
str += nsvg__parseSkewY(t, str);
|
||||
len = nsvg__parseSkewY(t, str);
|
||||
else{
|
||||
++str;
|
||||
continue;
|
||||
}
|
||||
if (len != 0) {
|
||||
str += len;
|
||||
} else {
|
||||
++str;
|
||||
continue;
|
||||
}
|
||||
|
||||
nsvg__xformPremultiply(xform, t);
|
||||
}
|
||||
@ -1627,9 +1697,9 @@ static void nsvg__parseUrl(char* id, const char* str)
|
||||
{
|
||||
int i = 0;
|
||||
str += 4; // "url(";
|
||||
if (*str == '#')
|
||||
if (*str && *str == '#')
|
||||
str++;
|
||||
while (i < 63 && *str != ')') {
|
||||
while (i < 63 && *str && *str != ')') {
|
||||
id[i] = *str++;
|
||||
i++;
|
||||
}
|
||||
@ -1878,8 +1948,11 @@ static int nsvg__getArgsPerElement(char cmd)
|
||||
case 'a':
|
||||
case 'A':
|
||||
return 7;
|
||||
case 'z':
|
||||
case 'Z':
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
|
||||
@ -2160,7 +2233,12 @@ static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args,
|
||||
// The loop assumes an iteration per end point (including start and end), this +1.
|
||||
ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f);
|
||||
hda = (da / (float)ndivs) / 2.0f;
|
||||
kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda));
|
||||
// Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite)
|
||||
if ((hda < 1e-3f) && (hda > -1e-3f))
|
||||
hda *= 0.5f;
|
||||
else
|
||||
hda = (1.0f - cosf(hda)) / sinf(hda);
|
||||
kappa = fabsf(4.0f / 3.0f * hda);
|
||||
if (da < 0.0f)
|
||||
kappa = -kappa;
|
||||
|
||||
@ -2189,6 +2267,7 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr)
|
||||
float args[10];
|
||||
int nargs;
|
||||
int rargs = 0;
|
||||
char initPoint;
|
||||
float cpx, cpy, cpx2, cpy2;
|
||||
const char* tmp[4];
|
||||
char closedFlag;
|
||||
@ -2211,13 +2290,18 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr)
|
||||
nsvg__resetPath(p);
|
||||
cpx = 0; cpy = 0;
|
||||
cpx2 = 0; cpy2 = 0;
|
||||
initPoint = 0;
|
||||
closedFlag = 0;
|
||||
nargs = 0;
|
||||
|
||||
while (*s) {
|
||||
s = nsvg__getNextPathItem(s, item);
|
||||
item[0] = '\0';
|
||||
if ((cmd == 'A' || cmd == 'a') && (nargs == 3 || nargs == 4))
|
||||
s = nsvg__getNextPathItemWhenArcFlag(s, item);
|
||||
if (!*item)
|
||||
s = nsvg__getNextPathItem(s, item);
|
||||
if (!*item) break;
|
||||
if (nsvg__isnum(item[0])) {
|
||||
if (cmd != '\0' && nsvg__isCoordinate(item)) {
|
||||
if (nargs < 10)
|
||||
args[nargs++] = (float)nsvg__atof(item);
|
||||
if (nargs >= rargs) {
|
||||
@ -2230,6 +2314,7 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr)
|
||||
cmd = (cmd == 'm') ? 'l' : 'L';
|
||||
rargs = nsvg__getArgsPerElement(cmd);
|
||||
cpx2 = cpx; cpy2 = cpy;
|
||||
initPoint = 1;
|
||||
break;
|
||||
case 'l':
|
||||
case 'L':
|
||||
@ -2279,7 +2364,6 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr)
|
||||
}
|
||||
} else {
|
||||
cmd = item[0];
|
||||
rargs = nsvg__getArgsPerElement(cmd);
|
||||
if (cmd == 'M' || cmd == 'm') {
|
||||
// Commit path.
|
||||
if (p->npts > 0)
|
||||
@ -2288,7 +2372,11 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr)
|
||||
nsvg__resetPath(p);
|
||||
closedFlag = 0;
|
||||
nargs = 0;
|
||||
} else if (cmd == 'Z' || cmd == 'z') {
|
||||
} else if (initPoint == 0) {
|
||||
// Do not allow other commands until initial point has been set (moveTo called once).
|
||||
cmd = '\0';
|
||||
}
|
||||
if (cmd == 'Z' || cmd == 'z') {
|
||||
closedFlag = 1;
|
||||
// Commit path.
|
||||
if (p->npts > 0) {
|
||||
@ -2304,6 +2392,12 @@ static void nsvg__parsePath(NSVGparser* p, const char** attr)
|
||||
closedFlag = 0;
|
||||
nargs = 0;
|
||||
}
|
||||
rargs = nsvg__getArgsPerElement(cmd);
|
||||
if (rargs == -1) {
|
||||
// Command not recognized
|
||||
cmd = '\0';
|
||||
rargs = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Commit path.
|
||||
@ -2550,7 +2644,7 @@ static void nsvg__parseSVG(NSVGparser* p, const char** attr)
|
||||
}
|
||||
}
|
||||
|
||||
static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type)
|
||||
static void nsvg__parseGradient(NSVGparser* p, const char** attr, signed char type)
|
||||
{
|
||||
int i;
|
||||
NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData));
|
||||
@ -2875,6 +2969,36 @@ static void nsvg__scaleToViewbox(NSVGparser* p, const char* units)
|
||||
}
|
||||
}
|
||||
|
||||
static void nsvg__createGradients(NSVGparser* p)
|
||||
{
|
||||
NSVGshape* shape;
|
||||
|
||||
for (shape = p->image->shapes; shape != NULL; shape = shape->next) {
|
||||
if (shape->fill.type == NSVG_PAINT_UNDEF) {
|
||||
if (shape->fillGradient[0] != '\0') {
|
||||
float inv[6], localBounds[4];
|
||||
nsvg__xformInverse(inv, shape->xform);
|
||||
nsvg__getLocalBounds(localBounds, shape, inv);
|
||||
shape->fill.gradient = nsvg__createGradient(p, shape->fillGradient, localBounds, shape->xform, &shape->fill.type);
|
||||
}
|
||||
if (shape->fill.type == NSVG_PAINT_UNDEF) {
|
||||
shape->fill.type = NSVG_PAINT_NONE;
|
||||
}
|
||||
}
|
||||
if (shape->stroke.type == NSVG_PAINT_UNDEF) {
|
||||
if (shape->strokeGradient[0] != '\0') {
|
||||
float inv[6], localBounds[4];
|
||||
nsvg__xformInverse(inv, shape->xform);
|
||||
nsvg__getLocalBounds(localBounds, shape, inv);
|
||||
shape->stroke.gradient = nsvg__createGradient(p, shape->strokeGradient, localBounds, shape->xform, &shape->stroke.type);
|
||||
}
|
||||
if (shape->stroke.type == NSVG_PAINT_UNDEF) {
|
||||
shape->stroke.type = NSVG_PAINT_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSVGimage* nsvgParse(char* input, const char* units, float dpi)
|
||||
{
|
||||
NSVGparser* p;
|
||||
@ -2888,6 +3012,9 @@ NSVGimage* nsvgParse(char* input, const char* units, float dpi)
|
||||
|
||||
nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p);
|
||||
|
||||
// Create gradients after all definitions have been parsed
|
||||
nsvg__createGradients(p);
|
||||
|
||||
// Scale to viewBox
|
||||
nsvg__scaleToViewbox(p, units);
|
||||
|
||||
@ -2899,7 +3026,10 @@ NSVGimage* nsvgParse(char* input, const char* units, float dpi)
|
||||
return ret;
|
||||
}
|
||||
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#if WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include "windows.h"
|
||||
#endif
|
||||
|
||||
NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi)
|
||||
{
|
||||
@ -2908,8 +3038,17 @@ NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi)
|
||||
char* data = NULL;
|
||||
NSVGimage* image = NULL;
|
||||
|
||||
fp = boost::nowide::fopen(filename, "rb");
|
||||
if (!fp) goto error;
|
||||
#if WIN32
|
||||
int name_len = MultiByteToWideChar(CP_UTF8, NULL, filename, strlen(filename), NULL, 0);
|
||||
wchar_t w_fname[512];
|
||||
memset(w_fname, 0, sizeof(w_fname));
|
||||
MultiByteToWideChar(CP_UTF8, NULL, filename, strlen(filename), w_fname, name_len);
|
||||
w_fname[name_len] = '\0';
|
||||
fp = _wfopen(w_fname, L"rb");
|
||||
#else
|
||||
fp = fopen(filename, "rb");
|
||||
#endif
|
||||
if (!fp) goto error;
|
||||
fseek(fp, 0, SEEK_END);
|
||||
size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
@ -2918,9 +3057,9 @@ NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi)
|
||||
if (fread(data, 1, size, fp) != size) goto error;
|
||||
data[size] = '\0'; // Must be null terminated.
|
||||
fclose(fp);
|
||||
|
||||
image = nsvgParse(data, units, dpi);
|
||||
free(data);
|
||||
|
||||
return image;
|
||||
|
||||
error:
|
||||
@ -2976,4 +3115,6 @@ void nsvgDelete(NSVGimage* image)
|
||||
free(image);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // NANOSVG_IMPLEMENTATION
|
||||
|
||||
#endif // NANOSVG_H
|
||||
|
@ -22,9 +22,17 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/* Modified by FLTK to support non-square X,Y axes scaling.
|
||||
*
|
||||
* Added: nsvgRasterizeXY()
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NANOSVGRAST_H
|
||||
#define NANOSVGRAST_H
|
||||
|
||||
#include "nanosvg.h"
|
||||
|
||||
#ifndef NANOSVGRAST_CPLUSPLUS
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -44,16 +52,19 @@ typedef struct NSVGrasterizer NSVGrasterizer;
|
||||
unsigned char* img = malloc(w*h*4);
|
||||
// Rasterize
|
||||
nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4);
|
||||
|
||||
// For non-square X,Y scaling, use
|
||||
nsvgRasterizeXY(rast, image, 0,0,1,1, img, w, h, w*4);
|
||||
*/
|
||||
|
||||
// Allocated rasterizer context.
|
||||
NSVGrasterizer* nsvgCreateRasterizer();
|
||||
NSVGrasterizer* nsvgCreateRasterizer(void);
|
||||
|
||||
// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha)
|
||||
// r - pointer to rasterizer context
|
||||
// image - pointer to image to rasterize
|
||||
// tx,ty - image offset (applied after scaling)
|
||||
// scale - image scale
|
||||
// scale - image scale (assumes square aspect ratio)
|
||||
// dst - pointer to destination image data, 4 bytes per pixel (RGBA)
|
||||
// w - width of the image to render
|
||||
// h - height of the image to render
|
||||
@ -62,6 +73,12 @@ void nsvgRasterize(NSVGrasterizer* r,
|
||||
NSVGimage* image, float tx, float ty, float scale,
|
||||
unsigned char* dst, int w, int h, int stride);
|
||||
|
||||
// As above, but allow X and Y axes to scale independently for non-square aspects
|
||||
void nsvgRasterizeXY(NSVGrasterizer* r,
|
||||
NSVGimage* image, float tx, float ty,
|
||||
float sx, float sy,
|
||||
unsigned char* dst, int w, int h, int stride);
|
||||
|
||||
// Deletes rasterizer context.
|
||||
void nsvgDeleteRasterizer(NSVGrasterizer*);
|
||||
|
||||
@ -72,11 +89,11 @@ void nsvgDeleteRasterizer(NSVGrasterizer*);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // NANOSVGRAST_H
|
||||
|
||||
#ifdef NANOSVGRAST_IMPLEMENTATION
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NSVG__SUBSAMPLES 5
|
||||
#define NSVG__FIXSHIFT 10
|
||||
@ -112,7 +129,7 @@ typedef struct NSVGmemPage {
|
||||
} NSVGmemPage;
|
||||
|
||||
typedef struct NSVGcachedPaint {
|
||||
char type;
|
||||
signed char type;
|
||||
char spread;
|
||||
float xform[6];
|
||||
unsigned int colors[256];
|
||||
@ -148,7 +165,7 @@ struct NSVGrasterizer
|
||||
int width, height, stride;
|
||||
};
|
||||
|
||||
NSVGrasterizer* nsvgCreateRasterizer()
|
||||
NSVGrasterizer* nsvgCreateRasterizer(void)
|
||||
{
|
||||
NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer));
|
||||
if (r == NULL) goto error;
|
||||
@ -329,6 +346,7 @@ static float nsvg__normalize(float *x, float* y)
|
||||
}
|
||||
|
||||
static float nsvg__absf(float x) { return x < 0 ? -x : x; }
|
||||
static float nsvg__roundf(float x) { return (x >= 0) ? floorf(x + 0.5) : ceilf(x - 0.5); }
|
||||
|
||||
static void nsvg__flattenCubicBez(NSVGrasterizer* r,
|
||||
float x1, float y1, float x2, float y2,
|
||||
@ -368,7 +386,7 @@ static void nsvg__flattenCubicBez(NSVGrasterizer* r,
|
||||
nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type);
|
||||
}
|
||||
|
||||
static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale)
|
||||
static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float sx, float sy)
|
||||
{
|
||||
int i, j;
|
||||
NSVGpath* path;
|
||||
@ -376,13 +394,13 @@ static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale)
|
||||
for (path = shape->paths; path != NULL; path = path->next) {
|
||||
r->npoints = 0;
|
||||
// Flatten path
|
||||
nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0);
|
||||
nsvg__addPathPoint(r, path->pts[0]*sx, path->pts[1]*sy, 0);
|
||||
for (i = 0; i < path->npts-1; i += 3) {
|
||||
float* p = &path->pts[i*2];
|
||||
nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0);
|
||||
nsvg__flattenCubicBez(r, p[0]*sx,p[1]*sy, p[2]*sx,p[3]*sy, p[4]*sx,p[5]*sy, p[6]*sx,p[7]*sy, 0, 0);
|
||||
}
|
||||
// Close path
|
||||
nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0);
|
||||
nsvg__addPathPoint(r, path->pts[0]*sx, path->pts[1]*sy, 0);
|
||||
// Build edges
|
||||
for (i = 0, j = r->npoints-1; i < r->npoints; j = i++)
|
||||
nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y);
|
||||
@ -732,7 +750,7 @@ static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoi
|
||||
}
|
||||
}
|
||||
|
||||
static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale)
|
||||
static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float sx, float sy)
|
||||
{
|
||||
int i, j, closed;
|
||||
NSVGpath* path;
|
||||
@ -740,15 +758,16 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float
|
||||
float miterLimit = shape->miterLimit;
|
||||
int lineJoin = shape->strokeLineJoin;
|
||||
int lineCap = shape->strokeLineCap;
|
||||
float lineWidth = shape->strokeWidth * scale;
|
||||
const float sw = (sx + sy) / 2; // average scaling factor
|
||||
const float lineWidth = shape->strokeWidth * sw; // FIXME (?)
|
||||
|
||||
for (path = shape->paths; path != NULL; path = path->next) {
|
||||
// Flatten path
|
||||
r->npoints = 0;
|
||||
nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER);
|
||||
nsvg__addPathPoint(r, path->pts[0]*sx, path->pts[1]*sy, NSVG_PT_CORNER);
|
||||
for (i = 0; i < path->npts-1; i += 3) {
|
||||
float* p = &path->pts[i*2];
|
||||
nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER);
|
||||
nsvg__flattenCubicBez(r, p[0]*sx,p[1]*sy, p[2]*sx,p[3]*sy, p[4]*sx,p[5]*sy, p[6]*sx,p[7]*sy, 0, NSVG_PT_CORNER);
|
||||
}
|
||||
if (r->npoints < 2)
|
||||
continue;
|
||||
@ -794,7 +813,7 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float
|
||||
dashOffset -= shape->strokeDashArray[idash];
|
||||
idash = (idash + 1) % shape->strokeDashCount;
|
||||
}
|
||||
dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale;
|
||||
dashLen = (shape->strokeDashArray[idash] - dashOffset) * sw;
|
||||
|
||||
for (j = 1; j < r->npoints2; ) {
|
||||
float dx = r->points2[j].x - cur.x;
|
||||
@ -816,7 +835,7 @@ static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float
|
||||
// Advance dash pattern
|
||||
dashState = !dashState;
|
||||
idash = (idash+1) % shape->strokeDashCount;
|
||||
dashLen = shape->strokeDashArray[idash] * scale;
|
||||
dashLen = shape->strokeDashArray[idash] * sw;
|
||||
// Restart
|
||||
cur.x = x;
|
||||
cur.y = y;
|
||||
@ -870,10 +889,10 @@ static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float sta
|
||||
// STBTT_assert(e->y0 <= start_point);
|
||||
// round dx down to avoid going too far
|
||||
if (dxdy < 0)
|
||||
z->dx = (int)(-floorf(NSVG__FIX * -dxdy));
|
||||
z->dx = (int)(-nsvg__roundf(NSVG__FIX * -dxdy));
|
||||
else
|
||||
z->dx = (int)floorf(NSVG__FIX * dxdy);
|
||||
z->x = (int)floorf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0)));
|
||||
z->dx = (int)nsvg__roundf(NSVG__FIX * dxdy);
|
||||
z->x = (int)nsvg__roundf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0)));
|
||||
// z->x -= off_x * FIX;
|
||||
z->ey = e->y1;
|
||||
z->next = 0;
|
||||
@ -956,7 +975,7 @@ static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a
|
||||
|
||||
static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
|
||||
{
|
||||
return (r) | (g << 8) | (b << 16) | (a << 24);
|
||||
return ((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16) | ((unsigned int)a << 24);
|
||||
}
|
||||
|
||||
static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u)
|
||||
@ -985,7 +1004,7 @@ static inline int nsvg__div255(int x)
|
||||
}
|
||||
|
||||
static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y,
|
||||
float tx, float ty, float scale, NSVGcachedPaint* cache)
|
||||
float tx, float ty, float sx, float sy, NSVGcachedPaint* cache)
|
||||
{
|
||||
|
||||
if (cache->type == NSVG_PAINT_COLOR) {
|
||||
@ -1026,9 +1045,9 @@ static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* co
|
||||
int i, cr, cg, cb, ca;
|
||||
unsigned int c;
|
||||
|
||||
fx = ((float)x - tx) / scale;
|
||||
fy = ((float)y - ty) / scale;
|
||||
dx = 1.0f / scale;
|
||||
fx = ((float)x - tx) / sx;
|
||||
fy = ((float)y - ty) / sy;
|
||||
dx = 1.0f / sx;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
int r,g,b,a,ia;
|
||||
@ -1071,9 +1090,9 @@ static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* co
|
||||
int i, cr, cg, cb, ca;
|
||||
unsigned int c;
|
||||
|
||||
fx = ((float)x - tx) / scale;
|
||||
fy = ((float)y - ty) / scale;
|
||||
dx = 1.0f / scale;
|
||||
fx = ((float)x - tx) / sx;
|
||||
fy = ((float)y - ty) / sy;
|
||||
dx = 1.0f / sx;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
int r,g,b,a,ia;
|
||||
@ -1112,7 +1131,7 @@ static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* co
|
||||
}
|
||||
}
|
||||
|
||||
static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule)
|
||||
static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float sx, float sy, NSVGcachedPaint* cache, char fillRule)
|
||||
{
|
||||
NSVGactiveEdge *active = NULL;
|
||||
int y, s;
|
||||
@ -1194,7 +1213,7 @@ static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, fl
|
||||
if (xmin < 0) xmin = 0;
|
||||
if (xmax > r->width-1) xmax = r->width-1;
|
||||
if (xmin <= xmax) {
|
||||
nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache);
|
||||
nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, sx, sy, cache);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1280,7 +1299,7 @@ static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opac
|
||||
if (grad->nstops == 0) {
|
||||
for (i = 0; i < 256; i++)
|
||||
cache->colors[i] = 0;
|
||||
} if (grad->nstops == 1) {
|
||||
} else if (grad->nstops == 1) {
|
||||
for (i = 0; i < 256; i++)
|
||||
cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity);
|
||||
} else {
|
||||
@ -1362,8 +1381,9 @@ static void dumpEdges(NSVGrasterizer* r, const char* name)
|
||||
}
|
||||
*/
|
||||
|
||||
void nsvgRasterize(NSVGrasterizer* r,
|
||||
NSVGimage* image, float tx, float ty, float scale,
|
||||
void nsvgRasterizeXY(NSVGrasterizer* r,
|
||||
NSVGimage* image, float tx, float ty,
|
||||
float sx, float sy,
|
||||
unsigned char* dst, int w, int h, int stride)
|
||||
{
|
||||
NSVGshape *shape = NULL;
|
||||
@ -1394,7 +1414,7 @@ void nsvgRasterize(NSVGrasterizer* r,
|
||||
r->freelist = NULL;
|
||||
r->nedges = 0;
|
||||
|
||||
nsvg__flattenShape(r, shape, scale);
|
||||
nsvg__flattenShape(r, shape, sx, sy);
|
||||
|
||||
// Scale and translate edges
|
||||
for (i = 0; i < r->nedges; i++) {
|
||||
@ -1406,19 +1426,20 @@ void nsvgRasterize(NSVGrasterizer* r,
|
||||
}
|
||||
|
||||
// Rasterize edges
|
||||
qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge);
|
||||
if (r->nedges != 0)
|
||||
qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge);
|
||||
|
||||
// now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
|
||||
nsvg__initPaint(&cache, &shape->fill, shape->opacity);
|
||||
|
||||
nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule);
|
||||
nsvg__rasterizeSortedEdges(r, tx,ty, sx, sy, &cache, shape->fillRule);
|
||||
}
|
||||
if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) {
|
||||
if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * sx) > 0.01f) {
|
||||
nsvg__resetPool(r);
|
||||
r->freelist = NULL;
|
||||
r->nedges = 0;
|
||||
|
||||
nsvg__flattenShapeStroke(r, shape, scale);
|
||||
nsvg__flattenShapeStroke(r, shape, sx, sy);
|
||||
|
||||
// dumpEdges(r, "edge.svg");
|
||||
|
||||
@ -1432,12 +1453,13 @@ void nsvgRasterize(NSVGrasterizer* r,
|
||||
}
|
||||
|
||||
// Rasterize edges
|
||||
qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge);
|
||||
if (r->nedges != 0)
|
||||
qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge);
|
||||
|
||||
// now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
|
||||
nsvg__initPaint(&cache, &shape->stroke, shape->opacity);
|
||||
|
||||
nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO);
|
||||
nsvg__rasterizeSortedEdges(r, tx,ty,sx, sy, &cache, NSVG_FILLRULE_NONZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1449,4 +1471,13 @@ void nsvgRasterize(NSVGrasterizer* r,
|
||||
r->stride = 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
void nsvgRasterize(NSVGrasterizer* r,
|
||||
NSVGimage* image, float tx, float ty, float scale,
|
||||
unsigned char* dst, int w, int h, int stride)
|
||||
{
|
||||
nsvgRasterizeXY(r,image, tx, ty, scale, scale, dst, w, h, stride);
|
||||
}
|
||||
|
||||
#endif // NANOSVGRAST_IMPLEMENTATION
|
||||
|
||||
#endif // NANOSVGRAST_H
|
||||
|
@ -147,6 +147,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Gizmos/GLGizmoSeam.hpp
|
||||
GUI/Gizmos/GLGizmoText.cpp
|
||||
GUI/Gizmos/GLGizmoText.hpp
|
||||
GUI/Gizmos/GLGizmoSVG.cpp
|
||||
GUI/Gizmos/GLGizmoSVG.hpp
|
||||
GUI/Gizmos/GLGizmoMeshBoolean.cpp
|
||||
GUI/Gizmos/GLGizmoMeshBoolean.hpp
|
||||
GUI/GLSelectionRectangle.cpp
|
||||
@ -187,6 +189,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/GUI_Utils.hpp
|
||||
GUI/I18N.cpp
|
||||
GUI/I18N.hpp
|
||||
GUI/IconManager.cpp
|
||||
GUI/IconManager.hpp
|
||||
GUI/MainFrame.cpp
|
||||
GUI/MainFrame.hpp
|
||||
GUI/BBLTopbar.cpp
|
||||
@ -299,6 +303,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/SendSystemInfoDialog.hpp
|
||||
GUI/StepMeshDialog.cpp
|
||||
GUI/StepMeshDialog.hpp
|
||||
GUI/SurfaceDrag.cpp
|
||||
GUI/SurfaceDrag.hpp
|
||||
GUI/PlateSettingsDialog.cpp
|
||||
GUI/PlateSettingsDialog.hpp
|
||||
GUI/ImGuiWrapper.hpp
|
||||
@ -328,6 +334,15 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/UpdateDialogs.hpp
|
||||
GUI/Jobs/Job.hpp
|
||||
GUI/Jobs/Job.cpp
|
||||
GUI/Jobs/JobNew.hpp
|
||||
GUI/Jobs/Worker.hpp
|
||||
GUI/Jobs/BoostThreadWorker.hpp
|
||||
GUI/Jobs/BoostThreadWorker.cpp
|
||||
GUI/Jobs/BusyCursorJob.hpp
|
||||
GUI/Jobs/ThreadSafeQueue.hpp
|
||||
GUI/Jobs/PlaterWorker.hpp
|
||||
GUI/Jobs/EmbossJob.cpp
|
||||
GUI/Jobs/EmbossJob.hpp
|
||||
GUI/Jobs/PlaterJob.hpp
|
||||
GUI/Jobs/PlaterJob.cpp
|
||||
GUI/Jobs/UpgradeNetworkJob.hpp
|
||||
@ -472,6 +487,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
Utils/PresetUpdater.hpp
|
||||
Utils/Process.cpp
|
||||
Utils/Process.hpp
|
||||
Utils/RaycastManager.cpp
|
||||
Utils/RaycastManager.hpp
|
||||
Utils/Profile.hpp
|
||||
Utils/UndoRedo.cpp
|
||||
Utils/UndoRedo.hpp
|
||||
|
@ -4542,6 +4542,38 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
else
|
||||
evt.Skip();
|
||||
|
||||
// Detection of doubleclick on text to open emboss edit window
|
||||
auto type = m_gizmos.get_current_type();
|
||||
if (evt.LeftDClick() && !m_hover_volume_idxs.empty() &&
|
||||
(type == GLGizmosManager::EType::Undefined //||type == GLGizmosManager::EType::Text ||
|
||||
//type == GLGizmosManager::EType::Svg
|
||||
)) {
|
||||
for (int hover_volume_id : m_hover_volume_idxs) {
|
||||
const GLVolume &hover_gl_volume = *m_volumes.volumes[hover_volume_id];
|
||||
int object_idx = hover_gl_volume.object_idx();
|
||||
if (object_idx < 0 || static_cast<size_t>(object_idx) >= m_model->objects.size())
|
||||
continue;
|
||||
const ModelObject *hover_object = m_model->objects[object_idx];
|
||||
int hover_volume_idx = hover_gl_volume.volume_idx();
|
||||
if (hover_volume_idx < 0 || static_cast<size_t>(hover_volume_idx) >= hover_object->volumes.size())
|
||||
continue;
|
||||
const ModelVolume *hover_volume = hover_object->volumes[hover_volume_idx];
|
||||
|
||||
/* if (hover_volume->text_configuration.has_value()) {
|
||||
m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id});
|
||||
if (type != GLGizmosManager::EType::Emboss) m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss);
|
||||
wxGetApp().obj_list()->update_selections();
|
||||
return;
|
||||
} else*/ if (hover_volume->emboss_shape.has_value()) {
|
||||
m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id});
|
||||
if (type != GLGizmosManager::EType::Svg)
|
||||
m_gizmos.open_gizmo(GLGizmosManager::EType::Svg);
|
||||
wxGetApp().obj_list()->update_selections();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_moving)
|
||||
show_sinking_contours();
|
||||
|
||||
@ -4658,7 +4690,7 @@ void GLCanvas3D::do_move(const std::string &snapshot_type)
|
||||
else if (selection_mode == Selection::Volume) {
|
||||
auto cur_mv = model_object->volumes[volume_idx];
|
||||
if (cur_mv->get_offset() != v->get_volume_offset()) {
|
||||
cur_mv->set_offset(v->get_volume_offset());
|
||||
cur_mv->set_transformation(v->get_volume_transformation());
|
||||
// BBS: backup
|
||||
Slic3r::save_object_mesh(*model_object);
|
||||
}
|
||||
@ -9836,5 +9868,57 @@ ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects)
|
||||
return get_model_volume(v, *objects[objext_idx]);
|
||||
}
|
||||
|
||||
GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas)
|
||||
{
|
||||
int hovered_id_signed = canvas.get_first_hover_volume_idx();
|
||||
if (hovered_id_signed < 0)
|
||||
return nullptr;
|
||||
|
||||
size_t hovered_id = static_cast<size_t>(hovered_id_signed);
|
||||
const GLVolumePtrs &volumes = canvas.get_volumes().volumes;
|
||||
if (hovered_id >= volumes.size())
|
||||
return nullptr;
|
||||
|
||||
return volumes[hovered_id];
|
||||
}
|
||||
|
||||
GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas)
|
||||
{
|
||||
const GLVolume *gl_volume = get_selected_gl_volume(canvas.get_selection());
|
||||
if (gl_volume == nullptr) return nullptr;
|
||||
|
||||
const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes;
|
||||
for (GLVolume *v : gl_volumes)
|
||||
if (v->composite_id == gl_volume->composite_id) return v;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model) { return get_model_object(gl_volume, model.objects); }
|
||||
|
||||
ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects)
|
||||
{
|
||||
if (gl_volume.object_idx() < 0) return nullptr;
|
||||
size_t objext_idx = static_cast<size_t>(gl_volume.object_idx());
|
||||
if (objext_idx >= objects.size()) return nullptr;
|
||||
return objects[objext_idx];
|
||||
}
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model &model) { return get_model_instance(gl_volume, model.objects); }
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects)
|
||||
{
|
||||
if (gl_volume.instance_idx() < 0) return nullptr;
|
||||
ModelObject *object = get_model_object(gl_volume, objects);
|
||||
return get_model_instance(gl_volume, *object);
|
||||
}
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object)
|
||||
{
|
||||
if (gl_volume.instance_idx() < 0) return nullptr;
|
||||
size_t instance_idx = static_cast<size_t>(gl_volume.instance_idx());
|
||||
if (instance_idx >= object.instances.size()) return nullptr;
|
||||
return object.instances[instance_idx];
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
@ -1228,6 +1228,16 @@ const ModelVolume *get_model_volume(const GLVolume &v, const Model &model);
|
||||
ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects);
|
||||
ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects);
|
||||
ModelVolume *get_model_volume(const GLVolume &v, const ModelObject &object);
|
||||
|
||||
GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas);
|
||||
GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas);
|
||||
|
||||
ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model);
|
||||
ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects);
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model &model);
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects);
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object);
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
|
@ -277,24 +277,28 @@ wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name,
|
||||
//-------------------------------------
|
||||
|
||||
// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
|
||||
#ifdef __WINDOWS__
|
||||
const std::vector<std::pair<std::string, std::string>> MenuFactory::ADD_VOLUME_MENU_ITEMS = {
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> ADD_VOLUME_MENU_ITEMS = {
|
||||
{L("Add part"), "menu_add_part" }, // ~ModelVolumeType::MODEL_PART
|
||||
{L("Add negative part"), "menu_add_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME
|
||||
{L("Add modifier"), "menu_add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
||||
{L("Add support blocker"), "menu_support_blocker"}, // ~ModelVolumeType::SUPPORT_BLOCKER
|
||||
{L("Add support enforcer"), "menu_support_enforcer"} // ~ModelVolumeType::SUPPORT_ENFORCER
|
||||
};
|
||||
#else
|
||||
const std::vector<std::pair<std::string, std::string>> MenuFactory::ADD_VOLUME_MENU_ITEMS = {
|
||||
{L("Add part"), "menu_add_part" }, // ~ModelVolumeType::MODEL_PART
|
||||
{L("Add negative part"), "menu_add_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME
|
||||
{L("Add modifier"), "menu_add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
||||
{L("Add support blocker"), "menu_support_blocker"}, // ~ModelVolumeType::SUPPORT_BLOCKER
|
||||
{L("Add support enforcer"), "menu_support_enforcer"} // ~ModelVolumeType::SUPPORT_ENFORCER
|
||||
};
|
||||
|
||||
#endif
|
||||
// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
|
||||
static const constexpr std::array<std::pair<const char *, const char *>, 3> TEXT_VOLUME_ICONS{{
|
||||
// menu_item Name menu_item bitmap name
|
||||
{L("Add text"), "add_text_part"}, // ~ModelVolumeType::MODEL_PART
|
||||
{L("Add negative text"), "add_text_negative"}, // ~ModelVolumeType::NEGATIVE_VOLUME
|
||||
{L("Add text modifier"), "add_text_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
||||
}};
|
||||
// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
|
||||
static const constexpr std::array<std::pair<const char *, const char *>, 3> SVG_VOLUME_ICONS{{
|
||||
{L("Add SVG part"), "svg_part"}, // ~ModelVolumeType::MODEL_PART
|
||||
{L("Add negative SVG"), "svg_negative"}, // ~ModelVolumeType::NEGATIVE_VOLUME
|
||||
{L("Add SVG modifier"), "svg_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
|
||||
}};
|
||||
|
||||
static Plater* plater()
|
||||
{
|
||||
@ -440,6 +444,22 @@ std::vector<wxBitmap> MenuFactory::get_volume_bitmaps()
|
||||
return volume_bmps;
|
||||
}
|
||||
|
||||
std::vector<wxBitmap> MenuFactory::get_text_volume_bitmaps()
|
||||
{
|
||||
std::vector<wxBitmap> volume_bmps;
|
||||
volume_bmps.reserve(TEXT_VOLUME_ICONS.size());
|
||||
for (const auto &item : TEXT_VOLUME_ICONS) volume_bmps.push_back(create_scaled_bitmap(item.second));
|
||||
return volume_bmps;
|
||||
}
|
||||
|
||||
std::vector<wxBitmap> MenuFactory::get_svg_volume_bitmaps()
|
||||
{
|
||||
std::vector<wxBitmap> volume_bmps;
|
||||
volume_bmps.reserve(SVG_VOLUME_ICONS.size());
|
||||
for (const auto &item : SVG_VOLUME_ICONS) volume_bmps.push_back(create_scaled_bitmap(item.second));
|
||||
return volume_bmps;
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_item_set_visible(wxMenu* menu)
|
||||
{
|
||||
bool has_one_shown = false;
|
||||
@ -479,6 +499,36 @@ void MenuFactory::append_menu_item_edit_text(wxMenu *menu)
|
||||
#endif
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_item_edit_svg(wxMenu *menu)
|
||||
{
|
||||
wxString name = _L("Edit SVG");
|
||||
auto can_edit_svg = []() {
|
||||
if (plater() == nullptr) return false;
|
||||
const Selection &selection = plater()->get_selection();
|
||||
if (selection.volumes_count() != 1) return false;
|
||||
const GLVolume *gl_volume = selection.get_first_volume();
|
||||
if (gl_volume == nullptr) return false;
|
||||
const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects);
|
||||
if (volume == nullptr) return false;
|
||||
return volume->is_svg();
|
||||
};
|
||||
|
||||
if (menu != &m_svg_part_menu) {
|
||||
const int menu_item_id = menu->FindItem(name);
|
||||
if (menu_item_id != wxNOT_FOUND) menu->Destroy(menu_item_id);
|
||||
if (!can_edit_svg()) return;
|
||||
}
|
||||
|
||||
wxString description = _L("Change SVG source file, projection, size, ...");
|
||||
std::string icon = "svg_part";
|
||||
auto open_svg = [](const wxCommandEvent &) {
|
||||
GLGizmosManager &mng = plater()->get_view3D_canvas3D()->get_gizmos_manager();
|
||||
if (mng.get_current_type() == GLGizmosManager::Svg) mng.open_gizmo(GLGizmosManager::Svg); // close() and reopen - move to be visible
|
||||
mng.open_gizmo(GLGizmosManager::Svg);
|
||||
};
|
||||
append_menu_item(menu, wxID_ANY, name, description, open_svg, icon, nullptr, can_edit_svg, m_parent);
|
||||
}
|
||||
|
||||
wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType type) {
|
||||
auto sub_menu = new wxMenu;
|
||||
|
||||
@ -1169,6 +1219,34 @@ void MenuFactory::create_part_menu()
|
||||
append_menu_item_per_object_settings(&m_part_menu);
|
||||
}
|
||||
|
||||
void MenuFactory::create_text_part_menu()
|
||||
{
|
||||
wxMenu *menu = &m_text_part_menu;
|
||||
|
||||
append_menu_item_edit_text(menu);
|
||||
append_menu_item_delete(menu);
|
||||
append_menu_item_fix_through_netfabb(menu);
|
||||
append_menu_item_simplify(menu);
|
||||
append_menu_items_mirror(menu);
|
||||
menu->AppendSeparator();
|
||||
append_menu_item_per_object_settings(menu);
|
||||
append_menu_item_change_type(menu);
|
||||
}
|
||||
|
||||
void MenuFactory::create_svg_part_menu()
|
||||
{
|
||||
wxMenu *menu = &m_svg_part_menu;
|
||||
|
||||
append_menu_item_edit_svg(menu);
|
||||
append_menu_item_delete(menu);
|
||||
append_menu_item_fix_through_netfabb(menu);
|
||||
append_menu_item_simplify(menu);
|
||||
append_menu_items_mirror(menu);
|
||||
menu->AppendSeparator();
|
||||
append_menu_item_per_object_settings(menu);
|
||||
append_menu_item_change_type(menu);
|
||||
}
|
||||
|
||||
void MenuFactory::create_bbl_part_menu()
|
||||
{
|
||||
wxMenu* menu = &m_part_menu;
|
||||
@ -1302,7 +1380,7 @@ void MenuFactory::init(wxWindow* parent)
|
||||
//create_object_menu();
|
||||
create_sla_object_menu();
|
||||
//create_part_menu();
|
||||
|
||||
create_svg_part_menu();
|
||||
create_bbl_object_menu();
|
||||
create_bbl_part_menu();
|
||||
create_bbl_assemble_object_menu();
|
||||
@ -1335,6 +1413,7 @@ wxMenu* MenuFactory::object_menu()
|
||||
append_menu_items_convert_unit(&m_object_menu);
|
||||
append_menu_items_flush_options(&m_object_menu);
|
||||
append_menu_item_invalidate_cut_info(&m_object_menu);
|
||||
append_menu_item_edit_svg(&m_object_menu);
|
||||
append_menu_item_change_filament(&m_object_menu);
|
||||
{
|
||||
NetworkAgent* agent = GUI::wxGetApp().getAgent();
|
||||
@ -1347,6 +1426,8 @@ wxMenu* MenuFactory::sla_object_menu()
|
||||
{
|
||||
append_menu_items_convert_unit(&m_sla_object_menu);
|
||||
append_menu_item_settings(&m_sla_object_menu);
|
||||
//append_menu_item_edit_text(&m_sla_object_menu);
|
||||
append_menu_item_edit_svg(&m_object_menu);
|
||||
//update_menu_items_instance_manipulation(mtObjectSLA);
|
||||
return &m_sla_object_menu;
|
||||
}
|
||||
@ -1363,6 +1444,22 @@ wxMenu* MenuFactory::part_menu()
|
||||
return &m_part_menu;
|
||||
}
|
||||
|
||||
wxMenu *MenuFactory::text_part_menu()
|
||||
{
|
||||
append_menu_item_change_filament(&m_text_part_menu);
|
||||
append_menu_item_per_object_settings(&m_text_part_menu);
|
||||
|
||||
return &m_text_part_menu;
|
||||
}
|
||||
|
||||
wxMenu *MenuFactory::svg_part_menu()
|
||||
{
|
||||
append_menu_item_change_filament(&m_svg_part_menu);
|
||||
append_menu_item_per_object_settings(&m_svg_part_menu);
|
||||
|
||||
return &m_svg_part_menu;
|
||||
}
|
||||
|
||||
wxMenu* MenuFactory::instance_menu()
|
||||
{
|
||||
return &m_instance_menu;
|
||||
|
@ -47,8 +47,9 @@ struct SettingsFactory
|
||||
class MenuFactory
|
||||
{
|
||||
public:
|
||||
static const std::vector<std::pair<std::string, std::string>> ADD_VOLUME_MENU_ITEMS;
|
||||
static std::vector<wxBitmap> get_volume_bitmaps();
|
||||
static std::vector<wxBitmap> get_text_volume_bitmaps();
|
||||
static std::vector<wxBitmap> get_svg_volume_bitmaps();
|
||||
|
||||
MenuFactory();
|
||||
~MenuFactory() = default;
|
||||
@ -66,6 +67,8 @@ public:
|
||||
wxMenu* object_menu();
|
||||
wxMenu* sla_object_menu();
|
||||
wxMenu* part_menu();
|
||||
wxMenu *text_part_menu();
|
||||
wxMenu *svg_part_menu();
|
||||
wxMenu* instance_menu();
|
||||
wxMenu* layer_menu();
|
||||
wxMenu* multi_selection_menu();
|
||||
@ -86,6 +89,8 @@ private:
|
||||
|
||||
MenuWithSeparators m_object_menu;
|
||||
MenuWithSeparators m_part_menu;
|
||||
MenuWithSeparators m_text_part_menu;
|
||||
MenuWithSeparators m_svg_part_menu;
|
||||
MenuWithSeparators m_sla_object_menu;
|
||||
MenuWithSeparators m_default_menu;
|
||||
MenuWithSeparators m_instance_menu;
|
||||
@ -112,6 +117,8 @@ private:
|
||||
void create_object_menu();
|
||||
void create_sla_object_menu();
|
||||
void create_part_menu();
|
||||
void create_text_part_menu();
|
||||
void create_svg_part_menu();
|
||||
//BBS: add part plate related logic
|
||||
void create_plate_menu();
|
||||
//BBS: add bbl object menu
|
||||
@ -136,7 +143,6 @@ private:
|
||||
void append_menu_item_change_extruder(wxMenu* menu);
|
||||
void append_menu_item_set_visible(wxMenu* menu);
|
||||
void append_menu_item_delete(wxMenu* menu);
|
||||
void append_menu_item_edit_text(wxMenu *menu);
|
||||
void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu);
|
||||
void append_menu_items_convert_unit(wxMenu* menu); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk"
|
||||
void append_menu_items_flush_options(wxMenu* menu);
|
||||
@ -145,6 +151,8 @@ private:
|
||||
void append_menu_item_merge_parts_to_single_part(wxMenu *menu);
|
||||
void append_menu_items_mirror(wxMenu *menu);
|
||||
void append_menu_item_invalidate_cut_info(wxMenu *menu);
|
||||
void append_menu_item_edit_text(wxMenu *menu);
|
||||
void append_menu_item_edit_svg(wxMenu *menu);
|
||||
|
||||
//void append_menu_items_instance_manipulation(wxMenu *menu);
|
||||
//void update_menu_items_instance_manipulation(MenuType type);
|
||||
|
@ -1350,20 +1350,27 @@ void ObjectList::show_context_menu(const bool evt_context_menu)
|
||||
if (volume_type != ModelVolumeType::MODEL_PART)
|
||||
return;
|
||||
}
|
||||
menu = plater->assemble_multi_selection_menu();
|
||||
}
|
||||
else {
|
||||
menu = type & itPlate ? plater->plate_menu() :
|
||||
type & itInstance ? plater->instance_menu() :
|
||||
type & itVolume ? plater->part_menu() :
|
||||
printer_technology() == ptFFF ? plater->object_menu() :
|
||||
plater->sla_object_menu();
|
||||
plater->SetPlateIndexByRightMenuInLeftUI(-1);
|
||||
if (type & itPlate) {
|
||||
int plate_idx = -1;
|
||||
const ItemType type0 = m_objects_model->GetItemType(item, plate_idx);
|
||||
if (plate_idx >= 0) {
|
||||
plater->SetPlateIndexByRightMenuInLeftUI(plate_idx);
|
||||
if (type & itVolume) {
|
||||
int obj_idx, vol_idx;
|
||||
get_selected_item_indexes(obj_idx, vol_idx, item);
|
||||
if (obj_idx < 0 || vol_idx < 0) return;
|
||||
const ModelVolume *volume = object(obj_idx)->volumes[vol_idx];
|
||||
|
||||
menu = volume->is_svg() ? plater->svg_part_menu() : // ORCA fixes missing "Edit SVG" item for Add/Negative/Modifier SVG objects in object list
|
||||
plater->part_menu();
|
||||
}
|
||||
else {
|
||||
menu = type & itPlate ? plater->plate_menu() :
|
||||
type & itInstance ? plater->instance_menu() :
|
||||
printer_technology() == ptFFF ? plater->object_menu() :
|
||||
plater->sla_object_menu();
|
||||
plater->SetPlateIndexByRightMenuInLeftUI(-1);
|
||||
if (type & itPlate) {
|
||||
int plate_idx = -1;
|
||||
const ItemType type0 = m_objects_model->GetItemType(item, plate_idx);
|
||||
if (plate_idx >= 0) { plater->SetPlateIndexByRightMenuInLeftUI(plate_idx); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2701,6 +2708,7 @@ void ObjectList::split()
|
||||
for (const ModelVolume* volume : model_object->volumes) {
|
||||
const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name),
|
||||
volume->type(),// is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART,
|
||||
volume->is_svg(),
|
||||
get_warning_icon_name(volume->mesh().stats()),
|
||||
volume->config.has("extruder") ? volume->config.extruder() : 0,
|
||||
false);
|
||||
@ -3899,6 +3907,7 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st
|
||||
object_item,
|
||||
from_u8(volume->name),
|
||||
volume->type(),
|
||||
volume->is_svg(),
|
||||
get_warning_icon_name(volume->mesh().stats()),
|
||||
volume->config.has("extruder") ? volume->config.extruder() : 0,
|
||||
false);
|
||||
@ -5204,8 +5213,17 @@ void ObjectList::change_part_type()
|
||||
}
|
||||
}
|
||||
|
||||
const wxString names[] = { _L("Part"), _L("Negative Part"), _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") };
|
||||
SingleChoiceDialog dlg(_L("Type:"), _L("Choose part type"), wxArrayString(5, names), int(type));
|
||||
// ORCA: Fix crash when changing type of svg / text modifier
|
||||
wxArrayString names;
|
||||
names.Add(_L("Part"));
|
||||
names.Add(_L("Negative Part"));
|
||||
names.Add(_L("Modifier"));
|
||||
if (!volume->is_svg()) {
|
||||
names.Add(_L("Support Blocker"));
|
||||
names.Add(_L("Support Enforcer"));
|
||||
}
|
||||
|
||||
SingleChoiceDialog dlg(_L("Type:"), _L("Choose part type"), names, int(type));
|
||||
auto new_type = ModelVolumeType(dlg.GetSingleChoiceIndex());
|
||||
|
||||
if (new_type == type || new_type == ModelVolumeType::INVALID)
|
||||
|
@ -481,6 +481,83 @@ void GLGizmoBase::set_dirty() {
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event)
|
||||
{
|
||||
bool is_dragging_finished = false;
|
||||
if (mouse_event.Moving()) {
|
||||
// it should not happen but for sure
|
||||
assert(!m_dragging);
|
||||
if (m_dragging)
|
||||
is_dragging_finished = true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mouse_event.LeftDown()) {
|
||||
Selection &selection = m_parent.get_selection();
|
||||
if (!selection.is_empty() && m_hover_id != -1 /* &&
|
||||
(m_grabbers.empty() || m_hover_id < static_cast<int>(m_grabbers.size()))*/) {
|
||||
selection.setup_cache();
|
||||
|
||||
m_dragging = true;
|
||||
for (auto &grabber : m_grabbers) grabber.dragging = false;
|
||||
// if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size()))
|
||||
// m_grabbers[m_hover_id].dragging = true;
|
||||
|
||||
on_start_dragging();
|
||||
|
||||
// Let the plater know that the dragging started
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED));
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
}
|
||||
} else if (m_dragging) {
|
||||
// when mouse cursor leave window than finish actual dragging operation
|
||||
bool is_leaving = mouse_event.Leaving();
|
||||
if (mouse_event.Dragging()) {
|
||||
Point mouse_coord(mouse_event.GetX(), mouse_event.GetY());
|
||||
auto ray = m_parent.mouse_ray(mouse_coord);
|
||||
UpdateData data(ray, mouse_coord);
|
||||
|
||||
update(data);
|
||||
|
||||
wxGetApp().obj_manipul()->set_dirty();
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
} else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) {
|
||||
do_stop_dragging(is_leaving);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup)
|
||||
{
|
||||
for (auto &grabber : m_grabbers) grabber.dragging = false;
|
||||
m_dragging = false;
|
||||
|
||||
// NOTE: This should be part of GLCanvas3D
|
||||
// Reset hover_id when leave window
|
||||
if (perform_mouse_cleanup) m_parent.mouse_up_cleanup();
|
||||
|
||||
on_stop_dragging();
|
||||
|
||||
// There is prediction that after draggign, data are changed
|
||||
// Data are updated twice also by canvas3D::reload_scene.
|
||||
// Should be fixed.
|
||||
m_parent.get_gizmos_manager().update_data();
|
||||
|
||||
wxGetApp().obj_manipul()->set_dirty();
|
||||
|
||||
// Let the plater know that the dragging finished, so a delayed
|
||||
// refresh of the scene with the background processing data should
|
||||
// be performed.
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
|
||||
// updates camera target constraints
|
||||
m_parent.refresh_camera_scene_box();
|
||||
}
|
||||
|
||||
void GLGizmoBase::render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
on_render_input_window(x, y, bottom_limit);
|
||||
|
@ -25,8 +25,8 @@ class Linef3;
|
||||
class ModelObject;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
#define MAX_NUM 9999.99
|
||||
#define MAX_SIZE "9999.99"
|
||||
|
||||
class ImGuiWrapper;
|
||||
class GLCanvas3D;
|
||||
@ -275,6 +275,22 @@ protected:
|
||||
|
||||
// Mark gizmo as dirty to Re-Render when idle()
|
||||
void set_dirty();
|
||||
|
||||
/// <summary>
|
||||
/// function which
|
||||
/// Set up m_dragging and call functions
|
||||
/// on_start_dragging / on_dragging / on_stop_dragging
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>same as on_mouse</returns>
|
||||
bool use_grabbers(const wxMouseEvent &mouse_event);
|
||||
void do_stop_dragging(bool perform_mouse_cleanup);
|
||||
template<typename T> void limit_value(T &value, T _min, T _max)
|
||||
{
|
||||
if (value >= _max) { value = _max;}
|
||||
if (value <= _min) { value = _min; }
|
||||
}
|
||||
|
||||
private:
|
||||
// Flag for dirty visible state of Gizmo
|
||||
// When True then need new rendering
|
||||
|
@ -360,7 +360,7 @@ void GLGizmoRotate::transform_to_local(const Selection &selection) const
|
||||
{
|
||||
glsafe(::glTranslated(m_center(0), m_center(1), m_center(2)));
|
||||
|
||||
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) {
|
||||
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes() || m_force_local_coordinate) {
|
||||
glsafe(::glMultMatrixd(Geometry::Transformation(m_orient_matrix).get_matrix_no_offset().data()));
|
||||
}
|
||||
|
||||
|
2168
src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp
Normal file
166
src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp
Normal file
@ -0,0 +1,166 @@
|
||||
#ifndef slic3r_GLGizmoSVG_hpp_
|
||||
#define slic3r_GLGizmoSVG_hpp_
|
||||
|
||||
#include "GLGizmoRotate.hpp"
|
||||
#include "slic3r/GUI/SurfaceDrag.hpp"
|
||||
#include "slic3r/GUI/GLTexture.hpp"
|
||||
|
||||
#include "slic3r/GUI/IconManager.hpp"
|
||||
//BBS: add size adjust related
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "slic3r/GUI/Jobs/EmbossJob.hpp"
|
||||
namespace Slic3r {
|
||||
class ModelVolume;
|
||||
enum class ModelVolumeType : int;
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
//#define DEBUG_SVG
|
||||
struct Camera;
|
||||
class Worker;
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
class GLGizmoSVG : public GLGizmoBase
|
||||
{
|
||||
Emboss::DataBasePtr create_emboss_data_base(std::shared_ptr<std::atomic<bool>> &cancel, ModelVolumeType volume_type, std::string_view filepath);
|
||||
Emboss::CreateVolumeParams create_input(GLCanvas3D &canvas, ModelVolumeType volume_type);
|
||||
|
||||
|
||||
public:
|
||||
GLGizmoSVG(GLCanvas3D& parent, unsigned int sprite_id);
|
||||
virtual ~GLGizmoSVG() = default;
|
||||
|
||||
std::string get_tooltip() const override;
|
||||
void data_changed(bool is_serializing) override;
|
||||
void on_set_hover_id() override { m_rotate_gizmo.set_hover_id(m_hover_id); }
|
||||
void on_enable_grabber(unsigned int id) override;
|
||||
void on_disable_grabber(unsigned int id) override;
|
||||
|
||||
bool create_volume(std::string_view svg_file, const Vec2d &mouse_pos, ModelVolumeType volume_type = ModelVolumeType::MODEL_PART);
|
||||
/// <summary>
|
||||
/// Check whether volume is object containing only emboss volume
|
||||
/// </summary>
|
||||
/// <param name="volume">Pointer to volume</param>
|
||||
/// <returns>True when object otherwise False</returns>
|
||||
static bool is_svg_object(const ModelVolume &volume);
|
||||
|
||||
/// <summary>
|
||||
/// Check whether volume has emboss data
|
||||
/// </summary>
|
||||
/// <param name="volume">Pointer to volume</param>
|
||||
/// <returns>True when constain emboss data otherwise False</returns>
|
||||
static bool is_svg(const ModelVolume &volume);
|
||||
|
||||
void register_single_mesh_pick();
|
||||
|
||||
void update_single_mesh_pick(GLVolume *v);
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
||||
protected:
|
||||
bool on_is_selectable() const override { return false; }
|
||||
virtual bool on_init() override;
|
||||
virtual std::string on_get_name() const override;
|
||||
std::string on_get_name_str() override { return "Move"; }
|
||||
virtual bool on_is_activable() const override;
|
||||
virtual void on_set_state() override;
|
||||
virtual void on_start_dragging() override;
|
||||
virtual void on_stop_dragging() override;
|
||||
/// <summary>
|
||||
/// Rotate by text on dragging rotate grabers
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Information about mouse</param>
|
||||
/// <returns>Propagete normaly return false.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
virtual void on_update(const UpdateData& data) override;
|
||||
virtual void on_render() override;
|
||||
virtual void on_render_for_picking() override;
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit);
|
||||
|
||||
private:
|
||||
void set_volume_by_selection();
|
||||
void delete_texture(Emboss::Texture &texture);
|
||||
void reset_volume();
|
||||
void calculate_scale();
|
||||
float get_scale_for_tolerance();
|
||||
std::vector<std::string> create_shape_warnings(const EmbossShape &shape, float scale);
|
||||
|
||||
bool process_job(bool make_snapshot = true); // process(bool make_snapshot = true)
|
||||
void close();
|
||||
void draw_window();
|
||||
bool init_texture(Emboss::Texture &texture, const ExPolygonsWithIds &shapes_with_ids, unsigned max_size_px, const std::vector<std::string> &shape_warnings);
|
||||
void draw_preview();
|
||||
void draw_filename();
|
||||
void draw_depth();
|
||||
void draw_size();
|
||||
void draw_use_surface();
|
||||
void draw_distance();
|
||||
void draw_rotation();
|
||||
void draw_mirroring();
|
||||
void draw_face_the_camera();
|
||||
void draw_model_type();
|
||||
|
||||
void volume_transformation_changed();
|
||||
|
||||
// process mouse event
|
||||
bool on_mouse_for_rotation(const wxMouseEvent &mouse_event);
|
||||
bool on_mouse_for_translate(const wxMouseEvent &mouse_event);
|
||||
|
||||
private:
|
||||
struct GuiCfg;
|
||||
std::unique_ptr<const GuiCfg> m_gui_cfg;
|
||||
// actual selected only one volume - with emboss data
|
||||
ModelVolume *m_volume = nullptr;
|
||||
const GLVolume * m_svg_volume{nullptr};
|
||||
// Is used to edit eboss and send changes to job
|
||||
// Inside volume is current state of shape WRT Volume
|
||||
EmbossShape m_volume_shape; // copy from m_volume for edit
|
||||
// same index as volumes in
|
||||
std::vector<std::string> m_shape_warnings;
|
||||
// cancel for previous update of volume to cancel finalize part
|
||||
std::shared_ptr<std::atomic<bool>> m_job_cancel = nullptr;
|
||||
// When work with undo redo stack there could be situation that
|
||||
// m_volume point to unexisting volume so One need also objectID
|
||||
ObjectID m_volume_id;
|
||||
// move gizmo
|
||||
Grabber m_move_grabber;
|
||||
// Rotation gizmo
|
||||
GLGizmoRotate m_rotate_gizmo;
|
||||
std::optional<float> m_angle;
|
||||
std::optional<float> m_distance;
|
||||
|
||||
bool m_can_use_surface;
|
||||
// Value is set only when dragging rotation to calculate actual angle
|
||||
std::optional<float> m_rotate_start_angle;
|
||||
// TODO: it should be accessible by other gizmo too.
|
||||
// May be move to plater?
|
||||
RaycastManager m_raycast_manager;
|
||||
RaycastManager::AllowVolumes m_raycast_condition;
|
||||
std::map<GLVolume *, std::shared_ptr<PickRaycaster>> m_mesh_raycaster_map;//for text
|
||||
// When true keep up vector otherwise relative rotation
|
||||
bool m_keep_up = true;
|
||||
// Keep size aspect ratio when True.
|
||||
bool m_keep_ratio = true;
|
||||
// Keep data about dragging only during drag&drop
|
||||
std::optional<SurfaceDrag> m_surface_drag;
|
||||
// For volume on scaled objects
|
||||
std::optional<float> m_scale_width;
|
||||
std::optional<float> m_scale_height;
|
||||
std::optional<float> m_scale_depth;
|
||||
|
||||
// keep SVG data rendered on GPU
|
||||
Emboss::Texture m_texture;
|
||||
// bounding box of shape
|
||||
// Note: Scaled mm to int value by m_volume_shape.scale
|
||||
BoundingBox m_shape_bb;
|
||||
BoundingBox m_origin_shape_bb;
|
||||
std::string m_filename_preview;
|
||||
IconManager m_icon_manager;
|
||||
IconManager::VIcons m_icons;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoMove_hpp_
|
@ -24,6 +24,7 @@
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoText.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSVG.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoAssembly.hpp"
|
||||
|
||||
@ -222,6 +223,7 @@ bool GLGizmosManager::init()
|
||||
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, m_is_dark ? "toolbar_support_dark.svg" : "toolbar_support.svg", EType::FdmSupports));
|
||||
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg", EType::Seam));
|
||||
m_gizmos.emplace_back(new GLGizmoText(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Text));
|
||||
m_gizmos.emplace_back(new GLGizmoSVG(m_parent, EType::Svg));
|
||||
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg", EType::MmuSegmentation));
|
||||
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg", EType::Measure));
|
||||
m_gizmos.emplace_back(new GLGizmoAssembly(m_parent, m_is_dark ? "toolbar_assembly_dark.svg" : "toolbar_assembly.svg", EType::Assembly));
|
||||
@ -377,6 +379,10 @@ bool GLGizmosManager::open_gizmo(EType type)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GLGizmosManager::open_gizmo(unsigned char type)
|
||||
{
|
||||
return open_gizmo((EType)type);
|
||||
}
|
||||
|
||||
bool GLGizmosManager::check_gizmos_closed_except(EType type) const
|
||||
{
|
||||
@ -766,6 +772,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
|
||||
return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == Text)
|
||||
return dynamic_cast<GLGizmoText*>(m_gizmos[Text].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == Svg)
|
||||
return dynamic_cast<GLGizmoSVG*>(m_gizmos[Svg].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == Measure)
|
||||
return dynamic_cast<GLGizmoMeasure *>(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == Assembly)
|
||||
@ -928,7 +936,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
|
||||
// mouse anywhere
|
||||
if (evt.Moving()) {
|
||||
m_tooltip = update_hover_state(mouse_pos);
|
||||
if (m_current == MmuSegmentation || m_current == FdmSupports || m_current == Text || m_current == BrimEars)
|
||||
if (m_current == MmuSegmentation || m_current == FdmSupports || m_current == Text || m_current == BrimEars || m_current == Svg)
|
||||
// BBS
|
||||
gizmo_event(SLAGizmoEventType::Moving, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown());
|
||||
} else if (evt.LeftUp()) {
|
||||
@ -1091,7 +1099,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
|
||||
m_tooltip.clear();
|
||||
|
||||
if (evt.LeftDown() && (!control_down || grabber_contains_mouse())) {
|
||||
if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports ||
|
||||
if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Svg ||
|
||||
m_current == Seam || m_current == MmuSegmentation || m_current == Text || m_current == Cut || m_current == MeshBoolean || m_current == BrimEars)
|
||||
&& gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown()))
|
||||
// the gizmo got the event and took some action, there is no need to do anything more
|
||||
@ -1155,6 +1163,10 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
|
||||
// in case SLA/FDM gizmo is selected, we just pass the LeftUp event and stop processing - neither
|
||||
// object moving or selecting is suppressed in that case
|
||||
processed = true;
|
||||
} else if (evt.LeftUp() && m_current == Svg && m_gizmos[m_current]->get_hover_id() != -1) {
|
||||
// BBS
|
||||
// wxGetApp().obj_manipul()->set_dirty();
|
||||
processed = true;
|
||||
}
|
||||
else if (evt.LeftUp() && m_current == Flatten && m_gizmos[m_current]->get_hover_id() != -1) {
|
||||
// to avoid to loose the selection when user clicks an the white faces of a different object while the Flatten gizmo is active
|
||||
@ -1714,13 +1726,14 @@ void GLGizmosManager::do_render_overlay() const
|
||||
|
||||
GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } });
|
||||
|
||||
if (idx == m_current) {
|
||||
if (idx == m_current// Orca: Show Svg dialog at the same place as emboss gizmo
|
||||
|| (m_current == Svg && idx == Text)) {
|
||||
//BBS: GUI refactor: GLToolbar&&Gizmo adjust
|
||||
//render_input_window uses a different coordination(imgui)
|
||||
//1. no need to scale by camera zoom, set {0,0} at left-up corner for imgui
|
||||
#if BBS_TOOLBAR_ON_TOP
|
||||
//gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top);
|
||||
gizmo->render_input_window(0.5 * cnv_w + zoomed_top_x * zoom, height, cnv_h);
|
||||
m_gizmos[m_current]->render_input_window(0.5 * cnv_w + zoomed_top_x * zoom, height, cnv_h);
|
||||
|
||||
is_render_current = true;
|
||||
#else
|
||||
|
@ -77,6 +77,7 @@ public:
|
||||
Seam,
|
||||
// BBS
|
||||
Text,
|
||||
Svg,
|
||||
MmuSegmentation,
|
||||
Measure,
|
||||
Assembly,
|
||||
@ -229,6 +230,7 @@ public:
|
||||
void reset_all_states();
|
||||
bool is_serializing() const { return m_serializing; }
|
||||
bool open_gizmo(EType type);
|
||||
bool open_gizmo(unsigned char type);
|
||||
bool check_gizmos_closed_except(EType) const;
|
||||
|
||||
void set_hover_id(int id);
|
||||
|
@ -18,8 +18,7 @@
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#define MAX_NUM 9999.99
|
||||
#define MAX_SIZE "9999.99"
|
||||
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
413
src/slic3r/GUI/IconManager.cpp
Normal file
@ -0,0 +1,413 @@
|
||||
#include "IconManager.hpp"
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include "nanosvg/nanosvg.h"
|
||||
#include "nanosvg/nanosvgrast.h"
|
||||
#include "libslic3r/Utils.hpp" // ScopeGuard
|
||||
|
||||
#include "3DScene.hpp" // glsafe
|
||||
#include "GL/glew.h"
|
||||
|
||||
#define STB_RECT_PACK_IMPLEMENTATION
|
||||
#include "imgui/imstb_rectpack.h" // distribute rectangles
|
||||
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
namespace priv {
|
||||
// set shared pointer to point on bad texture
|
||||
static void clear(IconManager::Icons &icons);
|
||||
static const std::vector<std::pair<int, bool>>& get_states(IconManager::RasterType type);
|
||||
static void draw_transparent_icon(const IconManager::Icon &icon); // only help function
|
||||
}
|
||||
|
||||
IconManager::~IconManager() {
|
||||
priv::clear(m_icons);
|
||||
// release opengl texture is made in ~GLTexture()
|
||||
|
||||
if (m_id != 0)
|
||||
glsafe(::glDeleteTextures(1, &m_id));
|
||||
}
|
||||
|
||||
namespace {
|
||||
NSVGimage *parse_file(const char * filepath) {
|
||||
FILE *fp = boost::nowide::fopen(filepath, "rb");
|
||||
assert(fp != nullptr);
|
||||
if (fp == nullptr)
|
||||
return nullptr;
|
||||
|
||||
Slic3r::ScopeGuard sg([fp]() { fclose(fp); });
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
size_t size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
// Note: +1 is for null termination
|
||||
auto data_ptr = std::make_unique<char[]>(size+1);
|
||||
data_ptr[size] = '\0'; // Must be null terminated.
|
||||
|
||||
size_t readed_size = fread(data_ptr.get(), 1, size, fp);
|
||||
assert(readed_size == size);
|
||||
if (readed_size != size)
|
||||
return nullptr;
|
||||
|
||||
return nsvgParse(data_ptr.get(), "px", 96.0f);
|
||||
}
|
||||
|
||||
void subdata(unsigned char *data, size_t data_stride, const std::vector<unsigned char> &data2, size_t data2_row) {
|
||||
assert(data_stride >= data2_row);
|
||||
for (size_t data2_offset = 0, data_offset = 0;
|
||||
data2_offset < data2.size();
|
||||
data2_offset += data2_row, data_offset += data_stride)
|
||||
::memcpy((void *)(data + data_offset), (const void *)(data2.data() + data2_offset), data2_row);
|
||||
}
|
||||
}
|
||||
|
||||
IconManager::Icons IconManager::init(const InitTypes &input)
|
||||
{
|
||||
assert(!input.empty());
|
||||
if (input.empty())
|
||||
return {};
|
||||
|
||||
// TODO: remove in future
|
||||
if (m_id != 0) {
|
||||
glsafe(::glDeleteTextures(1, &m_id));
|
||||
m_id = 0;
|
||||
}
|
||||
|
||||
int total_surface = 0;
|
||||
for (const InitType &i : input)
|
||||
total_surface += i.size.x * i.size.y;
|
||||
const int surface_sqrt = (int)sqrt((float)total_surface) + 1;
|
||||
|
||||
// Start packing
|
||||
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
|
||||
const int TEX_HEIGHT_MAX = 1024 * 32;
|
||||
int width = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512;
|
||||
|
||||
int num_nodes = width;
|
||||
std::vector<stbrp_node> nodes(num_nodes);
|
||||
stbrp_context context;
|
||||
stbrp_init_target(&context, width, TEX_HEIGHT_MAX, nodes.data(), num_nodes);
|
||||
|
||||
ImVector<stbrp_rect> pack_rects;
|
||||
pack_rects.resize(input.size());
|
||||
memset(pack_rects.Data, 0, (size_t) pack_rects.size_in_bytes());
|
||||
for (size_t i = 0; i < input.size(); i++) {
|
||||
const ImVec2 &size = input[i].size;
|
||||
assert(size.x > 1);
|
||||
assert(size.y > 1);
|
||||
pack_rects[i].w = size.x;
|
||||
pack_rects[i].h = size.y;
|
||||
}
|
||||
int pack_rects_res = stbrp_pack_rects(&context, &pack_rects[0], pack_rects.Size);
|
||||
assert(pack_rects_res == 1);
|
||||
if (pack_rects_res != 1)
|
||||
return {};
|
||||
|
||||
ImVec2 tex_size(width, width);
|
||||
for (const stbrp_rect &rect : pack_rects) {
|
||||
float x = rect.x + rect.w;
|
||||
float y = rect.y + rect.h;
|
||||
if(x > tex_size.x) tex_size.x = x;
|
||||
if(y > tex_size.y) tex_size.y = y;
|
||||
}
|
||||
|
||||
Icons result(input.size());
|
||||
for (int i = 0; i < pack_rects.Size; i++) {
|
||||
const stbrp_rect &rect = pack_rects[i];
|
||||
assert(rect.was_packed);
|
||||
if (!rect.was_packed)
|
||||
return {};
|
||||
|
||||
ImVec2 tl(rect.x / tex_size.x, rect.y / tex_size.y);
|
||||
ImVec2 br((rect.x + rect.w) / tex_size.x, (rect.y + rect.h) / tex_size.y);
|
||||
|
||||
assert(input[i].size.x == rect.w);
|
||||
assert(input[i].size.y == rect.h);
|
||||
Icon icon = {input[i].size, tl, br};
|
||||
result[i] = std::make_shared<Icon>(std::move(icon));
|
||||
}
|
||||
|
||||
NSVGrasterizer *rast = nsvgCreateRasterizer();
|
||||
assert(rast != nullptr);
|
||||
if (rast == nullptr)
|
||||
return {};
|
||||
ScopeGuard sg_rast([rast]() { ::nsvgDeleteRasterizer(rast); });
|
||||
|
||||
int channels = 4;
|
||||
int n_pixels = tex_size.x * tex_size.y;
|
||||
// store data for whole texture
|
||||
std::vector<unsigned char> data(n_pixels * channels, {0});
|
||||
|
||||
// initialize original index locations
|
||||
std::vector<size_t> idx(input.size());
|
||||
std::iota(idx.begin(), idx.end(), 0);
|
||||
|
||||
// Group same filename by sort inputs
|
||||
// sort indexes based on comparing values in input
|
||||
std::sort(idx.begin(), idx.end(), [&input](size_t i1, size_t i2) { return input[i1].filepath < input[i2].filepath; });
|
||||
for (size_t j: idx) {
|
||||
const InitType &i = input[j];
|
||||
if (i.filepath.empty())
|
||||
continue; // no file path only reservation of space for texture
|
||||
assert(boost::filesystem::exists(i.filepath));
|
||||
if (!boost::filesystem::exists(i.filepath))
|
||||
continue;
|
||||
assert(boost::algorithm::iends_with(i.filepath, ".svg"));
|
||||
if (!boost::algorithm::iends_with(i.filepath, ".svg"))
|
||||
continue;
|
||||
|
||||
NSVGimage *image = parse_file(i.filepath.c_str());
|
||||
assert(image != nullptr);
|
||||
if (image == nullptr)
|
||||
return {};
|
||||
|
||||
ScopeGuard sg_image([image]() { ::nsvgDelete(image); });
|
||||
|
||||
float svg_scale = i.size.y / image->height;
|
||||
// scale should be same in both directions
|
||||
assert(is_approx(svg_scale, i.size.y / image->width));
|
||||
|
||||
const stbrp_rect &rect = pack_rects[j];
|
||||
int n_pixels = rect.w * rect.h;
|
||||
std::vector<unsigned char> icon_data(n_pixels * channels, {0});
|
||||
::nsvgRasterize(rast, image, 0, 0, svg_scale, icon_data.data(), i.size.x, i.size.y, i.size.x * channels);
|
||||
|
||||
// makes white or gray only data in icon
|
||||
if (i.type == RasterType::white_only_data ||
|
||||
i.type == RasterType::gray_only_data) {
|
||||
unsigned char value = (i.type == RasterType::white_only_data) ? 255 : 127;
|
||||
for (size_t k = 0; k < icon_data.size(); k += channels)
|
||||
if (icon_data[k] != 0 || icon_data[k + 1] != 0 || icon_data[k + 2] != 0) {
|
||||
icon_data[k] = value;
|
||||
icon_data[k + 1] = value;
|
||||
icon_data[k + 2] = value;
|
||||
}
|
||||
}
|
||||
|
||||
int start_offset = (rect.y*tex_size.x + rect.x) * channels;
|
||||
int data_stride = tex_size.x * channels;
|
||||
subdata(data.data() + start_offset, data_stride, icon_data, rect.w * channels);
|
||||
}
|
||||
|
||||
if (m_id != 0)
|
||||
glsafe(::glDeleteTextures(1, &m_id));
|
||||
|
||||
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
|
||||
glsafe(::glGenTextures(1, &m_id));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint) m_id));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) tex_size.x, (GLsizei) tex_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*) data.data()));
|
||||
|
||||
// bind no texture
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
for (const auto &i : result)
|
||||
i->tex_id = m_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<IconManager::Icons> IconManager::init(const std::vector<std::string> &file_paths, const ImVec2 &size, RasterType type)
|
||||
{
|
||||
assert(!file_paths.empty());
|
||||
assert(size.x >= 1);
|
||||
assert(size.x < 256*16);
|
||||
|
||||
// TODO: remove in future
|
||||
if (!m_icons.empty()) {
|
||||
// not first initialization
|
||||
priv::clear(m_icons);
|
||||
m_icons.clear();
|
||||
m_icons_texture.reset();
|
||||
}
|
||||
|
||||
// only rectangle are supported
|
||||
assert(size.x == size.y);
|
||||
// no subpixel supported
|
||||
unsigned int width = static_cast<unsigned int>(std::abs(std::round(size.x)));
|
||||
assert(size.x == static_cast<float>(width));
|
||||
|
||||
// state order has to match the enum IconState
|
||||
const auto& states = priv::get_states(type);
|
||||
|
||||
bool compress = false;
|
||||
bool is_loaded = m_icons_texture.load_from_svg_files_as_sprites_array(file_paths, states, width, compress);
|
||||
if (!is_loaded || (size_t) m_icons_texture.get_width() < (states.size() * width) ||
|
||||
(size_t) m_icons_texture.get_height() < (file_paths.size() * width)) {
|
||||
// bad load of icons, but all usage of m_icons_texture check that texture is initialized
|
||||
assert(false);
|
||||
m_icons_texture.reset();
|
||||
return {};
|
||||
}
|
||||
|
||||
unsigned count_files = file_paths.size();
|
||||
// count icons per file
|
||||
unsigned count = states.size();
|
||||
// create result
|
||||
std::vector<Icons> result;
|
||||
result.reserve(count_files);
|
||||
|
||||
Icon def_icon;
|
||||
def_icon.tex_id = m_icons_texture.get_id();
|
||||
def_icon.size = size;
|
||||
|
||||
// float beacouse of dividing
|
||||
float tex_height = static_cast<float>(m_icons_texture.get_height());
|
||||
float tex_width = static_cast<float>(m_icons_texture.get_width());
|
||||
|
||||
//for (const auto &f: file_paths) {
|
||||
for (unsigned f = 0; f < count_files; ++f) {
|
||||
// NOTE: there are space between icons
|
||||
unsigned start_y = static_cast<unsigned>(f) * (width + 1) + 1;
|
||||
float y1 = start_y / tex_height;
|
||||
float y2 = (start_y + width) / tex_height;
|
||||
Icons file_icons;
|
||||
file_icons.reserve(count);
|
||||
//for (const auto &s : states) {
|
||||
for (unsigned j = 0; j < count; ++j) {
|
||||
auto icon = std::make_shared<Icon>(def_icon);
|
||||
// NOTE: there are space between icons
|
||||
unsigned start_x = static_cast<unsigned>(j) * (width + 1) + 1;
|
||||
float x1 = start_x / tex_width;
|
||||
float x2 = (start_x + width) / tex_width;
|
||||
icon->tl = ImVec2(x1, y1);
|
||||
icon->br = ImVec2(x2, y2);
|
||||
file_icons.push_back(icon);
|
||||
m_icons.push_back(std::move(icon));
|
||||
}
|
||||
result.emplace_back(std::move(file_icons));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void IconManager::release() {
|
||||
BOOST_LOG_TRIVIAL(error) << "Not implemented yet";
|
||||
}
|
||||
|
||||
void priv::clear(IconManager::Icons &icons) {
|
||||
std::string message;
|
||||
for (auto &icon : icons) {
|
||||
// Exist more than this instance of shared ptr?
|
||||
long count = icon.use_count();
|
||||
if (count != 1) {
|
||||
// in existing icon change texture to non existing one
|
||||
icon->tex_id = 0;
|
||||
|
||||
std::string descr =
|
||||
((count > 2) ? (std::to_string(count - 1) + "x") : "") + // count
|
||||
std::to_string(icon->size.x) + "x" + std::to_string(icon->size.y); // resolution
|
||||
if (message.empty())
|
||||
message = descr;
|
||||
else
|
||||
message += ", " + descr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!message.empty())
|
||||
BOOST_LOG_TRIVIAL(warning) << "There is still used icons(" << message << ").";
|
||||
}
|
||||
|
||||
const std::vector<std::pair<int, bool>> &priv::get_states(IconManager::RasterType type) {
|
||||
static std::vector<std::pair<int, bool>> color = {std::make_pair(0, false)};
|
||||
static std::vector<std::pair<int, bool>> white = {std::make_pair(1, false)};
|
||||
static std::vector<std::pair<int, bool>> gray = {std::make_pair(2, false)};
|
||||
static std::vector<std::pair<int, bool>> color_wite_gray = {
|
||||
std::make_pair(1, false), // Activable
|
||||
std::make_pair(0, false), // Hovered
|
||||
std::make_pair(2, false) // Disabled
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case IconManager::RasterType::color: return color;
|
||||
case IconManager::RasterType::white_only_data: return white;
|
||||
case IconManager::RasterType::gray_only_data: return gray;
|
||||
case IconManager::RasterType::color_wite_gray: return color_wite_gray;
|
||||
default: return color;
|
||||
}
|
||||
}
|
||||
|
||||
void priv::draw_transparent_icon(const IconManager::Icon &icon)
|
||||
{
|
||||
// Check input
|
||||
if (!icon.is_valid()) {
|
||||
assert(false);
|
||||
BOOST_LOG_TRIVIAL(warning) << "Drawing invalid Icon.";
|
||||
ImGui::Text("?");
|
||||
return;
|
||||
}
|
||||
|
||||
// size UV texture coors [in texture ratio]
|
||||
ImVec2 size_uv(icon.br.x - icon.tl.x, icon.br.y - icon.tl.y);
|
||||
ImVec2 one_px(size_uv.x / icon.size.x, size_uv.y / icon.size.y);
|
||||
|
||||
// use top left corner of first icon
|
||||
IconManager::Icon icon_px = icon; // copy
|
||||
// reduce uv coors to one pixel
|
||||
icon_px.tl = ImVec2(0, 0);
|
||||
icon_px.br = one_px;
|
||||
draw(icon_px);
|
||||
}
|
||||
|
||||
#include "imgui/imgui_internal.h" //ImGuiWindow
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
void draw(const IconManager::Icon &icon, const ImVec2 &size, const ImVec4 &tint_col, const ImVec4 &border_col)
|
||||
{
|
||||
// Check input
|
||||
if (!icon.is_valid()) {
|
||||
assert(false);
|
||||
BOOST_LOG_TRIVIAL(warning) << "Drawing invalid Icon.";
|
||||
ImGui::Text("?");
|
||||
return;
|
||||
}
|
||||
ImTextureID id = (void *)static_cast<intptr_t>(icon.tex_id);
|
||||
const ImVec2 &s = (size.x < 1 || size.y < 1) ? icon.size : size;
|
||||
|
||||
// Orca: Align icon center vertically
|
||||
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
||||
ImGuiContext &g = *GImGui;
|
||||
float cursor_y = window->DC.CursorPos.y;
|
||||
float line_height = ImGui::GetTextLineHeight() + g.Style.FramePadding.y * 2;
|
||||
int offset_y = (line_height - s.y) / 2; // Make sure its int otherwise it will be pixelated
|
||||
window->DC.CursorPos.y += offset_y;
|
||||
|
||||
ImGui::Image(id, s, icon.tl, icon.br, tint_col, border_col);
|
||||
|
||||
// Reset offset
|
||||
window->DC.CursorPosPrevLine.y = cursor_y;
|
||||
}
|
||||
|
||||
bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover)
|
||||
{
|
||||
// check of hover
|
||||
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
||||
float cursor_x = ImGui::GetCursorPosX()
|
||||
- window->DC.GroupOffset.x
|
||||
- window->DC.ColumnsOffset.x;
|
||||
priv::draw_transparent_icon(icon);
|
||||
ImGui::SameLine(cursor_x);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
// redraw image
|
||||
draw(icon_hover);
|
||||
} else {
|
||||
// redraw normal image
|
||||
draw(icon);
|
||||
}
|
||||
return ImGui::IsItemClicked();
|
||||
}
|
||||
|
||||
bool button(const IconManager::Icon &activ, const IconManager::Icon &hover, const IconManager::Icon &disable, bool disabled)
|
||||
{
|
||||
if (disabled) {
|
||||
draw(disable);
|
||||
return false;
|
||||
}
|
||||
return clickable(activ, hover);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
134
src/slic3r/GUI/IconManager.hpp
Normal file
@ -0,0 +1,134 @@
|
||||
#ifndef slic3r_IconManager_hpp_
|
||||
#define slic3r_IconManager_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "imgui/imgui.h" // ImVec2
|
||||
#include "slic3r/GUI/GLTexture.hpp" // texture storage
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
/// <summary>
|
||||
/// Keep texture with icons for UI
|
||||
/// Manage texture live -> create and destruct texture
|
||||
/// by live of icon shared pointers.
|
||||
/// </summary>
|
||||
class IconManager
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Release texture
|
||||
/// Set shared pointers to invalid texture
|
||||
/// </summary>
|
||||
~IconManager();
|
||||
|
||||
/// <summary>
|
||||
/// Define way to convert svg data to raster
|
||||
/// </summary>
|
||||
enum class RasterType: int{
|
||||
color = 1 << 1,
|
||||
white_only_data = 1 << 2,
|
||||
gray_only_data = 1 << 3,
|
||||
color_wite_gray = color | white_only_data | gray_only_data
|
||||
// TODO: add type with backgrounds
|
||||
};
|
||||
|
||||
struct InitType {
|
||||
// path to file with image .. svg
|
||||
std::string filepath;
|
||||
|
||||
// resolution of stored rasterized icon
|
||||
ImVec2 size; // float will be rounded
|
||||
|
||||
// could contain more than one type
|
||||
RasterType type = RasterType::color;
|
||||
// together color, white and gray = color | white_only_data | gray_only_data
|
||||
};
|
||||
using InitTypes = std::vector<InitType>;
|
||||
|
||||
/// <summary>
|
||||
/// Data for render texture with icon
|
||||
/// </summary>
|
||||
struct Icon {
|
||||
// stored texture size
|
||||
ImVec2 size = ImVec2(-1, -1); // [in px] --> unsigned int values stored as float
|
||||
|
||||
// SubTexture UV coordinate in range from 0. to 1.
|
||||
ImVec2 tl; // top left -> uv0
|
||||
ImVec2 br; // bottom right -> uv1
|
||||
|
||||
// OpenGL texture id
|
||||
unsigned int tex_id = 0;
|
||||
bool is_valid() const { return tex_id != 0;}
|
||||
// && size.x > 0 && size.y > 0 && tl.x != br.x && tl.y != br.y;
|
||||
};
|
||||
using Icons = std::vector<std::shared_ptr<Icon> >;
|
||||
// Vector of icons, each vector contain multiple use of a SVG render
|
||||
using VIcons = std::vector<Icons>;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize raster texture on GPU with given images
|
||||
/// NOTE: Have to be called after OpenGL initialization
|
||||
/// </summary>
|
||||
/// <param name="input">Define files and its size with rasterization</param>
|
||||
/// <returns>Rasterized icons stored on GPU,
|
||||
/// Same size and order as input, each item of vector is set of texture in order by RasterType</returns>
|
||||
Icons init(const InitTypes &input);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize multiple icons with same settings for size and type
|
||||
/// NOTE: Have to be called after OpenGL initialization
|
||||
/// </summary>
|
||||
/// <param name="file_paths">Define files with icon</param>
|
||||
/// <param name="size">Size of stored texture[in px], float will be rounded</param>
|
||||
/// <param name="type">Define way to rasterize icon,
|
||||
/// together color, white and gray = RasterType::color | RasterType::white_only_data | RasterType::gray_only_data</param>
|
||||
/// <returns>Rasterized icons stored on GPU,
|
||||
/// Same size and order as file_paths, each item of vector is set of texture in order by RasterType</returns>
|
||||
VIcons init(const std::vector<std::string> &file_paths, const ImVec2 &size, RasterType type = RasterType::color);
|
||||
|
||||
/// <summary>
|
||||
/// Release icons which are hold only by this manager
|
||||
/// May change texture and position of icons.
|
||||
/// </summary>
|
||||
void release();
|
||||
|
||||
private:
|
||||
// keep data stored on GPU
|
||||
GLTexture m_icons_texture;
|
||||
|
||||
unsigned int m_id{ 0 };
|
||||
Icons m_icons;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Draw imgui image with icon
|
||||
/// </summary>
|
||||
/// <param name="icon">Place in texture</param>
|
||||
/// <param name="size">[optional]Size of image, wen zero than use same size as stored texture</param>
|
||||
/// <param name="tint_col">viz ImGui::Image </param>
|
||||
/// <param name="border_col">viz ImGui::Image </param>
|
||||
void draw(const IconManager::Icon &icon,
|
||||
const ImVec2 &size = ImVec2(0, 0),
|
||||
const ImVec4 &tint_col = ImVec4(1, 1, 1, 1),
|
||||
const ImVec4 &border_col = ImVec4(0, 0, 0, 0));
|
||||
|
||||
/// <summary>
|
||||
/// Draw icon which change on hover
|
||||
/// </summary>
|
||||
/// <param name="icon">Draw when no hover</param>
|
||||
/// <param name="icon_hover">Draw when hover</param>
|
||||
/// <returns>True when click, otherwise False</returns>
|
||||
bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover);
|
||||
|
||||
/// <summary>
|
||||
/// Use icon as button with 3 states activ hover and disabled
|
||||
/// </summary>
|
||||
/// <param name="activ">Not disabled not hovered image</param>
|
||||
/// <param name="hover">Hovered image</param>
|
||||
/// <param name="disable">Disabled image</param>
|
||||
/// <returns>True when click on enabled, otherwise False</returns>
|
||||
bool button(const IconManager::Icon &activ, const IconManager::Icon &hover, const IconManager::Icon &disable, bool disabled = false);
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
#endif // slic3r_IconManager_hpp_
|
@ -519,7 +519,7 @@ void ImGuiWrapper::render()
|
||||
m_new_frame_open = false;
|
||||
}
|
||||
|
||||
ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width) const
|
||||
ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width)
|
||||
{
|
||||
auto text_utf8 = into_u8(text);
|
||||
ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width);
|
||||
@ -991,13 +991,13 @@ void ImGuiWrapper::text(const char *label)
|
||||
|
||||
void ImGuiWrapper::text(const std::string &label)
|
||||
{
|
||||
this->text(label.c_str());
|
||||
ImGuiWrapper::text(label.c_str());
|
||||
}
|
||||
|
||||
void ImGuiWrapper::text(const wxString &label)
|
||||
{
|
||||
auto label_utf8 = into_u8(label);
|
||||
this->text(label_utf8.c_str());
|
||||
ImGuiWrapper::text(label_utf8.c_str());
|
||||
}
|
||||
|
||||
void ImGuiWrapper::warning_text(const char *label)
|
||||
@ -1912,6 +1912,163 @@ bool ImGuiWrapper::want_any_input() const
|
||||
return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput;
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
static bool input_optional(std::optional<T> &v, Func &f, std::function<bool(const T &)> is_default, const T &def_val)
|
||||
{
|
||||
if (v.has_value()) {
|
||||
if (f(*v)) {
|
||||
if (is_default(*v)) v.reset();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
T val = def_val;
|
||||
if (f(val)) {
|
||||
if (!is_default(val)) v = val;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::input_optional_int(const char *label, std::optional<int> &v, int step, int step_fast, ImGuiInputTextFlags flags, int def_val)
|
||||
{
|
||||
auto func = [&](int &value) { return ImGui::InputInt(label, &value, step, step_fast, flags); };
|
||||
std::function<bool(const int &)> is_default = [def_val](const int &value) -> bool { return value == def_val; };
|
||||
return input_optional(v, func, is_default, def_val);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::input_optional_float(const char *label, std::optional<float> &v, float step, float step_fast, const char *format, ImGuiInputTextFlags flags, float def_val)
|
||||
{
|
||||
auto func = [&](float &value) { return ImGui::InputFloat(label, &value, step, step_fast, format, flags); };
|
||||
std::function<bool(const float &)> is_default = [def_val](const float &value) -> bool { return std::fabs(value - def_val) <= std::numeric_limits<float>::epsilon(); };
|
||||
return input_optional(v, func, is_default, def_val);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::drag_optional_float(const char *label, std::optional<float> &v, float v_speed, float v_min, float v_max, const char *format, float power, float def_val)
|
||||
{
|
||||
auto func = [&](float &value) { return ImGui::DragFloat(label, &value, v_speed, v_min, v_max, format, power); };
|
||||
std::function<bool(const float &)> is_default = [def_val](const float &value) -> bool { return std::fabs(value - def_val) <= std::numeric_limits<float>::epsilon(); };
|
||||
return input_optional(v, func, is_default, def_val);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::slider_optional_float(
|
||||
const char *label, std::optional<float> &v, float v_min, float v_max, const char *format, float power, bool clamp, const wxString &tooltip, bool show_edit_btn, float def_val)
|
||||
{
|
||||
auto func = [&](float &value) { return slider_float(label, &value, v_min, v_max, format, power, clamp, tooltip, show_edit_btn); };
|
||||
std::function<bool(const float &)> is_default = [def_val](const float &value) -> bool { return std::fabs(value - def_val) <= std::numeric_limits<float>::epsilon(); };
|
||||
return input_optional(v, func, is_default, def_val);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::slider_optional_int(
|
||||
const char *label, std::optional<int> &v, int v_min, int v_max, const char *format, float power, bool clamp, const wxString &tooltip, bool show_edit_btn, int def_val)
|
||||
{
|
||||
std::optional<float> val;
|
||||
if (v.has_value()) val = static_cast<float>(*v);
|
||||
auto func = [&](float &value) { return slider_float(label, &value, v_min, v_max, format, power, clamp, tooltip, show_edit_btn); };
|
||||
std::function<bool(const float &)> is_default = [def_val](const float &value) -> bool { return std::fabs(value - def_val) < 0.9f; };
|
||||
|
||||
float default_value = static_cast<float>(def_val);
|
||||
if (input_optional(val, func, is_default, default_value)) {
|
||||
if (val.has_value())
|
||||
v = static_cast<int>(std::round(*val));
|
||||
else
|
||||
v.reset();
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<ImVec2> ImGuiWrapper::change_window_position(const char *window_name, bool try_to_fix)
|
||||
{
|
||||
ImGuiWindow *window = ImGui::FindWindowByName(window_name);
|
||||
// is window just created
|
||||
if (window == NULL) return {};
|
||||
|
||||
// position of window on screen
|
||||
ImVec2 position = window->Pos;
|
||||
ImVec2 size = window->SizeFull;
|
||||
|
||||
// screen size
|
||||
ImVec2 screen = ImGui::GetMainViewport()->Size;
|
||||
|
||||
std::optional<ImVec2> output_window_offset;
|
||||
if (position.x < 0) {
|
||||
if (position.y < 0)
|
||||
// top left
|
||||
output_window_offset = ImVec2(0, 0);
|
||||
else
|
||||
// only left
|
||||
output_window_offset = ImVec2(0, position.y);
|
||||
} else if (position.y < 0) {
|
||||
// only top
|
||||
output_window_offset = ImVec2(position.x, 0);
|
||||
} else if (screen.x < (position.x + size.x)) {
|
||||
if (screen.y < (position.y + size.y))
|
||||
// right bottom
|
||||
output_window_offset = ImVec2(screen.x - size.x, screen.y - size.y);
|
||||
else
|
||||
// only right
|
||||
output_window_offset = ImVec2(screen.x - size.x, position.y);
|
||||
} else if (screen.y < (position.y + size.y)) {
|
||||
// only bottom
|
||||
output_window_offset = ImVec2(position.x, screen.y - size.y);
|
||||
}
|
||||
|
||||
if (!try_to_fix && output_window_offset.has_value()) output_window_offset = ImVec2(-1, -1); // Put on default position
|
||||
|
||||
return output_window_offset;
|
||||
}
|
||||
|
||||
void ImGuiWrapper::left_inputs() { ImGui::ClearActiveID(); }
|
||||
|
||||
std::string ImGuiWrapper::trunc(const std::string &text, float width, const char *tail)
|
||||
{
|
||||
float text_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||
if (text_width < width) return text;
|
||||
float tail_width = ImGui::CalcTextSize(tail).x;
|
||||
assert(width > tail_width);
|
||||
if (width <= tail_width) return "Error: Can't add tail and not be under wanted width.";
|
||||
float allowed_width = width - tail_width;
|
||||
|
||||
// guess approx count of letter
|
||||
wxString temp{"n"};
|
||||
float average_letter_width = calc_text_size(temp).x; // average letter width
|
||||
unsigned count_letter = static_cast<unsigned>(allowed_width / average_letter_width);
|
||||
|
||||
//std::string_view text_ = text;
|
||||
//std::string_view result_text = text_.substr(0, count_letter);
|
||||
wxString result_text(text.substr(0, count_letter));
|
||||
text_width = calc_text_size(result_text).x;
|
||||
if (text_width < allowed_width) {
|
||||
// increase letter count
|
||||
while (count_letter < text.length()) {
|
||||
++count_letter;
|
||||
wxString act_text(text.substr(0, count_letter));
|
||||
text_width = calc_text_size(act_text).x;
|
||||
if (text_width > allowed_width) break;
|
||||
result_text = act_text;
|
||||
}
|
||||
} else {
|
||||
// decrease letter count
|
||||
while (count_letter > 1) {
|
||||
--count_letter;
|
||||
result_text = text.substr(0, count_letter);
|
||||
text_width = calc_text_size(result_text).x;
|
||||
if (text_width < allowed_width) break;
|
||||
}
|
||||
}
|
||||
return result_text .ToStdString()+ tail;
|
||||
}
|
||||
|
||||
void ImGuiWrapper::escape_double_hash(std::string &text)
|
||||
{ // add space between hashes
|
||||
const std::string search = "##";
|
||||
const std::string replace = "# #";
|
||||
size_t pos = 0;
|
||||
while ((pos = text.find(search, pos)) != std::string::npos) text.replace(pos, search.length(), replace);
|
||||
}
|
||||
|
||||
|
||||
void ImGuiWrapper::disable_background_fadeout_animation()
|
||||
{
|
||||
GImGui->DimBgRatio = 1.0f;
|
||||
@ -2206,6 +2363,19 @@ void ImGuiWrapper::pop_combo_style()
|
||||
ImGui::PopStyleColor(7);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::push_radio_style()
|
||||
{
|
||||
if (m_is_dark_mode) {
|
||||
ImGui::PushStyleColor(ImGuiCol_CheckMark, to_ImVec4(decode_color_to_float_array("#00675b"))); // ORCA use orca color for radio buttons
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_CheckMark, to_ImVec4(decode_color_to_float_array("#009688"))); // ORCA use orca color for radio buttons
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiWrapper::pop_radio_style() {
|
||||
ImGui::PopStyleColor(1);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::init_font(bool compress)
|
||||
{
|
||||
destroy_font();
|
||||
|
@ -90,7 +90,7 @@ public:
|
||||
|
||||
float scaled(float x) const { return x * m_font_size; }
|
||||
ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); }
|
||||
ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f) const;
|
||||
static ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f);
|
||||
ImVec2 calc_button_size(const wxString &text, const ImVec2 &button_size = ImVec2(0, 0)) const;
|
||||
float find_widest_text(std::vector<wxString> &text_list);
|
||||
ImVec2 get_item_spacing() const;
|
||||
@ -140,9 +140,9 @@ public:
|
||||
bool bbl_checkbox(const wxString &label, bool &value);
|
||||
bool bbl_radio_button(const char *label, bool active);
|
||||
bool bbl_sliderin(const char *label, int *v, int v_min, int v_max, const char *format = "%d", ImGuiSliderFlags flags = 0);
|
||||
void text(const char *label);
|
||||
void text(const std::string &label);
|
||||
void text(const wxString &label);
|
||||
static void text(const char *label);
|
||||
static void text(const std::string &label);
|
||||
static void text(const wxString &label);
|
||||
void warning_text(const char *all_text);
|
||||
void warning_text(const wxString &all_text);
|
||||
void text_colored(const ImVec4& color, const char* label);
|
||||
@ -195,6 +195,71 @@ public:
|
||||
bool want_text_input() const;
|
||||
bool want_any_input() const;
|
||||
|
||||
// Optional inputs are used for set up value inside of an optional, with default value
|
||||
//
|
||||
// Extended function ImGui::InputInt to work with std::optional<int>, when value == def_val optional is released.
|
||||
static bool input_optional_int(const char *label, std::optional<int> &v, int step = 1, int step_fast = 100, ImGuiInputTextFlags flags = 0, int def_val = 0);
|
||||
// Extended function ImGui::InputFloat to work with std::optional<float> value near def_val cause release of optional
|
||||
static bool input_optional_float(
|
||||
const char *label, std::optional<float> &v, float step = 0.0f, float step_fast = 0.0f, const char *format = "%.3f", ImGuiInputTextFlags flags = 0, float def_val = .0f);
|
||||
// Extended function ImGui::DragFloat to work with std::optional<float> value near def_val cause release of optional
|
||||
static bool drag_optional_float(const char *label, std::optional<float> &v, float v_speed, float v_min, float v_max, const char *format, float power, float def_val = .0f);
|
||||
// Extended function ImGuiWrapper::slider_float to work with std::optional<float> value near def_val cause release of optional
|
||||
bool slider_optional_float(const char * label,
|
||||
std::optional<float> &v,
|
||||
float v_min,
|
||||
float v_max,
|
||||
const char * format = "%.3f",
|
||||
float power = 1.0f,
|
||||
bool clamp = true,
|
||||
const wxString & tooltip = {},
|
||||
bool show_edit_btn = true,
|
||||
float def_val = .0f);
|
||||
// Extended function ImGuiWrapper::slider_float to work with std::optional<int>, when value == def_val than optional release its value
|
||||
bool slider_optional_int(const char * label,
|
||||
std::optional<int> &v,
|
||||
int v_min,
|
||||
int v_max,
|
||||
const char * format = "%.3f",
|
||||
float power = 1.0f,
|
||||
bool clamp = true,
|
||||
const wxString & tooltip = {},
|
||||
bool show_edit_btn = true,
|
||||
int def_val = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Change position of imgui window
|
||||
/// </summary>
|
||||
/// <param name="window_name">ImGui identifier of window</param>
|
||||
/// <param name="output_window_offset">[output] optional </param>
|
||||
/// <param name="try_to_fix">When True Only move to be full visible otherwise reset position</param>
|
||||
/// <returns>New offset of window for function ImGui::SetNextWindowPos</returns>
|
||||
static std::optional<ImVec2> change_window_position(const char *window_name, bool try_to_fix);
|
||||
|
||||
/// <summary>
|
||||
/// Use ImGui internals to unactivate (lose focus) in input.
|
||||
/// When input is activ it can't change value by application.
|
||||
/// </summary>
|
||||
static void left_inputs();
|
||||
|
||||
/// <summary>
|
||||
/// Truncate text by ImGui draw function to specific width
|
||||
/// NOTE 1: ImGui must be initialized
|
||||
/// NOTE 2: Calculation for actual acive imgui font
|
||||
/// </summary>
|
||||
/// <param name="text">Text to be truncated</param>
|
||||
/// <param name="width">Maximal width before truncate</param>
|
||||
/// <param name="tail">String puted on end of text to be visible truncation</param>
|
||||
/// <returns>Truncated text</returns>
|
||||
static std::string trunc(const std::string &text, float width, const char *tail = " ..");
|
||||
|
||||
/// <summary>
|
||||
/// Escape ## in data by add space between hashes
|
||||
/// Needed when user written text is visualized by ImGui.
|
||||
/// </summary>
|
||||
/// <param name="text">In/Out text to be escaped</param>
|
||||
static void escape_double_hash(std::string &text);
|
||||
|
||||
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
bool requires_extra_frame() const { return m_requires_extra_frame; }
|
||||
void set_requires_extra_frame() { m_requires_extra_frame = true; }
|
||||
@ -241,7 +306,8 @@ public:
|
||||
static void pop_button_disable_style();
|
||||
static void push_combo_style(const float scale);
|
||||
static void pop_combo_style();
|
||||
|
||||
static void push_radio_style();
|
||||
static void pop_radio_style();
|
||||
//BBS
|
||||
static int TOOLBAR_WINDOW_FLAGS;
|
||||
|
||||
|
182
src/slic3r/GUI/Jobs/BoostThreadWorker.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
#include <exception>
|
||||
|
||||
#include "BoostThreadWorker.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void BoostThreadWorker::WorkerMessage::deliver(BoostThreadWorker &runner)
|
||||
{
|
||||
switch(MsgType(get_type())) {
|
||||
case Empty: break;
|
||||
case Status: {
|
||||
auto info = boost::get<StatusInfo>(m_data);
|
||||
if (runner.get_pri()) {
|
||||
runner.get_pri()->set_progress(info.status);
|
||||
runner.get_pri()->set_status_text(info.msg.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Finalize: {
|
||||
auto& entry = boost::get<JobEntry>(m_data);
|
||||
entry.job->finalize(entry.canceled, entry.eptr);
|
||||
|
||||
// Unhandled exceptions are rethrown without mercy.
|
||||
if (entry.eptr)
|
||||
std::rethrow_exception(entry.eptr);
|
||||
|
||||
break;
|
||||
}
|
||||
case MainThreadCall: {
|
||||
auto &calldata = boost::get<MainThreadCallData >(m_data);
|
||||
calldata.fn();
|
||||
calldata.promise.set_value();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BoostThreadWorker::run()
|
||||
{
|
||||
bool stop = false;
|
||||
while (!stop) {
|
||||
m_input_queue
|
||||
.consume_one(BlockingWait{0, &m_running}, [this, &stop](JobEntry &e) {
|
||||
if (!e.job)
|
||||
stop = true;
|
||||
else {
|
||||
m_canceled.store(false);
|
||||
|
||||
try {
|
||||
e.job->process(*this);
|
||||
} catch (...) {
|
||||
e.eptr = std::current_exception();
|
||||
}
|
||||
|
||||
e.canceled = m_canceled.load();
|
||||
m_output_queue.push(std::move(e)); // finalization message
|
||||
}
|
||||
m_running.store(false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
void BoostThreadWorker::update_status(int st, const std::string &msg)
|
||||
{
|
||||
m_output_queue.push(st, msg);
|
||||
}
|
||||
|
||||
std::future<void> BoostThreadWorker::call_on_main_thread(std::function<void ()> fn)
|
||||
{
|
||||
MainThreadCallData cbdata{std::move(fn), {}};
|
||||
std::future<void> future = cbdata.promise.get_future();
|
||||
|
||||
m_output_queue.push(std::move(cbdata));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
BoostThreadWorker::BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
boost::thread::attributes &attribs,
|
||||
const char * name)
|
||||
: m_progress(std::move(pri)), m_name{name}
|
||||
{
|
||||
if (m_progress)
|
||||
m_progress->set_cancel_callback([this](){ cancel(); });
|
||||
|
||||
m_thread = create_thread(attribs, [this] { this->run(); });
|
||||
|
||||
std::string nm{name};
|
||||
if (!nm.empty()) set_thread_name(m_thread, name);
|
||||
}
|
||||
|
||||
constexpr int ABORT_WAIT_MAX_MS = 10000;
|
||||
|
||||
BoostThreadWorker::~BoostThreadWorker()
|
||||
{
|
||||
bool joined = false;
|
||||
try {
|
||||
cancel_all();
|
||||
wait_for_idle(ABORT_WAIT_MAX_MS);
|
||||
m_input_queue.push(JobEntry{nullptr});
|
||||
joined = join(ABORT_WAIT_MAX_MS);
|
||||
} catch(...) {}
|
||||
|
||||
if (!joined)
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< "Could not join worker thread '" << m_name << "'";
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::join(int timeout_ms)
|
||||
{
|
||||
if (!m_thread.joinable())
|
||||
return true;
|
||||
|
||||
if (timeout_ms <= 0) {
|
||||
m_thread.join();
|
||||
}
|
||||
else if (m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) {
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BoostThreadWorker::process_events()
|
||||
{
|
||||
while (m_output_queue.consume_one([this](WorkerMessage &msg) {
|
||||
msg.deliver(*this);
|
||||
}));
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::wait_for_current_job(unsigned timeout_ms)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
if (!is_idle()) {
|
||||
bool was_finish = false;
|
||||
bool timeout_reached = false;
|
||||
while (!timeout_reached && !was_finish) {
|
||||
timeout_reached =
|
||||
!m_output_queue.consume_one(BlockingWait{timeout_ms},
|
||||
[this, &was_finish](
|
||||
WorkerMessage &msg) {
|
||||
msg.deliver(*this);
|
||||
if (msg.get_type() ==
|
||||
WorkerMessage::Finalize)
|
||||
was_finish = true;
|
||||
});
|
||||
}
|
||||
|
||||
ret = !timeout_reached;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::wait_for_idle(unsigned timeout_ms)
|
||||
{
|
||||
bool timeout_reached = false;
|
||||
while (!timeout_reached && !is_idle()) {
|
||||
timeout_reached = !m_output_queue
|
||||
.consume_one(BlockingWait{timeout_ms},
|
||||
[this](WorkerMessage &msg) {
|
||||
msg.deliver(*this);
|
||||
});
|
||||
}
|
||||
|
||||
return !timeout_reached;
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::push(std::unique_ptr<JobNew> job)
|
||||
{
|
||||
if (!job)
|
||||
return false;
|
||||
|
||||
m_input_queue.push(JobEntry{std::move(job)});
|
||||
return true;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
155
src/slic3r/GUI/Jobs/BoostThreadWorker.hpp
Normal file
@ -0,0 +1,155 @@
|
||||
#ifndef BOOSTTHREADWORKER_HPP
|
||||
#define BOOSTTHREADWORKER_HPP
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
#include "Worker.hpp"
|
||||
|
||||
#include <libslic3r/Thread.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "ThreadSafeQueue.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// An implementation of the Worker interface which uses the boost::thread
|
||||
// API and two thread safe message queues to communicate with the main thread
|
||||
// back and forth. The queue from the main thread to the worker thread holds the
|
||||
// job entries that will be performed on the worker. The other queue holds messages
|
||||
// from the worker to the main thread. These messages include status updates,
|
||||
// finishing operation and arbitrary functiors that need to be performed
|
||||
// on the main thread during the jobs execution, like displaying intermediate
|
||||
// results.
|
||||
class BoostThreadWorker : public Worker, private JobNew::Ctl
|
||||
{
|
||||
struct JobEntry // Goes into worker and also out of worker as a finalize msg
|
||||
{
|
||||
std::unique_ptr<JobNew> job;
|
||||
bool canceled = false;
|
||||
std::exception_ptr eptr = nullptr;
|
||||
};
|
||||
|
||||
// A message data for status updates. Only goes from worker to main thread.
|
||||
struct StatusInfo { int status; std::string msg; };
|
||||
|
||||
// An arbitrary callback to be called on the main thread. Only from worker
|
||||
// to main thread.
|
||||
struct MainThreadCallData
|
||||
{
|
||||
std::function<void()> fn;
|
||||
std::promise<void> promise;
|
||||
};
|
||||
|
||||
struct EmptyMessage {};
|
||||
|
||||
class WorkerMessage
|
||||
{
|
||||
public:
|
||||
enum MsgType { Empty, Status, Finalize, MainThreadCall };
|
||||
|
||||
private:
|
||||
boost::variant<EmptyMessage, StatusInfo, JobEntry, MainThreadCallData> m_data;
|
||||
|
||||
public:
|
||||
WorkerMessage() = default;
|
||||
WorkerMessage(int s, std::string txt)
|
||||
: m_data{StatusInfo{s, std::move(txt)}}
|
||||
{}
|
||||
WorkerMessage(JobEntry &&entry) : m_data{std::move(entry)} {}
|
||||
WorkerMessage(MainThreadCallData fn) : m_data{std::move(fn)} {}
|
||||
|
||||
int get_type () const { return m_data.which(); }
|
||||
|
||||
void deliver(BoostThreadWorker &runner);
|
||||
};
|
||||
|
||||
using JobQueue = ThreadSafeQueueSPSC<JobEntry>;
|
||||
using MessageQueue = ThreadSafeQueueSPSC<WorkerMessage>;
|
||||
|
||||
boost::thread m_thread;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
std::shared_ptr<ProgressIndicator> m_progress;
|
||||
JobQueue m_input_queue; // from main thread to worker
|
||||
MessageQueue m_output_queue; // form worker to main thread
|
||||
std::string m_name;
|
||||
|
||||
void run();
|
||||
|
||||
bool join(int timeout_ms = 0);
|
||||
|
||||
protected:
|
||||
// Implement Job::Ctl interface:
|
||||
|
||||
void update_status(int st, const std::string &msg = "") override;
|
||||
|
||||
bool was_canceled() const override { return m_canceled.load(); }
|
||||
|
||||
std::future<void> call_on_main_thread(std::function<void()> fn) override;
|
||||
|
||||
public:
|
||||
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
boost::thread::attributes & attr,
|
||||
const char * name = "");
|
||||
|
||||
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
boost::thread::attributes && attr,
|
||||
const char * name = "")
|
||||
: BoostThreadWorker{std::move(pri), attr, name}
|
||||
{}
|
||||
|
||||
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
const char * name = "")
|
||||
: BoostThreadWorker{std::move(pri), {}, name}
|
||||
{}
|
||||
|
||||
~BoostThreadWorker();
|
||||
|
||||
BoostThreadWorker(const BoostThreadWorker &) = delete;
|
||||
BoostThreadWorker(BoostThreadWorker &&) = delete;
|
||||
BoostThreadWorker &operator=(const BoostThreadWorker &) = delete;
|
||||
BoostThreadWorker &operator=(BoostThreadWorker &&) = delete;
|
||||
|
||||
bool push(std::unique_ptr<JobNew> job) override;
|
||||
|
||||
bool is_idle() const override
|
||||
{
|
||||
// The assumption is that jobs can only be queued from a single main
|
||||
// thread from which this method is also called. And the output
|
||||
// messages are also processed only in this calling thread. In that
|
||||
// case, if the input queue is empty, it will remain so during this
|
||||
// function call. If the worker thread is also not running and the
|
||||
// output queue is already processed, we can safely say that the
|
||||
// worker is dormant.
|
||||
return m_input_queue.empty() && !m_running.load() && m_output_queue.empty();
|
||||
}
|
||||
|
||||
void cancel() override { m_canceled.store(true); }
|
||||
void cancel_all() override { m_input_queue.clear(); cancel(); }
|
||||
|
||||
ProgressIndicator * get_pri() { return m_progress.get(); }
|
||||
const ProgressIndicator * get_pri() const { return m_progress.get(); }
|
||||
|
||||
void clear_percent() override
|
||||
{
|
||||
if (m_progress) {
|
||||
m_progress->clear_percent();
|
||||
}
|
||||
}
|
||||
|
||||
void show_error_info(const std::string &msg, int code, const std::string &description, const std::string &extra) override
|
||||
{
|
||||
if (m_progress) {
|
||||
m_progress->show_error_info(from_u8(msg), code, from_u8(description), from_u8(extra));
|
||||
}
|
||||
}
|
||||
|
||||
void process_events() override;
|
||||
bool wait_for_current_job(unsigned timeout_ms = 0) override;
|
||||
bool wait_for_idle(unsigned timeout_ms = 0) override;
|
||||
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // BOOSTTHREADWORKER_HPP
|
54
src/slic3r/GUI/Jobs/BusyCursorJob.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef BUSYCURSORJOB_HPP
|
||||
#define BUSYCURSORJOB_HPP
|
||||
|
||||
#include "JobNew.hpp"
|
||||
|
||||
#include <wx/utils.h>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
struct CursorSetterRAII
|
||||
{
|
||||
JobNew::Ctl &ctl;
|
||||
CursorSetterRAII(JobNew::Ctl &c) : ctl{c}
|
||||
{
|
||||
ctl.call_on_main_thread([] { wxBeginBusyCursor(); });
|
||||
}
|
||||
~CursorSetterRAII()
|
||||
{
|
||||
try {
|
||||
ctl.call_on_main_thread([] { wxEndBusyCursor(); });
|
||||
} catch(...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Can't revert cursor from busy to normal";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<class JobSubclass>
|
||||
class BusyCursored : public JobNew
|
||||
{
|
||||
JobSubclass m_job;
|
||||
|
||||
public:
|
||||
template<class... Args>
|
||||
BusyCursored(Args &&...args) : m_job{std::forward<Args>(args)...}
|
||||
{}
|
||||
|
||||
void process(Ctl &ctl) override
|
||||
{
|
||||
CursorSetterRAII cursor_setter{ctl};
|
||||
m_job.process(ctl);
|
||||
}
|
||||
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override
|
||||
{
|
||||
m_job.finalize(canceled, eptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // BUSYCURSORJOB_HPP
|
1153
src/slic3r/GUI/Jobs/EmbossJob.cpp
Normal file
351
src/slic3r/GUI/Jobs/EmbossJob.hpp
Normal file
@ -0,0 +1,351 @@
|
||||
#ifndef slic3r_EmbossJob_hpp_
|
||||
#define slic3r_EmbossJob_hpp_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <libslic3r/Emboss.hpp>
|
||||
#include <libslic3r/EmbossShape.hpp> // ExPolygonsWithIds
|
||||
#include "libslic3r/Point.hpp" // Transform3d
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
//#include "slic3r/GUI/TextLines.hpp"
|
||||
#include "slic3r/Utils/RaycastManager.hpp"
|
||||
#include "JobNew.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class TriangleMesh;
|
||||
class ModelVolume;
|
||||
class ModelObject;
|
||||
enum class ModelVolumeType : int;
|
||||
class BuildVolume;
|
||||
namespace GUI {
|
||||
//class Plater;
|
||||
class GLCanvas3D;
|
||||
class Worker;
|
||||
class Selection;
|
||||
namespace Emboss {
|
||||
class DataBase
|
||||
{
|
||||
public:
|
||||
DataBase(const std::string &volume_name, std::shared_ptr<std::atomic<bool>> cancel) : volume_name(volume_name), cancel(std::move(cancel)) {}
|
||||
DataBase(const std::string &volume_name, std::shared_ptr<std::atomic<bool>> cancel, EmbossShape &&shape)
|
||||
: volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape))
|
||||
{}
|
||||
DataBase(DataBase &&) = default;
|
||||
virtual ~DataBase() = default;
|
||||
|
||||
/// <summary>
|
||||
/// Create shape
|
||||
/// e.g. Text extract glyphs from font
|
||||
/// Not 'const' function because it could modify shape
|
||||
/// </summary>
|
||||
virtual EmbossShape &create_shape() { return shape; };
|
||||
|
||||
/// <summary>
|
||||
/// Write data how to reconstruct shape to volume
|
||||
/// </summary>
|
||||
/// <param name="volume">Data object for store emboss params</param>
|
||||
virtual void write(ModelVolume &volume) const;
|
||||
|
||||
// Define projection move
|
||||
// True (raised) .. move outside from surface (MODEL_PART)
|
||||
// False (engraved).. move into object (NEGATIVE_VOLUME)
|
||||
bool is_outside = true;
|
||||
|
||||
// Define per letter projection on one text line
|
||||
// [optional] It is not used when empty
|
||||
Slic3r::Emboss::TextLines text_lines = {};
|
||||
// [optional] Define distance for surface
|
||||
// It is used only for flat surface (not cutted)
|
||||
// Position of Zero(not set value) differ for MODEL_PART and NEGATIVE_VOLUME
|
||||
std::optional<float> from_surface;
|
||||
// new volume name
|
||||
std::string volume_name;
|
||||
// flag that job is canceled
|
||||
// for time after process.
|
||||
std::shared_ptr<std::atomic<bool>> cancel;
|
||||
// shape to emboss
|
||||
EmbossShape shape;
|
||||
};
|
||||
|
||||
struct DataCreateVolumeUtil : public DataBase // modfiy bu bbs //struct DataCreateVolume : public DataBase
|
||||
{
|
||||
// define embossed volume type
|
||||
ModelVolumeType volume_type;
|
||||
|
||||
// parent ModelObject index where to create volume
|
||||
ObjectID object_id;
|
||||
|
||||
// new created volume transformation
|
||||
Transform3d trmat;
|
||||
};
|
||||
using DataBasePtr = std::unique_ptr<DataBase>;
|
||||
/// <summary>
|
||||
/// Hold neccessary data to update embossed text object in job
|
||||
/// </summary>
|
||||
struct DataUpdate
|
||||
{
|
||||
// Hold data about shape
|
||||
DataBasePtr base;
|
||||
|
||||
// unique identifier of volume to change
|
||||
ObjectID volume_id;
|
||||
|
||||
// Used for prevent flooding Undo/Redo stack on slider.
|
||||
bool make_snapshot;
|
||||
};
|
||||
|
||||
struct CreateVolumeParams
|
||||
{
|
||||
GLCanvas3D &canvas;
|
||||
// Direction of ray into scene
|
||||
const Camera &camera;
|
||||
// To put new object on the build volume
|
||||
const BuildVolume &build_volume;
|
||||
// used to emplace job for execution
|
||||
Worker &worker;
|
||||
// Contain AABB trees from scene
|
||||
typedef std::function<void()> register_mesh_pick;
|
||||
register_mesh_pick on_register_mesh_pick{nullptr};
|
||||
RaycastManager &raycaster;
|
||||
RaycastManager::AllowVolumes& raycast_condition;
|
||||
// New created volume type
|
||||
ModelVolumeType volume_type;
|
||||
// Define which gizmo open on the success
|
||||
unsigned char gizmo_type; // GLGizmosManager::EType
|
||||
// Volume define object to add new volume
|
||||
const GLVolume *gl_volume;
|
||||
// Contain AABB trees from scene
|
||||
// RaycastManager &raycaster;
|
||||
// Wanted additionl move in Z(emboss) direction of new created volume
|
||||
std::optional<float> distance = {};
|
||||
// Wanted additionl rotation around Z of new created volume
|
||||
std::optional<float> angle = {};
|
||||
};
|
||||
struct DataCreateObject
|
||||
{
|
||||
// Hold data about shape
|
||||
DataBasePtr base;
|
||||
// define position on screen where to create object
|
||||
Vec2d screen_coor;
|
||||
// projection property
|
||||
const Camera &camera;
|
||||
// shape of bed in case of create volume on bed
|
||||
std::vector<Vec2d> bed_shape;
|
||||
// Define which gizmo open on the success
|
||||
unsigned char gizmo_type;
|
||||
// additionl rotation around Z axe, given by style settings
|
||||
std::optional<float> angle = {};
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Hold neccessary data to create ModelVolume in job
|
||||
/// Volume is created on the surface of existing volume in object.
|
||||
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
|
||||
/// </summary>
|
||||
struct DataCreateVolume
|
||||
{
|
||||
// Hold data about shape
|
||||
DataBasePtr base;
|
||||
|
||||
// define embossed volume type
|
||||
ModelVolumeType volume_type;
|
||||
|
||||
// parent ModelObject index where to create volume
|
||||
ObjectID object_id;
|
||||
|
||||
// new created volume transformation
|
||||
std::optional<Transform3d> trmat;
|
||||
|
||||
// Define which gizmo open on the success
|
||||
unsigned char gizmo_type;
|
||||
};
|
||||
|
||||
struct SurfaceVolumeData
|
||||
{
|
||||
// Transformation of volume inside of object
|
||||
Transform3d transform;
|
||||
|
||||
struct ModelSource
|
||||
{
|
||||
// source volumes
|
||||
std::shared_ptr<const TriangleMesh> mesh;
|
||||
// Transformation of volume inside of object
|
||||
Transform3d tr;
|
||||
};
|
||||
using ModelSources = std::vector<ModelSource>;
|
||||
ModelSources sources;
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Hold neccessary data to update embossed text object in job
|
||||
/// </summary>
|
||||
struct UpdateSurfaceVolumeData : public DataUpdate, public SurfaceVolumeData
|
||||
{};
|
||||
|
||||
static bool was_canceled(const JobNew::Ctl &ctl, const DataBase &base);
|
||||
static bool exception_process(std::exception_ptr &eptr);
|
||||
static bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input);
|
||||
/// <summary>
|
||||
/// Update text volume to use surface from object
|
||||
/// </summary>
|
||||
class UpdateSurfaceVolumeJob : public JobNew
|
||||
{
|
||||
UpdateSurfaceVolumeData m_input;
|
||||
TriangleMesh m_result;
|
||||
|
||||
public:
|
||||
// move params to private variable
|
||||
explicit UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input);
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||
static bool is_use_surfae_error;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Update text shape in existing text volume
|
||||
/// Predict that there is only one runnig(not canceled) instance of it
|
||||
/// </summary>
|
||||
class UpdateJob : public JobNew
|
||||
{
|
||||
DataUpdate m_input;
|
||||
TriangleMesh m_result;
|
||||
|
||||
public:
|
||||
// move params to private variable
|
||||
explicit UpdateJob(DataUpdate &&input);
|
||||
|
||||
/// <summary>
|
||||
/// Create new embossed volume by m_input data and store to m_result
|
||||
/// </summary>
|
||||
/// <param name="ctl">Control containing cancel flag</param>
|
||||
void process(Ctl &ctl) override;
|
||||
|
||||
/// <summary>
|
||||
/// Update volume - change object_id
|
||||
/// </summary>
|
||||
/// <param name="canceled">Was process canceled.
|
||||
/// NOTE: Be carefull it doesn't care about
|
||||
/// time between finished process and started finalize part.</param>
|
||||
/// <param name="">unused</param>
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||
|
||||
/// <summary>
|
||||
/// Update text volume
|
||||
/// </summary>
|
||||
/// <param name="volume">Volume to be updated</param>
|
||||
/// <param name="mesh">New Triangle mesh for volume</param>
|
||||
/// <param name="base">Data to write into volume</param>
|
||||
static void update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create new TextObject on the platter
|
||||
/// Should not be stopped
|
||||
/// </summary>
|
||||
class CreateObjectJob : public JobNew
|
||||
{
|
||||
DataCreateObject m_input;
|
||||
TriangleMesh m_result;
|
||||
Transform3d m_transformation;
|
||||
|
||||
public:
|
||||
explicit CreateObjectJob(DataCreateObject &&input);
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||
};
|
||||
|
||||
struct Texture
|
||||
{
|
||||
unsigned id{0};
|
||||
unsigned width{0};
|
||||
unsigned height{0};
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Hold neccessary data to create(cut) volume from surface object in job
|
||||
/// </summary>
|
||||
struct CreateSurfaceVolumeData : public SurfaceVolumeData
|
||||
{
|
||||
// Hold data about shape
|
||||
DataBasePtr base;
|
||||
// define embossed volume type
|
||||
ModelVolumeType volume_type;
|
||||
// parent ModelObject index where to create volume
|
||||
ObjectID object_id;
|
||||
// Define which gizmo open on the success
|
||||
unsigned char gizmo_type;
|
||||
};
|
||||
class CreateSurfaceVolumeJob : public JobNew
|
||||
{
|
||||
CreateSurfaceVolumeData m_input;
|
||||
TriangleMesh m_result;
|
||||
|
||||
public:
|
||||
explicit CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input);
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||
};
|
||||
|
||||
class CreateVolumeJob : public JobNew
|
||||
{
|
||||
DataCreateVolume m_input;
|
||||
TriangleMesh m_result;
|
||||
|
||||
public:
|
||||
explicit CreateVolumeJob(DataCreateVolume &&input);
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||
};
|
||||
static bool check(unsigned char gizmo_type);
|
||||
static bool check(const DataBase &input, bool check_fontfile, bool use_surface = false);
|
||||
static bool check(const CreateVolumeParams &input);
|
||||
static bool check(const DataCreateObject &input);
|
||||
static bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false);
|
||||
static bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false);
|
||||
static bool check(const DataCreateVolume &input, bool is_main_thread = false);
|
||||
static bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false);
|
||||
bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss_data, const Vec2d &coor);
|
||||
bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr data);
|
||||
bool start_create_volume_job( Worker &worker, const ModelObject &object, const std::optional<Transform3d> &volume_tr, DataBasePtr data, ModelVolumeType volume_type, unsigned char gizmo_type);
|
||||
bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos);
|
||||
bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos);
|
||||
static ExPolygons create_shape(DataBase &input);
|
||||
static TriangleMesh create_mesh_per_glyph(DataBase &input);
|
||||
static TriangleMesh try_create_mesh(DataBase &input);
|
||||
static TriangleMesh create_mesh(DataBase &input);
|
||||
static indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d &tr, const SurfaceVolumeData::ModelSources &sources, DataBase &input);
|
||||
static TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &input2);
|
||||
static TriangleMesh cut_surface(DataBase &input1, const SurfaceVolumeData &input2);
|
||||
static void _update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr = nullptr);
|
||||
static void create_volume(
|
||||
TriangleMesh &&mesh, const ObjectID &object_id, const ModelVolumeType type, const std::optional<Transform3d> &trmat, const DataBase &data, unsigned char gizmo_type);
|
||||
/// Update text volume
|
||||
/// </summary>
|
||||
/// <param name="volume">Volume to be updated</param>
|
||||
/// <param name="mesh">New Triangle mesh for volume</param>
|
||||
/// <param name="base">Data to write into volume</param>
|
||||
bool start_update_volume(DataUpdate &&data, const ModelVolume &volume, const Selection &selection, RaycastManager &raycaster);
|
||||
|
||||
/// <summary>
|
||||
/// Copied triangles from object to be able create mesh for cut surface from
|
||||
/// </summary>
|
||||
/// <param name="volume">Define embossed volume</param>
|
||||
/// <returns>Source data for cut surface from</returns>
|
||||
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume);
|
||||
/// <summary>
|
||||
/// Copied triangles from object to be able create mesh for cut surface from
|
||||
/// </summary>
|
||||
/// <param name="volumes">Source object volumes for cut surface from</param>
|
||||
/// <param name="text_volume_id">Source volume id</param>
|
||||
/// <returns>Source data for cut surface from</returns>
|
||||
SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id = {});
|
||||
}
|
||||
} // namespace Slic3r::GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_EmbossJob_hpp_
|
68
src/slic3r/GUI/Jobs/JobNew.hpp
Normal file
@ -0,0 +1,68 @@
|
||||
#ifndef JOBNEW_HPP
|
||||
#define JOBNEW_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
|
||||
#include <wx/window.h>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "ProgressIndicator.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// A class representing a job that is to be run in the background, not blocking
|
||||
// the main thread. Running it is up to a Worker object (see Worker interface)
|
||||
class JobNew {
|
||||
public:
|
||||
|
||||
enum JobPrepareState {
|
||||
PREPARE_STATE_DEFAULT = 0,
|
||||
PREPARE_STATE_MENU = 1,
|
||||
};
|
||||
|
||||
// A controller interface that informs the job about cancellation and
|
||||
// makes it possible for the job to advertise its status.
|
||||
class Ctl {
|
||||
public:
|
||||
virtual ~Ctl() = default;
|
||||
|
||||
// status update, to be used from the work thread (process() method)
|
||||
virtual void update_status(int st, const std::string &msg = "") = 0;
|
||||
|
||||
// Returns true if the job was asked to cancel itself.
|
||||
virtual bool was_canceled() const = 0;
|
||||
|
||||
// Orca:
|
||||
virtual void clear_percent() = 0;
|
||||
virtual void show_error_info(const std::string &msg, int code, const std::string &description, const std::string &extra) = 0;
|
||||
|
||||
// Execute a functor on the main thread. Note that the exact time of
|
||||
// execution is hard to determine. This can be used to make modifications
|
||||
// on the UI, like displaying some intermediate results or modify the
|
||||
// cursor.
|
||||
// This function returns a std::future<void> object which enables the
|
||||
// caller to optionally wait for the main thread to finish the function call.
|
||||
virtual std::future<void> call_on_main_thread(std::function<void()> fn) = 0;
|
||||
};
|
||||
|
||||
virtual ~JobNew() = default;
|
||||
|
||||
// The method where the actual work of the job should be defined. This is
|
||||
// run on the worker thread.
|
||||
virtual void process(Ctl &ctl) = 0;
|
||||
|
||||
// Launched when the job is finished on the UI thread.
|
||||
// If the job was cancelled, the first parameter will have a true value.
|
||||
// Exceptions occuring in process() are redirected from the worker thread
|
||||
// into the main (UI) thread. This method receives the exception and can
|
||||
// handle it properly. Assign nullptr to this second argument before
|
||||
// function return to prevent further action. Leaving it with a non-null
|
||||
// value will result in rethrowing by the worker.
|
||||
virtual void finalize(bool /*canceled*/, std::exception_ptr &) {}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // JOB_HPP
|
@ -22,11 +22,14 @@ void NotificationProgressIndicator::set_range(int range)
|
||||
|
||||
void NotificationProgressIndicator::set_cancel_callback(CancelFn fn)
|
||||
{
|
||||
m_nm->progress_indicator_set_cancel_callback(std::move(fn));
|
||||
m_cancelfn = std::move(fn);
|
||||
m_nm->progress_indicator_set_cancel_callback(m_cancelfn);
|
||||
}
|
||||
|
||||
void NotificationProgressIndicator::set_progress(int pr)
|
||||
{
|
||||
if (!pr)
|
||||
set_cancel_callback(m_cancelfn);
|
||||
m_nm->progress_indicator_set_progress(pr);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ class NotificationManager;
|
||||
|
||||
class NotificationProgressIndicator: public ProgressIndicator {
|
||||
NotificationManager *m_nm = nullptr;
|
||||
CancelFn m_cancelfn;
|
||||
|
||||
public:
|
||||
|
||||
|
155
src/slic3r/GUI/Jobs/PlaterWorker.hpp
Normal file
@ -0,0 +1,155 @@
|
||||
#ifndef PLATERWORKER_HPP
|
||||
#define PLATERWORKER_HPP
|
||||
|
||||
#include <map>
|
||||
#include <chrono>
|
||||
|
||||
#include "Worker.hpp"
|
||||
#include "BusyCursorJob.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
template<class WorkerSubclass>
|
||||
class PlaterWorker: public Worker {
|
||||
WorkerSubclass m_w;
|
||||
wxWindow *m_plater;
|
||||
|
||||
class PlaterJob : public JobNew {
|
||||
std::unique_ptr<JobNew> m_job;
|
||||
wxWindow *m_plater;
|
||||
long long m_process_duration; // [ms]
|
||||
|
||||
public:
|
||||
void process(Ctl &c) override
|
||||
{
|
||||
// Ensure that wxWidgets processing wakes up to handle outgoing
|
||||
// messages in plater's wxIdle handler. Otherwise it might happen
|
||||
// that the message will only be processed when an event like mouse
|
||||
// move comes along which might be too late.
|
||||
struct WakeUpCtl: Ctl {
|
||||
Ctl &ctl;
|
||||
WakeUpCtl(Ctl &c) : ctl{c} {}
|
||||
|
||||
void update_status(int st, const std::string &msg = "") override
|
||||
{
|
||||
ctl.update_status(st, msg);
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
|
||||
bool was_canceled() const override { return ctl.was_canceled(); }
|
||||
|
||||
std::future<void> call_on_main_thread(std::function<void()> fn) override
|
||||
{
|
||||
auto ftr = ctl.call_on_main_thread(std::move(fn));
|
||||
wxWakeUpIdle();
|
||||
|
||||
return ftr;
|
||||
}
|
||||
|
||||
void clear_percent() override {
|
||||
ctl.clear_percent();
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
|
||||
void show_error_info(const std::string &msg, int code, const std::string &description, const std::string &extra) override
|
||||
{
|
||||
ctl.show_error_info(msg, code, description, extra);
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
|
||||
} wctl{c};
|
||||
|
||||
CursorSetterRAII busycursor{wctl};
|
||||
|
||||
using namespace std::chrono;
|
||||
steady_clock::time_point process_start = steady_clock::now();
|
||||
m_job->process(wctl);
|
||||
steady_clock::time_point process_end = steady_clock::now();
|
||||
m_process_duration = duration_cast<milliseconds>(process_end - process_start).count();
|
||||
}
|
||||
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override
|
||||
{
|
||||
using namespace std::chrono;
|
||||
steady_clock::time_point finalize_start = steady_clock::now();
|
||||
m_job->finalize(canceled, eptr);
|
||||
steady_clock::time_point finalize_end = steady_clock::now();
|
||||
long long finalize_duration = duration_cast<milliseconds>(finalize_end - finalize_start).count();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info)
|
||||
<< std::fixed // do not use scientific notations
|
||||
<< "Job '" << typeid(*m_job).name() << "' "
|
||||
<< "spend " << m_process_duration + finalize_duration << "ms "
|
||||
<< "(process " << m_process_duration << "ms + finalize " << finalize_duration << "ms)";
|
||||
|
||||
if (eptr) try {
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (std::exception &e) {
|
||||
show_error(m_plater, _L("An unexpected error occured") + ": " + e.what());
|
||||
eptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PlaterJob(wxWindow *p, std::unique_ptr<JobNew> j)
|
||||
: m_job{std::move(j)}, m_plater{p}
|
||||
{
|
||||
// TODO: decide if disabling slice button during UI job is what we
|
||||
// want.
|
||||
// if (m_plater)
|
||||
// m_plater->sidebar().enable_buttons(false);
|
||||
}
|
||||
|
||||
~PlaterJob() override
|
||||
{
|
||||
// TODO: decide if disabling slice button during UI job is what we want.
|
||||
|
||||
// Reload scene ensures that the slice button gets properly
|
||||
// enabled or disabled after the job finishes, depending on the
|
||||
// state of slicing. This might be an overkill but works for now.
|
||||
// if (m_plater)
|
||||
// m_plater->canvas3D()->reload_scene(false);
|
||||
}
|
||||
};
|
||||
|
||||
EventGuard on_idle_evt;
|
||||
EventGuard on_paint_evt;
|
||||
|
||||
public:
|
||||
|
||||
template<class... WorkerArgs>
|
||||
PlaterWorker(wxWindow *plater, WorkerArgs &&...args)
|
||||
: m_w{std::forward<WorkerArgs>(args)...}
|
||||
, m_plater{plater}
|
||||
// Ensure that messages from the worker thread to the UI thread are
|
||||
// processed continuously.
|
||||
, on_idle_evt(plater, wxEVT_IDLE, [this](wxIdleEvent&) { process_events(); })
|
||||
, on_paint_evt(plater, wxEVT_PAINT, [this](wxPaintEvent&) { process_events(); })
|
||||
{
|
||||
}
|
||||
|
||||
// Always package the job argument into a PlaterJob
|
||||
bool push(std::unique_ptr<JobNew> job) override
|
||||
{
|
||||
return m_w.push(std::make_unique<PlaterJob>(m_plater, std::move(job)));
|
||||
}
|
||||
|
||||
bool is_idle() const override { return m_w.is_idle(); }
|
||||
void cancel() override { m_w.cancel(); }
|
||||
void cancel_all() override { m_w.cancel_all(); }
|
||||
void process_events() override { m_w.process_events(); }
|
||||
bool wait_for_current_job(unsigned timeout_ms = 0) override
|
||||
{
|
||||
return m_w.wait_for_current_job(timeout_ms);
|
||||
}
|
||||
bool wait_for_idle(unsigned timeout_ms = 0) override
|
||||
{
|
||||
return m_w.wait_for_idle(timeout_ms);
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // PLATERJOB_HPP
|
124
src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp
Normal file
@ -0,0 +1,124 @@
|
||||
#ifndef THREADSAFEQUEUE_HPP
|
||||
#define THREADSAFEQUEUE_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Helper structure for overloads of ThreadSafeQueueSPSC::consume_one()
|
||||
// to block if the queue is empty.
|
||||
struct BlockingWait
|
||||
{
|
||||
// Timeout to wait for the arrival of new element into the queue.
|
||||
unsigned timeout_ms = 0;
|
||||
|
||||
// An optional atomic flag to set true if an incoming element gets
|
||||
// consumed. The flag will be atomically set to true when popping the
|
||||
// front of the queue.
|
||||
std::atomic<bool> *pop_flag = nullptr;
|
||||
};
|
||||
|
||||
// A thread safe queue for one producer and one consumer.
|
||||
template<class T,
|
||||
template<class, class...> class Container = std::deque,
|
||||
class... ContainerArgs>
|
||||
class ThreadSafeQueueSPSC
|
||||
{
|
||||
std::queue<T, Container<T, ContainerArgs...>> m_queue;
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_cond_var;
|
||||
public:
|
||||
|
||||
// Consume one element, block if the queue is empty.
|
||||
template<class Fn> bool consume_one(const BlockingWait &blkw, Fn &&fn)
|
||||
{
|
||||
static_assert(!std::is_reference_v<T>, "");
|
||||
static_assert(std::is_default_constructible_v<T>, "");
|
||||
static_assert(std::is_move_assignable_v<T> || std::is_copy_assignable_v<T>, "");
|
||||
|
||||
T el;
|
||||
{
|
||||
std::unique_lock lk{m_mutex};
|
||||
|
||||
auto pred = [this]{ return !m_queue.empty(); };
|
||||
if (blkw.timeout_ms > 0) {
|
||||
auto timeout = std::chrono::milliseconds(blkw.timeout_ms);
|
||||
if (!m_cond_var.wait_for(lk, timeout, pred))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
m_cond_var.wait(lk, pred);
|
||||
|
||||
if constexpr (std::is_move_assignable_v<T>)
|
||||
el = std::move(m_queue.front());
|
||||
else
|
||||
el = m_queue.front();
|
||||
|
||||
m_queue.pop();
|
||||
|
||||
if (blkw.pop_flag)
|
||||
// The optional flag is set before the lock us unlocked.
|
||||
blkw.pop_flag->store(true);
|
||||
}
|
||||
|
||||
fn(el);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consume one element, return true if consumed, false if queue was empty.
|
||||
template<class Fn> bool consume_one(Fn &&fn)
|
||||
{
|
||||
T el;
|
||||
{
|
||||
std::unique_lock lk{m_mutex};
|
||||
if (!m_queue.empty()) {
|
||||
if constexpr (std::is_move_assignable_v<T>)
|
||||
el = std::move(m_queue.front());
|
||||
else
|
||||
el = m_queue.front();
|
||||
|
||||
m_queue.pop();
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
fn(el);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Push element into the queue.
|
||||
template<class...TArgs> void push(TArgs&&...el)
|
||||
{
|
||||
std::lock_guard lk{m_mutex};
|
||||
m_queue.emplace(std::forward<TArgs>(el)...);
|
||||
m_cond_var.notify_one();
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
std::lock_guard lk{m_mutex};
|
||||
return m_queue.empty();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
std::lock_guard lk{m_mutex};
|
||||
return m_queue.size();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
std::lock_guard lk{m_mutex};
|
||||
while (!m_queue.empty())
|
||||
m_queue.pop();
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // THREADSAFEQUEUE_HPP
|
140
src/slic3r/GUI/Jobs/Worker.hpp
Normal file
@ -0,0 +1,140 @@
|
||||
#ifndef PRUSALSICER_WORKER_HPP
|
||||
#define PRUSALSICER_WORKER_HPP
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "JobNew.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
// #define EXECUTE_UPDATE_ON_MAIN_THREAD // debug execution on main thread
|
||||
// An interface of a worker that runs jobs on a dedicated worker thread, one
|
||||
// after the other. It is assumed that every method of this class is called
|
||||
// from the same main thread.
|
||||
|
||||
#ifdef EXECUTE_UPDATE_ON_MAIN_THREAD
|
||||
namespace {
|
||||
// Run Job on main thread (blocking) - ONLY DEBUG
|
||||
static inline bool execute_job(std::shared_ptr<JobNew> j)
|
||||
{
|
||||
struct MyCtl : public JobNew::Ctl
|
||||
{
|
||||
void update_status(int st, const std::string &msg = "") override{};
|
||||
bool was_canceled() const override { return false; }
|
||||
std::future<void> call_on_main_thread(std::function<void()> fn) override { return std::future<void>{}; }
|
||||
} ctl;
|
||||
j->process(ctl);
|
||||
wxGetApp().plater()->CallAfter([j]() {
|
||||
std::exception_ptr e_ptr = nullptr;
|
||||
j->finalize(false, e_ptr);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
class Worker {
|
||||
public:
|
||||
// Queue up a new job after the current one. This call does not block.
|
||||
// Returns false if the job gets discarded.
|
||||
virtual bool push(std::unique_ptr<JobNew> job) = 0;
|
||||
|
||||
// Returns true if no job is running, the job queue is empty and no job
|
||||
// message is left to be processed. This means that nothing is left to
|
||||
// finalize or take care of in the main thread.
|
||||
virtual bool is_idle() const = 0;
|
||||
|
||||
// Ask the current job gracefully to cancel. This call is not blocking and
|
||||
// the job may or may not cancel eventually, depending on its
|
||||
// implementation. Note that it is not trivial to kill a thread forcefully
|
||||
// and we don't need that.
|
||||
virtual void cancel() = 0;
|
||||
|
||||
// This method will delete the queued jobs and cancel the current one.
|
||||
virtual void cancel_all() = 0;
|
||||
|
||||
// Needs to be called continuously to process events (like status update
|
||||
// or finalizing of jobs) in the main thread. This can be done e.g. in a
|
||||
// wxIdle handler.
|
||||
virtual void process_events() = 0;
|
||||
|
||||
// Wait until the current job finishes. Timeout will only be considered
|
||||
// if not zero. Returns false if timeout is reached but the job has not
|
||||
// finished.
|
||||
virtual bool wait_for_current_job(unsigned timeout_ms = 0) = 0;
|
||||
|
||||
// Wait until the whole job queue finishes. Timeout will only be considered
|
||||
// if not zero. Returns false only if timeout is reached but the worker has
|
||||
// not reached the idle state.
|
||||
virtual bool wait_for_idle(unsigned timeout_ms = 0) = 0;
|
||||
|
||||
// The destructor shall properly close the worker thread.
|
||||
virtual ~Worker() = default;
|
||||
};
|
||||
|
||||
template<class Fn> constexpr bool IsProcessFn = std::is_invocable_v<Fn, JobNew::Ctl&>;
|
||||
template<class Fn> constexpr bool IsFinishFn = std::is_invocable_v<Fn, bool, std::exception_ptr&>;
|
||||
|
||||
// Helper function to use the worker with arbitrary functors.
|
||||
template<class ProcessFn, class FinishFn,
|
||||
class = std::enable_if_t<IsProcessFn<ProcessFn>>,
|
||||
class = std::enable_if_t<IsFinishFn<FinishFn>> >
|
||||
bool queue_job(Worker &w, ProcessFn fn, FinishFn finishfn)
|
||||
{
|
||||
struct LambdaJob: JobNew {
|
||||
ProcessFn fn;
|
||||
FinishFn finishfn;
|
||||
|
||||
LambdaJob(ProcessFn pfn, FinishFn ffn)
|
||||
: fn{std::move(pfn)}, finishfn{std::move(ffn)}
|
||||
{}
|
||||
|
||||
void process(Ctl &ctl) override { fn(ctl); }
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override
|
||||
{
|
||||
finishfn(canceled, eptr);
|
||||
}
|
||||
};
|
||||
|
||||
auto j = std::make_unique<LambdaJob>(std::move(fn), std::move(finishfn));
|
||||
return w.push(std::move(j));
|
||||
}
|
||||
|
||||
template<class ProcessFn, class = std::enable_if_t<IsProcessFn<ProcessFn>>>
|
||||
bool queue_job(Worker &w, ProcessFn fn)
|
||||
{
|
||||
return queue_job(w, std::move(fn), [](bool, std::exception_ptr &) {});
|
||||
}
|
||||
|
||||
inline bool queue_job(Worker &w, std::unique_ptr<JobNew> j)
|
||||
{
|
||||
return w.push(std::move(j));
|
||||
}
|
||||
|
||||
// Replace the current job queue with a new job. The signature is the same
|
||||
// as for queue_job(). This cancels all jobs and
|
||||
// will not wait. The new job will begin after the queue cancels properly.
|
||||
// Note that this can be called from the UI thread and will not block it if
|
||||
// the jobs take longer to cancel.
|
||||
template<class...Args> bool replace_job(Worker &w, Args&& ...args)
|
||||
{
|
||||
w.cancel_all();
|
||||
return queue_job(w, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Cancel the current job and wait for it to actually be stopped.
|
||||
inline bool stop_current_job(Worker &w, unsigned timeout_ms = 0)
|
||||
{
|
||||
w.cancel();
|
||||
return w.wait_for_current_job(timeout_ms);
|
||||
}
|
||||
|
||||
// Cancel all pending jobs including current one and wait until the worker
|
||||
// becomes idle.
|
||||
inline bool stop_queue(Worker &w, unsigned timeout_ms = 0)
|
||||
{
|
||||
w.cancel_all();
|
||||
return w.wait_for_idle(timeout_ms);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // WORKER_HPP
|
@ -883,6 +883,7 @@ void MainFrame::shutdown()
|
||||
#endif // _WIN32
|
||||
|
||||
if (m_plater != nullptr) {
|
||||
m_plater->get_ui_job_worker().cancel_all();
|
||||
m_plater->stop_jobs();
|
||||
|
||||
// Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC,
|
||||
|
@ -147,8 +147,6 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n,
|
||||
, m_evt_handler (evt_handler)
|
||||
, m_notification_start (GLCanvas3D::timestamp_now())
|
||||
{
|
||||
m_is_dark = wxGetApp().plater()->get_current_canvas3D()->get_dark_mode_status();
|
||||
|
||||
m_ErrorColor = ImVec4(0.9, 0.36, 0.36, 1);
|
||||
m_WarnColor = ImVec4(0.99, 0.69, 0.455, 1);
|
||||
m_NormalColor = ImVec4(0.03, 0.6, 0.18, 1);
|
||||
@ -158,17 +156,31 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n,
|
||||
m_WindowBkgColor = ImVec4(1, 1, 1, 1);
|
||||
m_TextColor = ImVec4(.2f, .2f, .2f, 1.0f);
|
||||
m_HyperTextColor = ImVec4(0.03, 0.6, 0.18, 1);
|
||||
}
|
||||
|
||||
m_WindowRadius = 4.0f * wxGetApp().plater()->get_current_canvas3D()->get_scale();
|
||||
// We cannot call plater()->get_current_canvas3D() from constructor, so we do it here
|
||||
void NotificationManager::PopNotification::ensure_ui_inited()
|
||||
{
|
||||
if (!m_is_dark_inited) {
|
||||
m_is_dark = wxGetApp().plater()->get_current_canvas3D()->get_dark_mode_status();
|
||||
m_is_dark_inited = true;
|
||||
}
|
||||
|
||||
if (!m_WindowRadius_inited) {
|
||||
m_WindowRadius = 4.0f * wxGetApp().plater()->get_current_canvas3D()->get_scale();
|
||||
m_WindowRadius_inited = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::PopNotification::on_change_color_mode(bool is_dark)
|
||||
{
|
||||
m_is_dark_inited = true;
|
||||
m_is_dark = is_dark;
|
||||
}
|
||||
|
||||
void NotificationManager::PopNotification::use_bbl_theme()
|
||||
{
|
||||
ensure_ui_inited();
|
||||
ImGuiStyle &OldStyle = ImGui::GetStyle();
|
||||
|
||||
m_DefaultTheme.mWindowPadding = OldStyle.WindowPadding;
|
||||
@ -356,7 +368,7 @@ void NotificationManager::PopNotification::bbl_render_block_notification(GLCanva
|
||||
std::string name = "!!Ntfctn" + std::to_string(m_id);
|
||||
|
||||
use_bbl_theme();
|
||||
if (m_data.level == NotificationLevel::SeriousWarningNotificationLevel)
|
||||
if (m_data.level == NotificationLevel::SeriousWarningNotificationLevel)
|
||||
{
|
||||
push_style_color(ImGuiCol_Border, {245.f / 255.f, 155 / 255.f, 22 / 255.f, 1}, true, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_WindowBg, {245.f / 255.f, 155 / 255.f, 22 / 255.f, 1}, true, m_current_fade_opacity);
|
||||
@ -704,17 +716,17 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui,
|
||||
|
||||
//hover color
|
||||
ImVec4 HyperColor = m_HyperTextColor;//ImVec4(150.f / 255.f, 100.f / 255.f, 0.f / 255.f, 1)
|
||||
if (m_data.level == NotificationLevel::SeriousWarningNotificationLevel)
|
||||
HyperColor = ImVec4(0.f, 0.f, 0.f, 0.4f);
|
||||
if (m_data.level == NotificationLevel::ErrorNotificationLevel)
|
||||
HyperColor = ImVec4(135.f / 255.f, 43 / 255.f, 43 / 255.f, 1);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
|
||||
{
|
||||
HyperColor.y += 0.1f;
|
||||
if (m_data.level == NotificationLevel::SeriousWarningNotificationLevel || m_data.level == NotificationLevel::SeriousWarningNotificationLevel)
|
||||
HyperColor.x += 0.2f;
|
||||
if (m_data.level == NotificationLevel::SeriousWarningNotificationLevel)
|
||||
HyperColor = ImVec4(0.f, 0.f, 0.f, 0.4f);
|
||||
if (m_data.level == NotificationLevel::ErrorNotificationLevel)
|
||||
HyperColor = ImVec4(135.f / 255.f, 43 / 255.f, 43 / 255.f, 1);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
|
||||
{
|
||||
HyperColor.y += 0.1f;
|
||||
if (m_data.level == NotificationLevel::SeriousWarningNotificationLevel || m_data.level == NotificationLevel::SeriousWarningNotificationLevel)
|
||||
HyperColor.x += 0.2f;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//text
|
||||
push_style_color(ImGuiCol_Text, HyperColor, m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
@ -856,11 +868,12 @@ void NotificationManager::PopNotification::bbl_render_block_notif_left_sign(ImGu
|
||||
ImGui::SetCursorPosX(m_line_height / 3);
|
||||
ImGui::SetCursorPosY(m_window_height / 2 - m_line_height);
|
||||
imgui.text(text.c_str());
|
||||
|
||||
|
||||
}
|
||||
|
||||
void NotificationManager::PopNotification::bbl_render_left_sign(ImGuiWrapper &imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
ensure_ui_inited();
|
||||
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 round_rect_pos = ImVec2(win_pos_x - win_size_x + ImGui::GetStyle().WindowBorderSize, win_pos_y + ImGui::GetStyle().WindowBorderSize);
|
||||
ImVec2 round_rect_size = ImVec2(m_WindowRadius * 2, win_size_y - 2 * ImGui::GetStyle().WindowBorderSize);
|
||||
@ -892,6 +905,7 @@ void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui)
|
||||
}
|
||||
void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
ensure_ui_inited();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
@ -1075,6 +1089,7 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper&
|
||||
|
||||
void NotificationManager::ExportFinishedNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
ensure_ui_inited();
|
||||
PopNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
if (m_to_removable && !m_eject_pending)
|
||||
render_eject_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
@ -1883,7 +1898,7 @@ void NotificationManager::upload_job_notification_show_error(int id, const std::
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::push_slicing_serious_warning_notification(const std::string &text, std::vector<ModelObject const *> objs)
|
||||
void NotificationManager::push_slicing_serious_warning_notification(const std::string &text, std::vector<ModelObject const *> objs)
|
||||
{
|
||||
std::vector<ObjectID> ids;
|
||||
for (auto optr : objs) {
|
||||
@ -1924,7 +1939,7 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s
|
||||
set_slicing_progress_hidden();
|
||||
}
|
||||
|
||||
void NotificationManager::close_slicing_serious_warning_notification(const std::string &text)
|
||||
void NotificationManager::close_slicing_serious_warning_notification(const std::string &text)
|
||||
{
|
||||
for (std::unique_ptr<PopNotification> ¬ification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::SlicingSeriousWarning && notification->compare_text(_u8L("Serious warning:") + "\n" + text)) { notification->close(); }
|
||||
@ -2199,7 +2214,7 @@ bool NotificationManager::push_notification_data(std::unique_ptr<NotificationMan
|
||||
}
|
||||
} else {
|
||||
m_pop_notifications.emplace_back(std::move(notification));
|
||||
|
||||
|
||||
retval = true;
|
||||
}
|
||||
if (!m_initialized)
|
||||
@ -2244,7 +2259,7 @@ void NotificationManager::render_notifications(GLCanvas3D &canvas, float overlay
|
||||
for (const auto& notification : m_pop_notifications) {
|
||||
if (notification->get_data().level == NotificationLevel::ErrorNotificationLevel || notification->get_data().level == NotificationLevel::SeriousWarningNotificationLevel) {
|
||||
notification->bbl_render_block_notification(canvas, bottom_up_last_y, m_move_from_overlay && !m_in_preview, overlay_width * m_scale, right_margin * m_scale);
|
||||
if (notification->get_state() != PopNotification::EState::Finished)
|
||||
if (notification->get_state() != PopNotification::EState::Finished)
|
||||
bottom_up_last_y = notification->get_top() + GAP_WIDTH;
|
||||
}
|
||||
else {
|
||||
@ -2392,7 +2407,7 @@ void NotificationManager::set_in_preview(bool preview)
|
||||
notification->hide(preview);
|
||||
if (m_in_preview && notification->get_type() == NotificationType::DidYouKnowHint)
|
||||
notification->close();
|
||||
if (notification->get_type() == NotificationType::ValidateWarning)
|
||||
if (notification->get_type() == NotificationType::ValidateWarning)
|
||||
notification->hide(preview);
|
||||
}
|
||||
}
|
||||
|
@ -462,9 +462,9 @@ private:
|
||||
virtual bool push_background_color();
|
||||
// used this function instead of reading directly m_data.duration. Some notifications might need to return changing value.
|
||||
virtual int get_duration() { return m_data.duration; }
|
||||
|
||||
void ensure_ui_inited();
|
||||
bool m_is_dark = false;
|
||||
|
||||
bool m_is_dark_inited = false;
|
||||
const NotificationData m_data;
|
||||
// For reusing ImGUI windows.
|
||||
NotificationIDProvider &m_id_provider;
|
||||
@ -493,7 +493,7 @@ private:
|
||||
ImVec4 m_CurrentColor;
|
||||
|
||||
float m_WindowRadius;
|
||||
|
||||
bool m_WindowRadius_inited = false;
|
||||
void use_bbl_theme();
|
||||
void restore_default_theme();
|
||||
|
||||
@ -867,7 +867,7 @@ private:
|
||||
}},
|
||||
|
||||
NotificationData{NotificationType::BBLUserPresetExceedLimit, NotificationLevel::WarningNotificationLevel, BBL_NOTICE_MAX_INTERVAL,
|
||||
_u8L("The number of user presets cached in the cloud has exceeded the upper limit, newly created user presets can only be used locally."),
|
||||
_u8L("The number of user presets cached in the cloud has exceeded the upper limit, newly created user presets can only be used locally."),
|
||||
_u8L("Wiki"),
|
||||
[](wxEvtHandler* evnthndlr) {
|
||||
wxLaunchDefaultBrowser("https://wiki.bambulab.com/en/software/bambu-studio/3rd-party-printer-profile#cloud-user-presets-limit");
|
||||
|
@ -72,6 +72,7 @@ const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
|
||||
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
|
||||
const wxString& sub_obj_name,
|
||||
Slic3r::ModelVolumeType type,
|
||||
const bool is_svg_volume,
|
||||
const wxBitmap& bmp,
|
||||
const wxString& extruder,
|
||||
const int idx/* = -1*/,
|
||||
@ -80,6 +81,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* pare
|
||||
m_name(sub_obj_name),
|
||||
m_type(itVolume),
|
||||
m_volume_type(type),
|
||||
m_is_svg_volume(is_svg_volume),
|
||||
m_idx(idx),
|
||||
m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : ""),
|
||||
m_warning_icon_name(warning_icon_name)
|
||||
@ -356,10 +358,10 @@ bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col)
|
||||
return false;
|
||||
}
|
||||
|
||||
void ObjectDataViewModelNode::SetName(const wxString &tempName)
|
||||
{
|
||||
void ObjectDataViewModelNode::SetName(const wxString &tempName)
|
||||
{
|
||||
if (m_name != tempName) {
|
||||
m_name = tempName;
|
||||
m_name = tempName;
|
||||
}
|
||||
}
|
||||
|
||||
@ -450,6 +452,8 @@ ObjectDataViewModel::ObjectDataViewModel()
|
||||
m_bitmap_cache = new Slic3r::GUI::BitmapCache;
|
||||
|
||||
m_volume_bmps = MenuFactory::get_volume_bitmaps();
|
||||
m_text_volume_bmps = MenuFactory::get_text_volume_bitmaps();
|
||||
m_svg_volume_bmps = MenuFactory::get_svg_volume_bitmaps();
|
||||
m_warning_bmp = create_scaled_bitmap(WarningIcon);
|
||||
m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon);
|
||||
m_lock_bmp = create_scaled_bitmap(LockIcon);
|
||||
@ -511,7 +515,7 @@ wxDataViewItem ObjectDataViewModel::AddPlate(PartPlate* part_plate, wxString nam
|
||||
if (!is_added) {
|
||||
m_plates.push_back(plate_node);
|
||||
}
|
||||
|
||||
|
||||
wxDataViewItem plate_item(plate_node);
|
||||
if (refresh) {
|
||||
ItemAdded(wxDataViewItem(nullptr), plate_item);
|
||||
@ -542,7 +546,10 @@ void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node)
|
||||
is_volume_node &= (vol_type >= int(ModelVolumeType::MODEL_PART) && vol_type <= int(ModelVolumeType::SUPPORT_ENFORCER));
|
||||
|
||||
if (!node->has_warning_icon() && !node->has_lock()) {
|
||||
node->SetBitmap(is_volume_node ? m_volume_bmps.at(vol_type) : m_empty_bmp);
|
||||
node->SetBitmap(is_volume_node ? (node->is_text_volume() ? m_text_volume_bmps.at(vol_type) :
|
||||
node->is_svg_volume() ? m_svg_volume_bmps.at(vol_type) :
|
||||
m_volume_bmps.at(vol_type)) :
|
||||
m_empty_bmp);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -561,16 +568,22 @@ void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node)
|
||||
bmps.emplace_back(node->warning_icon_name() == WarningIcon ? m_warning_bmp : m_warning_manifold_bmp);
|
||||
if (node->has_lock())
|
||||
bmps.emplace_back(m_lock_bmp);
|
||||
if (is_volume_node)
|
||||
bmps.emplace_back(m_volume_bmps[vol_type]);
|
||||
if (is_volume_node) {
|
||||
if (!bmps.empty()) // ORCA: Add spacing between icons if there are multiple
|
||||
bmps.emplace_back(create_scaled_bitmap("dot", nullptr, int(wxGetApp().em_unit() / 10) * 4));
|
||||
bmps.emplace_back(node->is_text_volume() ? m_text_volume_bmps[vol_type] :
|
||||
node->is_svg_volume() ? m_svg_volume_bmps[vol_type] :
|
||||
m_volume_bmps[vol_type]);
|
||||
}
|
||||
bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps);
|
||||
}
|
||||
|
||||
node->SetBitmap(*bmp);
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node, bool has_lock)
|
||||
void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node, const std::string &warning_icon_name, bool has_lock)
|
||||
{
|
||||
node->SetWarningIconName(warning_icon_name);
|
||||
node->SetLock(has_lock);
|
||||
UpdateBitmapForNode(node);
|
||||
}
|
||||
@ -597,7 +610,7 @@ wxDataViewItem ObjectDataViewModel::AddObject(ModelObject *model_object, std::st
|
||||
const wxString extruder_str = wxString::Format("%d", extruder);
|
||||
auto obj_node = new ObjectDataViewModelNode(name, extruder_str, plate_idx, model_object);
|
||||
obj_node->SetWarningBitmap(GetWarningBitmap(warning_bitmap), warning_bitmap);
|
||||
UpdateBitmapForNode(obj_node, has_lock);
|
||||
UpdateBitmapForNode(obj_node, warning_bitmap, has_lock);
|
||||
|
||||
if (plate_node != nullptr) {
|
||||
obj_node->m_parent = plate_node;
|
||||
@ -623,6 +636,7 @@ wxDataViewItem ObjectDataViewModel::AddObject(ModelObject *model_object, std::st
|
||||
wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item,
|
||||
const wxString &name,
|
||||
const Slic3r::ModelVolumeType volume_type,
|
||||
const bool is_svg_volume,
|
||||
const std::string& warning_icon_name/* = std::string()*/,
|
||||
const int extruder/* = 0*/,
|
||||
const bool create_frst_child/* = true*/)
|
||||
@ -638,8 +652,9 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent
|
||||
if (create_frst_child && root->m_volumes_cnt == 0)
|
||||
{
|
||||
const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART;
|
||||
const auto node = new ObjectDataViewModelNode(root, root->m_name, type, GetVolumeIcon(type, root->m_warning_icon_name), root->m_extruder, 0, root->m_warning_icon_name);
|
||||
|
||||
const auto node = new ObjectDataViewModelNode(root, root->m_name, type, is_svg_volume, GetVolumeIcon(type, root->m_warning_icon_name), root->m_extruder,
|
||||
0, root->m_warning_icon_name);
|
||||
UpdateBitmapForNode(node, root->m_warning_icon_name, root->has_lock());
|
||||
insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position);
|
||||
// notify control
|
||||
const wxDataViewItem child((void*)node);
|
||||
@ -661,15 +676,16 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent
|
||||
extruder_str = wxString::Format("%d", extruder);
|
||||
}
|
||||
|
||||
const auto node = new ObjectDataViewModelNode(root, name, volume_type, GetVolumeIcon(volume_type, warning_icon_name),
|
||||
const auto node = new ObjectDataViewModelNode(root, name, volume_type, is_svg_volume, GetVolumeIcon(volume_type, warning_icon_name),
|
||||
extruder_str, root->m_volumes_cnt, warning_icon_name);
|
||||
UpdateBitmapForNode(node, warning_icon_name, root->has_lock() && volume_type < ModelVolumeType::PARAMETER_MODIFIER);
|
||||
insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position);
|
||||
|
||||
// if part with errors is added, but object wasn't marked, then mark it
|
||||
if (!warning_icon_name.empty() && warning_icon_name != root->m_warning_icon_name &&
|
||||
(root->m_warning_icon_name.empty() || root->m_warning_icon_name == WarningManifoldIcon) )
|
||||
if (!warning_icon_name.empty() && warning_icon_name != root->m_warning_icon_name && (root->m_warning_icon_name.empty() || root->m_warning_icon_name == WarningManifoldIcon)) {
|
||||
root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
|
||||
UpdateBitmapForNode(root);
|
||||
}
|
||||
// notify control
|
||||
const wxDataViewItem child((void*)node);
|
||||
ItemAdded(parent_item, child);
|
||||
@ -1291,9 +1307,9 @@ wxDataViewItem ObjectDataViewModel::GetItemByPlateId(int plate_idx)
|
||||
return wxDataViewItem(nullptr);
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::SetCurSelectedPlateFullName(int plate_idx, const std::string & custom_name) {
|
||||
void ObjectDataViewModel::SetCurSelectedPlateFullName(int plate_idx, const std::string & custom_name) {
|
||||
for (auto plate : m_plates) {
|
||||
if (plate->m_plate_idx == plate_idx) {
|
||||
if (plate->m_plate_idx == plate_idx) {
|
||||
wxString plate_full_name =_L("Plate");
|
||||
plate_full_name += wxString::Format(" %d", plate_idx + 1);
|
||||
if (!custom_name.empty()) {
|
||||
@ -2100,7 +2116,7 @@ bool ObjectDataViewModel::HasInfoItem(InfoItemType type) const
|
||||
|
||||
ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const
|
||||
{
|
||||
if (!item.IsOk())
|
||||
if (!item.IsOk())
|
||||
return itUndef;
|
||||
ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode *>(item.GetID());
|
||||
return node->m_type < 0 ? itUndef : node->m_type;
|
||||
@ -2350,6 +2366,8 @@ void ObjectDataViewModel::SetSinkState(const bool painted, wxDataViewItem obj_it
|
||||
void ObjectDataViewModel::Rescale()
|
||||
{
|
||||
m_volume_bmps = MenuFactory::get_volume_bitmaps();
|
||||
m_text_volume_bmps = MenuFactory::get_text_volume_bitmaps();
|
||||
m_svg_volume_bmps = MenuFactory::get_svg_volume_bitmaps();
|
||||
m_warning_bmp = create_scaled_bitmap(WarningIcon);
|
||||
m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon);
|
||||
m_lock_bmp = create_scaled_bitmap(LockIcon);
|
||||
@ -2371,10 +2389,8 @@ void ObjectDataViewModel::Rescale()
|
||||
switch (node->m_type)
|
||||
{
|
||||
case itObject:
|
||||
if (node->m_bmp.IsOk()) node->m_bmp = GetWarningBitmap(node->m_warning_icon_name);
|
||||
break;
|
||||
case itVolume:
|
||||
node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_warning_icon_name);
|
||||
UpdateBitmapForNode(node);
|
||||
break;
|
||||
case itLayerRoot:
|
||||
node->m_bmp = create_scaled_bitmap(LayerRootIcon);
|
||||
@ -2420,13 +2436,14 @@ void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std::
|
||||
ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID());
|
||||
|
||||
if (node->GetType() & itObject) {
|
||||
node->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
UpdateBitmapForNode(node, warning_icon_name, node->has_lock());
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->GetType() & itVolume) {
|
||||
node->SetWarningBitmap(GetVolumeIcon(node->GetVolumeType(), warning_icon_name), warning_icon_name);
|
||||
node->GetParent()->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
UpdateBitmapForNode(node, warning_icon_name, node->has_lock());
|
||||
if (ObjectDataViewModelNode *parent = node->GetParent())
|
||||
UpdateBitmapForNode(parent, warning_icon_name, parent->has_lock());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -2447,6 +2464,7 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo
|
||||
}
|
||||
|
||||
node->SetWarningBitmap(wxNullBitmap, "");
|
||||
UpdateBitmapForNode(node);
|
||||
if (unmark_object)
|
||||
{
|
||||
wxDataViewItemArray children;
|
||||
|
@ -109,6 +109,8 @@ class ObjectDataViewModelNode
|
||||
|
||||
std::string m_action_icon_name = "";
|
||||
ModelVolumeType m_volume_type = ModelVolumeType(-1);
|
||||
bool m_is_text_volume{false};
|
||||
bool m_is_svg_volume{false};
|
||||
InfoItemType m_info_item_type {InfoItemType::Undef};
|
||||
bool m_action_enable = false; // can undo all settings
|
||||
// BBS
|
||||
@ -139,6 +141,7 @@ public:
|
||||
ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
|
||||
const wxString& sub_obj_name,
|
||||
Slic3r::ModelVolumeType type,
|
||||
const bool is_svg_volume,
|
||||
const wxBitmap& bmp,
|
||||
const wxString& extruder,
|
||||
const int idx = -1,
|
||||
@ -237,6 +240,7 @@ public:
|
||||
void SetVolumeType(ModelVolumeType type) { m_volume_type = type; }
|
||||
void SetBitmap(const wxBitmap &icon) { m_bmp = icon; }
|
||||
void SetExtruder(const wxString &extruder) { m_extruder = extruder; }
|
||||
void SetWarningIconName(const std::string &warning_icon_name) { m_warning_icon_name = warning_icon_name; }
|
||||
void SetWarningBitmap(const wxBitmap& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; }
|
||||
void SetLock(bool has_lock) { m_has_lock = has_lock; }
|
||||
const wxBitmap& GetBitmap() const { return m_bmp; }
|
||||
@ -307,6 +311,8 @@ public:
|
||||
void update_settings_digest_bitmaps();
|
||||
bool update_settings_digest(const std::vector<std::string>& categories);
|
||||
int volume_type() const { return int(m_volume_type); }
|
||||
bool is_text_volume() const { return m_is_text_volume; }
|
||||
bool is_svg_volume() const { return m_is_svg_volume; }
|
||||
void msw_rescale();
|
||||
|
||||
#ifndef NDEBUG
|
||||
@ -335,6 +341,8 @@ class ObjectDataViewModel :public wxDataViewModel
|
||||
std::vector<ObjectDataViewModelNode*> m_plates;
|
||||
std::vector<ObjectDataViewModelNode*> m_objects;
|
||||
std::vector<wxBitmap> m_volume_bmps;
|
||||
std::vector<wxBitmap> m_text_volume_bmps;
|
||||
std::vector<wxBitmap> m_svg_volume_bmps;
|
||||
std::map<InfoItemType, wxBitmap> m_info_bmps;
|
||||
wxBitmap m_empty_bmp;
|
||||
wxBitmap m_warning_bmp;
|
||||
@ -356,7 +364,7 @@ public:
|
||||
std::map<int, int> &get_ui_and_3d_volume_map() { return m_ui_and_3d_volume_map; }
|
||||
int get_real_volume_index_in_3d(int ui_value)
|
||||
{
|
||||
if (m_ui_and_3d_volume_map.find(ui_value) != m_ui_and_3d_volume_map.end()) {
|
||||
if (m_ui_and_3d_volume_map.find(ui_value) != m_ui_and_3d_volume_map.end()) {
|
||||
return m_ui_and_3d_volume_map[ui_value];
|
||||
}
|
||||
return ui_value;
|
||||
@ -375,6 +383,7 @@ public:
|
||||
wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item,
|
||||
const wxString &name,
|
||||
const Slic3r::ModelVolumeType volume_type,
|
||||
const bool is_svg_volume,
|
||||
const std::string& warning_icon_name = std::string(),
|
||||
const int extruder = 0,
|
||||
const bool create_frst_child = true);
|
||||
@ -540,7 +549,7 @@ private:
|
||||
wxDataViewItem AddOutsidePlate(bool refresh = true);
|
||||
|
||||
void UpdateBitmapForNode(ObjectDataViewModelNode *node);
|
||||
void UpdateBitmapForNode(ObjectDataViewModelNode *node, bool has_lock);
|
||||
void UpdateBitmapForNode(ObjectDataViewModelNode *node, const std::string &warning_icon_name, bool has_lock);
|
||||
};
|
||||
|
||||
|
||||
|
@ -88,6 +88,8 @@
|
||||
#include "Jobs/SLAImportJob.hpp"
|
||||
#include "Jobs/PrintJob.hpp"
|
||||
#include "Jobs/NotificationProgressIndicator.hpp"
|
||||
#include "Jobs/PlaterWorker.hpp"
|
||||
#include "Jobs/BoostThreadWorker.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "SelectMachine.hpp"
|
||||
#include "SendMultiMachinePage.hpp"
|
||||
@ -107,7 +109,7 @@
|
||||
#include "MsgDialog.hpp"
|
||||
#include "ProjectDirtyStateManager.hpp"
|
||||
#include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification
|
||||
|
||||
#include "Gizmos/GLGizmoSVG.hpp" // Drop SVG file
|
||||
// BBS
|
||||
#include "Widgets/ProgressDialog.hpp"
|
||||
#include "BBLStatusBar.hpp"
|
||||
@ -2218,7 +2220,9 @@ struct Plater::priv
|
||||
|
||||
BackgroundSlicingProcess background_process;
|
||||
bool suppressed_backround_processing_update { false };
|
||||
|
||||
// UIThreadWorker can be used as a replacement for BoostThreadWorker if
|
||||
// no additional worker threads are desired (useful for debugging or profiling)
|
||||
PlaterWorker<BoostThreadWorker> m_worker;
|
||||
// Jobs defined inside the group class will be managed so that only one can
|
||||
// run at a time. Also, the background process will be stopped if a job is
|
||||
// started. It is up the the plater to ensure that the background slicing
|
||||
@ -2747,6 +2751,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
}))
|
||||
, sidebar(new Sidebar(q))
|
||||
, notification_manager(std::make_unique<NotificationManager>(q))
|
||||
, m_worker{q, std::make_unique<NotificationProgressIndicator>(notification_manager.get()), "ui_worker"}
|
||||
, m_ui_jobs(this)
|
||||
, m_job_prepare_state(Job::JobPrepareState::PREPARE_STATE_DEFAULT)
|
||||
, delayed_scene_refresh(false)
|
||||
@ -3364,6 +3369,10 @@ void Plater::priv::reset_all_gizmos()
|
||||
view3D->get_canvas3d()->reset_all_gizmos();
|
||||
}
|
||||
|
||||
Worker &Plater::get_ui_job_worker() { return p->m_worker; }
|
||||
|
||||
const Worker &Plater::get_ui_job_worker() const { return p->m_worker; }
|
||||
|
||||
// Called after the Preferences dialog is closed and the program settings are saved.
|
||||
// Update the UI based on the current preferences.
|
||||
void Plater::priv::update_ui_from_settings()
|
||||
@ -7357,8 +7366,15 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
|
||||
menu = is_some_full_instances ? menus.assemble_object_menu() :
|
||||
is_part ? menus.assemble_part_menu() : menus.assemble_multi_selection_menu();
|
||||
} else {
|
||||
menu = is_some_full_instances ? menus.object_menu() :
|
||||
is_part ? menus.part_menu() : menus.multi_selection_menu();
|
||||
if (is_some_full_instances)
|
||||
menu = printer_technology == ptSLA ? menus.sla_object_menu() : menus.object_menu();
|
||||
else if (is_part) {
|
||||
const GLVolume * gl_volume = selection.get_first_volume();
|
||||
const ModelVolume *model_volume = get_model_volume(*gl_volume, selection.get_model()->objects);
|
||||
menu = (model_volume != nullptr && model_volume->is_svg()) ? menus.svg_part_menu() :
|
||||
menus.part_menu();
|
||||
} else
|
||||
menu = menus.multi_selection_menu();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10338,6 +10354,26 @@ void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
Refresh();
|
||||
}
|
||||
|
||||
bool Plater::emboss_svg(const wxString &svg_file)
|
||||
{
|
||||
std::string svg_file_str = into_u8(svg_file);
|
||||
GLCanvas3D *canvas = canvas3D();
|
||||
if (canvas == nullptr)
|
||||
return false;
|
||||
auto base_svg = canvas->get_gizmos_manager().get_gizmo(GLGizmosManager::Svg);
|
||||
if (base_svg == nullptr)
|
||||
return false;
|
||||
GLGizmoSVG *svg = dynamic_cast<GLGizmoSVG*>(base_svg);
|
||||
if (svg == nullptr)
|
||||
return false;
|
||||
// Refresh hover state to find surface point under mouse
|
||||
wxMouseEvent evt(wxEVT_MOTION);
|
||||
auto mouse_drop_position =canvas->get_local_mouse_position();
|
||||
evt.SetPosition(wxPoint(mouse_drop_position.x(), mouse_drop_position.y()));
|
||||
canvas->on_mouse(evt); // call render where is call GLCanvas3D::_picking_pass()
|
||||
return svg->create_volume(svg_file_str, mouse_drop_position, ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
|
||||
//BBS: remove GCodeViewer as seperate APP logic
|
||||
bool Plater::load_files(const wxArrayString& filenames)
|
||||
{
|
||||
@ -10347,6 +10383,20 @@ bool Plater::load_files(const wxArrayString& filenames)
|
||||
std::vector<fs::path> normal_paths;
|
||||
std::vector<fs::path> gcode_paths;
|
||||
|
||||
// When only one .svg file is dropped on scene
|
||||
if (filenames.size() == 1) {
|
||||
const wxString &filename = filenames.Last();
|
||||
const wxString file_extension = filename.substr(filename.length() - 4);
|
||||
if (file_extension.CmpNoCase(".svg") == 0) {
|
||||
// BBS: GUI refactor: move sidebar to the left
|
||||
/* const wxPoint offset = GetPosition() + p->current_panel->GetPosition();
|
||||
Vec2d mouse_position(x - offset.x, y - offset.y);*/
|
||||
// Scale for retina displays
|
||||
//canvas->apply_retina_scale(mouse_position);
|
||||
return emboss_svg(filename);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& filename : filenames) {
|
||||
fs::path path(into_path(filename));
|
||||
if (std::regex_match(path.string(), pattern_drop))
|
||||
@ -10765,6 +10815,7 @@ void Plater::set_selected_visible(bool visible)
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(this, "Set Selected Objects Visible in AssembleView");
|
||||
get_ui_job_worker().cancel_all();
|
||||
p->m_ui_jobs.cancel_all();
|
||||
|
||||
p->get_current_canvas3D()->set_selected_visible(visible);
|
||||
@ -10783,6 +10834,7 @@ void Plater::remove_selected()
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(this, "Delete Selected Objects");
|
||||
get_ui_job_worker().cancel_all();
|
||||
p->m_ui_jobs.cancel_all();
|
||||
|
||||
//BBS delete current selected
|
||||
@ -11560,6 +11612,101 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls)
|
||||
; // store failed
|
||||
}
|
||||
}*/
|
||||
namespace {
|
||||
std::string get_file_name(const std::string &file_path)
|
||||
{
|
||||
size_t pos_last_delimiter = file_path.find_last_of("/\\");
|
||||
size_t pos_point = file_path.find_last_of('.');
|
||||
size_t offset = pos_last_delimiter + 1;
|
||||
size_t count = pos_point - pos_last_delimiter - 1;
|
||||
return file_path.substr(offset, count);
|
||||
}
|
||||
using SvgFile = EmbossShape::SvgFile;
|
||||
using SvgFiles = std::vector<SvgFile *>;
|
||||
std::string create_unique_3mf_filepath(const std::string &file, const SvgFiles svgs)
|
||||
{
|
||||
// const std::string MODEL_FOLDER = "3D/"; // copy from file 3mf.cpp
|
||||
std::string path_in_3mf = "3D/" + file + ".svg";
|
||||
size_t suffix_number = 0;
|
||||
bool is_unique = false;
|
||||
do {
|
||||
is_unique = true;
|
||||
path_in_3mf = "3D/" + file + ((suffix_number++) ? ("_" + std::to_string(suffix_number)) : "") + ".svg";
|
||||
for (SvgFile *svgfile : svgs) {
|
||||
if (svgfile->path_in_3mf.empty()) continue;
|
||||
if (svgfile->path_in_3mf.compare(path_in_3mf) == 0) {
|
||||
is_unique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!is_unique);
|
||||
return path_in_3mf;
|
||||
}
|
||||
|
||||
bool set_by_local_path(SvgFile &svg, const SvgFiles &svgs)
|
||||
{
|
||||
// Try to find already used svg file
|
||||
for (SvgFile *svg_ : svgs) {
|
||||
if (svg_->path_in_3mf.empty()) continue;
|
||||
if (svg.path.compare(svg_->path) == 0) {
|
||||
svg.path_in_3mf = svg_->path_in_3mf;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function to secure private data before store to 3mf
|
||||
/// </summary>
|
||||
/// <param name="model">Data(also private) to clean before publishing</param>
|
||||
void publish(Model &model, SaveStrategy strategy)
|
||||
{
|
||||
// SVG file publishing
|
||||
bool exist_new = false;
|
||||
SvgFiles svgfiles;
|
||||
for (ModelObject *object : model.objects) {
|
||||
for (ModelVolume *volume : object->volumes) {
|
||||
if (!volume->emboss_shape.has_value()) continue;
|
||||
|
||||
assert(volume->emboss_shape->svg_file.has_value());
|
||||
if (!volume->emboss_shape->svg_file.has_value()) continue;
|
||||
|
||||
SvgFile *svg = &(*volume->emboss_shape->svg_file);
|
||||
if (svg->path_in_3mf.empty()) exist_new = true;
|
||||
svgfiles.push_back(svg);
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: don't show this in silence mode
|
||||
if (exist_new && !(strategy & SaveStrategy::Silence)) {
|
||||
MessageDialog dialog(nullptr,
|
||||
_L("Are you sure you want to store original SVGs with their local paths into the 3MF file?\n"
|
||||
"If you hit 'NO', all SVGs in the project will not be editable any more."),
|
||||
_L("Private protection"), wxYES_NO | wxICON_QUESTION);
|
||||
if (dialog.ShowModal() == wxID_NO) {
|
||||
for (ModelObject *object : model.objects)
|
||||
for (ModelVolume *volume : object->volumes)
|
||||
if (volume->emboss_shape.has_value()) volume->emboss_shape.reset();
|
||||
}
|
||||
}
|
||||
|
||||
for (SvgFile *svgfile : svgfiles) {
|
||||
if (!svgfile->path_in_3mf.empty())
|
||||
continue; // already suggested path (previous save)
|
||||
|
||||
// create unique name for svgs, when local path differ
|
||||
std::string filename = "unknown";
|
||||
if (!svgfile->path.empty()) {
|
||||
if (set_by_local_path(*svgfile, svgfiles))
|
||||
continue;
|
||||
// check whether original filename is already in:
|
||||
filename = get_file_name(svgfile->path);
|
||||
}
|
||||
svgfile->path_in_3mf = create_unique_3mf_filepath(filename, svgfiles);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// BBS: backup
|
||||
int Plater::export_3mf(const boost::filesystem::path& output_path, SaveStrategy strategy, int export_plate_idx, Export3mfProgressFn proFn)
|
||||
@ -11579,6 +11726,9 @@ int Plater::export_3mf(const boost::filesystem::path& output_path, SaveStrategy
|
||||
|
||||
if (!path.Lower().EndsWith(".3mf"))
|
||||
return -1;
|
||||
// take care about private data stored into .3mf
|
||||
// modify model
|
||||
publish(p->model, strategy);
|
||||
|
||||
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
|
||||
const std::string path_u8 = into_u8(path);
|
||||
@ -12832,6 +12982,28 @@ void Plater::changed_mesh(int obj_idx)
|
||||
p->schedule_background_process();
|
||||
}
|
||||
|
||||
void Plater::changed_object(ModelObject &object)
|
||||
{
|
||||
assert(object.get_model() == &p->model); // is object from same model?
|
||||
object.invalidate_bounding_box();
|
||||
|
||||
// recenter and re - align to Z = 0
|
||||
object.ensure_on_bed(p->printer_technology != ptSLA);
|
||||
|
||||
if (p->printer_technology == ptSLA) {
|
||||
// Update the SLAPrint from the current Model, so that the reload_scene()
|
||||
// pulls the correct data, update the 3D scene.
|
||||
p->update_restart_background_process(true, false);
|
||||
} else
|
||||
p->view3D->reload_scene(false);
|
||||
|
||||
// update print
|
||||
p->schedule_background_process();
|
||||
|
||||
// Check outside bed
|
||||
get_current_canvas3D()->requires_check_outside_state();
|
||||
}
|
||||
|
||||
void Plater::changed_object(int obj_idx)
|
||||
{
|
||||
if (obj_idx < 0)
|
||||
@ -14073,7 +14245,8 @@ bool Plater::PopupObjectTableBySelection()
|
||||
|
||||
wxMenu* Plater::plate_menu() { return p->menus.plate_menu(); }
|
||||
wxMenu* Plater::object_menu() { return p->menus.object_menu(); }
|
||||
wxMenu* Plater::part_menu() { return p->menus.part_menu(); }
|
||||
wxMenu *Plater::part_menu() { return p->menus.part_menu(); }
|
||||
wxMenu *Plater::svg_part_menu() { return p->menus.svg_part_menu(); }
|
||||
wxMenu* Plater::sla_object_menu() { return p->menus.sla_object_menu(); }
|
||||
wxMenu* Plater::default_menu() { return p->menus.default_menu(); }
|
||||
wxMenu* Plater::instance_menu() { return p->menus.instance_menu(); }
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "Jobs/Job.hpp"
|
||||
#include "Jobs/Worker.hpp"
|
||||
#include "Search.hpp"
|
||||
#include "PartPlate.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
@ -287,12 +288,15 @@ public:
|
||||
// To be called when providing a list of files to the GUI slic3r on command line.
|
||||
std::vector<size_t> load_files(const std::vector<std::string>& input_files, LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig, bool ask_multi = false);
|
||||
// to be called on drag and drop
|
||||
bool emboss_svg(const wxString &svg_file);
|
||||
bool load_files(const wxArrayString& filenames);
|
||||
|
||||
const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; }
|
||||
|
||||
void update(bool conside_update_flag = false, bool force_background_processing_update = false);
|
||||
//BBS
|
||||
Worker & get_ui_job_worker();
|
||||
const Worker &get_ui_job_worker() const;
|
||||
void object_list_changed();
|
||||
void stop_jobs();
|
||||
bool is_any_job_running() const;
|
||||
@ -382,6 +386,7 @@ public:
|
||||
void clear_before_change_mesh(int obj_idx);
|
||||
void changed_mesh(int obj_idx);
|
||||
|
||||
void changed_object(ModelObject &object);
|
||||
void changed_object(int obj_idx);
|
||||
void changed_objects(const std::vector<size_t>& object_idxs);
|
||||
void schedule_background_process(bool schedule = true);
|
||||
@ -719,6 +724,7 @@ public:
|
||||
wxMenu* plate_menu();
|
||||
wxMenu* object_menu();
|
||||
wxMenu* part_menu();
|
||||
wxMenu* svg_part_menu();
|
||||
wxMenu* sla_object_menu();
|
||||
wxMenu* default_menu();
|
||||
wxMenu* instance_menu();
|
||||
|
@ -149,6 +149,10 @@ void Selection::set_model(Model* model)
|
||||
update_valid();
|
||||
}
|
||||
|
||||
void Selection::set_mode(EMode mode) {
|
||||
m_mode = mode;
|
||||
}
|
||||
|
||||
int Selection::query_real_volume_idx_from_other_view(unsigned int object_idx, unsigned int instance_idx, unsigned int model_volume_idx)
|
||||
{
|
||||
for (int i = 0; i < m_volumes->size(); i++) {
|
||||
@ -876,6 +880,10 @@ const GLVolume* Selection::get_volume(unsigned int volume_idx) const
|
||||
return (m_valid && (volume_idx < (unsigned int)m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr;
|
||||
}
|
||||
|
||||
GLVolume *Selection::get_volume(unsigned int volume_idx) {
|
||||
return (m_valid && (volume_idx < (unsigned int) m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr;
|
||||
}
|
||||
|
||||
const BoundingBoxf3& Selection::get_bounding_box() const
|
||||
{
|
||||
if (!m_bounding_box.has_value()) {
|
||||
@ -3244,5 +3252,52 @@ void Selection::transform_volume_relative(
|
||||
assert(false);
|
||||
}
|
||||
|
||||
ModelVolume *get_selected_volume(const Selection &selection)
|
||||
{
|
||||
const GLVolume *gl_volume = get_selected_gl_volume(selection);
|
||||
if (gl_volume == nullptr) return nullptr;
|
||||
const ModelObjectPtrs &objects = selection.get_model()->objects;
|
||||
return get_model_volume(*gl_volume, objects);
|
||||
}
|
||||
|
||||
const GLVolume *get_selected_gl_volume(const Selection &selection)
|
||||
{
|
||||
int object_idx = selection.get_object_idx();
|
||||
// is more object selected?
|
||||
if (object_idx == -1) return nullptr;
|
||||
|
||||
const auto &list = selection.get_volume_idxs();
|
||||
// is more volumes selected?
|
||||
if (list.size() != 1) return nullptr;
|
||||
|
||||
unsigned int volume_idx = *list.begin();
|
||||
return selection.get_volume(volume_idx);
|
||||
}
|
||||
|
||||
ModelVolume *get_selected_volume(const ObjectID &volume_id, const Selection &selection)
|
||||
{
|
||||
const Selection::IndicesList &volume_ids = selection.get_volume_idxs();
|
||||
const ModelObjectPtrs & model_objects = selection.get_model()->objects;
|
||||
for (auto id : volume_ids) {
|
||||
const GLVolume * selected_volume = selection.get_volume(id);
|
||||
const GLVolume::CompositeID &cid = selected_volume->composite_id;
|
||||
ModelObject * obj = model_objects[cid.object_id];
|
||||
ModelVolume * volume = obj->volumes[cid.volume_id];
|
||||
if (volume_id == volume->id()) return volume;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModelVolume *get_volume(const ObjectID &volume_id, const Selection &selection)
|
||||
{
|
||||
const ModelObjectPtrs &objects = selection.get_model()->objects;
|
||||
for (const ModelObject *object : objects) {
|
||||
for (ModelVolume *volume : object->volumes) {
|
||||
if (volume->id() == volume_id) return volume;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
@ -14,6 +14,7 @@ class Model;
|
||||
class ModelObject;
|
||||
class ModelVolume;
|
||||
class ModelInstance;
|
||||
class ObjectID;
|
||||
class GLVolume;
|
||||
class GLArrow;
|
||||
class GLCurvedArrow;
|
||||
@ -291,7 +292,7 @@ public:
|
||||
void set_model(Model* model);
|
||||
|
||||
EMode get_mode() const { return m_mode; }
|
||||
void set_mode(EMode mode) { m_mode = mode; }
|
||||
void set_mode(EMode mode);
|
||||
|
||||
int query_real_volume_idx_from_other_view(unsigned int object_idx, unsigned int instance_idx, unsigned int model_volume_idx);
|
||||
void add(unsigned int volume_idx, bool as_single_selection = true, bool check_for_already_contained = false);
|
||||
@ -376,6 +377,7 @@ public:
|
||||
|
||||
const IndicesList& get_volume_idxs() const { return m_list; }
|
||||
const GLVolume* get_volume(unsigned int volume_idx) const;
|
||||
GLVolume* get_volume(unsigned int volume_idx);
|
||||
const GLVolume* get_first_volume() const { return get_volume(*m_list.begin()); }
|
||||
const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; }
|
||||
|
||||
@ -526,7 +528,11 @@ private:
|
||||
void transform_volume_relative(
|
||||
GLVolume &volume, const VolumeCache &volume_data, TransformationType transformation_type, const Transform3d &transform, const Vec3d &world_pivot);
|
||||
};
|
||||
ModelVolume * get_selected_volume(const Selection &selection);
|
||||
const GLVolume *get_selected_gl_volume(const Selection &selection);
|
||||
|
||||
ModelVolume *get_selected_volume(const ObjectID &volume_id, const Selection &selection);
|
||||
ModelVolume *get_volume(const ObjectID &volume_id, const Selection &selection);
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
|