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)
This commit is contained in:
zhou.xu 2024-07-24 14:48:05 +08:00 committed by Lane.Wei
parent 4091f3e042
commit 9541e2d05e
105 changed files with 17947 additions and 992 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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)

View File

@ -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

View 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
View 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);
}

View File

@ -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);

View File

@ -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.
------------------------------------------------------------------------------
*/

View File

@ -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 &section, const std::string &key, const std::string &value)
{
#ifndef NDEBUG

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

473
src/libslic3r/Emboss.hpp Normal file
View 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_

View 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_

File diff suppressed because it is too large Load Diff

View File

@ -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; };

View 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_

View 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; }

View 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_

View File

@ -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

View File

@ -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);

View 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)); }
}

View 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_

View File

@ -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;

View File

@ -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
View 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 &param);
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
} // namespace
namespace Slic3r {
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams &param)
{
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 &param)
{
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 &param)
{
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 &param)
{
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 &param)
{
// 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

View 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 &param);
// 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 &param);
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_

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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; };

View File

@ -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

View File

@ -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);

View 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
View 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
View 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_

View 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;
}

View 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_

View File

@ -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);

View File

@ -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 = "&quot;"; break;
case '&': replacement = "&amp;"; break;
case '<': replacement = "&lt;"; break;
case '\r': replacement = "&#xD;"; break;
case '\n': replacement = "&#xA;"; break;
case '\t': replacement = "&#x9;"; break;
default: break;
}
text.replace(pos, 1, replacement);
pos += replacement.size();
}
return text;
}
std::string xml_unescape(std::string s)
{
std::string ret;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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()));
}

File diff suppressed because it is too large Load Diff

View 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_

View File

@ -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

View File

@ -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);

View File

@ -18,8 +18,7 @@
#include <boost/algorithm/string.hpp>
#define MAX_NUM 9999.99
#define MAX_SIZE "9999.99"
namespace Slic3r
{

View 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

View 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_

View File

@ -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();

View File

@ -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;

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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_

View 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

View File

@ -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);
}

View File

@ -9,6 +9,7 @@ class NotificationManager;
class NotificationProgressIndicator: public ProgressIndicator {
NotificationManager *m_nm = nullptr;
CancelFn m_cancelfn;
public:

View 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

View 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

View 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

View File

@ -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,

View File

@ -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> &notification : 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);
}
}

View File

@ -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");

View File

@ -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;

View File

@ -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);
};

View File

@ -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(); }

View File

@ -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();

View File

@ -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

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More