Merge remote-tracking branch 'remotes/origin/fs_emboss'

This commit is contained in:
Vojtech Bubnik 2022-11-28 17:46:12 +01:00
commit 9a682a10cb
124 changed files with 27968 additions and 7958 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="m 2,1 v 4 h 4 v 10 h 4 v -10 h 4 v -4 z m 1.121094,1 h 1.414062 l 2,2 H 5.115235 Z m 2.121094,0 h 1.414062 l 2.34375,2.34375 v 1.4140625 z m 2.121093,0 H 8.771485 L 10.769532,3.998 H 9.355469 Z m 2.121094,0 h 1.414063 L 12.890625,4 h -1.414062 z m 2.121094,0 H 13 L 13,3.3964375 Z M 3,2.59175 4.408203,4 H 3 Z M 7,4.4706562 9,6.4647969 V 7.8788594 L 7,5.8847188 Z M 7,6.59175 9,8.5858906 V 10 L 7,8 Z m 0,2.1210938 2,2 v 1.414063 L 7,10.126906 Z m 0,2.1210942 2,2 V 14 L 8.7611513,14 7,12.248 Z M 7,13 8,14 H 7 Z" />
<path fill="#ed6b21" d="M 3.5 10 A 2.5 2.5 0 0 0 1 12.5 A 2.5 2.5 0 0 0 3.5 15 A 2.5 2.5 0 0 0 6 12.5 A 2.5 2.5 0 0 0 3.5 10 z M 3.6445312 11 A 1.5 1.5 0 0 1 5 12.355469 L 3.6445312 11 z M 3.0117188 11 L 4.9179688 13 A 1.5 1.5 0 0 1 4.2890625 13.773438 L 2.2246094 11.708984 A 1.5 1.5 0 0 1 3 11 z M 2 12.21875 L 3.78125 14 A 1.5 1.5 0 0 1 3.5 14 A 1.5 1.5 0 0 1 2 12.5 A 1.5 1.5 0 0 1 2 12.21875 z " />
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 0,0 V 6 H 5 V 16 H 10.293 11 V 15.293 6 h 5 V 0 Z m 1,1 h 14 v 3.293 l -1,-1 V 1.5 H 13 V 3 H 8 V 13 H 6.5 v 1 l 1.793,0 1,1 H 6 V 5 L 4,3 H 1.5 V 4 H 3.583984 L 4.587891,5 H 1 Z M 9.707,4 H 13.293 l 1,1 H 10.707 Z M 9,4.707 l 1,1 V 14.293 l -1,-1 z" />
<path fill="#ed6b21" d="M 1,12 C 0.5,12 0,12 0,12.5 0,13 0.5,13 1,13 h 3 c 0.5,0 1,0 1,-0.5 0,-0.5 -0.5,-0.5 -1,-0.5 z" />
</svg>

After

Width:  |  Height:  |  Size: 498 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 1,0 V 4.7128906 L 3.419922,7 H 7 v 7.257812 l -1,-1 V 7.5 H 5 v 6 L 7.53125,16 H 12 V 7 h 4 V 2.5 L 13.625,0 Z m 1.695312,1 h 10.580079 l 1,1 H 3.695312 Z M 2,1.703125 l 1,1 V 5.40625 l -1,-1 z M 4,3 h 11 v 3 h -4 v 9 H 8 V 6 H 4 Z" />
<path fill="#ed6b21" d="M 2.5,10 C 2,10 2,10.5 2,11 v 1 H 1 C 0.5,12 0,12 0,12.5 0,13 0.5,13 1,13 h 1 v 1 c 0,0.5 0,1 0.5,1 C 3,15 3,14.5 3,14 V 13 H 4 C 4.5,13 5,13 5,12.5 5,12 4.5,12 4,12 H 3 V 11 C 3,10.5 3,10 2.5,10 Z" />
</svg>

After

Width:  |  Height:  |  Size: 583 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="m 4.25,1.25 v 13.5 H 6.4238281 C 7.9550132,14.7449 9.3017782,14.70695 10.224609,14.236328 11.146154,13.766363 11.714577,12.89426 11.748047,11.001953 11.715611,9.759759 11.205071,8.9432111 10.546875,8.4101562 9.1029625,7.398789 8.344894,7.5382005 7.2507941,7.4731562 8.4769831,7.2602927 9.6564272,6.8632046 10.164063,6.1347656 10.274222,5.9766906 10.75,5.2822758 10.75,4.203125 10.75,3.2734782 10.376638,2.5663256 9.6445312,2.0566406 8.9124241,1.5469557 7.8014876,1.25 6.359375,1.25 Z M 6.3964844,1.734375 C 7.2161251,1.749043 8.1487931,1.833985 8.9101562,2.2109375 9.6715196,2.5878899 10.247584,3.3058125 10.248047,4.4316406 10.248331,5.1225624 10.039341,5.6730907 9.6875,6.0800781 9.3356593,6.4870656 8.8548801,6.7482198 8.3261719,6.9179688 7.2687554,7.25747 5.999968,7.25 5,7.25 H 4.75 V 1.7519531 L 4.9980469,1.75 C 5.3500853,1.7484474 5.8499795,1.7245951 6.3964844,1.734375 Z M 4.75,7.75 H 5 c 0.9307414,0 1.1815757,-0.01401 2.0039062,0 1.5407673,7.868e-4 2.5967243,0.4032699 3.2636718,1.0253906 0.6676,0.6227287 0.926463,1.4487864 0.929688,2.2265624 0.0033,0.784756 -0.253554,1.602823 -0.863282,2.222656 C 9.7242569,13.844443 8.7711925,14.25 7.4414062,14.25 c -0.5191067,0.0053 -1.7497727,0.0034 -2.4433593,0 L 4.75,14.248047 V 8 Z" />
<path fill="#ED6B21" d="M 3 0 L 3 16 L 6.3613281 16 C 10.584392 16.000011 13.000613 14.99955 13.066406 11.099609 C 13.000613 8.4746378 11.421875 7.5967 10.5625 7.296875 C 11 7.0001348 12 6.4851964 12 4 C 11.998007 1.2820714 9.9990238 2.4680048e-07 6.359375 0 L 3 0 z M 4 1 L 6.359375 1 C 9.3115238 1 11 2.203125 11 4.203125 C 11 6.4383214 9.96875 6.8438848 8.484375 7.375 C 9.78125 7.4092 11.934207 8.3750284 12 11 C 11.93421 14.899941 9.4906414 14.989816 6.4238281 15 L 4 15 L 4 1 z M 6.3925781 1.984375 C 5.8561577 1.9747756 5.3619154 1.9984038 5 2 L 5 7 C 7 7 9.9991037 7.0007673 9.9980469 4.4316406 C 9.997186 2.3388824 8.0018393 2.0131733 6.3925781 1.984375 z M 6 3.0058594 C 7.4476617 2.9994748 8.9991037 3.0007673 8.9824219 4.4472656 C 8.9653933 5.9238329 7.5802442 6.000795 6 6.0097656 L 6 3.0058594 z M 5 8 L 5 14 C 5.6931417 14.0034 6.9277744 14.0053 7.4414062 14 C 10.00096 14 10.95332 12.463982 10.947266 11.003906 C 10.941263 9.5560542 10 8 7 8 C 6.1813678 7.9860533 5.9332322 8 5 8 z M 6 9 C 6.9332322 9 6.5973834 8.9977721 7.4160156 9.0117188 C 8.9999302 9.0009732 9.9881376 9.5560542 9.9941406 11.003906 C 10.000194 12.463982 9.0009597 13 7.3632812 13 C 6.8496494 13.0053 6.6931417 13.0034 6 13 L 6 9 z " />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 3 0 L 3 2 L 5 2 L 5 13.5 L 5.1523438 13.5 L 7 6.6035156 L 7 2.5 L 6 2.5 L 6.7695312 0 L 3 0 z M 3 14 L 3 14 L 3 14 L 3 14 z " />
<path fill="#ED6B21" d="M 9,16 3,16 3.5358984,14 H 5.5358985 L 8.7512887,2 H 6.7512886 L 7.2871873,0 13.287187,0 12.751289,2 h -2 L 7.5358984,14 h 2 z" />
</svg>

After

Width:  |  Height:  |  Size: 405 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 3 0 L 3 16 L 6.3613281 16 C 10.584392 16.000011 13.000613 14.99955 13.066406 11.099609 C 13.000613 8.4746378 11.421875 7.5967 10.5625 7.296875 C 11 7.0001348 12 6.4851964 12 4 C 11.998007 1.2820714 9.9990238 2.4680048e-07 6.359375 0 L 3 0 z M 3.5 0.5 L 6.359375 0.5 C 7.9033731 0.5 9.1632916 0.80839367 10.072266 1.4414062 C 10.981239 2.0744189 11.5 3.0625 11.5 4.203125 C 11.5 5.397618 11.202666 6.2262446 10.654297 6.7792969 C 10.380321 7.0556129 9.9925213 7.1486579 9.6542969 7.3125 C 10.122334 7.4649494 10.58062 7.4742236 11.017578 7.828125 C 11.825156 8.4821989 12.463719 9.5408321 12.5 10.988281 L 12.5 10.998047 L 12.5 11.007812 C 12.465345 13.062131 11.72635 14.311861 10.564453 14.904297 C 9.4025567 15.4967 7.9616779 15.4949 6.4238281 15.5 L 3.5 15.5 L 3.5 0.5 z M 6.6914062 2.5117188 C 6.2771596 2.5022917 5.8703114 2.5182618 5.5 2.5253906 L 5.5 6.4511719 C 6.3843057 6.4419785 7.3560744 6.4413087 8.0976562 6.203125 C 8.5398322 6.0611055 8.8878766 5.8595959 9.1210938 5.5898438 C 9.3543108 5.3200916 9.498271 4.976613 9.4980469 4.4316406 C 9.4978035 3.8400832 9.33166 3.4925368 9.0664062 3.2246094 C 8.8011525 2.956682 8.4018312 2.7685186 7.9238281 2.6523438 C 7.5272979 2.5559702 7.1056529 2.5211458 6.6914062 2.5117188 z M 6 3.0058594 C 7.4476617 2.9994748 8.9991037 3.0007673 8.9824219 4.4472656 C 8.9653933 5.9238329 7.5802442 6.000795 6 6.0097656 L 6 3.0058594 z M 5.5 8.5 L 5.5 13.5 C 6.184944 13.502 7.0345824 13.5041 7.4355469 13.5 L 7.4375 13.5 L 7.4414062 13.5 C 8.6211924 13.5 9.3462736 13.159029 9.7988281 12.699219 C 10.251383 12.239408 10.449866 11.626375 10.447266 11.005859 C 10.44471 10.389517 10.253823 9.7888382 9.7558594 9.3242188 C 9.2578953 8.8595992 8.4135287 8.5 7 8.5 L 6.9960938 8.5 L 6.9921875 8.5 C 6.3845564 8.489648 6.0006946 8.49674 5.5 8.5 z M 6 9 C 6.9332322 9 6.5973834 8.9977721 7.4160156 9.0117188 C 8.9999302 9.0009732 9.9881376 9.5560542 9.9941406 11.003906 C 10.000194 12.463982 9.0009597 13 7.3632812 13 C 6.8496494 13.0053 6.6931417 13.0034 6 13 L 6 9 z M 4.5 14.498047 L 4.5 14.5 L 4.9980469 14.5 L 4.5 14.498047 z " />
<path fill="#ED6B21" d="m 12,11 c -0.06579,3.899941 -2.5100604,3.989816 -5.5768737,4 H 4 V 1 H 6.3603512 C 9.3125,1 11,2.203125 11,4.203125 11,6.4383214 9.96875,6.84375 8.484375,7.3748652 9.78125,7.4090652 11.934207,8.3750284 12,11 Z M 9.9989432,4.4308733 C 9.9977954,1.640529 6.4476617,1.9936154 5,2 V 7 C 7,7 10,7 9.9989432,4.4308733 Z M 10.947071,11.004336 C 10.941068,9.556484 10,8 7,8 6.1813678,7.9860533 5.9332322,8 5,8 v 6 c 0.6931417,0.0034 1.9268147,0.0053 2.4404466,0 C 10,14 10.953125,12.464412 10.947071,11.004336 Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 9.5,0 V 2.5 H 8.6171875 L 7.5,6.6777344 V 13.5 h 0.171875 L 10.751953,2 h 2 l 0.535156,-2 z" />
<path fill="#ED6B21" d="m 9,16 -6,0 L 3,14 H 5 V 2 H 3 L 3,0 9,0 V 2 H 7 V 14 h 2 z" />
</svg>

After

Width:  |  Height:  |  Size: 305 B

View file

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px">
<path fill="#808080" d="M 13.261719 14.867188 L 15.742188 17.347656 C 15.363281 18.070313 15.324219 18.789063 15.722656 19.1875 L 20.25 23.714844 C 20.820313 24.285156 22.0625 23.972656 23.015625 23.015625 C 23.972656 22.058594 24.285156 20.820313 23.714844 20.25 L 19.191406 15.722656 C 18.789063 15.324219 18.070313 15.363281 17.347656 15.738281 L 14.867188 13.261719 Z M 8.5 0 C 3.804688 0 0 3.804688 0 8.5 C 0 13.195313 3.804688 17 8.5 17 C 13.195313 17 17 13.195313 17 8.5 C 17 3.804688 13.195313 0 8.5 0 Z M 8.5 15 C 4.910156 15 2 12.089844 2 8.5 C 2 4.910156 4.910156 2 8.5 2 C 12.089844 2 15 4.910156 15 8.5 C 15 12.089844 12.089844 15 8.5 15 Z"/>
<path fill="#ED6B21" d="M 13.261719 14.867188 L 19.191406 15.722656 C 18.789063 15.324219 18.070313 15.363281 17.347656 15.738281 M 8.5 0 C 3.804688 0 0 3.804688 0 8.5 C 0 13.195313 3.804688 17 8.5 17 C 13.195313 17 17 13.195313 17 8.5 C 17 3.804688 13.195313 0 8.5 0 Z M 8.5 15 C 4.910156 15 2 12.089844 2 8.5 C 2 4.910156 4.910156 2 8.5 2 C 12.089844 2 15 4.910156 15 8.5 C 15 12.089844 12.089844 15 8.5 15 Z"/>
</svg>
<path fill="#ED6B21" d="M 8.5,0 C 3.804688,0 0,3.804688 0,8.5 0,13.195313 3.804688,17 8.5,17 13.195313,17 17,13.195313 17,8.5 17,3.804688 13.195313,0 8.5,0 Z m 0,15 C 4.910156,15 2,12.089844 2,8.5 2,4.910156 4.910156,2 8.5,2 12.089844,2 15,4.910156 15,8.5 15,12.089844 12.089844,15 8.5,15 Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -207,6 +207,30 @@ bool its_write_obj(const indexed_triangle_set &its, const char *file)
return true;
}
bool its_write_obj(const indexed_triangle_set& its, const std::vector<obj_color> &color, const char* file)
{
Slic3r::CNumericLocalesSetter locales_setter;
FILE* fp = fopen(file, "w");
if (fp == nullptr) {
return false;
}
for (size_t i = 0; i < its.vertices.size(); ++i)
fprintf(fp, "v %f %f %f %f %f %f\n",
its.vertices[i](0),
its.vertices[i](1),
its.vertices[i](2),
color[i](0),
color[i](1),
color[i](2));
for (size_t i = 0; i < its.indices.size(); ++i)
fprintf(fp, "f %d %d %d\n",
its.indices[i][0] + 1,
its.indices[i][1] + 1,
its.indices[i][2] + 1);
fclose(fp);
return true;
}
// Check validity of the mesh, assert on error.
bool stl_validate(const stl_file *stl, const indexed_triangle_set &its)

View file

@ -303,6 +303,17 @@ extern bool its_write_obj(const indexed_triangle_set &its, const char *file);
extern bool its_write_off(const indexed_triangle_set &its, const char *file);
extern bool its_write_vrml(const indexed_triangle_set &its, const char *file);
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> obj_color; // Vec3f
/// <summary>
/// write idexed triangle set into obj file with color
/// </summary>
/// <param name="its">input model</param>
/// <param name="color">color of stored model</param>
/// <param name="file">define place to store</param>
/// <returns>True on success otherwise FALSE</returns>
extern bool its_write_obj(const indexed_triangle_set& its, const std::vector<obj_color> &color, const char* file);
extern bool stl_write_dxf(stl_file *stl, const char *file, char *label);
inline void stl_calculate_normal(stl_normal &normal, stl_facet *facet) {
normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]);

View file

@ -3,14 +3,17 @@ project(imgui)
add_library(imgui STATIC
imconfig.h
imgui.cpp
imgui.h
imgui_demo.cpp
imgui_draw.cpp
imgui_internal.h
imgui_stdlib.cpp
imgui_stdlib.h
imgui_tables.cpp
imgui_widgets.cpp
# imgui STB
imstb_rectpack.h
imstb_textedit.h
imstb_truetype.h
imgui_tables.cpp
imgui.cpp
imgui_demo.cpp
imgui_draw.cpp
imgui_widgets.cpp
)

View file

@ -4,6 +4,19 @@ For more information go to https://github.com/ocornut/imgui
THIS DIRECTORY CONTAINS THE imgui-1.83 ad5d1a8 SOURCE DISTRIBUTION.
Customized with the following commits:
f93d0001baa5443da2c6510d11b03c675e652418
b71d787f695c779e571865d5214d4da8d50aa7c5
imgui_stdlib.h + imgui_stdlib.cpp are move from directory /imgui/misc/cpp/
InputText() wrappers for C++ standard library (STL) type: std::string.
This is also an example of how you may wrap your own similar types.
imstb_truetype.h modification:
Hot fix for open symbolic fonts on windows
62bdfe6f8d04b88e8bd511cd613be80c0baa7f55
Hot fix for open curved fonts mainly on MAC
2148e49f75d82cb19dc6ec409fb7825296ed005c

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

@ -1437,6 +1437,7 @@ static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, in
switch(ttUSHORT(data+encoding_record)) {
case STBTT_PLATFORM_ID_MICROSOFT:
switch (ttUSHORT(data+encoding_record+2)) {
case STBTT_MS_EID_SYMBOL:
case STBTT_MS_EID_UNICODE_BMP:
case STBTT_MS_EID_UNICODE_FULL:
// MS/Unicode
@ -1734,7 +1735,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s
// now start the new one
start_off = !(flags & 1);
if (start_off) {
if (start_off && (i + 1) < n) {
// if we start off with an off-curve point, then when we need to find a point on the curve
// where we can start, and we need to save some state for when we wraparound.
scx = x;

View file

@ -90,20 +90,21 @@ inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over
// Finding a closest line, its closest point and squared distance to the closest point
// Returns squared distance to the closest point or -1 if the input is empty.
// or no closer point than max_sq_dist
template<typename LineType, typename TreeType, typename VectorType>
inline typename VectorType::Scalar squared_distance_to_indexed_lines(const std::vector<LineType> &lines,
const TreeType &tree,
const VectorType &point,
size_t &hit_idx_out,
Eigen::PlainObjectBase<VectorType> &hit_point_out)
inline typename VectorType::Scalar squared_distance_to_indexed_lines(
const std::vector<LineType> &lines,
const TreeType &tree,
const VectorType &point,
size_t &hit_idx_out,
Eigen::PlainObjectBase<VectorType> &hit_point_out,
typename VectorType::Scalar max_sqr_dist = std::numeric_limits<typename VectorType::Scalar>::infinity())
{
using Scalar = typename VectorType::Scalar;
using Scalar = typename VectorType::Scalar;
if (tree.empty()) return Scalar(-1);
auto distancer = detail::IndexedLinesDistancer<LineType, TreeType, VectorType>{lines, tree, point};
return tree.empty() ?
Scalar(-1) :
AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0),
std::numeric_limits<Scalar>::infinity(),
hit_idx_out, hit_point_out);
return AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive(
distancer, size_t(0), Scalar(0), max_sqr_dist, hit_idx_out, hit_point_out);
}
// Returns all lines within the given radius limit

View file

@ -39,6 +39,7 @@ static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-conte
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
const std::string AppConfig::SECTION_EMBOSS_STYLE = "font";
void AppConfig::reset()
{

View file

@ -167,6 +167,7 @@ public:
static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS;
static const std::string SECTION_EMBOSS_STYLE;
private:
template<typename T>

View file

@ -53,7 +53,7 @@ public:
PointClass size() const;
double radius() const;
void translate(coordf_t x, coordf_t y) { assert(this->defined); PointClass v(x, y); this->min += v; this->max += v; }
void translate(const Vec2d &v) { this->min += v; this->max += v; }
void translate(const PointClass &v) { this->min += v; this->max += v; }
void offset(coordf_t delta);
BoundingBoxBase<PointClass> inflated(coordf_t delta) const throw() { BoundingBoxBase<PointClass> out(*this); out.offset(delta); return out; }
PointClass center() const;
@ -174,6 +174,7 @@ public:
BoundingBox rotated(double angle, const Point &center) const;
void rotate(double angle) { (*this) = this->rotated(angle); }
void rotate(double angle, const Point &center) { (*this) = this->rotated(angle, center); }
bool intersects(const BoundingBox &other) const { return this->min(0) <= other.max(0) && this->max(0) >= other.min(0) && this->min(1) <= other.max(1) && this->max(1) >= other.min(1); }
// 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);

View file

@ -19,6 +19,7 @@ set(SLIC3R_SOURCES
pchheader.hpp
AStar.hpp
AABBTreeIndirect.hpp
AABBTreeLines.hpp
AABBMesh.hpp
AABBMesh.cpp
BoundingBox.cpp
@ -42,9 +43,13 @@ set(SLIC3R_SOURCES
EdgeGrid.hpp
ElephantFootCompensation.cpp
ElephantFootCompensation.hpp
Emboss.cpp
Emboss.hpp
enum_bitmask.hpp
ExPolygon.cpp
ExPolygon.hpp
ExPolygonsIndex.cpp
ExPolygonsIndex.hpp
Extruder.cpp
Extruder.hpp
ExtrusionEntity.cpp
@ -178,6 +183,7 @@ set(SLIC3R_SOURCES
Model.hpp
ModelArrange.hpp
ModelArrange.cpp
#ModelVolumeType.hpp
MultiMaterialSegmentation.cpp
MultiMaterialSegmentation.hpp
MeshNormals.hpp
@ -194,6 +200,8 @@ set(SLIC3R_SOURCES
MutablePriorityQueue.hpp
NormalUtils.cpp
NormalUtils.hpp
NSVGUtils.cpp
NSVGUtils.hpp
ObjectID.cpp
ObjectID.hpp
PerimeterGenerator.cpp
@ -266,6 +274,7 @@ set(SLIC3R_SOURCES
Technologies.hpp
Tesselate.cpp
Tesselate.hpp
TextConfiguration.hpp
TreeSupport.cpp
TreeSupport.hpp
TreeModelVolumes.cpp
@ -280,6 +289,8 @@ set(SLIC3R_SOURCES
Utils.hpp
Time.cpp
Time.hpp
Timer.cpp
Timer.hpp
Thread.cpp
Thread.hpp
TriangleSelector.cpp
@ -403,8 +414,14 @@ cmake_policy(SET CMP0011 NEW)
find_package(CGAL REQUIRED)
cmake_policy(POP)
add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp TryCatchSignal.hpp
TryCatchSignal.cpp Geometry/VoronoiUtilsCgal.hpp Geometry/VoronoiUtilsCgal.cpp)
add_library(libslic3r_cgal STATIC
CutSurface.hpp CutSurface.cpp
Geometry/VoronoiUtilsCgal.hpp Geometry/VoronoiUtilsCgal.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
@ -476,6 +493,7 @@ if (APPLE)
# This flag prevents the need for minimum SDK version 10.14
# currently, PS targets v10.12
target_compile_options(libslic3r PUBLIC "-fno-aligned-allocation")
target_compile_options(libslic3r_cgal PUBLIC "-fno-aligned-allocation")
endif ()
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)

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

1294
src/libslic3r/Emboss.cpp Normal file

File diff suppressed because it is too large Load diff

349
src/libslic3r/Emboss.hpp Normal file
View file

@ -0,0 +1,349 @@
#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 "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
{
// every glyph's shape point is divided by SHAPE_SCALE - increase precission of fixed point value
// stored in fonts (to be able represents curve by sequence of lines)
static constexpr double SHAPE_SCALE = 0.001; // SCALING_FACTOR promile is fine enough
/// <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) {}
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
using HFONT = void*;
void * can_load(HFONT hfont);
std::unique_ptr<FontFile> create_font_file(HFONT 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>
ExPolygons text2shapes(FontFileWithCache &font, const char *text, const FontProp &font_prop, std::function<bool()> was_canceled = nullptr);
/// <summary>
/// Fix intersections and self intersections in polygons glyph shape
/// </summary>
/// <param name="shape">Input shape to heal</param>
/// <returns>Healed shapes</returns>
ExPolygons heal_shape(const Polygons &shape);
/// <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_shape(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="font_prop">Z-move as surface distance(FontProp::distance)
/// Z-rotation as angle to Y axis(FontProp::angle)</param>
/// <param name="transformation">In / Out transformation to modify by property</param>
void apply_transformation(const FontProp &font_prop, 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_shape_scale(const FontProp &fp, const FontFile &ff);
/// <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>
/// 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 Vec3f &position, const Vec3f &normal, float up_limit = 0.9f);
class ProjectZ : public IProjection
{
public:
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 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;
};
} // namespace Emboss
} // namespace Slic3r
#endif // slic3r_Emboss_hpp_

View file

@ -85,6 +85,25 @@ public:
inline bool operator==(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour == rhs.contour && lhs.holes == rhs.holes; }
inline bool operator!=(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour != rhs.contour || lhs.holes != rhs.holes; }
inline size_t count_points(const ExPolygons &expolys)
{
size_t n_points = 0;
for (const auto &expoly : expolys) {
n_points += expoly.contour.points.size();
for (const auto &hole : expoly.holes)
n_points += hole.points.size();
}
return n_points;
}
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();
return n_points;
}
// Count a nuber of polygons stored inside the vector of expolygons.
// Useful for allocating space for polygons when converting expolygons to polygons.
inline size_t number_polygons(const ExPolygons &expolys)
@ -97,11 +116,8 @@ inline size_t number_polygons(const ExPolygons &expolys)
inline Lines to_lines(const ExPolygon &src)
{
size_t n_lines = src.contour.points.size();
for (size_t i = 0; i < src.holes.size(); ++ i)
n_lines += src.holes[i].points.size();
Lines lines;
lines.reserve(n_lines);
lines.reserve(count_points(src));
for (size_t i = 0; i <= src.holes.size(); ++ i) {
const Polygon &poly = (i == 0) ? src.contour : src.holes[i - 1];
for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it)
@ -113,14 +129,8 @@ inline Lines to_lines(const ExPolygon &src)
inline Lines to_lines(const ExPolygons &src)
{
size_t n_lines = 0;
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
n_lines += it_expoly->contour.points.size();
for (size_t i = 0; i < it_expoly->holes.size(); ++ i)
n_lines += it_expoly->holes[i].points.size();
}
Lines lines;
lines.reserve(n_lines);
lines.reserve(count_points(src));
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
for (size_t i = 0; i <= it_expoly->holes.size(); ++ i) {
const Points &points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points;
@ -132,16 +142,40 @@ inline Lines to_lines(const ExPolygons &src)
return lines;
}
inline std::vector<Linef> to_unscaled_linesf(const ExPolygons &src)
// Line is from point index(see to_points) to next point.
// Next point of last point in polygon is first polygon point.
inline Linesf to_linesf(const ExPolygons &src, uint32_t count_lines = 0)
{
size_t n_lines = 0;
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
n_lines += it_expoly->contour.points.size();
for (size_t i = 0; i < it_expoly->holes.size(); ++ i)
n_lines += it_expoly->holes[i].points.size();
assert(count_lines == 0 || count_lines == count_points(src));
if (count_lines == 0) count_lines = count_points(src);
Linesf lines;
lines.reserve(count_lines);
Vec2d prev_pd;
auto to_lines = [&lines, &prev_pd](const Points &pts) {
assert(pts.size() >= 3);
if (pts.size() < 2) return;
bool is_first = true;
for (const Point &p : pts) {
Vec2d pd = p.cast<double>();
if (is_first) is_first = false;
else lines.emplace_back(prev_pd, pd);
prev_pd = pd;
}
lines.emplace_back(prev_pd, pts.front().cast<double>());
};
for (const ExPolygon& expoly: src) {
to_lines(expoly.contour.points);
for (const Polygon &hole : expoly.holes)
to_lines(hole.points);
}
std::vector<Linef> lines;
lines.reserve(n_lines);
assert(lines.size() == count_lines);
return lines;
}
inline Linesf to_unscaled_linesf(const ExPolygons &src)
{
Linesf lines;
lines.reserve(count_points(src));
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
for (size_t i = 0; i <= it_expoly->holes.size(); ++ i) {
const Points &points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points;
@ -159,6 +193,19 @@ inline std::vector<Linef> to_unscaled_linesf(const ExPolygons &src)
}
inline Points to_points(const ExPolygons &src)
{
Points points;
size_t count = count_points(src);
points.reserve(count);
for (const ExPolygon &expolygon : src) {
append(points, expolygon.contour.points);
for (const Polygon &hole : expolygon.holes)
append(points, hole.points);
}
return points;
}
inline Polylines to_polylines(const ExPolygon &src)
{
Polylines polylines;
@ -278,8 +325,9 @@ inline Polygons to_polygons(ExPolygon &&src)
Polygons polygons;
polygons.reserve(src.holes.size() + 1);
polygons.push_back(std::move(src.contour));
std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(polygons));
src.holes.clear();
polygons.insert(polygons.end(),
std::make_move_iterator(src.holes.begin()),
std::make_move_iterator(src.holes.end()));
return polygons;
}
@ -287,10 +335,11 @@ inline Polygons to_polygons(ExPolygons &&src)
{
Polygons polygons;
polygons.reserve(number_polygons(src));
for (ExPolygons::iterator it = src.begin(); it != src.end(); ++it) {
polygons.push_back(std::move(it->contour));
std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(polygons));
it->holes.clear();
for (ExPolygon& expoly: src) {
polygons.push_back(std::move(expoly.contour));
polygons.insert(polygons.end(),
std::make_move_iterator(expoly.holes.begin()),
std::make_move_iterator(expoly.holes.end()));
}
return polygons;
}
@ -315,11 +364,8 @@ inline ExPolygons to_expolygons(Polygons &&polys)
inline Points to_points(const ExPolygon &expoly)
{
size_t cnt = expoly.contour.size();
for (const Polygon &hole : expoly.holes)
cnt += hole.size();
Points out;
out.reserve(cnt);
out.reserve(count_points(expoly));
append(out, expoly.contour.points);
for (const Polygon &hole : expoly.holes)
append(out, hole.points);
@ -345,18 +391,20 @@ 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));
std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(dst));
src.holes.clear();
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 (ExPolygons::iterator it = src.begin(); it != src.end(); ++ it) {
dst.push_back(std::move(it->contour));
std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(dst));
it->holes.clear();
for (ExPolygon& expoly: src) {
dst.push_back(std::move(expoly.contour));
dst.insert(dst.end(),
std::make_move_iterator(expoly.holes.begin()),
std::make_move_iterator(expoly.holes.end()));
}
}
@ -370,8 +418,9 @@ inline void expolygons_append(ExPolygons &dst, ExPolygons &&src)
if (dst.empty()) {
dst = std::move(src);
} else {
std::move(std::begin(src), std::end(src), std::back_inserter(dst));
src.clear();
dst.insert(dst.end(),
std::make_move_iterator(src.begin()),
std::make_move_iterator(src.end()));
}
}

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

@ -76,7 +76,11 @@ public:
if (entities.empty())
entities = std::move(src);
else {
std::move(std::begin(src), std::end(src), std::back_inserter(entities));
entities.insert(entities.end(),
std::make_move_iterator(src.begin()),
std::make_move_iterator(src.end()));
// Removing pointers to polymorphic extrusions from the donor object
// so that they will not be deleted twice.
src.clear();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,170 @@
#include "IntersectionPoints.hpp"
//#define USE_CGAL_SWEEP_LINE
#ifdef USE_CGAL_SWEEP_LINE
#include <CGAL/Cartesian.h>
#include <CGAL/MP_Float.h>
#include <CGAL/Quotient.h>
#include <CGAL/Arr_segment_traits_2.h>
#include <CGAL/Sweep_line_2_algorithms.h>
using NT = CGAL::Quotient<CGAL::MP_Float>;
using Kernel = CGAL::Cartesian<NT>;
using P2 = Kernel::Point_2;
using Traits_2 = CGAL::Arr_segment_traits_2<Kernel>;
using Segment = Traits_2::Curve_2;
using Segments = std::vector<Segment>;
namespace priv {
P2 convert(const Slic3r::Point &p) { return P2(p.x(), p.y()); }
Slic3r::Vec2d convert(const P2 &p)
{
return Slic3r::Vec2d(CGAL::to_double(p.x()), CGAL::to_double(p.y()));
}
Slic3r::Pointfs compute_intersections(const Segments &segments)
{
std::vector<P2> intersections;
// Compute all intersection points.
CGAL::compute_intersection_points(segments.begin(), segments.end(),
std::back_inserter(intersections));
if (intersections.empty()) return {};
Slic3r::Pointfs pts;
pts.reserve(intersections.size());
for (const P2 &p : intersections) pts.push_back(convert(p));
return pts;
}
void add_polygon(const Slic3r::Polygon &polygon, Segments &segments)
{
if (polygon.points.size() < 2) return;
P2 prev_point = priv::convert(polygon.last_point());
for (const Slic3r::Point &p : polygon.points) {
P2 act_point = priv::convert(p);
if (prev_point == act_point) continue;
segments.emplace_back(prev_point, act_point);
prev_point = act_point;
}
}
Slic3r::Pointfs Slic3r::intersection_points(const Lines &lines)
{
return priv::compute_intersections2(lines);
Segments segments;
segments.reserve(lines.size());
for (Line l : lines)
segments.emplace_back(priv::convert(l.a), priv::convert(l.b));
return priv::compute_intersections(segments);
}
Slic3r::Pointfs Slic3r::intersection_points(const Polygon &polygon)
{
Segments segments;
segments.reserve(polygon.points.size());
priv::add_polygon(polygon, segments);
return priv::compute_intersections(segments);
}
Slic3r::Pointfs Slic3r::intersection_points(const Polygons &polygons)
{
Segments segments;
segments.reserve(count_points(polygons));
for (const Polygon &polygon : polygons)
priv::add_polygon(polygon, segments);
return priv::compute_intersections(segments);
}
Slic3r::Pointfs Slic3r::intersection_points(const ExPolygon &expolygon)
{
Segments segments;
segments.reserve(count_points(expolygon));
priv::add_polygon(expolygon.contour, segments);
for (const Polygon &hole : expolygon.holes)
priv::add_polygon(hole, segments);
return priv::compute_intersections(segments);
}
Slic3r::Pointfs Slic3r::intersection_points(const ExPolygons &expolygons)
{
Segments segments;
segments.reserve(count_points(expolygons));
for (const ExPolygon &expolygon : expolygons) {
priv::add_polygon(expolygon.contour, segments);
for (const Polygon &hole : expolygon.holes)
priv::add_polygon(hole, segments);
}
return priv::compute_intersections(segments);
}
} // namespace priv
#else // USE_CGAL_SWEEP_LINE
// use bounding boxes
#include <libslic3r/BoundingBox.hpp>
namespace priv {
Slic3r::Pointfs compute_intersections(const Slic3r::Lines &lines)
{
using namespace Slic3r;
// IMPROVE0: BoundingBoxes of Polygons
// IMPROVE1: Polygon's neighbor lines can't intersect
// e.g. use indices to Point to find same points
// IMPROVE2: Use BentleyOttmann algorithm
// https://doc.cgal.org/latest/Surface_sweep_2/index.html -- CGAL implementation is significantly slower
// https://stackoverflow.com/questions/4407493/is-there-a-robust-c-implementation-of-the-bentley-ottmann-algorithm
Pointfs pts;
Point i;
for (size_t li = 0; li < lines.size(); ++li) {
const Line &l = lines[li];
const Point &a = l.a;
const Point &b = l.b;
Point min(std::min(a.x(), b.x()), std::min(a.y(), b.y()));
Point max(std::max(a.x(), b.x()), std::max(a.y(), b.y()));
BoundingBox bb(min, max);
for (size_t li_ = li + 1; li_ < lines.size(); ++li_) {
const Line &l_ = lines[li_];
const Point &a_ = l_.a;
const Point &b_ = l_.b;
if (a == b_ || b == a_ || a == a_ || b == b_) continue;
Point min_(std::min(a_.x(), b_.x()), std::min(a_.y(), b_.y()));
Point max_(std::max(a_.x(), b_.x()), std::max(a_.y(), b_.y()));
BoundingBox bb_(min_, max_);
// intersect of BB compare min max
if (bb.intersects(bb_) &&
l.intersection(l_, &i))
pts.push_back(i.cast<double>());
}
}
return pts;
}
} // namespace priv
Slic3r::Pointfs Slic3r::intersection_points(const Lines &lines)
{
return priv::compute_intersections(lines);
}
Slic3r::Pointfs Slic3r::intersection_points(const Polygon &polygon)
{
return priv::compute_intersections(to_lines(polygon));
}
Slic3r::Pointfs Slic3r::intersection_points(const Polygons &polygons)
{
return priv::compute_intersections(to_lines(polygons));
}
Slic3r::Pointfs Slic3r::intersection_points(const ExPolygon &expolygon)
{
return priv::compute_intersections(to_lines(expolygon));
}
Slic3r::Pointfs Slic3r::intersection_points(const ExPolygons &expolygons)
{
return priv::compute_intersections(to_lines(expolygons));
}
#endif // USE_CGAL_SWEEP_LINE

View file

@ -0,0 +1,16 @@
#ifndef slic3r_IntersectionPoints_hpp_
#define slic3r_IntersectionPoints_hpp_
#include "ExPolygon.hpp"
namespace Slic3r {
// collect all intersecting points
Pointfs intersection_points(const Lines &lines);
Pointfs intersection_points(const Polygon &polygon);
Pointfs intersection_points(const Polygons &polygons);
Pointfs intersection_points(const ExPolygon &expolygon);
Pointfs intersection_points(const ExPolygons &expolygons);
} // namespace Slic3r
#endif // slic3r_IntersectionPoints_hpp_

View file

@ -210,6 +210,7 @@ public:
static const constexpr int Dim = 2;
using Scalar = Vec2d::Scalar;
};
using Linesf = std::vector<Linef>;
class Linef3
{

View file

@ -14,12 +14,15 @@
#include "Arrange.hpp"
#include "CustomGCode.hpp"
#include "enum_bitmask.hpp"
//#include "ModelVolumeType.hpp"
#include "TextConfiguration.hpp"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <optional>
namespace cereal {
class BinaryInputArchive;
@ -765,6 +768,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;
@ -778,6 +782,10 @@ public:
// List of mesh facets painted for MMU segmentation.
FacetsAnnotation mmu_segmentation_facets;
// Is set only when volume is Embossed Text type
// Contain information how to re-create volume
std::optional<TextConfiguration> text_configuration;
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; }
ModelVolumeType type() const { return m_type; }
@ -928,23 +936,8 @@ private:
// 1 -> is splittable
mutable int m_is_splittable{ -1 };
ModelVolume(ModelObject *object, const TriangleMesh &mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART) : m_mesh(new TriangleMesh(mesh)), m_type(type), object(object)
{
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
if (mesh.facets_count() > 1)
calculate_convex_hull();
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) :
m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) {
assert(this->id().valid());
inline bool check() {
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
@ -953,6 +946,24 @@ private:
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
return true;
}
ModelVolume(ModelObject *object, const TriangleMesh &mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART) :
m_mesh(new TriangleMesh(mesh)), m_type(type), object(object)
{
assert(check());
if (m_mesh->facets_count() > 1) calculate_convex_hull();
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART)
: m_mesh(new TriangleMesh(std::move(mesh))), m_type(type), object(object)
{
assert(check());
if (m_mesh->facets_count() > 1) calculate_convex_hull();
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) :
m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) {
assert(check());
}
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
@ -961,7 +972,8 @@ 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),
cut_info(other.cut_info)
cut_info(other.cut_info),
text_configuration(other.text_configuration)
{
assert(this->id().valid());
assert(this->config.id().valid());
@ -982,7 +994,8 @@ private:
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) :
name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation),
cut_info(other.cut_info)
cut_info(other.cut_info),
text_configuration(other.text_configuration)
{
assert(this->id().valid());
assert(this->config.id().valid());
@ -1029,6 +1042,7 @@ private:
cereal::load_by_value(ar, seam_facets);
cereal::load_by_value(ar, mmu_segmentation_facets);
cereal::load_by_value(ar, config);
cereal::load(ar, text_configuration);
assert(m_mesh);
if (has_convex_hull) {
cereal::load_optional(ar, m_convex_hull);
@ -1045,6 +1059,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, text_configuration);
if (has_convex_hull)
cereal::save_optional(ar, m_convex_hull);
}

View file

@ -0,0 +1,16 @@
#ifndef slic3r_ModelVolumeType_hpp_
#define slic3r_ModelVolumeType_hpp_
namespace Slic3r {
enum class ModelVolumeType : int {
INVALID = -1,
MODEL_PART = 0,
NEGATIVE_VOLUME,
PARAMETER_MODIFIER,
SUPPORT_BLOCKER,
SUPPORT_ENFORCER,
};
} // namespace Slic3r
#endif /* slic3r_ModelVolumeType_hpp_ */

View file

@ -0,0 +1,83 @@
#include "NSVGUtils.hpp"
#include "ClipperUtils.hpp"
using namespace Slic3r;
void NSVGUtils::flatten_cubic_bez(Polygon &polygon,
float tessTol,
Vec2f p1,
Vec2f p2,
Vec2f p3,
Vec2f p4,
int level)
{
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 pd = p4 - p1;
Vec2f pd2 = p2 - p4;
float d2 = std::abs(pd2.x() * pd.y() - pd2.y() * pd.x());
Vec2f pd3 = p3 - p4;
float d3 = std::abs(pd3.x() * pd.y() - pd3.y() * pd.x());
float d23 = d2 + d3;
if ((d23 * d23) < tessTol * (pd.x() * pd.x() + pd.y() * pd.y())) {
polygon.points.emplace_back(p4.x(), p4.y());
return;
}
--level;
if (level == 0) return;
Vec2f p234 = (p23 + p34) * 0.5f;
Vec2f p1234 = (p123 + p234) * 0.5f;
flatten_cubic_bez(polygon, tessTol, p1, p12, p123, p1234, level);
flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level);
}
Polygons NSVGUtils::to_polygons(NSVGimage *image, float tessTol, int max_level)
{
Polygons polygons;
for (NSVGshape *shape = image->shapes; shape != NULL;
shape = shape->next) {
if (!(shape->flags & NSVG_FLAGS_VISIBLE)) continue;
Slic3r::Polygon polygon;
if (shape->fill.type != NSVG_PAINT_NONE) {
for (NSVGpath *path = shape->paths; path != NULL;
path = path->next) {
// Flatten path
polygon.points.emplace_back(path->pts[0], path->pts[1]);
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) {
float *p = &path->pts[i * 2];
Vec2f p1(p[0], p[1]), p2(p[2], p[3]), p3(p[4], p[5]),
p4(p[6], p[7]);
flatten_cubic_bez(polygon, tessTol, p1, p2, p3, p4,
max_level);
}
if (path->closed && !polygon.empty()) {
polygons.push_back(polygon);
polygon = Slic3r::Polygon();
}
}
}
if (!polygon.empty())
polygons.push_back(polygon);
}
return polygons;
}
ExPolygons NSVGUtils::to_ExPolygons(NSVGimage *image,
float tessTol,
int max_level)
{
Polygons polygons = to_polygons(image, tessTol, max_level);
// Fix Y axis
for (Polygon &polygon : polygons)
for (Point &p : polygon.points) p.y() *= -1;
return Slic3r::union_ex(polygons);
}

View file

@ -0,0 +1,34 @@
#ifndef slic3r_NSVGUtils_hpp_
#define slic3r_NSVGUtils_hpp_
#include "Polygon.hpp"
#include "ExPolygon.hpp"
#include "nanosvg/nanosvg.h" // load SVG file
namespace Slic3r {
// Helper function to work with nano svg
class NSVGUtils
{
public:
NSVGUtils() = delete;
// inspired by nanosvgrast.h function nsvgRasterize->nsvg__flattenShape
static void flatten_cubic_bez(Polygon &polygon,
float tessTol,
Vec2f p1,
Vec2f p2,
Vec2f p3,
Vec2f p4,
int level);
// convert svg image to ExPolygons
static ExPolygons to_ExPolygons(NSVGimage *image,
float tessTol = 10.,
int max_level = 10);
// convert svg paths to Polygons
static Polygons to_polygons(NSVGimage *image,
float tessTol = 10.,
int max_level = 10);
};
} // namespace Slic3r
#endif // slic3r_NSVGUtils_hpp_

View file

@ -66,6 +66,24 @@ bool has_duplicate_points(std::vector<Point> &&pts)
return false;
}
Points collect_duplications(Points pts /* Copy */)
{
std::stable_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;
}
BoundingBox get_extents(const Points &pts)
{
return BoundingBox(pts);

View file

@ -267,6 +267,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_duplications(Points pts /* Copy */);
inline bool shorter_then(const Point& p0, const coord_t len)
{
if (p0.x() > len || p0.x() < -len)
@ -547,6 +550,7 @@ namespace boost { namespace polygon {
} }
// end Boost
#include <cereal/cereal.hpp>
// Serialization through the Cereal library
namespace cereal {
// template<class Archive> void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); }
@ -560,10 +564,11 @@ namespace cereal {
template<class Archive> void serialize(Archive& archive, Slic3r::Vec2d &v) { archive(v.x(), v.y()); }
template<class Archive> void serialize(Archive& archive, Slic3r::Vec3d &v) { archive(v.x(), v.y(), v.z()); }
template<class Archive> void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); }
template<class Archive> void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); }
template<class Archive> void load(Archive& archive, Slic3r::Transform3d& m) { archive.loadBinary((char*)m.data(), sizeof(double) * 16); }
template<class Archive> void save(Archive& archive, const Slic3r::Transform3d& m) { archive.saveBinary((char*)m.data(), sizeof(double) * 16); }
template<class Archive> void serialize(Archive& archive, Slic3r::Matrix4d &m){ archive(binary_data(m.data(), 4*4*sizeof(double))); }
template<class Archive> void serialize(Archive& archive, Slic3r::Matrix2f &m){ archive(binary_data(m.data(), 2*2*sizeof(float))); }
// Eigen Transformation serialization
template<class Archive, class T, int N> inline void serialize(Archive& archive, Eigen::Transform<T, N, Eigen::Affine, Eigen::DontAlign>& t){ archive(t.matrix()); }
}
// To be able to use Vec<> and Mat<> in range based for loops:

View file

@ -169,13 +169,16 @@ inline Points to_points(const Polygon &poly)
return poly.points;
}
inline size_t count_points(const Polygons &polys) {
size_t n_points = 0;
for (const auto &poly: polys) n_points += poly.points.size();
return n_points;
}
inline Points to_points(const Polygons &polys)
{
size_t n_points = 0;
for (size_t i = 0; i < polys.size(); ++ i)
n_points += polys[i].points.size();
Points points;
points.reserve(n_points);
points.reserve(count_points(polys));
for (const Polygon &poly : polys)
append(points, poly.points);
return points;
@ -195,11 +198,8 @@ inline Lines to_lines(const Polygon &poly)
inline Lines to_lines(const Polygons &polys)
{
size_t n_lines = 0;
for (size_t i = 0; i < polys.size(); ++ i)
n_lines += polys[i].points.size();
Lines lines;
lines.reserve(n_lines);
lines.reserve(count_points(polys));
for (size_t i = 0; i < polys.size(); ++ i) {
const Polygon &poly = polys[i];
for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it)

View file

@ -184,7 +184,7 @@ void Slic3r::its_quadric_edge_collapse(
throw_on_cancel();
status_fn(status_init_size);
//its_store_triangle(its, "triangle.obj", 1182);
//its_store_triangle_to_obj(its, "triangle.obj", 1182);
//store_surround("triangle_surround1.obj", 1182, 1, its, v_infos, e_infos);
// convert from triangle index to mutable priority queue index
@ -904,7 +904,7 @@ void QuadricEdgeCollapse::store_surround(const char *obj_filename,
std::vector<size_t> trs;
trs.reserve(triangles.size());
for (size_t ti : triangles) trs.push_back(ti);
its_store_triangles(its, obj_filename, trs);
its_store_triangles_to_obj(its, obj_filename, trs);
// its_write_obj(its,"original.obj");
}

View file

@ -1,3 +1,6 @@
#ifndef slic3r_quadric_edge_collapse_hpp_
#define slic3r_quadric_edge_collapse_hpp_
// paper: https://people.eecs.berkeley.edu/~jrs/meshpapers/GarlandHeckbert2.pdf
// sum up: https://users.csc.calpoly.edu/~zwood/teaching/csc570/final06/jseeba/
// inspiration: https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
@ -26,3 +29,4 @@ void its_quadric_edge_collapse(
std::function<void(int)> statusfn = nullptr);
} // namespace Slic3r
#endif // slic3r_quadric_edge_collapse_hpp_

View file

@ -378,4 +378,10 @@ void SVG::export_expolygons(const char *path, const std::vector<std::pair<Slic3r
svg.Close();
}
float SVG::to_svg_coord(float x) throw()
{
// return x;
return unscale<float>(x) * 10.f;
}
} // namespace Slic3r

View file

@ -167,9 +167,9 @@ public:
{ export_expolygons(path.c_str(), expolygons_with_attributes); }
private:
static float to_svg_coord(float x) throw() { return unscale<float>(x) * 10.f; }
static float to_svg_x(float x) throw() { return to_svg_coord(x); }
float to_svg_y(float x) const throw() { return flipY ? this->height - to_svg_coord(x) : to_svg_coord(x); }
static float to_svg_coord(float x) throw();
static float to_svg_x(float x) throw() { return to_svg_coord(x); }
float to_svg_y(float x) const throw() { return flipY ? this->height - to_svg_coord(x) : to_svg_coord(x); }
};
}

View file

@ -1,69 +1,69 @@
#ifndef _prusaslicer_technologies_h_
#define _prusaslicer_technologies_h_
//=============
// debug techs
//=============
// Shows camera target in the 3D scene
#define ENABLE_SHOW_CAMERA_TARGET 0
// Log debug messages to console when changing selection
#define ENABLE_SELECTION_DEBUG_OUTPUT 0
// Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active
#define ENABLE_RENDER_SELECTION_CENTER 0
// Shows an imgui dialog with camera related data
#define ENABLE_CAMERA_STATISTICS 0
// Enable extracting thumbnails from selected gcode and save them as png files
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG 0
// Disable synchronization of unselected instances
#define DISABLE_INSTANCES_SYNCH 0
// Use wxDataViewRender instead of wxDataViewCustomRenderer
#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING 0
// Enable G-Code viewer statistics imgui dialog
#define ENABLE_GCODE_VIEWER_STATISTICS 0
// Enable G-Code viewer comparison between toolpaths height and width detected from gcode and calculated at gcode generation
#define ENABLE_GCODE_VIEWER_DATA_CHECKING 0
// Enable project dirty state manager debug window
#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW 0
// Disable using instanced models to render options in gcode preview
#define DISABLE_GCODEVIEWER_INSTANCED_MODELS 1
// Enable Measure Gizmo debug window
#define ENABLE_MEASURE_GIZMO_DEBUG 0
// Enable rendering of objects using environment map
#define ENABLE_ENVIRONMENT_MAP 0
// Enable smoothing of objects normals
#define ENABLE_SMOOTH_NORMALS 0
//====================
// 2.5.0.alpha1 techs
//====================
#define ENABLE_2_5_0_ALPHA1 1
// Enable removal of legacy OpenGL calls
#define ENABLE_LEGACY_OPENGL_REMOVAL (1 && ENABLE_2_5_0_ALPHA1)
// Enable OpenGL ES
#define ENABLE_OPENGL_ES (0 && ENABLE_LEGACY_OPENGL_REMOVAL)
// Enable OpenGL core profile context (tested against Mesa 20.1.8 on Windows)
#define ENABLE_GL_CORE_PROFILE (1 && ENABLE_LEGACY_OPENGL_REMOVAL && !ENABLE_OPENGL_ES)
// Enable OpenGL debug messages using debug context
#define ENABLE_OPENGL_DEBUG_OPTION (1 && ENABLE_GL_CORE_PROFILE)
// Shows an imgui dialog with GLModel statistics data
#define ENABLE_GLMODEL_STATISTICS (0 && ENABLE_LEGACY_OPENGL_REMOVAL)
// Enable rework of Reload from disk command
#define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_5_0_ALPHA1)
// Enable editing volumes transformation in world coordinates and instances in local coordinates
#define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1)
// Enable alternative version of file_wildcards()
#define ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR (1 && ENABLE_2_5_0_ALPHA1)
// Enable processing of gcode G2 and G3 lines
#define ENABLE_PROCESS_G2_G3_LINES (1 && ENABLE_2_5_0_ALPHA1)
// Enable fix of used filament data exported to gcode file
#define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1)
// Enable picking using raytracing
#define ENABLE_RAYCAST_PICKING (1 && ENABLE_LEGACY_OPENGL_REMOVAL)
#define ENABLE_RAYCAST_PICKING_DEBUG (0 && ENABLE_RAYCAST_PICKING)
#endif // _prusaslicer_technologies_h_
#ifndef _prusaslicer_technologies_h_
#define _prusaslicer_technologies_h_
//=============
// debug techs
//=============
// Shows camera target in the 3D scene
#define ENABLE_SHOW_CAMERA_TARGET 0
// Log debug messages to console when changing selection
#define ENABLE_SELECTION_DEBUG_OUTPUT 0
// Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active
#define ENABLE_RENDER_SELECTION_CENTER 0
// Shows an imgui dialog with camera related data
#define ENABLE_CAMERA_STATISTICS 0
// Enable extracting thumbnails from selected gcode and save them as png files
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG 0
// Disable synchronization of unselected instances
#define DISABLE_INSTANCES_SYNCH 0
// Use wxDataViewRender instead of wxDataViewCustomRenderer
#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING 0
// Enable G-Code viewer statistics imgui dialog
#define ENABLE_GCODE_VIEWER_STATISTICS 0
// Enable G-Code viewer comparison between toolpaths height and width detected from gcode and calculated at gcode generation
#define ENABLE_GCODE_VIEWER_DATA_CHECKING 0
// Enable project dirty state manager debug window
#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW 0
// Disable using instanced models to render options in gcode preview
#define DISABLE_GCODEVIEWER_INSTANCED_MODELS 1
// Enable Measure Gizmo debug window
#define ENABLE_MEASURE_GIZMO_DEBUG 0
// Enable rendering of objects using environment map
#define ENABLE_ENVIRONMENT_MAP 0
// Enable smoothing of objects normals
#define ENABLE_SMOOTH_NORMALS 0
//====================
// 2.5.0.alpha1 techs
//====================
#define ENABLE_2_5_0_ALPHA1 1
// Enable removal of legacy OpenGL calls
#define ENABLE_LEGACY_OPENGL_REMOVAL (1 && ENABLE_2_5_0_ALPHA1)
// Enable OpenGL ES
#define ENABLE_OPENGL_ES (0 && ENABLE_LEGACY_OPENGL_REMOVAL)
// Enable OpenGL core profile context (tested against Mesa 20.1.8 on Windows)
#define ENABLE_GL_CORE_PROFILE (1 && ENABLE_LEGACY_OPENGL_REMOVAL && !ENABLE_OPENGL_ES)
// Enable OpenGL debug messages using debug context
#define ENABLE_OPENGL_DEBUG_OPTION (1 && ENABLE_GL_CORE_PROFILE)
// Shows an imgui dialog with GLModel statistics data
#define ENABLE_GLMODEL_STATISTICS (0 && ENABLE_LEGACY_OPENGL_REMOVAL)
// Enable rework of Reload from disk command
#define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_5_0_ALPHA1)
// Enable editing volumes transformation in world coordinates and instances in local coordinates
#define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1)
// Enable alternative version of file_wildcards()
#define ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR (1 && ENABLE_2_5_0_ALPHA1)
// Enable processing of gcode G2 and G3 lines
#define ENABLE_PROCESS_G2_G3_LINES (1 && ENABLE_2_5_0_ALPHA1)
// Enable fix of used filament data exported to gcode file
#define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1)
// Enable picking using raytracing
#define ENABLE_RAYCAST_PICKING (1 && ENABLE_LEGACY_OPENGL_REMOVAL)
#define ENABLE_RAYCAST_PICKING_DEBUG (0 && ENABLE_RAYCAST_PICKING)
#endif // _prusaslicer_technologies_h_

View file

@ -0,0 +1,239 @@
#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]
// Z depth of text
float emboss; // [in mm]
// Flag that text should use surface cutted from object
// FontProp::distance should without value
// FontProp::emboss should be positive number
// Note: default value is false
bool use_surface;
// 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]
// distance from surface point
// used for move over model surface
// When not set value is zero and is not stored
std::optional<float> distance; // [in mm]
// change up vector direction of font
// When not set value is zero and is not stored
std::optional<float> angle; // [in radians]
// Parameter for True Type Font collections
// Select index of font in collection
std::optional<unsigned int> collection_number;
//enum class Align {
// left,
// right,
// center,
// top_left,
// top_right,
// top_center,
// bottom_left,
// bottom_right,
// bottom_center
//};
//// change pivot of text
//// When not set, center is used and is not stored
//std::optional<Align> align;
//////
// 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, float depth = 2.f)
: emboss(depth), size_in_mm(line_height), use_surface(false)
{}
bool operator==(const FontProp& other) const {
return
char_gap == other.char_gap &&
line_gap == other.line_gap &&
use_surface == other.use_surface &&
is_approx(emboss, other.emboss) &&
is_approx(size_in_mm, other.size_in_mm) &&
is_approx(boldness, other.boldness) &&
is_approx(skew, other.skew) &&
is_approx(distance, other.distance) &&
is_approx(angle, other.angle);
}
// undo / redo stack recovery
template<class Archive> void save(Archive &ar) const
{
ar(emboss, use_surface, size_in_mm);
cereal::save(ar, char_gap);
cereal::save(ar, line_gap);
cereal::save(ar, boldness);
cereal::save(ar, skew);
cereal::save(ar, distance);
cereal::save(ar, angle);
cereal::save(ar, collection_number);
cereal::save(ar, family);
cereal::save(ar, face_name);
cereal::save(ar, style);
cereal::save(ar, weight);
}
template<class Archive> void load(Archive &ar)
{
ar(emboss, use_surface, size_in_mm);
cereal::load(ar, char_gap);
cereal::load(ar, line_gap);
cereal::load(ar, boldness);
cereal::load(ar, skew);
cereal::load(ar, distance);
cereal::load(ar, angle);
cereal::load(ar, collection_number);
cereal::load(ar, family);
cereal::load(ar, face_name);
cereal::load(ar, style);
cereal::load(ar, weight);
}
};
/// <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";
// !!! Volume stored in .3mf has transformed vertices.
// (baked transformation into vertices position)
// Only place for fill this is when load from .3mf
// This is correct volume transformation
std::optional<Transform3d> fix_3mf_tr;
// undo / redo stack recovery
template<class Archive> void save(Archive &ar) const{
ar(text, style);
cereal::save(ar, fix_3mf_tr);
}
template<class Archive> void load(Archive &ar){
ar(text, style);
cereal::load(ar, fix_3mf_tr);
}
};
} // namespace Slic3r
#endif // slic3r_TextConfiguration_hpp_

12
src/libslic3r/Timer.cpp Normal file
View file

@ -0,0 +1,12 @@
#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";
}

31
src/libslic3r/Timer.hpp Normal file
View file

@ -0,0 +1,31 @@
#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 Slic3r
#endif // libslic3r_Timer_hpp_

View file

@ -783,9 +783,9 @@ int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit)
return removed;
}
bool its_store_triangle(const indexed_triangle_set &its,
const char * obj_filename,
size_t triangle_index)
bool its_store_triangle_to_obj(const indexed_triangle_set &its,
const char *obj_filename,
size_t triangle_index)
{
if (its.indices.size() <= triangle_index) return false;
Vec3i t = its.indices[triangle_index];
@ -796,9 +796,9 @@ bool its_store_triangle(const indexed_triangle_set &its,
return its_write_obj(its2, obj_filename);
}
bool its_store_triangles(const indexed_triangle_set &its,
const char * obj_filename,
const std::vector<size_t> & triangles)
bool its_store_triangles_to_obj(const indexed_triangle_set &its,
const char *obj_filename,
const std::vector<size_t> &triangles)
{
indexed_triangle_set its2;
its2.vertices.reserve(triangles.size() * 3);
@ -1206,6 +1206,23 @@ void its_reverse_all_facets(indexed_triangle_set &its)
std::swap(face[0], face[1]);
}
void its_merge(indexed_triangle_set &its, indexed_triangle_set &&its_add)
{
if (its.empty()) {
its = std::move(its_add);
return;
}
auto &verts = its.vertices;
size_t verts_size = verts.size();
Slic3r::append(verts, std::move(its_add.vertices));
// increase face indices
int offset = static_cast<int>(verts_size);
for (auto &face : its_add.indices)
for (int i = 0; i < 3; ++i) face[i] += offset;
Slic3r::append(its.indices, std::move(its_add.indices));
}
void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B)
{
auto N = int(A.vertices.size());

View file

@ -211,8 +211,8 @@ int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit =
int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit = true);
// store part of index triangle set
bool its_store_triangle(const indexed_triangle_set &its, const char *obj_filename, size_t triangle_index);
bool its_store_triangles(const indexed_triangle_set &its, const char *obj_filename, const std::vector<size_t>& triangles);
bool its_store_triangle_to_obj(const indexed_triangle_set &its, const char *obj_filename, size_t triangle_index);
bool its_store_triangles_to_obj(const indexed_triangle_set &its, const char *obj_filename, const std::vector<size_t>& triangles);
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its);
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its, std::vector<Vec3i> &face_neighbors);
@ -285,6 +285,14 @@ inline stl_normal its_unnormalized_normal(const indexed_triangle_set &its,
float its_volume(const indexed_triangle_set &its);
float its_average_edge_length(const indexed_triangle_set &its);
/// <summary>
/// Merge one triangle mesh to another
/// Added triangle set will be consumed
/// </summary>
/// <param name="its">IN/OUT triangle mesh</param>
/// <param name="its_add">Triangle mesh (will be consumed)</param>
void its_merge(indexed_triangle_set &its, indexed_triangle_set &&its_add);
void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B);
void its_merge(indexed_triangle_set &A, const std::vector<Vec3f> &triangles);
void its_merge(indexed_triangle_set &A, const Pointf3s &triangles);

View file

@ -0,0 +1,328 @@
#include "Triangulation.hpp"
#include "IntersectionPoints.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 !intersection_points(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<Vec3i> 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<Vec3i> 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_duplications(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 (Vec3i &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_duplications(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<Vec3i>;
/// <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

@ -237,6 +237,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);
#if defined __GNUC__ && __GNUC__ < 5 && !defined __clang__

View file

@ -21,6 +21,7 @@
#include <cassert>
#include <cmath>
#include <type_traits>
#include <optional>
#ifdef _WIN32
// On MSVC, std::deque degenerates to a list of pointers, which defeats its purpose of reducing allocator load and memory fragmentation.
@ -109,9 +110,10 @@ template <typename T>
inline void append(std::vector<T>& dest, const std::vector<T>& src)
{
if (dest.empty())
dest = src;
dest = src; // copy
else
dest.insert(dest.end(), src.begin(), src.end());
// NOTE: insert reserve space when needed
}
template <typename T>
@ -120,11 +122,14 @@ inline void append(std::vector<T>& dest, std::vector<T>&& src)
if (dest.empty())
dest = std::move(src);
else {
dest.reserve(dest.size() + src.size());
std::move(std::begin(src), std::end(src), std::back_inserter(dest));
dest.insert(dest.end(),
std::make_move_iterator(src.begin()),
std::make_move_iterator(src.end()));
// Vojta wants back compatibility
src.clear();
src.shrink_to_fit();
}
src.clear();
src.shrink_to_fit();
}
template<class T, class... Args> // Arbitrary allocator can be used
@ -140,8 +145,8 @@ void clear_and_shrink(std::vector<T, Args...>& vec)
template <typename T>
inline void append_reversed(std::vector<T>& dest, const std::vector<T>& src)
{
if (dest.empty())
dest = src;
if (dest.empty())
dest = {src.rbegin(), src.rend()};
else
dest.insert(dest.end(), src.rbegin(), src.rend());
}
@ -151,11 +156,14 @@ template <typename T>
inline void append_reversed(std::vector<T>& dest, std::vector<T>&& src)
{
if (dest.empty())
dest = std::move(src);
else {
dest.reserve(dest.size() + src.size());
std::move(std::rbegin(src), std::rend(src), std::back_inserter(dest));
}
dest = {std::make_move_iterator(src.rbegin),
std::make_move_iterator(src.rend)};
else
dest.insert(dest.end(),
std::make_move_iterator(src.rbegin()),
std::make_move_iterator(src.rend()));
// Vojta wants back compatibility
src.clear();
src.shrink_to_fit();
}
@ -268,6 +276,14 @@ constexpr inline bool is_approx(Number value, Number test_value, Number precisio
return std::fabs(double(value) - double(test_value)) < double(precision);
}
template<typename Number>
constexpr inline bool is_approx(const std::optional<Number> &value,
const std::optional<Number> &test_value)
{
return (!value.has_value() && !test_value.has_value()) ||
(value.has_value() && test_value.has_value() && is_approx<Number>(*value, *test_value));
}
// A meta-predicate which is true for integers wider than or equal to coord_t
template<class I> struct is_scaled_coord
{

View file

@ -944,7 +944,34 @@ std::string xml_escape(std::string text, bool is_marked/* = false*/)
case '\'': replacement = "&apos;"; break;
case '&': replacement = "&amp;"; break;
case '<': replacement = is_marked ? "<" :"&lt;"; break;
case '>': replacement = is_marked ? ">" :"&gt;"; break;
case '>': replacement = is_marked ? ">" : "&gt;"; break;
default: break;
}
text.replace(pos, 1, replacement);
pos += replacement.size();
}
return text;
}
// Definition of escape symbols https://www.w3.org/TR/REC-xml/#AVNormalize
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;
}

View file

@ -39,6 +39,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmosCommon.hpp
GUI/Gizmos/GLGizmoBase.cpp
GUI/Gizmos/GLGizmoBase.hpp
GUI/Gizmos/GLGizmoEmboss.cpp
GUI/Gizmos/GLGizmoEmboss.hpp
GUI/Gizmos/GLGizmoMove.cpp
GUI/Gizmos/GLGizmoMove.hpp
GUI/Gizmos/GLGizmoRotate.cpp
@ -139,6 +141,8 @@ set(SLIC3R_GUI_SOURCES
GUI/CoordAxes.hpp
GUI/Camera.cpp
GUI/Camera.hpp
GUI/CameraUtils.cpp
GUI/CameraUtils.hpp
GUI/wxExtensions.cpp
GUI/wxExtensions.hpp
GUI/ExtruderSequenceDialog.cpp
@ -185,6 +189,12 @@ set(SLIC3R_GUI_SOURCES
GUI/Jobs/PlaterWorker.hpp
GUI/Jobs/ArrangeJob.hpp
GUI/Jobs/ArrangeJob.cpp
GUI/Jobs/CreateFontNameImageJob.cpp
GUI/Jobs/CreateFontNameImageJob.hpp
GUI/Jobs/CreateFontStyleImagesJob.cpp
GUI/Jobs/CreateFontStyleImagesJob.hpp
GUI/Jobs/EmbossJob.cpp
GUI/Jobs/EmbossJob.hpp
GUI/Jobs/RotoptimizeJob.hpp
GUI/Jobs/RotoptimizeJob.cpp
GUI/Jobs/FillBedJob.hpp
@ -232,8 +242,14 @@ set(SLIC3R_GUI_SOURCES
Utils/OctoPrint.hpp
Utils/Duet.cpp
Utils/Duet.hpp
Utils/EmbossStyleManager.cpp
Utils/EmbossStyleManager.hpp
Utils/EmbossStylesSerializable.cpp
Utils/EmbossStylesSerializable.hpp
Utils/FlashAir.cpp
Utils/FlashAir.hpp
Utils/FontConfigHelp.cpp
Utils/FontConfigHelp.hpp
Utils/AstroBox.cpp
Utils/AstroBox.hpp
Utils/Repetier.cpp
@ -246,6 +262,8 @@ set(SLIC3R_GUI_SOURCES
Utils/PresetUpdater.hpp
Utils/Process.cpp
Utils/Process.hpp
Utils/RaycastManager.cpp
Utils/RaycastManager.hpp
Utils/UndoRedo.cpp
Utils/UndoRedo.hpp
Utils/HexFile.cpp
@ -256,6 +274,8 @@ set(SLIC3R_GUI_SOURCES
Utils/MKS.hpp
Utils/WinRegistry.cpp
Utils/WinRegistry.hpp
Utils/WxFontUtils.cpp
Utils/WxFontUtils.hpp
)
find_package(NanoSVG REQUIRED)
@ -310,5 +330,5 @@ endif ()
if (UNIX AND NOT APPLE)
find_package(GTK${SLIC3R_GTK} REQUIRED)
target_include_directories(libslic3r_gui PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS})
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES})
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig)
endif ()

View file

@ -324,64 +324,73 @@ void GLVolume::SinkingContours::update()
const int object_idx = m_parent.object_idx();
const Model& model = GUI::wxGetApp().plater()->model();
if (0 <= object_idx && object_idx < int(model.objects.size()) && m_parent.is_sinking() && !m_parent.is_below_printbed()) {
const BoundingBoxf3& box = m_parent.transformed_convex_hull_bounding_box();
if (!m_old_box.size().isApprox(box.size()) || m_old_box.min.z() != box.min.z()) {
m_old_box = box;
m_shift = Vec3d::Zero();
const TriangleMesh& mesh = model.objects[object_idx]->volumes[m_parent.volume_idx()]->mesh();
m_model.reset();
GUI::GLModel::Geometry init_data;
#if ENABLE_LEGACY_OPENGL_REMOVAL
init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3 };
init_data.color = ColorRGBA::WHITE();
unsigned int vertices_counter = 0;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
MeshSlicingParams slicing_params;
slicing_params.trafo = m_parent.world_matrix();
const Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params));
for (const ExPolygon& expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) {
#if ENABLE_LEGACY_OPENGL_REMOVAL
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(expoly);
init_data.reserve_vertices(init_data.vertices_count() + triangulation.size());
init_data.reserve_indices(init_data.indices_count() + triangulation.size());
for (const Vec3d& v : triangulation) {
init_data.add_vertex((Vec3f)(v.cast<float>() + 0.015f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting
++vertices_counter;
if (vertices_counter % 3 == 0)
init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
}
}
m_model.init_from(std::move(init_data));
#else
GUI::GLModel::Geometry::Entity entity;
entity.type = GUI::GLModel::EPrimitiveType::Triangles;
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(expoly);
entity.positions.reserve(entity.positions.size() + triangulation.size());
entity.normals.reserve(entity.normals.size() + triangulation.size());
entity.indices.reserve(entity.indices.size() + triangulation.size() / 3);
for (const Vec3d& v : triangulation) {
entity.positions.emplace_back(v.cast<float>() + 0.015f * Vec3f::UnitZ()); // add a small positive z to avoid z-fighting
entity.normals.emplace_back(Vec3f::UnitZ());
const size_t positions_count = entity.positions.size();
if (positions_count % 3 == 0) {
entity.indices.emplace_back(positions_count - 3);
entity.indices.emplace_back(positions_count - 2);
entity.indices.emplace_back(positions_count - 1);
}
}
init_data.entities.emplace_back(entity);
}
m_model.init_from(init_data);
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
}
else
m_shift = box.center() - m_old_box.center();
}
else
if (object_idx < 0 ||
object_idx >= int(model.objects.size()) ||
!m_parent.is_sinking() ||
m_parent.is_below_printbed()){
m_model.reset();
return;
}
const BoundingBoxf3& box = m_parent.transformed_convex_hull_bounding_box();
if (m_old_box.size().isApprox(box.size()) &&
m_old_box.min.z() == box.min.z()){
// Fix it !!! It is not working all the time
m_shift = box.center() - m_old_box.center();
return;
}
m_old_box = box;
m_shift = Vec3d::Zero();
const TriangleMesh& mesh = model.objects[object_idx]->volumes[m_parent.volume_idx()]->mesh();
m_model.reset();
GUI::GLModel::Geometry init_data;
#if ENABLE_LEGACY_OPENGL_REMOVAL
init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3 };
init_data.color = ColorRGBA::WHITE();
unsigned int vertices_counter = 0;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
MeshSlicingParams slicing_params;
slicing_params.trafo = m_parent.world_matrix();
const Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params));
if (polygons.empty()) return;
for (const ExPolygon& expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) {
#if ENABLE_LEGACY_OPENGL_REMOVAL
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(expoly);
init_data.reserve_vertices(init_data.vertices_count() + triangulation.size());
init_data.reserve_indices(init_data.indices_count() + triangulation.size());
for (const Vec3d& v : triangulation) {
init_data.add_vertex((Vec3f)(v.cast<float>() + 0.015f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting
++vertices_counter;
if (vertices_counter % 3 == 0)
init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
}
}
m_model.init_from(std::move(init_data));
#else
GUI::GLModel::Geometry::Entity entity;
entity.type = GUI::GLModel::EPrimitiveType::Triangles;
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(expoly);
entity.positions.reserve(entity.positions.size() + triangulation.size());
entity.normals.reserve(entity.normals.size() + triangulation.size());
entity.indices.reserve(entity.indices.size() + triangulation.size() / 3);
for (const Vec3d& v : triangulation) {
entity.positions.emplace_back(v.cast<float>() + 0.015f * Vec3f::UnitZ()); // add a small positive z to avoid z-fighting
entity.normals.emplace_back(Vec3f::UnitZ());
const size_t positions_count = entity.positions.size();
if (positions_count % 3 == 0) {
entity.indices.emplace_back(positions_count - 3);
entity.indices.emplace_back(positions_count - 2);
entity.indices.emplace_back(positions_count - 1);
}
}
init_data.entities.emplace_back(entity);
}
m_model.init_from(init_data);
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
}
void GLVolume::NonManifoldEdges::render()
@ -1291,9 +1300,15 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
#endif // ENABLE_GL_CORE_PROFILE
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
ScopeGuard transparent_sg;
if (type == ERenderType::Transparent) {
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glDepthMask(false));
transparent_sg = ScopeGuard([]() {
glsafe(::glDisable(GL_BLEND));
glsafe(::glDepthMask(true));
});
}
glsafe(::glCullFace(GL_BACK));
@ -1421,9 +1436,6 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
if (disable_cullface)
glsafe(::glEnable(GL_CULL_FACE));
if (type == ERenderType::Transparent)
glsafe(::glDisable(GL_BLEND));
}
bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, ModelInstanceEPrintVolumeState *out_state) const

View file

@ -0,0 +1,123 @@
#include "CameraUtils.hpp"
#include <igl/project.h> // projecting points
#include "slic3r/GUI/3DScene.hpp" // GLVolume
#include "libslic3r/Geometry/ConvexHull.hpp"
using namespace Slic3r;
using namespace GUI;
Points CameraUtils::project(const Camera & camera,
const std::vector<Vec3d> &points)
{
Vec4i viewport(camera.get_viewport().data());
// Convert our std::vector to Eigen dynamic matrix.
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign>
pts(points.size(), 3);
for (size_t i = 0; i < points.size(); ++i)
pts.block<1, 3>(i, 0) = points[i];
// Get the projections.
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign> projections;
igl::project(pts, camera.get_view_matrix().matrix(),
camera.get_projection_matrix().matrix(), viewport, projections);
Points result;
result.reserve(points.size());
int window_height = viewport[3];
// convert to points --> loss precision
for (int i = 0; i < projections.rows(); ++i) {
double x = projections(i, 0);
double y = projections(i, 1);
// opposit direction o Y
result.emplace_back(x, window_height - y);
}
return result;
}
Point CameraUtils::project(const Camera &camera, const Vec3d &point)
{
// IMPROVE: do it faster when you need it (inspire in project multi point)
return project(camera, std::vector{point}).front();
}
Slic3r::Polygon CameraUtils::create_hull2d(const Camera & camera,
const GLVolume &volume)
{
std::vector<Vec3d> vertices;
const TriangleMesh *hull = volume.convex_hull();
if (hull != nullptr) {
const indexed_triangle_set &its = hull->its;
vertices.reserve(its.vertices.size());
// cast vector
for (const Vec3f &vertex : its.vertices)
vertices.emplace_back(vertex.cast<double>());
} else {
// Negative volume doesn't have convex hull so use bounding box
auto bb = volume.bounding_box();
Vec3d &min = bb.min;
Vec3d &max = bb.max;
vertices = {min,
Vec3d(min.x(), min.y(), max.z()),
Vec3d(min.x(), max.y(), min.z()),
Vec3d(min.x(), max.y(), max.z()),
Vec3d(max.x(), min.y(), min.z()),
Vec3d(max.x(), min.y(), max.z()),
Vec3d(max.x(), max.y(), min.z()),
max};
}
const Transform3d &trafoMat =
volume.get_instance_transformation().get_matrix() *
volume.get_volume_transformation().get_matrix();
for (Vec3d &vertex : vertices)
vertex = trafoMat * vertex.cast<double>();
Points vertices_2d = project(camera, vertices);
return Geometry::convex_hull(vertices_2d);
}
#include <igl/unproject.h>
Vec3d CameraUtils::create_ray(const Camera &camera, const Vec2d &coor) {
if (camera.get_type() == Camera::EType::Ortho)
return camera.get_dir_forward();
// check that it is known camera no other tha ORTHO or Persepective
assert(camera.get_type() == Camera::EType::Perspective);
Matrix4d modelview = camera.get_view_matrix().matrix();
Matrix4d projection = camera.get_projection_matrix().matrix();
Vec4i viewport(camera.get_viewport().data());
Vec3d scene_point(coor.x(), viewport[3] - coor.y(), 0.);
Vec3d unprojected_point;
igl::unproject(scene_point, modelview, projection, viewport, unprojected_point);
Vec3d p0 = camera.get_position();
Vec3d dir = unprojected_point - p0;
dir.normalize();
return dir;
}
Vec2d CameraUtils::get_z0_position(const Camera &camera, const Vec2d & coor)
{
Vec3d dir = CameraUtils::create_ray(camera, coor);
Vec3d p0 = camera.get_position();
if (camera.get_type() == Camera::EType::Ortho) {
Matrix4d modelview = camera.get_view_matrix().matrix();
Matrix4d projection = camera.get_projection_matrix().matrix();
Vec4i viewport(camera.get_viewport().data());
igl::unproject(Vec3d(coor.x(), viewport[3] - coor.y(), 0.), modelview, projection, viewport, p0);
}
// is approx zero
if ((fabs(dir.z()) - 1e-4) < 0)
return Vec2d(std::numeric_limits<double>::max(),
std::numeric_limits<double>::max());
// find position of ray cross plane(z = 0)
double t = p0.z() / dir.z();
Vec3d p = p0 - t * dir;
return Vec2d(p.x(), p.y());
}

View file

@ -0,0 +1,58 @@
#ifndef slic3r_CameraUtils_hpp_
#define slic3r_CameraUtils_hpp_
#include "Camera.hpp"
#include "libslic3r/Point.hpp"
namespace Slic3r {
class GLVolume;
}
namespace Slic3r::GUI {
/// <summary>
/// Help divide camera data and camera functions
/// This utility work with camera data by static funtions
/// </summary>
class CameraUtils
{
public:
CameraUtils() = delete; // only static functions
/// <summary>
/// Project point throw camera to 2d coordinate into imgui window
/// </summary>
/// <param name="camera">Projection params</param>
/// <param name="points">Point to project.</param>
/// <returns>projected points by camera into coordinate of camera.
/// x(from left to right), y(from top to bottom)</returns>
static Points project(const Camera& camera, const std::vector<Vec3d> &points);
static Point project(const Camera& camera, const Vec3d &point);
/// <summary>
/// Create hull around GLVolume in 2d space of camera
/// </summary>
/// <param name="camera">Projection params</param>
/// <param name="volume">Outline by 3d object</param>
/// <returns>Polygon around object</returns>
static Polygon create_hull2d(const Camera &camera, const GLVolume &volume);
/// <summary>
/// Unproject screen coordinate to scene direction start from camera position
/// </summary>
/// <param name="camera">Projection params</param>
/// <param name="coor">Coordinate on screen</param>
/// <returns>Scene direction</returns>
static Vec3d create_ray(const Camera &camera, const Vec2d &coor);
/// <summary>
/// Unproject mouse coordinate to get position in space where z coor is zero
/// Platter surface should be in z == 0
/// </summary>
/// <param name="camera">Projection params</param>
/// <param name="coor">Mouse position</param>
/// <returns>Position on platter under mouse</returns>
static Vec2d get_z0_position(const Camera &camera, const Vec2d &coor);
};
} // Slic3r::GUI
#endif /* slic3r_CameraUtils_hpp_ */

View file

@ -3727,6 +3727,24 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
else
evt.Skip();
// Detection of doubleclick on text to open emboss edit window
if (evt.LeftDClick() && m_gizmos.get_current() == nullptr && !m_hover_volume_idxs.empty()) {
for (int hover_volume_id : m_hover_volume_idxs) {
const GLVolume &hover_gl_volume = *m_volumes.volumes[hover_volume_id];
const ModelObject* hover_object = m_model->objects[hover_gl_volume.object_idx()];
int hover_volume_idx = hover_gl_volume.volume_idx();
const ModelVolume* hover_volume = hover_object->volumes[hover_volume_idx];
if (hover_volume->text_configuration.has_value()) {
//m_selection.set_mode(Selection::EMode::Volume);
//m_selection.add(hover_volume_id); // add whole instance
m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id});
m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss);
wxGetApp().obj_list()->update_selections();
return;
}
}
}
if (m_moving)
show_sinking_contours();
@ -4382,6 +4400,14 @@ bool GLCanvas3D::is_object_sinking(int object_idx) const
return false;
}
void GLCanvas3D::apply_retina_scale(Vec2d &screen_coordinate) const
{
#if ENABLE_RETINA_GL
double scale = static_cast<double>(m_retina_helper->get_scale_factor());
screen_coordinate *= scale;
#endif // ENABLE_RETINA_GL
}
bool GLCanvas3D::_is_shown_on_screen() const
{
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
@ -7246,7 +7272,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
_3DScene::extrusionentity_to_verts(layerm->perimeters(), float(layer->print_z), copy,
select_geometry(idx_layer, layerm->region().config().perimeter_extruder.value, 0));
#else
_3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
_3DScene::extrusionentity_to_verts(layerm->perimeters(), float(layer->print_z), copy,
volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0));
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
if (ctxt.has_infill) {

View file

@ -830,6 +830,12 @@ public:
Size get_canvas_size() const;
Vec2d get_local_mouse_position() const;
// store opening position of menu
std::optional<Vec2d> m_popup_menu_positon; // position of mouse right click
void set_popup_menu_position(const Vec2d &position) { m_popup_menu_positon = position; }
const std::optional<Vec2d>& get_popup_menu_position() const { return m_popup_menu_positon; }
void clear_popup_menu_position() { m_popup_menu_positon.reset(); }
void set_tooltip(const std::string& tooltip);
// the following methods add a snapshot to the undo/redo stack, unless the given string is empty
@ -961,6 +967,8 @@ public:
bool is_object_sinking(int object_idx) const;
void apply_retina_scale(Vec2d &screen_coordinate) const;
private:
bool _is_shown_on_screen() const;

View file

@ -1,5 +1,6 @@
#include "GLSelectionRectangle.hpp"
#include "Camera.hpp"
#include "CameraUtils.hpp"
#include "3DScene.hpp"
#include "GLCanvas3D.hpp"
#include "GUI_App.hpp"
@ -44,30 +45,15 @@ namespace GUI {
m_state = EState::Off;
#endif // !ENABLE_RAYCAST_PICKING
const Camera& camera = wxGetApp().plater()->get_camera();
const Matrix4d modelview = camera.get_view_matrix().matrix();
const Matrix4d projection= camera.get_projection_matrix().matrix();
const Vec4i viewport(camera.get_viewport().data());
// Convert our std::vector to Eigen dynamic matrix.
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign> pts(points.size(), 3);
for (size_t i=0; i<points.size(); ++i)
pts.block<1, 3>(i, 0) = points[i];
// Get the projections.
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign> projections;
igl::project(pts, modelview, projection, viewport, projections);
// bounding box created from the rectangle corners - will take care of order of the corners
const BoundingBox rectangle(Points{ Point(m_start_corner.cast<coord_t>()), Point(m_end_corner.cast<coord_t>()) });
// Iterate over all points and determine whether they're in the rectangle.
for (int i = 0; i<projections.rows(); ++i)
#if ENABLE_RAYCAST_PICKING
if (rectangle.contains(Point(projections(i, 0), viewport[3] - projections(i, 1))))
#else
if (rectangle.contains(Point(projections(i, 0), canvas.get_canvas_size().get_height() - projections(i, 1))))
#endif // ENABLE_RAYCAST_PICKING
const Camera &camera = wxGetApp().plater()->get_camera();
Points points_2d = CameraUtils::project(camera, points);
unsigned int size = static_cast<unsigned int>(points.size());
for (unsigned int i = 0; i< size; ++i)
if (rectangle.contains(points_2d[i]))
out.push_back(i);
return out;

View file

@ -469,6 +469,7 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
/* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } },
/* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv, ".step"sv, ".stp"sv } },
/* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } },
/* FT_FONTS */ { "Font files"sv, { ".ttc"sv, ".ttf"sv } },
/* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } },
/* FT_INI */ { "INI files"sv, { ".ini"sv } },

View file

@ -62,6 +62,7 @@ enum FileType
FT_GCODE,
FT_MODEL,
FT_PROJECT,
FT_FONTS,
FT_GALLERY,
FT_INI,

View file

@ -13,6 +13,7 @@
#include "GLCanvas3D.hpp"
#include "Selection.hpp"
#include "format.hpp"
#include "Gizmos/GLGizmoEmboss.hpp"
#include <boost/algorithm/string.hpp>
#include "slic3r/Utils/FixModelByWin10.hpp"
@ -163,6 +164,14 @@ const std::vector<std::pair<std::string, std::string>> MenuFactory::ADD_VOLUME_M
{L("Add support enforcer"), "support_enforcer"}, // ~ModelVolumeType::SUPPORT_ENFORCER
};
// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
const std::vector<std::pair<std::string, std::string>> MenuFactory::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
};
static Plater* plater()
{
return wxGetApp().plater();
@ -438,6 +447,15 @@ std::vector<wxBitmapBundle*> MenuFactory::get_volume_bitmaps()
return volume_bmps;
}
std::vector<wxBitmapBundle*> MenuFactory::get_text_volume_bitmaps()
{
std::vector<wxBitmapBundle*> volume_bmps;
volume_bmps.reserve(TEXT_VOLUME_ICONS.size());
for (auto item : TEXT_VOLUME_ICONS)
volume_bmps.push_back(get_bmp_bundle(item.second));
return volume_bmps;
}
void MenuFactory::append_menu_item_delete(wxMenu* menu)
{
append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"),
@ -451,21 +469,27 @@ void MenuFactory::append_menu_item_delete(wxMenu* menu)
wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType type) {
auto sub_menu = new wxMenu;
if (wxGetApp().get_mode() == comExpert && type != ModelVolumeType::INVALID) {
const ConfigOptionMode mode = wxGetApp().get_mode();
if (mode == comExpert && type != ModelVolumeType::INVALID ||
mode == comAdvanced && type == ModelVolumeType::MODEL_PART) {
append_menu_item(sub_menu, wxID_ANY, _L("Load") + " " + dots, "",
[type](wxCommandEvent&) { obj_list()->load_subobject(type); }, "", menu);
sub_menu->AppendSeparator();
}
for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") })
{
if (type == ModelVolumeType::INVALID && strncmp(item, "Slab", 4) == 0)
continue;
append_menu_item(sub_menu, wxID_ANY, _(item), "",
[type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu);
}
if (!(type == ModelVolumeType::MODEL_PART && mode == comAdvanced))
for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") })
{
if (type == ModelVolumeType::INVALID && strncmp(item, "Slab", 4) == 0)
continue;
append_menu_item(sub_menu, wxID_ANY, _(item), "",
[type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu);
}
if (wxGetApp().get_mode() >= comAdvanced) {
append_menu_item_add_text(sub_menu, type);
if (mode >= comAdvanced) {
sub_menu->AppendSeparator();
append_menu_item(sub_menu, wxID_ANY, _L("Gallery"), "",
[type](wxCommandEvent&) { obj_list()->load_subobject(type, true); }, "", menu);
@ -474,30 +498,66 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty
return sub_menu;
}
void MenuFactory::append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item/* = true*/)
{
auto add_text = [type](wxCommandEvent& evt) {
GLCanvas3D* canvas = plater()->canvas3D();
GLGizmosManager& mng = canvas->get_gizmos_manager();
GLGizmoBase* gizmo = mng.get_gizmo(GLGizmosManager::Emboss);
GLGizmoEmboss* emboss = dynamic_cast<GLGizmoEmboss *>(gizmo);
assert(emboss != nullptr);
if (emboss == nullptr) return;
ModelVolumeType volume_type = type;
// no selected object means create new object
if (volume_type == ModelVolumeType::INVALID)
volume_type = ModelVolumeType::MODEL_PART;
auto screen_position = canvas->get_popup_menu_position();
if (screen_position.has_value()) {
emboss->create_volume(volume_type, *screen_position);
} else {
emboss->create_volume(volume_type);
}
};
if ( type == ModelVolumeType::MODEL_PART
|| type == ModelVolumeType::NEGATIVE_VOLUME
|| type == ModelVolumeType::PARAMETER_MODIFIER
|| type == ModelVolumeType::INVALID // cannot use gizmo without selected object
) {
wxString item_name = is_submenu_item ? "" : _(ADD_VOLUME_MENU_ITEMS[int(type)].first) + ": ";
item_name += _L("Text");
const std::string icon_name = is_submenu_item ? "" : ADD_VOLUME_MENU_ITEMS[int(type)].second;
append_menu_item(menu, wxID_ANY, item_name, "", add_text, icon_name, menu);
}
}
void MenuFactory::append_menu_items_add_volume(wxMenu* menu)
{
// Update "add" items(delete old & create new) settings popupmenu
// Update "add" items(delete old & create new) items popupmenu
for (auto& item : ADD_VOLUME_MENU_ITEMS) {
const auto settings_id = menu->FindItem(_(item.first));
if (settings_id != wxNOT_FOUND)
menu->Destroy(settings_id);
const wxString item_name = _(item.first);
int item_id = menu->FindItem(item_name);
if (item_id != wxNOT_FOUND)
menu->Destroy(item_id);
item_id = menu->FindItem(item_name + ": " + _L("Text"));
if (item_id != wxNOT_FOUND)
menu->Destroy(item_id);
}
// Update "Height range Modifier" item (delete old & create new)
if (const auto range_id = menu->FindItem(_L("Height range Modifier")); range_id != wxNOT_FOUND)
menu->Destroy(range_id);
const ConfigOptionMode mode = wxGetApp().get_mode();
if (const auto range_id = menu->FindItem(_L("Height range Modifier")); range_id != wxNOT_FOUND)
menu->Destroy(range_id);
if (wxGetApp().get_mode() == comSimple) {
append_menu_item_add_text(menu, ModelVolumeType::MODEL_PART, false);
append_menu_item_add_text(menu, ModelVolumeType::NEGATIVE_VOLUME, false);
if (mode == comAdvanced) {
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "",
[](wxCommandEvent&) { obj_list()->load_subobject(ModelVolumeType::MODEL_PART); },
ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr,
[]() { return obj_list()->is_instance_or_object_selected()
&& !obj_list()->is_selected_object_cut();
}, m_parent);
}
if (mode == comSimple) {
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "",
[](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); },
ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second, nullptr,
@ -510,11 +570,9 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu)
return;
}
for (size_t type = (mode == comExpert ? 0 : 1); type < ADD_VOLUME_MENU_ITEMS.size(); type++)
{
auto& item = ADD_VOLUME_MENU_ITEMS[type];
wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type));
int type = 0;
for (auto& item : ADD_VOLUME_MENU_ITEMS) {
wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type++));
append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second,
[type]() {
bool can_add = type < size_t(ModelVolumeType::PARAMETER_MODIFIER) ? !obj_list()->is_selected_object_cut() : true;
@ -907,6 +965,37 @@ void MenuFactory::append_menu_items_mirror(wxMenu* menu)
[]() { return plater()->can_mirror(); }, m_parent);
}
void MenuFactory::append_menu_item_edit_text(wxMenu *menu)
{
wxString name = _L("Edit text");
auto can_edit_text = []() {
const auto& sel = plater()->get_selection();
if (sel.volumes_count() != 1) return false;
auto cid = sel.get_volume(*sel.get_volume_idxs().begin());
const ModelVolume* vol = plater()->canvas3D()->get_model()
->objects[cid->object_idx()]->volumes[cid->volume_idx()];
return vol->text_configuration.has_value();
};
if (menu == &m_object_menu) {
auto menu_item_id = menu->FindItem(name);
if (menu_item_id != wxNOT_FOUND)
menu->Destroy(menu_item_id);
if (!can_edit_text())
return;
}
wxString description = _L("Ability to change text, font, size, ...");
std::string icon = "";
append_menu_item(
menu, wxID_ANY, name, description,
[can_edit_text](wxCommandEvent &) {
plater()->canvas3D()->get_gizmos_manager().open_gizmo(GLGizmosManager::Emboss);
},
icon, nullptr, can_edit_text, m_parent);
}
MenuFactory::MenuFactory()
{
for (int i = 0; i < mtCount; i++) {
@ -979,6 +1068,20 @@ void MenuFactory::create_sla_object_menu()
m_sla_object_menu.AppendSeparator();
}
void MenuFactory::append_immutable_part_menu_items(wxMenu* menu)
{
append_menu_items_mirror(menu);
menu->AppendSeparator();
append_menu_item_change_type(menu);
}
void MenuFactory::append_mutable_part_menu_items(wxMenu* menu)
{
append_menu_item_settings(menu);
append_menu_item_change_extruder(menu);
}
void MenuFactory::create_part_menu()
{
wxMenu* menu = &m_part_menu;
@ -991,15 +1094,24 @@ void MenuFactory::create_part_menu()
append_menu_item_export_stl(menu);
append_menu_item_fix_through_netfabb(menu);
append_menu_item_simplify(menu);
append_menu_items_mirror(menu);
append_menu_item(menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual parts"),
[](wxCommandEvent&) { plater()->split_volume(); }, "split_parts_SMALL", nullptr,
[](wxCommandEvent&) { plater()->split_volume(); }, "split_parts_SMALL", nullptr,
[]() { return plater()->can_split(false); }, m_parent);
menu->AppendSeparator();
append_menu_item_change_type(menu);
append_immutable_part_menu_items(menu);
}
void MenuFactory::create_text_part_menu()
{
wxMenu* menu = &m_text_part_menu;
append_menu_item_delete(menu);
append_menu_item_edit_text(menu);
append_menu_item_fix_through_netfabb(menu);
append_menu_item_simplify(menu);
append_immutable_part_menu_items(menu);
}
void MenuFactory::create_instance_menu()
@ -1018,6 +1130,7 @@ void MenuFactory::init(wxWindow* parent)
create_object_menu();
create_sla_object_menu();
create_part_menu();
create_text_part_menu();
create_instance_menu();
}
@ -1039,6 +1152,7 @@ wxMenu* MenuFactory::object_menu()
append_menu_item_change_extruder(&m_object_menu);
update_menu_items_instance_manipulation(mtObjectFFF);
append_menu_item_invalidate_cut_info(&m_object_menu);
append_menu_item_edit_text(&m_object_menu);
return &m_object_menu;
}
@ -1049,6 +1163,7 @@ wxMenu* MenuFactory::sla_object_menu()
append_menu_item_settings(&m_sla_object_menu);
update_menu_items_instance_manipulation(mtObjectSLA);
append_menu_item_invalidate_cut_info(&m_sla_object_menu);
append_menu_item_edit_text(&m_sla_object_menu);
return &m_sla_object_menu;
}
@ -1056,12 +1171,19 @@ wxMenu* MenuFactory::sla_object_menu()
wxMenu* MenuFactory::part_menu()
{
append_menu_items_convert_unit(&m_part_menu, 2);
append_menu_item_settings(&m_part_menu);
append_menu_item_change_extruder(&m_part_menu);
append_mutable_part_menu_items(&m_part_menu);
return &m_part_menu;
}
wxMenu* MenuFactory::text_part_menu()
{
append_mutable_part_menu_items(&m_text_part_menu);
return &m_text_part_menu;
}
wxMenu* MenuFactory::instance_menu()
{
return &m_instance_menu;

View file

@ -34,7 +34,9 @@ class MenuFactory
{
public:
static const std::vector<std::pair<std::string, std::string>> ADD_VOLUME_MENU_ITEMS;
static std::vector<wxBitmapBundle*> get_volume_bitmaps();
static const std::vector<std::pair<std::string, std::string>> TEXT_VOLUME_ICONS;
static std::vector<wxBitmapBundle*> get_volume_bitmaps();
static std::vector<wxBitmapBundle*> get_text_volume_bitmaps();
MenuFactory();
~MenuFactory() = default;
@ -51,6 +53,7 @@ public:
wxMenu* object_menu();
wxMenu* sla_object_menu();
wxMenu* part_menu();
wxMenu* text_part_menu();
wxMenu* instance_menu();
wxMenu* layer_menu();
wxMenu* multi_selection_menu();
@ -66,6 +69,7 @@ private:
MenuWithSeparators m_object_menu;
MenuWithSeparators m_part_menu;
MenuWithSeparators m_text_part_menu;
MenuWithSeparators m_sla_object_menu;
MenuWithSeparators m_default_menu;
MenuWithSeparators m_instance_menu;
@ -79,10 +83,14 @@ private:
void create_common_object_menu(wxMenu *menu);
void create_object_menu();
void create_sla_object_menu();
void append_immutable_part_menu_items(wxMenu* menu);
void append_mutable_part_menu_items(wxMenu* menu);
void create_part_menu();
void create_text_part_menu();
void create_instance_menu();
wxMenu* append_submenu_add_generic(wxMenu* menu, ModelVolumeType type);
void append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item = true);
void append_menu_items_add_volume(wxMenu* menu);
wxMenuItem* append_menu_item_layers_editing(wxMenu* menu);
wxMenuItem* append_menu_item_settings(wxMenu* menu);
@ -103,6 +111,7 @@ private:
void append_menu_item_merge_to_multipart_object(wxMenu *menu);
// void append_menu_item_merge_to_single_object(wxMenu *menu);
void append_menu_items_mirror(wxMenu *menu);
void append_menu_item_edit_text(wxMenu *menu);
void append_menu_items_instance_manipulation(wxMenu *menu);
void update_menu_items_instance_manipulation(MenuType type);
};

View file

@ -1,5 +1,6 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/TextConfiguration.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_Factories.hpp"
#include "GUI_ObjectManipulation.hpp"
@ -649,14 +650,22 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const
ModelObject* obj = object(obj_idx);
if (m_objects_model->GetItemType(item) & itObject) {
obj->name = m_objects_model->GetName(item).ToUTF8().data();
obj->name = into_u8(m_objects_model->GetName(item));
// if object has just one volume, rename this volume too
if (obj->volumes.size() == 1)
if (obj->volumes.size() == 1 && !obj->volumes[0]->text_configuration.has_value())
obj->volumes[0]->name = obj->name;
return;
}
if (volume_id < 0) return;
if (volume_id < 0)
return;
// Renaming of the text volume is suppressed
// So, revert the name in object list
if (obj->volumes[volume_id]->text_configuration.has_value()) {
m_objects_model->SetName(from_u8(obj->volumes[volume_id]->name), item);
return;
}
obj->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data();
}
@ -672,6 +681,10 @@ void ObjectList::update_name_in_list(int obj_idx, int vol_idx) const
return;
m_objects_model->SetName(new_name, item);
// if object has just one volume, rename object too
if (ModelObject* obj = object(obj_idx); obj->volumes.size() == 1)
obj->name = obj->volumes.front()->name;
}
void ObjectList::selection_changed()
@ -968,11 +981,17 @@ void ObjectList::show_context_menu(const bool evt_context_menu)
const ItemType type = m_objects_model->GetItemType(item);
if (!(type & (itObject | itVolume | itLayer | itInstance)))
return;
menu = type & itInstance ? plater->instance_menu() :
type & itLayer ? plater->layer_menu() :
m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? plater->part_menu() :
printer_technology() == ptFFF ? plater->object_menu() : plater->sla_object_menu();
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;
menu = object(obj_idx)->volumes[vol_idx]->text_configuration.has_value() ? plater->text_part_menu() : plater->part_menu();
}
else
menu = type & itInstance ? plater->instance_menu() :
type & itLayer ? plater->layer_menu() :
printer_technology() == ptFFF ? plater->object_menu() : plater->sla_object_menu();
}
else if (evt_context_menu)
menu = plater->default_menu();
@ -1749,7 +1768,7 @@ void ObjectList::load_shape_object(const std::string& type_name)
// Create mesh
BoundingBoxf3 bb;
TriangleMesh mesh = create_mesh(type_name, bb);
load_mesh_object(mesh, _L("Shape") + "-" + _(type_name));
load_mesh_object(mesh, _u8L("Shape") + "-" + type_name);
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (!m_objects->empty())
m_objects->back()->volumes.front()->source.is_from_builtin_objects = true;
@ -1789,7 +1808,12 @@ void ObjectList::load_shape_object_from_gallery(const wxArrayString& input_files
wxGetApp().mainframe->update_title();
}
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center)
void ObjectList::load_mesh_object(
const TriangleMesh & mesh,
const std::string & name,
bool center,
const TextConfiguration *text_config /* = nullptr*/,
const Transform3d * transformation /* = nullptr*/)
{
// Add mesh to model as a new object
Model& model = wxGetApp().plater()->model();
@ -1799,22 +1823,29 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
#endif /* _DEBUG */
std::vector<size_t> object_idxs;
auto bb = mesh.bounding_box();
ModelObject* new_object = model.add_object();
new_object->name = into_u8(name);
new_object->name = name;
new_object->add_instance(); // each object should have at list one instance
ModelVolume* new_volume = new_object->add_volume(mesh);
new_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1");
new_volume->name = into_u8(name);
new_volume->name = name;
if (text_config)
new_volume->text_configuration = *text_config;
// set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
new_object->invalidate_bounding_box();
new_object->translate(-bb.center());
new_object->instances[0]->set_offset(center ?
to_3d(wxGetApp().plater()->build_volume().bounding_volume2d().center(), -new_object->origin_translation.z()) :
if (transformation) {
assert(!center);
Slic3r::Geometry::Transformation tr(*transformation);
new_object->instances[0]->set_transformation(tr);
} else {
auto bb = mesh.bounding_box();
new_object->translate(-bb.center());
new_object->instances[0]->set_offset(
center ? to_3d(wxGetApp().plater()->build_volume().bounding_volume2d().center(), -new_object->origin_translation.z()) :
bb.center());
}
new_object->ensure_on_bed();
@ -2898,6 +2929,7 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st
from_u8(volume->name),
volume_idx,
volume->type(),
volume->text_configuration.has_value(),
get_warning_icon_name(volume->mesh().stats()),
extruder2str(volume->config.has("extruder") ? volume->config.extruder() : 0));
add_settings_item(vol_item, &volume->config.get());
@ -4160,14 +4192,14 @@ void ObjectList::change_part_type()
}
const bool is_cut_object = obj->is_cut();
wxArrayString names;
names.Alloc(is_cut_object ? 3 : 5);
if (!is_cut_object)
for (const wxString& type : { _L("Part"), _L("Negative Volume") })
names.Add(type);
for (const wxString& type : { _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") } )
names.Add(type);
names.Add(_L("Modifier"));
if (!volume->text_configuration.has_value())
for (const wxString& name : { _L("Support Blocker"), _L("Support Enforcer") })
names.Add(name);
const int type_shift = is_cut_object ? 2 : 0;
auto new_type = ModelVolumeType(type_shift + wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), names, int(type) - type_shift));

View file

@ -27,6 +27,7 @@ class ModelConfig;
class ModelObject;
class ModelVolume;
class TriangleMesh;
struct TextConfiguration;
enum class ModelVolumeType : int;
// FIXME: broken build on mac os because of this is missing:
@ -257,7 +258,8 @@ public:
void load_shape_object(const std::string &type_name);
void load_shape_object_from_gallery();
void load_shape_object_from_gallery(const wxArrayString& input_files);
void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true);
void load_mesh_object(const TriangleMesh &mesh, const std::string &name, bool center = true,
const TextConfiguration* text_config = nullptr, const Transform3d* transformation = nullptr);
bool del_object(const int obj_idx);
bool del_subobject_item(wxDataViewItem& item);
void del_settings_from_config(const wxDataViewItem& parent_item);

View file

@ -23,7 +23,7 @@ namespace GUI
{
const double ObjectManipulation::in_to_mm = 25.4;
const double ObjectManipulation::mm_to_in = 0.0393700787;
const double ObjectManipulation::mm_to_in = 1 / ObjectManipulation::in_to_mm;
// Helper function to be used by drop to bed button. Returns lowest point of this
// volume in world coordinate system.

File diff suppressed because it is too large Load diff

View file

@ -1,113 +1,116 @@
#ifndef slic3r_GLGizmoBase_hpp_
#define slic3r_GLGizmoBase_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/Color.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GLModel.hpp"
#if ENABLE_RAYCAST_PICKING
#include "slic3r/GUI/MeshUtils.hpp"
#include "slic3r/GUI/SceneRaycaster.hpp"
#endif // ENABLE_RAYCAST_PICKING
#include <cereal/archives/binary.hpp>
class wxWindow;
class wxMouseEvent;
namespace Slic3r {
class BoundingBoxf3;
class Linef3;
class ModelObject;
namespace GUI {
static const ColorRGBA DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f };
static const ColorRGBA DEFAULT_DRAG_COLOR = ColorRGBA::WHITE();
static const ColorRGBA DEFAULT_HIGHLIGHT_COLOR = ColorRGBA::ORANGE();
static const std::array<ColorRGBA, 3> AXES_COLOR = {{ ColorRGBA::X(), ColorRGBA::Y(), ColorRGBA::Z() }};
static const ColorRGBA CONSTRAINED_COLOR = ColorRGBA::GRAY();
class ImGuiWrapper;
class GLCanvas3D;
enum class CommonGizmosDataID;
class CommonGizmosDataPool;
class GLGizmoBase
{
public:
// Starting value for ids to avoid clashing with ids used by GLVolumes
// (254 is choosen to leave some space for forward compatibility)
static const unsigned int BASE_ID = 255 * 255 * 254;
static const unsigned int GRABBER_ELEMENTS_MAX_COUNT = 7;
enum class EGrabberExtension
{
None = 0,
PosX = 1 << 0,
NegX = 1 << 1,
PosY = 1 << 2,
NegY = 1 << 3,
PosZ = 1 << 4,
NegZ = 1 << 5,
};
protected:
struct Grabber
{
static const float SizeFactor;
static const float MinHalfSize;
static const float DraggingScaleFactor;
bool enabled{ true };
bool dragging{ false };
Vec3d center{ Vec3d::Zero() };
Vec3d angles{ Vec3d::Zero() };
#if ENABLE_LEGACY_OPENGL_REMOVAL
Transform3d matrix{ Transform3d::Identity() };
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
ColorRGBA color{ ColorRGBA::WHITE() };
EGrabberExtension extensions{ EGrabberExtension::None };
#if ENABLE_RAYCAST_PICKING
// the picking id shared by all the elements
int picking_id{ -1 };
std::array<std::shared_ptr<SceneRaycasterItem>, GRABBER_ELEMENTS_MAX_COUNT> raycasters = { nullptr };
#endif // ENABLE_RAYCAST_PICKING
Grabber() = default;
~Grabber();
#if ENABLE_RAYCAST_PICKING
void render(bool hover, float size) { render(size, hover ? complementary(color) : color); }
#else
void render(bool hover, float size) { render(size, hover ? complementary(color) : color, false); }
void render_for_picking(float size) { render(size, color, true); }
#endif // ENABLE_RAYCAST_PICKING
float get_half_size(float size) const;
float get_dragging_half_size(float size) const;
#if ENABLE_RAYCAST_PICKING
void register_raycasters_for_picking(int id);
void unregister_raycasters_for_picking();
#endif // ENABLE_RAYCAST_PICKING
private:
#if ENABLE_RAYCAST_PICKING
void render(float size, const ColorRGBA& render_color);
#else
void render(float size, const ColorRGBA& render_color, bool picking);
#endif // ENABLE_RAYCAST_PICKING
#if ENABLE_RAYCAST_PICKING
static PickingModel s_cube;
static PickingModel s_cone;
#else
static GLModel s_cube;
static GLModel s_cone;
#endif // ENABLE_RAYCAST_PICKING
#ifndef slic3r_GLGizmoBase_hpp_
#define slic3r_GLGizmoBase_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/Color.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GLModel.hpp"
#if ENABLE_RAYCAST_PICKING
#include "slic3r/GUI/MeshUtils.hpp"
#include "slic3r/GUI/SceneRaycaster.hpp"
#endif // ENABLE_RAYCAST_PICKING
#include <cereal/archives/binary.hpp>
class wxWindow;
class wxMouseEvent;
namespace Slic3r {
class BoundingBoxf3;
class Linef3;
class ModelObject;
namespace GUI {
static const ColorRGBA DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f };
static const ColorRGBA DEFAULT_DRAG_COLOR = ColorRGBA::WHITE();
static const ColorRGBA DEFAULT_HIGHLIGHT_COLOR = ColorRGBA::ORANGE();
static const std::array<ColorRGBA, 3> AXES_COLOR = {{ ColorRGBA::X(), ColorRGBA::Y(), ColorRGBA::Z() }};
static const ColorRGBA CONSTRAINED_COLOR = ColorRGBA::GRAY();
class ImGuiWrapper;
class GLCanvas3D;
enum class CommonGizmosDataID;
class CommonGizmosDataPool;
class GLGizmoBase
{
public:
// Starting value for ids to avoid clashing with ids used by GLVolumes
// (254 is choosen to leave some space for forward compatibility)
static const unsigned int BASE_ID = 255 * 255 * 254;
static const unsigned int GRABBER_ELEMENTS_MAX_COUNT = 7;
enum class EGrabberExtension
{
None = 0,
PosX = 1 << 0,
NegX = 1 << 1,
PosY = 1 << 2,
NegY = 1 << 3,
PosZ = 1 << 4,
NegZ = 1 << 5,
};
// Represents NO key(button on keyboard) value
static const int NO_SHORTCUT_KEY_VALUE = 0;
protected:
struct Grabber
{
static const float SizeFactor;
static const float MinHalfSize;
static const float DraggingScaleFactor;
bool enabled{ true };
bool dragging{ false };
Vec3d center{ Vec3d::Zero() };
Vec3d angles{ Vec3d::Zero() };
#if ENABLE_LEGACY_OPENGL_REMOVAL
Transform3d matrix{ Transform3d::Identity() };
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
ColorRGBA color{ ColorRGBA::WHITE() };
EGrabberExtension extensions{ EGrabberExtension::None };
#if ENABLE_RAYCAST_PICKING
// the picking id shared by all the elements
int picking_id{ -1 };
std::array<std::shared_ptr<SceneRaycasterItem>, GRABBER_ELEMENTS_MAX_COUNT> raycasters = { nullptr };
#endif // ENABLE_RAYCAST_PICKING
Grabber() = default;
~Grabber();
#if ENABLE_RAYCAST_PICKING
void render(bool hover, float size) { render(size, hover ? complementary(color) : color); }
#else
void render(bool hover, float size) { render(size, hover ? complementary(color) : color, false); }
void render_for_picking(float size) { render(size, color, true); }
#endif // ENABLE_RAYCAST_PICKING
float get_half_size(float size) const;
float get_dragging_half_size(float size) const;
#if ENABLE_RAYCAST_PICKING
void register_raycasters_for_picking(int id);
void unregister_raycasters_for_picking();
#endif // ENABLE_RAYCAST_PICKING
private:
#if ENABLE_RAYCAST_PICKING
void render(float size, const ColorRGBA& render_color);
#else
void render(float size, const ColorRGBA& render_color, bool picking);
#endif // ENABLE_RAYCAST_PICKING
#if ENABLE_RAYCAST_PICKING
static PickingModel s_cube;
static PickingModel s_cone;
#else
static GLModel s_cube;
static GLModel s_cone;
#endif // ENABLE_RAYCAST_PICKING
};
public:
@ -131,17 +134,17 @@ public:
protected:
GLCanvas3D& m_parent;
int m_group_id{ -1 }; // TODO: remove only for rotate
EState m_state{ Off };
int m_shortcut_key{ 0 };
int m_group_id; // TODO: remove only for rotate
EState m_state;
int m_shortcut_key;
std::string m_icon_filename;
unsigned int m_sprite_id;
int m_hover_id{ -1 };
bool m_dragging{ false };
int m_hover_id{ -1 };
bool m_dragging{ false };
mutable std::vector<Grabber> m_grabbers;
ImGuiWrapper* m_imgui;
bool m_first_input_window_render{ true };
CommonGizmosDataPool* m_c{ nullptr };
bool m_first_input_window_render{ true };
CommonGizmosDataPool* m_c{ nullptr };
public:
GLGizmoBase(GLCanvas3D& parent,
@ -183,9 +186,9 @@ public:
bool update_items_state();
void render() { on_render(); }
#if !ENABLE_RAYCAST_PICKING
#if !ENABLE_RAYCAST_PICKING
void render_for_picking() { on_render_for_picking(); }
#endif // !ENABLE_RAYCAST_PICKING
#endif // !ENABLE_RAYCAST_PICKING
void render_input_window(float x, float y, float bottom_limit);
/// <summary>
@ -207,13 +210,13 @@ public:
/// <returns>Return True when use the information and don't want to propagate it otherwise False.</returns>
virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; }
#if ENABLE_RAYCAST_PICKING
void register_raycasters_for_picking() { register_grabbers_for_picking(); on_register_raycasters_for_picking(); }
void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); }
#if ENABLE_RAYCAST_PICKING
void register_raycasters_for_picking() { register_grabbers_for_picking(); on_register_raycasters_for_picking(); }
void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); }
#endif // ENABLE_RAYCAST_PICKING
virtual bool is_in_editing_mode() const { return false; }
virtual bool is_selection_rectangle_dragging() const { return false; }
virtual bool is_in_editing_mode() const { return false; }
virtual bool is_selection_rectangle_dragging() const { return false; }
protected:
virtual bool on_init() = 0;
@ -234,27 +237,27 @@ protected:
virtual void on_dragging(const UpdateData& data) {}
virtual void on_render() = 0;
#if !ENABLE_RAYCAST_PICKING
#if !ENABLE_RAYCAST_PICKING
virtual void on_render_for_picking() = 0;
#endif // !ENABLE_RAYCAST_PICKING
#endif // !ENABLE_RAYCAST_PICKING
virtual void on_render_input_window(float x, float y, float bottom_limit) {}
#if ENABLE_RAYCAST_PICKING
void register_grabbers_for_picking();
void unregister_grabbers_for_picking();
virtual void on_register_raycasters_for_picking() {}
virtual void on_unregister_raycasters_for_picking() {}
#else
#if ENABLE_RAYCAST_PICKING
void register_grabbers_for_picking();
void unregister_grabbers_for_picking();
virtual void on_register_raycasters_for_picking() {}
virtual void on_unregister_raycasters_for_picking() {}
#else
// Returns the picking color for the given id, based on the BASE_ID constant
// No check is made for clashing with other picking color (i.e. GLVolumes)
ColorRGBA picking_color_component(unsigned int id) const;
#endif // ENABLE_RAYCAST_PICKING
#endif // ENABLE_RAYCAST_PICKING
void render_grabbers(const BoundingBoxf3& box) const;
void render_grabbers(float size) const;
#if !ENABLE_RAYCAST_PICKING
#if !ENABLE_RAYCAST_PICKING
void render_grabbers_for_picking(const BoundingBoxf3& box) const;
#endif // !ENABLE_RAYCAST_PICKING
#endif // !ENABLE_RAYCAST_PICKING
std::string format(float value, unsigned int decimals) const;
@ -277,7 +280,7 @@ protected:
private:
// Flag for dirty visible state of Gizmo
// When True then need new rendering
bool m_dirty{ false };
bool m_dirty{ false };
};
} // namespace GUI

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,360 @@
#ifndef slic3r_GLGizmoEmboss_hpp_
#define slic3r_GLGizmoEmboss_hpp_
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
// which overrides our localization "L" macro.
#include "GLGizmoBase.hpp"
#include "GLGizmoRotate.hpp"
#include "slic3r/GUI/GLTexture.hpp"
#include "slic3r/Utils/RaycastManager.hpp"
#include "slic3r/Utils/EmbossStyleManager.hpp"
#include "admesh/stl.h" // indexed_triangle_set
#include <optional>
#include <memory>
#include <mutex>
#include <thread>
#include <atomic>
#include "libslic3r/Emboss.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/TextConfiguration.hpp"
#include <imgui/imgui.h>
#include <GL/glew.h>
class wxFont;
namespace Slic3r{
class AppConfig;
class GLVolume;
enum class ModelVolumeType : int;
}
namespace Slic3r::GUI {
class MeshRaycaster;
class GLGizmoEmboss : public GLGizmoBase
{
public:
GLGizmoEmboss(GLCanvas3D& parent);
/// <summary>
/// Create new embossed text volume by type on position of mouse
/// </summary>
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
/// <param name="mouse_pos">Define position of new volume</param>
void create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos);
/// <summary>
/// Create new text without given position
/// </summary>
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
void create_volume(ModelVolumeType volume_type);
protected:
bool on_init() override;
std::string on_get_name() const override;
void on_render() override;
#if ENABLE_RAYCAST_PICKING
virtual void on_register_raycasters_for_picking() override;
virtual void on_unregister_raycasters_for_picking() override;
#else // !ENABLE_RAYCAST_PICKING
void on_render_for_picking() override;
#endif // ENABLE_RAYCAST_PICKING
void on_render_input_window(float x, float y, float bottom_limit) override;
bool on_is_activable() const override { return true; }
bool on_is_selectable() const override { return false; }
void on_set_state() override;
void on_set_hover_id() override{ m_rotate_gizmo.set_hover_id(m_hover_id); }
void on_enable_grabber(unsigned int id) override { m_rotate_gizmo.enable_grabber(); }
void on_disable_grabber(unsigned int id) override { m_rotate_gizmo.disable_grabber(); }
void on_start_dragging() override;
void on_stop_dragging() override;
void on_dragging(const UpdateData &data) 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;
bool wants_enter_leave_snapshots() const override { return true; }
std::string get_gizmo_entering_text() const override { return _u8L("Enter emboss gizmo"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leave emboss gizmo"); }
std::string get_action_snapshot_name() override { return _u8L("Embossing actions"); }
private:
void initialize();
static EmbossStyles create_default_styles();
// localized default text
void set_default_text();
void check_selection();
ModelVolume *get_selected_volume();
// create volume from text - main functionality
bool process();
void close();
void discard_and_close();
void draw_window();
void draw_text_input();
void draw_model_type();
void fix_transformation(const FontProp &from, const FontProp &to);
void draw_style_list();
void draw_delete_style_button();
void draw_style_rename_popup();
void draw_style_rename_button();
void draw_style_save_button(bool is_modified);
void draw_style_save_as_popup();
void draw_style_add_button();
void init_font_name_texture();
struct FaceName;
void draw_font_preview(FaceName &face, bool is_visible);
void draw_font_list();
void draw_style_edit();
bool draw_italic_button();
bool draw_bold_button();
void draw_advanced();
bool select_facename(const wxString& facename);
void init_face_names();
void do_translate(const Vec3d& relative_move);
void do_rotate(float relative_z_angle);
/// <summary>
/// Move window for edit emboss text near to embossed object
/// NOTE: embossed object must be selected
/// </summary>
void set_fine_position();
/// <summary>
/// Reversible input float with option to restor default value
/// TODO: make more general, static and move to ImGuiWrapper
/// </summary>
/// <returns>True when value changed otherwise FALSE.</returns>
bool rev_input(const std::string &name, float &value, const float *default_value,
const std::string &undo_tooltip, float step, float step_fast, const char *format,
ImGuiInputTextFlags flags = 0);
bool rev_checkbox(const std::string &name, bool &value, const bool* default_value, const std::string &undo_tooltip);
bool rev_slider(const std::string &name, std::optional<int>& value, const std::optional<int> *default_value,
const std::string &undo_tooltip, int v_min, int v_max, const std::string &format, const wxString &tooltip);
bool rev_slider(const std::string &name, std::optional<float>& value, const std::optional<float> *default_value,
const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip);
bool rev_slider(const std::string &name, float &value, const float *default_value,
const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip);
template<typename T, typename Draw>
bool revertible(const std::string &name, T &value, const T *default_value, const std::string &undo_tooltip, float undo_offset, Draw draw);
void set_minimal_window_size(bool is_advance_edit_style);
const ImVec2 &get_minimal_window_size() const;
// process mouse event
bool on_mouse_for_rotation(const wxMouseEvent &mouse_event);
bool on_mouse_for_translate(const wxMouseEvent &mouse_event);
bool choose_font_by_wxdialog();
bool choose_true_type_file();
bool choose_svg_file();
bool load_configuration(ModelVolume *volume);
// When open text loaded from .3mf it could be written with unknown font
bool m_is_unknown_font;
void create_notification_not_valid_font(const TextConfiguration& tc);
void remove_notification_not_valid_font();
// This configs holds GUI layout size given by translated texts.
// etc. When language changes, GUI is recreated and this class constructed again,
// so the change takes effect. (info by GLGizmoFdmSupports.hpp)
struct GuiCfg
{
// Zero means it is calculated in init function
ImVec2 minimal_window_size = ImVec2(0, 0);
ImVec2 minimal_window_size_with_advance = ImVec2(0, 0);
ImVec2 minimal_window_size_with_collections = ImVec2(0, 0);
float input_width = 0.f;
float delete_pos_x = 0.f;
float max_style_name_width = 0.f;
unsigned int icon_width = 0;
// maximal width and height of style image
Vec2i max_style_image_size = Vec2i(0, 0);
float style_offset = 0.f;
float input_offset = 0.f;
float advanced_input_offset = 0.f;
ImVec2 text_size;
// maximal size of face name image
Vec2i face_name_size = Vec2i(100, 0);
float face_name_max_width = 100.f;
float face_name_texture_offset_x = 105.f;
// maximal texture generate jobs running at once
unsigned int max_count_opened_font_files = 10;
// Only translations needed for calc GUI size
struct Translations
{
std::string type;
std::string style;
std::string font;
std::string size;
std::string depth;
std::string use_surface;
// advanced
std::string char_gap;
std::string line_gap;
std::string boldness;
std::string italic;
std::string surface_distance;
std::string angle;
std::string collection;
};
Translations translations;
GuiCfg() = default;
};
std::optional<const GuiCfg> m_gui_cfg;
// setted only when wanted to use - not all the time
std::optional<ImVec2> m_set_window_offset;
bool m_is_advanced_edit_style = false;
Emboss::StyleManager m_style_manager;
struct FaceName{
wxString wx_name;
std::string name_truncated = "";
size_t texture_index = 0;
// State for generation of texture
// when start generate create share pointers
std::shared_ptr<std::atomic<bool>> cancel = nullptr;
// R/W only on main thread - finalize of job
std::shared_ptr<bool> is_created = nullptr;
};
// Keep sorted list of loadable face names
struct Facenames
{
// flag to keep need of enumeration fonts from OS
// false .. wants new enumeration check by Hash
// true .. already enumerated(During opened combo box)
bool is_init = false;
// data of can_load() faces
std::vector<FaceName> faces = {};
// Sorter set of Non valid face names in OS
std::vector<wxString> bad = {};
// Configuration of font encoding
const wxFontEncoding encoding = wxFontEncoding::wxFONTENCODING_SYSTEM;
// Identify if preview texture exists
GLuint texture_id = 0;
// protection for open too much font files together
// Gtk:ERROR:../../../../gtk/gtkiconhelper.c:494:ensure_surface_for_gicon: assertion failed (error == NULL): Failed to load /usr/share/icons/Yaru/48x48/status/image-missing.png: Error opening file /usr/share/icons/Yaru/48x48/status/image-missing.png: Too many open files (g-io-error-quark, 31)
unsigned int count_opened_font_files = 0;
// Configuration for texture height
const int count_cached_textures = 32;
// index for new generated texture index(must be lower than count_cached_textures)
size_t texture_index = 0;
// hash created from enumerated font from OS
// check when new font was installed
size_t hash = 0;
} m_face_names;
static bool store(const Facenames &facenames);
static bool load(Facenames &facenames);
// Text to emboss
std::string m_text;
// actual volume
ModelVolume *m_volume;
// state of volume when open EmbossGizmo
struct EmbossVolume
{
TriangleMesh tm;
TextConfiguration tc;
Transform3d tr;
std::string name;
};
std::optional<EmbossVolume> m_unmodified_volume;
// True when m_text contain character unknown by selected font
bool m_text_contain_unknown_glyph = false;
// cancel for previous update of volume to cancel finalize part
std::shared_ptr<std::atomic<bool>> m_update_job_cancel;
// Rotation gizmo
GLGizmoRotate m_rotate_gizmo;
// Value is set only when dragging rotation to calculate actual angle
std::optional<float> m_rotate_start_angle;
// when draging with text object hold screen offset of cursor from object center
std::optional<Vec2d> m_dragging_mouse_offset;
// TODO: it should be accessible by other gizmo too.
// May be move to plater?
RaycastManager m_raycast_manager;
// Only when drag text object it stores world position
std::optional<Transform3d> m_temp_transformation;
// drawing icons
GLTexture m_icons_texture;
void init_icons();
enum class IconType : unsigned {
rename = 0,
erase,
add,
save,
undo,
italic,
unitalic,
bold,
unbold,
system_selector,
open_file,
// VolumeType icons
part,
negative,
modifier,
// automatic calc of icon's count
_count
};
enum class IconState: unsigned { activable = 0, hovered /*1*/, disabled /*2*/};
void draw_icon(IconType icon, IconState state, ImVec2 size = ImVec2(0,0));
void draw_transparent_icon();
bool draw_clickable(IconType icon, IconState state, IconType hover_icon, IconState hover_state);
bool draw_button(IconType icon, bool disable = false);
// only temporary solution
static const std::string M_ICON_FILENAME;
public:
/// <summary>
/// Check if text is last solid part of object
/// TODO: move to emboss gui utils
/// </summary>
/// <param name="text">Model volume of Text</param>
/// <returns>True when object otherwise False</returns>
static bool is_text_object(const ModelVolume *text);
// TODO: move to file utils
static std::string get_file_name(const std::string &file_path);
};
} // namespace Slic3r::GUI
#endif // slic3r_GLGizmoEmboss_hpp_

View file

@ -295,7 +295,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float buttons_width = m_imgui->scaled(0.5f);
const float minimal_slider_width = m_imgui->scaled(4.f);
const float color_button_width = m_imgui->calc_text_size("").x + m_imgui->scaled(1.75f);
const float color_button_width = m_imgui->scaled(1.75f);
const float combo_label_width = std::max(m_imgui->calc_text_size(m_desc.at("first_color")).x,
m_imgui->calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f);

View file

@ -220,8 +220,8 @@ void GLGizmoMove3D::on_render()
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
init_data.color = AXES_COLOR[id];
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
init_data.vertices.reserve(2);
init_data.indices.reserve(2);
// vertices
#if ENABLE_WORLD_COORDINATE
@ -484,4 +484,4 @@ void GLGizmoMove3D::calc_selection_box_and_center()
#endif // ENABLE_WORLD_COORDINATE
} // namespace GUI
} // namespace Slic3r
} // namespace Slic3r

View file

@ -1,88 +1,88 @@
#ifndef slic3r_GLGizmoMove_hpp_
#define slic3r_GLGizmoMove_hpp_
#include "GLGizmoBase.hpp"
namespace Slic3r {
namespace GUI {
#if ENABLE_WORLD_COORDINATE
class Selection;
#endif // ENABLE_WORLD_COORDINATE
class GLGizmoMove3D : public GLGizmoBase
{
static const double Offset;
Vec3d m_displacement{ Vec3d::Zero() };
#if ENABLE_WORLD_COORDINATE
Vec3d m_center{ Vec3d::Zero() };
BoundingBoxf3 m_bounding_box;
#endif // ENABLE_WORLD_COORDINATE
double m_snap_step{ 1.0 };
Vec3d m_starting_drag_position{ Vec3d::Zero() };
Vec3d m_starting_box_center{ Vec3d::Zero() };
Vec3d m_starting_box_bottom_center{ Vec3d::Zero() };
#if ENABLE_LEGACY_OPENGL_REMOVAL
struct GrabberConnection
{
GLModel model;
Vec3d old_center{ Vec3d::Zero() };
};
std::array<GrabberConnection, 3> m_grabber_connections;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
public:
GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoMove3D() = default;
double get_snap_step(double step) const { return m_snap_step; }
void set_snap_step(double step) { m_snap_step = step; }
std::string get_tooltip() const override;
/// <summary>
/// Postpone to Grabber for move
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
/// <summary>
/// Detect reduction of move for wipetover on selection change
/// </summary>
void data_changed() override;
protected:
bool on_init() override;
std::string on_get_name() const override;
bool on_is_activable() const override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_dragging(const UpdateData& data) override;
void on_render() override;
#if ENABLE_RAYCAST_PICKING
virtual void on_register_raycasters_for_picking() override;
virtual void on_unregister_raycasters_for_picking() override;
#else
void on_render_for_picking() override;
#endif // ENABLE_RAYCAST_PICKING
private:
double calc_projection(const UpdateData& data) const;
#if ENABLE_WORLD_COORDINATE
#if ENABLE_LEGACY_OPENGL_REMOVAL
Transform3d local_transform(const Selection& selection) const;
#else
void transform_to_local(const Selection& selection) const;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
void calc_selection_box_and_center();
#endif // ENABLE_WORLD_COORDINATE
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoMove_hpp_
#ifndef slic3r_GLGizmoMove_hpp_
#define slic3r_GLGizmoMove_hpp_
#include "GLGizmoBase.hpp"
namespace Slic3r {
namespace GUI {
#if ENABLE_WORLD_COORDINATE
class Selection;
#endif // ENABLE_WORLD_COORDINATE
class GLGizmoMove3D : public GLGizmoBase
{
static const double Offset;
Vec3d m_displacement{ Vec3d::Zero() };
#if ENABLE_WORLD_COORDINATE
Vec3d m_center{ Vec3d::Zero() };
BoundingBoxf3 m_bounding_box;
#endif // ENABLE_WORLD_COORDINATE
double m_snap_step{ 1.0 };
Vec3d m_starting_drag_position{ Vec3d::Zero() };
Vec3d m_starting_box_center{ Vec3d::Zero() };
Vec3d m_starting_box_bottom_center{ Vec3d::Zero() };
#if ENABLE_LEGACY_OPENGL_REMOVAL
struct GrabberConnection
{
GLModel model;
Vec3d old_center{ Vec3d::Zero() };
};
std::array<GrabberConnection, 3> m_grabber_connections;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
public:
GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoMove3D() = default;
double get_snap_step(double step) const { return m_snap_step; }
void set_snap_step(double step) { m_snap_step = step; }
std::string get_tooltip() const override;
/// <summary>
/// Postpone to Grabber for move
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
/// <summary>
/// Detect reduction of move for wipetover on selection change
/// </summary>
void data_changed() override;
protected:
bool on_init() override;
std::string on_get_name() const override;
bool on_is_activable() const override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_dragging(const UpdateData& data) override;
void on_render() override;
#if ENABLE_RAYCAST_PICKING
virtual void on_register_raycasters_for_picking() override;
virtual void on_unregister_raycasters_for_picking() override;
#else
void on_render_for_picking() override;
#endif // ENABLE_RAYCAST_PICKING
private:
double calc_projection(const UpdateData& data) const;
#if ENABLE_WORLD_COORDINATE
#if ENABLE_LEGACY_OPENGL_REMOVAL
Transform3d local_transform(const Selection& selection) const;
#else
void transform_to_local(const Selection& selection) const;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
void calc_selection_box_and_center();
#endif // ENABLE_WORLD_COORDINATE
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoMove_hpp_

View file

@ -287,7 +287,8 @@ void GLGizmoRotate::on_render_for_picking()
void GLGizmoRotate::init_data_from_selection(const Selection& selection)
{
ECoordinatesType coordinates_type;
if (selection.is_wipe_tower())
if (m_using_local_coordinate ||
selection.is_wipe_tower())
coordinates_type = ECoordinatesType::Local;
else
coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();

View file

@ -57,6 +57,9 @@ private:
float m_old_angle{ 0.0f };
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
// emboss need to draw rotation gizmo in local coordinate systems
bool m_using_local_coordinate{false};
ColorRGBA m_drag_color;
ColorRGBA m_highlight_color;
@ -69,6 +72,9 @@ public:
std::string get_tooltip() const override;
void set_group_id(int group_id) { m_group_id = group_id; }
void set_using_local_coordinate(bool use) { m_using_local_coordinate =use;}
void start_dragging();
void stop_dragging();
@ -230,4 +236,4 @@ private:
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoRotate_hpp_
#endif // slic3r_GLGizmoRotate_hpp_

View file

@ -937,4 +937,4 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const
#endif // ENABLE_WORLD_COORDINATE
} // namespace GUI
} // namespace Slic3r
} // namespace Slic3r

View file

@ -125,4 +125,4 @@ private:
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoScale_hpp_
#endif // slic3r_GLGizmoScale_hpp_

View file

@ -95,10 +95,8 @@ static std::string create_volumes_name(const std::set<ObjectID>& ids, const Sele
return name;
}
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
const std::string &icon_filename,
unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, -1)
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D &parent)
: GLGizmoBase(parent, M_ICON_FILENAME, -1)
, m_show_wireframe(false)
, m_move_to_center(false)
, m_original_triangle_count(0)
@ -588,7 +586,7 @@ void GLGizmoSimplify::on_set_state()
void GLGizmoSimplify::create_gui_cfg() {
if (m_gui_cfg.has_value()) return;
int space_size = m_imgui->calc_text_size(":MM").x;
int space_size = m_imgui->calc_text_size(std::string_view{":MM"}).x;
GuiCfg cfg;
cfg.top_left_width = std::max(m_imgui->calc_text_size(tr_mesh_name).x,
m_imgui->calc_text_size(tr_triangles).x)
@ -844,4 +842,7 @@ void GLGizmoSimplify::Configuration::fix_count_by_ratio(size_t triangle_count)
triangle_count * (100.f - decimate_ratio) / 100.f));
}
// any existing icon filename to not influence GUI
const std::string GLGizmoSimplify::M_ICON_FILENAME = "cut.svg";
} // namespace Slic3r::GUI

View file

@ -20,7 +20,7 @@ class NotificationManager; // for simplify suggestion
class GLGizmoSimplify: public GLGizmoBase
{
public:
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
GLGizmoSimplify(GLCanvas3D& parent);
virtual ~GLGizmoSimplify();
bool on_esc_key_down();
static void add_simplify_suggestion_notification(
@ -155,6 +155,9 @@ private:
return L("Model simplification has been canceled");
}
};
// only temporary solution
static const std::string M_ICON_FILENAME;
};
} // namespace GUI

View file

@ -21,6 +21,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoMeasure.hpp"
#include "libslic3r/format.hpp"
@ -106,8 +107,9 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7));
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8));
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9));
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "cut.svg", 10));
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 11));
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 10));
m_gizmos.emplace_back(new GLGizmoEmboss(m_parent));
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent));
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
@ -1064,9 +1066,12 @@ bool GLGizmosManager::generate_icons_texture()
{
std::string path = resources_dir() + "/icons/";
std::vector<std::string> filenames;
for (size_t idx = 0; idx<m_gizmos.size(); ++idx) {
if (m_gizmos[idx] != nullptr) {
const std::string& icon_filename = m_gizmos[idx]->get_icon_filename();
for (size_t idx=0; idx<m_gizmos.size(); ++idx)
{
auto &gizmo = m_gizmos[idx];
if (gizmo != nullptr)
{
const std::string& icon_filename = gizmo->get_icon_filename();
if (!icon_filename.empty())
filenames.push_back(path + icon_filename);
}

View file

@ -79,8 +79,9 @@ public:
FdmSupports,
Seam,
MmuSegmentation,
Simplify,
Measure,
Emboss,
Simplify,
Undefined
};

View file

@ -38,6 +38,9 @@
#include <nanosvg/nanosvg.h>
#include <nanosvg/nanosvgrast.h>
// suggest location
#include "libslic3r/ClipperUtils.hpp" // Slic3r::intersection
namespace Slic3r {
namespace GUI {
@ -300,10 +303,27 @@ 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(std::string_view text,
bool hide_text_after_double_hash,
float wrap_width)
{
return ImGui::CalcTextSize(text.data(), text.data() + text.length(),
hide_text_after_double_hash, wrap_width);
}
ImVec2 ImGuiWrapper::calc_text_size(const std::string& text,
bool hide_text_after_double_hash,
float wrap_width)
{
return ImGui::CalcTextSize(text.c_str(), NULL, hide_text_after_double_hash, wrap_width);
}
ImVec2 ImGuiWrapper::calc_text_size(const wxString &text,
bool hide_text_after_double_hash,
float wrap_width)
{
auto text_utf8 = into_u8(text);
ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width);
ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, hide_text_after_double_hash, wrap_width);
/*#ifdef __linux__
size.x *= m_style_scaling;
@ -389,6 +409,18 @@ bool ImGuiWrapper::button(const wxString& label, float width, float height)
return ImGui::Button(label_utf8.c_str(), ImVec2(width, height));
}
bool ImGuiWrapper::button(const wxString& label, const ImVec2 &size, bool enable)
{
disabled_begin(!enable);
auto label_utf8 = into_u8(label);
bool res = ImGui::Button(label_utf8.c_str(), size);
disabled_end();
return (enable) ? res : false;
}
bool ImGuiWrapper::radio_button(const wxString &label, bool active)
{
auto label_utf8 = into_u8(label);
@ -442,13 +474,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::text_colored(const ImVec4& color, const char* label)
@ -458,13 +490,13 @@ void ImGuiWrapper::text_colored(const ImVec4& color, const char* label)
void ImGuiWrapper::text_colored(const ImVec4& color, const std::string& label)
{
this->text_colored(color, label.c_str());
ImGuiWrapper::text_colored(color, label.c_str());
}
void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label)
{
auto label_utf8 = into_u8(label);
this->text_colored(color, label_utf8.c_str());
ImGuiWrapper::text_colored(color, label_utf8.c_str());
}
void ImGuiWrapper::text_wrapped(const char *label, float wrap_width)
@ -1152,6 +1184,307 @@ ColorRGBA ImGuiWrapper::from_ImVec4(const ImVec4& color)
return { color.x, color.y, color.z, color.w };
}
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::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
float average_letter_width = calc_text_size(std::string_view("n")).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);
text_width = calc_text_size(result_text).x;
if (text_width < allowed_width) {
// increase letter count
while (count_letter < text.length()) {
++count_letter;
std::string_view 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 std::string(result_text) + 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);
}
ImVec2 ImGuiWrapper::suggest_location(const ImVec2 &dialog_size,
const Slic3r::Polygon &interest,
const ImVec2 &canvas_size)
{
// IMPROVE 1: do not select place over menu
// BoundingBox top_menu;
// GLGizmosManager &gizmo_mng = canvas->get_gizmos_manager();
// BoundingBox side_menu; // gizmo_mng.get_size();
// BoundingBox left_bottom_menu; // is permanent?
// NotificationManager *notify_mng = plater->get_notification_manager();
// BoundingBox notifications; // notify_mng->get_size();
// m_window_width, m_window_height + position
// IMPROVE 2: use polygon of interest not only bounding box
BoundingBox bb(interest.points);
Point center = bb.center(); // interest.centroid();
// area size
Point window_center(canvas_size.x / 2, canvas_size.y / 2);
// mov on side
Point bb_half_size = (bb.max - bb.min) / 2 + Point(1,1);
Point diff_center = window_center - center;
Vec2d diff_norm(diff_center.x() / (double) bb_half_size.x(),
diff_center.y() / (double) bb_half_size.y());
if (diff_norm.x() > 1.) diff_norm.x() = 1.;
if (diff_norm.x() < -1.) diff_norm.x() = -1.;
if (diff_norm.y() > 1.) diff_norm.y() = 1.;
if (diff_norm.y() < -1.) diff_norm.y() = -1.;
Vec2d abs_diff(abs(diff_norm.x()), abs(diff_norm.y()));
if (abs_diff.x() < 1. && abs_diff.y() < 1.) {
if (abs_diff.x() > abs_diff.y())
diff_norm.x() = (diff_norm.x() < 0.) ? (-1.) : 1.;
else
diff_norm.y() = (diff_norm.y() < 0.) ? (-1.) : 1.;
}
Point half_dialog_size(dialog_size.x / 2., dialog_size.y / 2.);
Point move_size = bb_half_size + half_dialog_size;
Point offseted_center = center - half_dialog_size;
Vec2d offset(offseted_center.x() + diff_norm.x() * move_size.x(),
offseted_center.y() + diff_norm.y() * move_size.y());
// move offset close to center
Points window_polygon = {offset.cast<int>(),
Point(offset.x(), offset.y() + dialog_size.y),
Point(offset.x() + dialog_size.x,
offset.y() + dialog_size.y),
Point(offset.x() + dialog_size.x, offset.y())};
// check that position by Bounding box is not intersecting
assert(Slic3r::intersection(interest, Polygon(window_polygon)).empty());
double allowed_space = 10; // in px
double allowed_space_sq = allowed_space * allowed_space;
Vec2d move_vec = (center - (offset.cast<int>() + half_dialog_size))
.cast<double>();
Vec2d result_move(0, 0);
do {
move_vec = move_vec / 2.;
Point move_point = (move_vec + result_move).cast<int>();
Points moved_polygon = window_polygon; // copy
for (Point &p : moved_polygon) p += move_point;
if (Slic3r::intersection(interest, Polygon(moved_polygon)).empty())
result_move += move_vec;
} while (move_vec.squaredNorm() >= allowed_space_sq);
offset += result_move;
return ImVec2(offset.x(), offset.y());
}
void ImGuiWrapper::draw(
const Polygon &polygon,
ImDrawList * draw_list /* = ImGui::GetOverlayDrawList()*/,
ImU32 color /* = ImGui::GetColorU32(COL_ORANGE_LIGHT)*/,
float thickness /* = 3.f*/)
{
// minimal one line consist of 2 points
if (polygon.size() < 2) return;
// need a place to draw
if (draw_list == nullptr) return;
const Point *prev_point = &polygon.points.back();
for (const Point &point : polygon.points) {
ImVec2 p1(prev_point->x(), prev_point->y());
ImVec2 p2(point.x(), point.y());
draw_list->AddLine(p1, p2, color, thickness);
prev_point = &point;
}
}
bool ImGuiWrapper::contain_all_glyphs(const ImFont *font,
const std::string &text)
{
if (font == nullptr) return false;
if (!font->IsLoaded()) return false;
const ImFontConfig *fc = font->ConfigData;
if (fc == nullptr) return false;
if (text.empty()) return true;
return is_chars_in_ranges(fc->GlyphRanges, text.c_str());
}
bool ImGuiWrapper::is_char_in_ranges(const ImWchar *ranges,
unsigned int letter)
{
for (const ImWchar *range = ranges; range[0] && range[1]; range += 2) {
ImWchar from = range[0];
ImWchar to = range[1];
if (from <= letter && letter <= to) return true;
if (letter < to) return false; // ranges should be sorted
}
return false;
};
bool ImGuiWrapper::is_chars_in_ranges(const ImWchar *ranges,
const char *chars_ptr)
{
while (*chars_ptr) {
unsigned int c = 0;
// UTF-8 to 32-bit character need imgui_internal
int c_len = ImTextCharFromUtf8(&c, chars_ptr, NULL);
chars_ptr += c_len;
if (c_len == 0) break;
if (!is_char_in_ranges(ranges, c)) return false;
}
return true;
}
#ifdef __APPLE__
static const ImWchar ranges_keyboard_shortcuts[] =
{
@ -1206,7 +1539,7 @@ void ImGuiWrapper::init_font(bool compress)
// Create ranges of characters from m_glyph_ranges, possibly adding some OS specific special characters.
ImVector<ImWchar> ranges;
ImFontAtlas::GlyphRangesBuilder builder;
ImFontGlyphRangesBuilder builder;
builder.AddRanges(m_glyph_ranges);
if (m_font_cjk) {

View file

@ -2,6 +2,7 @@
#define slic3r_ImGuiWrapper_hpp_
#include <string>
#include <string_view>
#include <map>
#include <imgui/imgui.h>
@ -10,6 +11,7 @@
#include "libslic3r/Point.hpp"
#include "libslic3r/Color.hpp"
#include "libslic3r/Polygon.hpp"
namespace Slic3r {
namespace Search {
@ -52,8 +54,6 @@ public:
ImGuiWrapper();
~ImGuiWrapper();
void read_glsl_version();
void set_language(const std::string &language);
void set_display_size(float w, float h);
void set_scaling(float font_size, float scale_style, float scale_both);
@ -62,13 +62,19 @@ public:
float get_font_size() const { return m_font_size; }
float get_style_scaling() const { return m_style_scaling; }
const ImWchar *get_glyph_ranges() const { return m_glyph_ranges; } // language specific
void new_frame();
void render();
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;
/// <summary>
/// Extend ImGui::CalcTextSize to use string_view
/// </summary>
static ImVec2 calc_text_size(std::string_view text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f);
static ImVec2 calc_text_size(const std::string& text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f);
static ImVec2 calc_text_size(const wxString &text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f);
ImVec2 calc_button_size(const wxString &text, const ImVec2 &button_size = ImVec2(0, 0)) const;
ImVec2 get_item_spacing() const;
@ -87,15 +93,16 @@ public:
bool button(const wxString &label);
bool button(const wxString& label, float width, float height);
bool button(const wxString& label, const ImVec2 &size, bool enable); // default size = ImVec2(0.f, 0.f)
bool radio_button(const wxString &label, bool active);
bool draw_radio_button(const std::string& name, float size, bool active, std::function<void(ImGuiWindow& window, const ImVec2& pos, float size)> draw_callback);
bool checkbox(const wxString &label, bool &value);
void text(const char *label);
void text(const std::string &label);
void text(const wxString &label);
void text_colored(const ImVec4& color, const char* label);
void text_colored(const ImVec4& color, const std::string& label);
void text_colored(const ImVec4& color, const wxString& label);
static void text(const char *label);
static void text(const std::string &label);
static void text(const wxString &label);
static void text_colored(const ImVec4& color, const char* label);
static void text_colored(const ImVec4& color, const std::string& label);
static void text_colored(const ImVec4& color, const wxString& label);
void text_wrapped(const char *label, float wrap_width);
void text_wrapped(const std::string &label, float wrap_width);
void text_wrapped(const wxString &label, float wrap_width);
@ -126,6 +133,77 @@ 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>
/// 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);
/// <summary>
/// Suggest loacation of dialog window,
/// dependent on actual visible thing on platter
/// like Gizmo menu size, notifications, ...
/// To be near of polygon interest and not over it.
/// And also not out of visible area.
/// </summary>
/// <param name="dialog_size">Define width and height of diaog window</param>
/// <param name="interest">Area of interest. Result should be close to it</param>
/// <param name="canvas_size">Available space a.k.a GLCanvas3D::get_current_canvas3D()</param>
/// <returns>Suggestion for dialog offest</returns>
static ImVec2 suggest_location(const ImVec2 &dialog_size,
const Slic3r::Polygon &interest,
const ImVec2 &canvas_size);
/// <summary>
/// Visualization of polygon
/// </summary>
/// <param name="polygon">Define what to draw</param>
/// <param name="draw_list">Define where to draw it</param>
/// <param name="color">Color of polygon</param>
/// <param name="thickness">Width of polygon line</param>
static void draw(const Polygon &polygon,
ImDrawList * draw_list = ImGui::GetOverlayDrawList(),
ImU32 color = ImGui::GetColorU32(COL_ORANGE_LIGHT),
float thickness = 3.f);
/// <summary>
/// Check that font ranges contain all chars in string
/// (rendered Unicodes are stored in GlyphRanges)
/// </summary>
/// <param name="font">Contain glyph ranges</param>
/// <param name="text">Vector of character to check</param>
/// <returns>True when all glyphs from text are in font ranges</returns>
static bool contain_all_glyphs(const ImFont *font, const std::string &text);
static bool is_chars_in_ranges(const ImWchar *ranges, const char *chars_ptr);
static bool is_char_in_ranges(const ImWchar *ranges, unsigned int letter);
bool requires_extra_frame() const { return m_requires_extra_frame; }
void set_requires_extra_frame() { m_requires_extra_frame = true; }
void reset_requires_extra_frame() { m_requires_extra_frame = false; }

View file

@ -0,0 +1,144 @@
#include "CreateFontNameImageJob.hpp"
#include "libslic3r/Emboss.hpp"
// rasterization of ExPoly
#include "libslic3r/SLA/AGGRaster.hpp"
#include "slic3r/Utils/WxFontUtils.hpp"
#include "slic3r/GUI/3DScene.hpp" // ::glsafe
// ability to request new frame after finish rendering
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "wx/fontenum.h"
#include <boost/log/trivial.hpp>
using namespace Slic3r;
using namespace Slic3r::GUI;
CreateFontImageJob::CreateFontImageJob(FontImageData &&input)
: m_input(std::move(input))
{
assert(!m_input.text.empty());
assert(wxFontEnumerator::IsValidFacename(m_input.font_name));
assert(m_input.gray_level > 0 && m_input.gray_level < 255);
assert(m_input.texture_id != 0);
}
void CreateFontImageJob::process(Ctl &ctl)
{
if (!wxFontEnumerator::IsValidFacename(m_input.font_name)) return;
// Select font
wxFont wx_font(
wxFontInfo().FaceName(m_input.font_name).Encoding(m_input.encoding));
if (!wx_font.IsOk()) return;
std::unique_ptr<Emboss::FontFile> font_file =
WxFontUtils::create_font_file(wx_font);
if (font_file == nullptr) return;
Emboss::FontFileWithCache font_file_with_cache(std::move(font_file));
FontProp fp;
// use only first line of text
std::string text = m_input.text;
size_t enter_pos = text.find('\n');
if (enter_pos < text.size()) {
// text start with enter
if (enter_pos == 0) return;
// exist enter, soo delete all after enter
text = text.substr(0, enter_pos);
}
std::function<bool()> was_canceled = [&ctl, cancel = m_input.cancel]() -> bool {
if (ctl.was_canceled()) return true;
if (cancel->load()) return true;
return false;
};
ExPolygons shapes = Emboss::text2shapes(font_file_with_cache, text.c_str(), fp, was_canceled);
// normalize height of font
BoundingBox bounding_box;
for (ExPolygon &shape : shapes)
bounding_box.merge(BoundingBox(shape.contour.points));
if (bounding_box.size().x() < 1 || bounding_box.size().y() < 1) return;
double scale = m_input.size.y() / (double) bounding_box.size().y();
BoundingBoxf bb2(bounding_box.min.cast<double>(),
bounding_box.max.cast<double>());
bb2.scale(scale);
Vec2d size_f = bb2.size();
m_tex_size = Point(std::ceil(size_f.x()), std::ceil(size_f.y()));
// crop image width
if (m_tex_size.x() > m_input.size.x()) m_tex_size.x() = m_input.size.x();
if (m_tex_size.y() > m_input.size.y()) m_tex_size.y() = m_input.size.y();
// Set up result
m_result = std::vector<unsigned char>(m_tex_size.x() * m_tex_size.y() * 4, {255});
sla::Resolution resolution(m_tex_size.x(), m_tex_size.y());
double pixel_dim = SCALING_FACTOR / scale;
sla::PixelDim dim(pixel_dim, pixel_dim);
double gamma = 1.;
std::unique_ptr<sla::RasterBase> r =
sla::create_raster_grayscale_aa(resolution, dim, gamma);
for (ExPolygon &shape : shapes) shape.translate(-bounding_box.min);
for (const ExPolygon &shape : shapes) r->draw(shape);
// copy rastered data to pixels
sla::RasterEncoder encoder =
[&pix = m_result, w = m_tex_size.x(), h = m_tex_size.y(),
gray_level = m_input.gray_level]
(const void *ptr, size_t width, size_t height, size_t num_components) {
size_t size {static_cast<size_t>(w*h)};
const unsigned char *ptr2 = (const unsigned char *) ptr;
for (size_t x = 0; x < width; ++x)
for (size_t y = 0; y < height; ++y) {
size_t index = y*w + x;
assert(index < size);
if (index >= size) continue;
pix[3+4*index] = ptr2[y * width + x] / gray_level;
}
return sla::EncodedRaster();
};
r->encode(encoder);
}
void CreateFontImageJob::finalize(bool canceled, std::exception_ptr &)
{
if (m_input.count_opened_font_files)
--(*m_input.count_opened_font_files);
if (canceled || m_input.cancel->load()) return;
*m_input.is_created = true;
// Exist result bitmap with preview?
// (not valid input. e.g. not loadable font)
if (m_result.empty()) {
// TODO: write text cannot load into texture
m_result = std::vector<unsigned char>(m_tex_size.x() * m_tex_size.y() * 4, {255});
}
// upload texture on GPU
const GLenum target = GL_TEXTURE_2D;
glsafe(::glBindTexture(target, m_input.texture_id));
GLint
w = m_tex_size.x(), h = m_tex_size.y(),
xoffset = m_input.size.x() - m_tex_size.x(), // arrange right
yoffset = m_input.size.y() * m_input.index;
glsafe(::glTexSubImage2D(target, m_input.level, xoffset, yoffset, w, h,
m_input.format, m_input.type, m_result.data()));
// bind default texture
GLuint no_texture_id = 0;
glsafe(::glBindTexture(target, no_texture_id));
// show rendered texture
wxGetApp().plater()->canvas3D()->schedule_extra_frame(0);
BOOST_LOG_TRIVIAL(info)
<< "Generate Preview font('" << m_input.font_name << "' id:" << m_input.index << ") "
<< "with text: '" << m_input.text << "' "
<< "texture_size " << m_input.size.x() << " x " << m_input.size.y();
}

View file

@ -0,0 +1,75 @@
#ifndef slic3r_CreateFontNameImageJob_hpp_
#define slic3r_CreateFontNameImageJob_hpp_
#include <vector>
#include <string>
#include <GL/glew.h>
#include <wx/string.h>
#include <wx/fontenc.h>
#include "Job.hpp"
#include "libslic3r/Point.hpp" // Vec2i
namespace Slic3r::GUI {
/// <summary>
/// Keep data for rasterization of text by font face
/// </summary>
struct FontImageData
{
// Text to rasterize
std::string text;
// Define font face
wxString font_name;
wxFontEncoding encoding;
// texture for copy result to
// texture MUST BE initialized
GLuint texture_id;
// Index of face name, define place in texture
size_t index;
// Height of each text
// And Limit for width
Vec2i size; // in px
// bigger value create darker image
// divide value 255
unsigned char gray_level = 5;
// texture meta data
GLenum format = GL_ALPHA, type = GL_UNSIGNED_BYTE;
GLint level = 0;
// prevent opening too much files
// it is decreased in finalize phase
unsigned int *count_opened_font_files = nullptr;
std::shared_ptr<std::atomic<bool>> cancel = nullptr;
std::shared_ptr<bool> is_created = nullptr;
};
/// <summary>
/// Create image for face name
/// </summary>
class CreateFontImageJob : public Job
{
FontImageData m_input;
std::vector<unsigned char> m_result;
Point m_tex_size;
public:
CreateFontImageJob(FontImageData &&input);
/// <summary>
/// Rasterize text into image (result)
/// </summary>
/// <param name="ctl">Check for cancelation</param>
void process(Ctl &ctl) override;
/// <summary>
/// Copy image data into OpenGL texture
/// </summary>
/// <param name="canceled"></param>
/// <param name=""></param>
void finalize(bool canceled, std::exception_ptr &) override;
};
} // namespace Slic3r::GUI
#endif // slic3r_CreateFontNameImageJob_hpp_

View file

@ -0,0 +1,164 @@
#include "CreateFontStyleImagesJob.hpp"
// rasterization of ExPoly
#include "libslic3r/SLA/AGGRaster.hpp"
// for get DPI
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/3DScene.hpp" // ::glsafe
// ability to request new frame after finish rendering
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
using namespace Slic3r;
using namespace Slic3r::Emboss;
using namespace Slic3r::GUI;
using namespace Slic3r::GUI::Emboss;
CreateFontStyleImagesJob::CreateFontStyleImagesJob(
StyleManager::StyleImagesData &&input)
: m_input(std::move(input))
{
assert(m_input.result != nullptr);
assert(!m_input.styles.empty());
assert(!m_input.text.empty());
assert(m_input.max_size.x() > 1);
assert(m_input.max_size.y() > 1);
}
void CreateFontStyleImagesJob::process(Ctl &ctl)
{
// create shapes and calc size (bounding boxes)
std::vector<ExPolygons> name_shapes(m_input.styles.size());
std::vector<double> scales(m_input.styles.size());
images = std::vector<StyleManager::StyleImage>(m_input.styles.size());
for (auto &item : m_input.styles) {
size_t index = &item - &m_input.styles.front();
ExPolygons &shapes = name_shapes[index];
shapes = text2shapes(item.font, m_input.text.c_str(), item.prop);
// create image description
StyleManager::StyleImage &image = images[index];
BoundingBox &bounding_box = image.bounding_box;
for (ExPolygon &shape : shapes)
bounding_box.merge(BoundingBox(shape.contour.points));
for (ExPolygon &shape : shapes) shape.translate(-bounding_box.min);
// calculate conversion from FontPoint to screen pixels by size of font
auto mf = wxGetApp().mainframe;
// dot per inch for monitor
int dpi = get_dpi_for_window(mf);
double ppm = dpi / 25.4; // pixel per milimeter
const auto &cn = item.prop.collection_number;
unsigned int font_index = (cn.has_value()) ? *cn : 0;
double unit_per_em = item.font.font_file->infos[font_index].unit_per_em;
double scale = item.prop.size_in_mm / unit_per_em * SHAPE_SCALE * ppm;
scales[index] = scale;
//double scale = font_prop.size_in_mm * SCALING_FACTOR;
BoundingBoxf bb2(bounding_box.min.cast<double>(),
bounding_box.max.cast<double>());
bb2.scale(scale);
image.tex_size.x = std::ceil(bb2.max.x() - bb2.min.x());
image.tex_size.y = std::ceil(bb2.max.y() - bb2.min.y());
// crop image width
if (image.tex_size.x > m_input.max_size.x())
image.tex_size.x = m_input.max_size.x();
// crop image height
if (image.tex_size.y > m_input.max_size.y())
image.tex_size.y = m_input.max_size.y();
}
// arrange bounding boxes
int offset_y = 0;
width = 0;
for (StyleManager::StyleImage &image : images) {
image.offset.y() = offset_y;
offset_y += image.tex_size.y+1;
if (width < image.tex_size.x)
width = image.tex_size.x;
}
height = offset_y;
for (StyleManager::StyleImage &image : images) {
const Point &o = image.offset;
const ImVec2 &s = image.tex_size;
image.uv0 = ImVec2(o.x() / (double) width,
o.y() / (double) height);
image.uv1 = ImVec2((o.x() + s.x) / (double) width,
(o.y() + s.y) / (double) height);
}
// Set up result
pixels = std::vector<unsigned char>(4*width * height, {255});
// upload sub textures
for (StyleManager::StyleImage &image : images) {
sla::Resolution resolution(image.tex_size.x, image.tex_size.y);
size_t index = &image - &images.front();
double pixel_dim = SCALING_FACTOR / scales[index];
sla::PixelDim dim(pixel_dim, pixel_dim);
double gamma = 1.;
std::unique_ptr<sla::RasterBase> r =
sla::create_raster_grayscale_aa(resolution, dim, gamma);
for (const ExPolygon &shape : name_shapes[index]) r->draw(shape);
// copy rastered data to pixels
sla::RasterEncoder encoder = [&offset = image.offset, &pix = pixels, w=width,h=height]
(const void *ptr, size_t width, size_t height, size_t num_components) {
// bigger value create darker image
unsigned char gray_level = 5;
size_t size {static_cast<size_t>(w*h)};
assert((offset.x() + width) <= (size_t)w);
assert((offset.y() + height) <= (size_t)h);
const unsigned char *ptr2 = (const unsigned char *) ptr;
for (size_t x = 0; x < width; ++x)
for (size_t y = 0; y < height; ++y) {
size_t index = (offset.y() + y)*w + offset.x() + x;
assert(index < size);
if (index >= size) continue;
pix[4*index+3] = ptr2[y * width + x] / gray_level;
}
return sla::EncodedRaster();
};
r->encode(encoder);
}
}
void CreateFontStyleImagesJob::finalize(bool canceled, std::exception_ptr &)
{
if (canceled) return;
// upload texture on GPU
GLuint tex_id;
GLenum target = GL_TEXTURE_2D, format = GL_RGBA, type = GL_UNSIGNED_BYTE;
GLint level = 0, border = 0;
glsafe(::glGenTextures(1, &tex_id));
glsafe(::glBindTexture(target, tex_id));
glsafe(::glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(::glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
GLint w = width, h=height;
glsafe(::glTexImage2D(target, level, GL_RGBA, w, h, border, format, type,
(const void *) pixels.data()));
// set up texture id
void *texture_id = (void *) (intptr_t) tex_id;
for (StyleManager::StyleImage &image : images)
image.texture_id = texture_id;
// move to result
m_input.result->styles = std::move(m_input.styles);
m_input.result->images = std::move(images);
// bind default texture
GLuint no_texture_id = 0;
glsafe(::glBindTexture(target, no_texture_id));
// show rendered texture
wxGetApp().plater()->canvas3D()->schedule_extra_frame(0);
}

View file

@ -0,0 +1,36 @@
#ifndef slic3r_CreateFontStyleImagesJob_hpp_
#define slic3r_CreateFontStyleImagesJob_hpp_
#include <vector>
#include <string>
#include <libslic3r/Emboss.hpp>
#include "slic3r/Utils/EmbossStyleManager.hpp"
#include "Job.hpp"
namespace Slic3r::GUI::Emboss {
/// <summary>
/// Create texture with name of styles written by its style
/// NOTE: Access to glyph cache is possible only from job
/// </summary>
class CreateFontStyleImagesJob : public Job
{
StyleManager::StyleImagesData m_input;
// Output data
// texture size
int width, height;
// texture data
std::vector<unsigned char> pixels;
// descriptors of sub textures
std::vector<StyleManager::StyleImage> images;
public:
CreateFontStyleImagesJob(StyleManager::StyleImagesData &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &) override;
};
} // namespace Slic3r::GUI
#endif // slic3r_CreateFontStyleImagesJob_hpp_

View file

@ -0,0 +1,791 @@
#include "EmbossJob.hpp"
#include <stdexcept>
#include <libslic3r/Model.hpp>
#include <libslic3r/Format/OBJ.hpp> // load_obj for default mesh
#include <libslic3r/CutSurface.hpp> // use surface cuts
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp"
#include "slic3r/GUI/CameraUtils.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
using namespace Slic3r;
using namespace Slic3r::Emboss;
using namespace Slic3r::GUI;
using namespace Slic3r::GUI::Emboss;
// private namespace
namespace priv{
// create sure that emboss object is bigger than source object [in mm]
constexpr float safe_extension = 1.0f;
/// <summary>
/// Assert check of inputs data
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
bool check(const DataBase &input, bool check_fontfile = true, bool use_surface = false);
bool check(const DataCreateVolume &input, bool is_main_thread = false);
bool check(const DataCreateObject &input);
bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false);
bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false);
bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false);
// <summary>
/// Try to create mesh from text
/// </summary>
/// <param name="input">Text to convert on mesh
/// + Shape of characters + Property of font</param>
/// <param name="font">Font file with cache
/// NOTE: Cache glyphs is changed</param>
/// <param name="was_canceled">To check if process was canceled</param>
/// <returns>Triangle mesh model</returns>
template<typename Fnc> static TriangleMesh try_create_mesh(const DataBase &input, FontFileWithCache &font, Fnc was_canceled);
template<typename Fnc> static TriangleMesh create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl &ctl);
/// <summary>
/// Create default mesh for embossed text
/// </summary>
/// <returns>Not empty model(index trinagle set - its)</returns>
static TriangleMesh create_default_mesh();
/// <summary>
/// Must be called on main thread
/// </summary>
/// <param name="mesh">New mesh data</param>
/// <param name="data">Text configuration, ...</param>
static void update_volume(TriangleMesh &&mesh, const DataUpdate &data);
/// <summary>
/// Add new volume to object
/// </summary>
/// <param name="mesh">triangles of new volume</param>
/// <param name="object_id">Object where to add volume</param>
/// <param name="type">Type of new volume</param>
/// <param name="trmat">Transformation of volume inside of object</param>
/// <param name="data">Text configuration and New VolumeName</param>
static void create_volume(TriangleMesh &&mesh, const ObjectID& object_id,
const ModelVolumeType type, const Transform3d trmat, const DataBase &data);
/// <summary>
/// Select Volume from objects
/// </summary>
/// <param name="objects">All objects in scene</param>
/// <param name="volume_id">Identifier of volume in object</param>
/// <returns>Pointer to volume when exist otherwise nullptr</returns>
static ModelVolume *get_volume(ModelObjectPtrs &objects, const ObjectID &volume_id);
/// <summary>
/// Create projection for cut surface from mesh
/// </summary>
/// <param name="tr">Volume transformation in object</param>
/// <param name="shape_scale">Convert shape to milimeters</param>
/// <param name="z_range">Bounding box 3d of model volume for projection ranges</param>
/// <returns>Orthogonal cut_projection</returns>
static OrthoProject create_projection_for_cut(Transform3d tr, double shape_scale, const std::pair<float, float> &z_range);
/// <summary>
/// Create tranformation for emboss Cutted surface
/// </summary>
/// <param name="is_outside">True .. raise, False .. engrave</param>
/// <param name="emboss">Depth of embossing</param>
/// <param name="tr">Text voliume transformation inside object</param>
/// <param name="cut">Cutted surface from model</param>
/// <returns>Projection</returns>
static OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut);
/// <summary>
/// Cut surface into triangle mesh
/// </summary>
/// <param name="input1">(can't be const - cache of font)</param>
/// <param name="input2">SurfaceVolume data</param>
/// <param name="was_canceled">Check to interupt execution</param>
/// <returns>Extruded object from cuted surace</returns>
static TriangleMesh cut_surface(/*const*/ DataBase &input1, const SurfaceVolumeData &input2, std::function<bool()> was_canceled);
static void create_message(const std::string &message); // only in finalize
static bool process(std::exception_ptr &eptr);
class JobException : public std::runtime_error {
public: JobException(const char* message):runtime_error(message){}};
}// namespace priv
/////////////////
/// Create Volume
CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input)
: m_input(std::move(input))
{
assert(priv::check(m_input, true));
}
void CreateVolumeJob::process(Ctl &ctl) {
if (!priv::check(m_input)) throw std::runtime_error("Bad input data for EmbossCreateVolumeJob.");
auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); };
m_result = priv::create_mesh(m_input, was_canceled, ctl);
// center result
Vec3f c = m_result.bounding_box().center().cast<float>();
if (!c.isApprox(Vec3f::Zero())) m_result.translate(-c);
}
void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
// doesn't care about exception when process was canceled by user
if (canceled) {
eptr = nullptr;
return;
}
if (priv::process(eptr)) return;
if (m_result.its.empty())
return priv::create_message(_u8L("Can't create empty volume."));
priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, m_input);
}
/////////////////
/// Create Object
CreateObjectJob::CreateObjectJob(DataCreateObject &&input)
: m_input(std::move(input))
{
assert(priv::check(m_input));
}
void CreateObjectJob::process(Ctl &ctl)
{
if (!priv::check(m_input))
throw std::runtime_error("Bad input data for EmbossCreateObjectJob.");
auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); };
m_result = priv::create_mesh(m_input, was_canceled, ctl);
if (was_canceled()) return;
// Create new object
// calculate X,Y offset position for lay on platter in place of
// mouse click
Vec2d bed_coor = CameraUtils::get_z0_position(
m_input.camera, m_input.screen_coor);
// check point is on build plate:
Points bed_shape_;
bed_shape_.reserve(m_input.bed_shape.size());
for (const Vec2d &p : m_input.bed_shape)
bed_shape_.emplace_back(p.cast<int>());
Polygon bed(bed_shape_);
if (!bed.contains(bed_coor.cast<int>()))
// mouse pose is out of build plate so create object in center of plate
bed_coor = bed.centroid().cast<double>();
double z = m_input.text_configuration.style.prop.emboss / 2;
Vec3d offset(bed_coor.x(), bed_coor.y(), z);
offset -= m_result.center();
Transform3d::TranslationType tt(offset.x(), offset.y(), offset.z());
m_transformation = Transform3d(tt);
}
void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr)
{
// doesn't care about exception when process was canceled by user
if (canceled) {
eptr = nullptr;
return;
}
if (priv::process(eptr)) return;
// only for sure
if (m_result.empty())
return priv::create_message(_u8L("Can't create empty object."));
GUI_App &app = wxGetApp();
Plater *plater = app.plater();
ObjectList *obj_list = app.obj_list();
GLCanvas3D *canvas = plater->canvas3D();
plater->take_snapshot(_L("Add Emboss text object"));
// Create new object and change selection
bool center = false;
obj_list->load_mesh_object(std::move(m_result), m_input.volume_name,
center, &m_input.text_configuration,
&m_transformation);
// When add new object selection is empty.
// When cursor move and no one object is selected than
// Manager::reset_all() So Gizmo could be closed before end of creation object
GLGizmosManager &manager = canvas->get_gizmos_manager();
if (manager.get_current_type() != GLGizmosManager::Emboss)
manager.open_gizmo(GLGizmosManager::Emboss);
// redraw scene
canvas->reload_scene(true);
}
/////////////////
/// Update Volume
UpdateJob::UpdateJob(DataUpdate&& input)
: m_input(std::move(input))
{
assert(priv::check(m_input, true));
}
void UpdateJob::process(Ctl &ctl)
{
if (!priv::check(m_input))
throw std::runtime_error("Bad input data for EmbossUpdateJob.");
auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool {
if (cancel->load()) return true;
return ctl.was_canceled();
};
m_result = priv::try_create_mesh(m_input, m_input.font_file, was_canceled);
if (was_canceled()) return;
if (m_result.its.empty())
throw priv::JobException(_u8L("Created text volume is empty. Change text or font.").c_str());
// center triangle mesh
Vec3d shift = m_result.bounding_box().center();
m_result.translate(-shift.cast<float>());
}
void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr)
{
// doesn't care about exception when process was canceled by user
if (canceled || m_input.cancel->load()) {
eptr = nullptr;
return;
}
if (priv::process(eptr)) return;
priv::update_volume(std::move(m_result), m_input);
}
namespace Slic3r::GUI::Emboss {
SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id)
{
SurfaceVolumeData::ModelSources result;
result.reserve(volumes.size() - 1);
for (const ModelVolume *v : volumes) {
if (text_volume_id.has_value() && v->id().id == *text_volume_id) continue;
// skip modifiers and negative volumes, ...
if (!v->is_model_part()) continue;
const TriangleMesh &tm = v->mesh();
if (tm.empty()) continue;
if (tm.its.empty()) continue;
result.push_back({v->get_mesh_shared_ptr(), v->get_matrix()});
}
return result;
}
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume)
{
if (text_volume == nullptr) return {};
if (!text_volume->text_configuration.has_value()) return {};
const ModelVolumePtrs &volumes = text_volume->get_object()->volumes;
// no other volume in object
if (volumes.size() <= 1) return {};
return create_sources(volumes, text_volume->id().id);
}
} // namespace Slic3r::GUI::Emboss
/////////////////
/// Create Surface volume
CreateSurfaceVolumeJob::CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input)
: m_input(std::move(input))
{
assert(priv::check(m_input, true));
}
void CreateSurfaceVolumeJob::process(Ctl &ctl) {
if (!priv::check(m_input))
throw std::runtime_error("Bad input data for CreateSurfaceVolumeJob.");
// check cancelation of process
auto was_canceled = [&ctl]() -> bool { return ctl.was_canceled(); };
m_result = priv::cut_surface(m_input, m_input, was_canceled);
}
void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
// doesn't care about exception when process was canceled by user
if (canceled) return;
if (priv::process(eptr)) return;
// TODO: Find better way to Not center volume data when add !!!
TriangleMesh mesh = m_result; // Part1: copy
priv::create_volume(std::move(m_result), m_input.object_id,
m_input.volume_type, m_input.text_tr, m_input);
// Part2: update volume data
//auto vol = wxGetApp().plater()->model().objects[m_input.object_idx]->volumes.back();
//UpdateJob::update_volume(vol, std::move(mesh), m_input.text_configuration, m_input.volume_name);
}
/////////////////
/// Cut Surface
UpdateSurfaceVolumeJob::UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input)
: m_input(std::move(input))
{
assert(priv::check(m_input, true));
}
void UpdateSurfaceVolumeJob::process(Ctl &ctl)
{
if (!priv::check(m_input))
throw std::runtime_error("Bad input data for UseSurfaceJob.");
// check cancelation of process
auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool {
if (cancel->load()) return true;
return ctl.was_canceled();
};
m_result = priv::cut_surface(m_input, m_input, was_canceled);
}
void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr)
{
// doesn't care about exception when process was canceled by user
if (m_input.cancel->load()) {
eptr = nullptr;
return;
}
if (canceled) return;
if (priv::process(eptr)) return;
priv::update_volume(std::move(m_result), m_input);
}
////////////////////////////
/// private namespace implementation
bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface)
{
bool res = true;
if (check_fontfile) {
assert(input.font_file.has_value());
res &= input.font_file.has_value();
}
assert(!input.text_configuration.fix_3mf_tr.has_value());
res &= !input.text_configuration.fix_3mf_tr.has_value();
assert(!input.text_configuration.text.empty());
res &= !input.text_configuration.text.empty();
assert(!input.volume_name.empty());
res &= !input.volume_name.empty();
assert(input.text_configuration.style.prop.use_surface == use_surface);
res &= input.text_configuration.style.prop.use_surface == use_surface;
return res;
}
bool priv::check(const DataCreateVolume &input, bool is_main_thread) {
bool check_fontfile = false;
bool res = check((DataBase) input, check_fontfile);
assert(input.volume_type != ModelVolumeType::INVALID);
res &= input.volume_type != ModelVolumeType::INVALID;
assert(input.object_id.id >= 0);
res &= input.object_id.id >= 0;
return res;
}
bool priv::check(const DataCreateObject &input) {
bool check_fontfile = false;
bool res = check((DataBase) input, check_fontfile);
assert(input.screen_coor.x() >= 0.);
res &= input.screen_coor.x() >= 0.;
assert(input.screen_coor.y() >= 0.);
res &= input.screen_coor.y() >= 0.;
assert(input.bed_shape.size() >= 3); // at least triangle
res &= input.bed_shape.size() >= 3;
return res;
}
bool priv::check(const DataUpdate &input, bool is_main_thread, bool use_surface){
bool check_fontfile = true;
bool res = check((DataBase) input, check_fontfile, use_surface);
assert(input.volume_id.id >= 0);
res &= input.volume_id.id >= 0;
if (is_main_thread)
assert(get_volume(wxGetApp().model().objects, input.volume_id) != nullptr);
assert(input.cancel != nullptr);
res &= input.cancel != nullptr;
if (is_main_thread)
assert(!input.cancel->load());
return res;
}
bool priv::check(const CreateSurfaceVolumeData &input, bool is_main_thread)
{
bool use_surface = true;
bool res = check((DataBase)input, is_main_thread, use_surface);
assert(!input.sources.empty());
res &= !input.sources.empty();
return res;
}
bool priv::check(const UpdateSurfaceVolumeData &input, bool is_main_thread){
bool use_surface = true;
bool res = check((DataUpdate)input, is_main_thread, use_surface);
assert(!input.sources.empty());
res &= !input.sources.empty();
return res;
}
template<typename Fnc>
TriangleMesh priv::try_create_mesh(const DataBase &input, FontFileWithCache &font, Fnc was_canceled)
{
const TextConfiguration &tc = input.text_configuration;
const char *text = tc.text.c_str();
const FontProp &prop = tc.style.prop;
assert(font.has_value());
if (!font.has_value()) return {};
ExPolygons shapes = text2shapes(font, text, prop, was_canceled);
if (shapes.empty()) return {};
if (was_canceled()) return {};
const auto &cn = prop.collection_number;
unsigned int font_index = (cn.has_value()) ? *cn : 0;
assert(font_index < font.font_file->infos.size());
int unit_per_em = font.font_file->infos[font_index].unit_per_em;
float scale = prop.size_in_mm / unit_per_em;
float depth = prop.emboss / scale;
auto projectZ = std::make_unique<ProjectZ>(depth);
ProjectScale project(std::move(projectZ), scale);
if (was_canceled()) return {};
return TriangleMesh(polygons2model(shapes, project));
}
template<typename Fnc>
TriangleMesh priv::create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl& ctl)
{
// It is neccessary to create some shape
// Emboss text window is opened by creation new emboss text object
TriangleMesh result;
if (input.font_file.has_value()) {
result = try_create_mesh(input, input.font_file, was_canceled);
if (was_canceled()) return {};
}
if (result.its.empty()) {
result = priv::create_default_mesh();
if (was_canceled()) return {};
// only info
ctl.call_on_main_thread([]() {
create_message(_u8L("It is used default volume for embossed "
"text, try to change text or font for fix it."));
});
}
assert(!result.its.empty());
return result;
}
TriangleMesh priv::create_default_mesh()
{
// When cant load any font use default object loaded from file
std::string path = Slic3r::resources_dir() + "/data/embossed_text.stl";
TriangleMesh triangle_mesh;
if (!load_obj(path.c_str(), &triangle_mesh)) {
// when can't load mesh use cube
return TriangleMesh(its_make_cube(36., 4., 2.5));
}
return triangle_mesh;
}
void UpdateJob::update_volume(ModelVolume *volume,
TriangleMesh &&mesh,
const TextConfiguration &text_configuration,
const std::string &volume_name)
{
// check inputs
bool is_valid_input =
volume != nullptr &&
!mesh.empty() &&
!volume_name.empty();
assert(is_valid_input);
if (!is_valid_input) return;
// update volume
volume->set_mesh(std::move(mesh));
volume->set_new_unique_id();
volume->calculate_convex_hull();
volume->get_object()->invalidate_bounding_box();
volume->text_configuration = text_configuration;
GUI_App &app = wxGetApp(); // may be move to input
GLCanvas3D *canvas = app.plater()->canvas3D();
const Selection &selection = canvas->get_selection();
const GLVolume *gl_volume = selection.get_volume(*selection.get_volume_idxs().begin());
int object_idx = gl_volume->object_idx();
if (volume->name != volume_name) {
volume->name = volume_name;
// update volume name in right panel( volume / object name)
int volume_idx = gl_volume->volume_idx();
ObjectList *obj_list = app.obj_list();
obj_list->update_name_in_list(object_idx, volume_idx);
}
// update printable state on canvas
if (volume->type() == ModelVolumeType::MODEL_PART)
canvas->update_instance_printable_state_for_object((size_t) object_idx);
// Move object on bed
if (GLGizmoEmboss::is_text_object(volume)) volume->get_object()->ensure_on_bed();
// redraw scene
bool refresh_immediately = false;
canvas->reload_scene(refresh_immediately);
}
void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data)
{
// for sure that some object will be created
if (mesh.its.empty())
return priv::create_message("Empty mesh can't be created.");
Plater *plater = wxGetApp().plater();
GLCanvas3D *canvas = plater->canvas3D();
// Check emboss gizmo is still open
GLGizmosManager &manager = canvas->get_gizmos_manager();
if (manager.get_current_type() != GLGizmosManager::Emboss) return;
std::string snap_name = GUI::format(_L("Text: %1%"), data.text_configuration.text);
Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction);
ModelVolume *volume = get_volume(plater->model().objects, data.volume_id);
// could appear when user delete edited volume
if (volume == nullptr)
return;
// apply fix matrix made by store to .3mf
const auto &tc = volume->text_configuration;
assert(tc.has_value());
if (tc.has_value() && tc->fix_3mf_tr.has_value())
volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse());
UpdateJob::update_volume(volume, std::move(mesh), data.text_configuration, data.volume_name);
}
void priv::create_volume(
TriangleMesh &&mesh, const ObjectID& object_id,
const ModelVolumeType type, const Transform3d trmat, const DataBase &data)
{
GUI_App &app = wxGetApp();
Plater *plater = app.plater();
ObjectList *obj_list = app.obj_list();
GLCanvas3D *canvas = plater->canvas3D();
ModelObjectPtrs &objects = plater->model().objects;
ModelObject *obj = nullptr;
size_t object_idx = 0;
for (; object_idx < objects.size(); ++object_idx) {
ModelObject *o = objects[object_idx];
if (o->id() == object_id) {
obj = o;
break;
}
}
// Parent object for text volume was propably removed.
// Assumption: User know what he does, so text volume is no more needed.
if (obj == nullptr)
return priv::create_message(_u8L("Bad object to create volume."));
if (mesh.its.empty())
return priv::create_message(_u8L("Can't create empty volume."));
plater->take_snapshot(_L("Add Emboss text Volume"));
// NOTE: be carefull add volume also center mesh !!!
// So first add simple shape(convex hull is also calculated)
ModelVolume *volume = obj->add_volume(make_cube(1., 1., 1.), type);
// TODO: Refactor to create better way to not set cube at begining
// Revert mesh centering by set mesh after add cube
volume->set_mesh(std::move(mesh));
volume->calculate_convex_hull();
// set a default extruder value, since user can't add it manually
volume->config.set_key_value("extruder", new ConfigOptionInt(0));
// do not allow model reload from disk
volume->source.is_from_builtin_objects = true;
volume->name = data.volume_name; // copy
volume->text_configuration = data.text_configuration; // copy
volume->set_transformation(trmat);
// update volume name in object list
// updata selection after new volume added
// change name of volume in right panel
// select only actual volume
// when new volume is created change selection to this volume
auto add_to_selection = [volume](const ModelVolume *vol) { return vol == volume; };
wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(object_idx, add_to_selection);
if (!sel.IsEmpty()) obj_list->select_item(sel.front());
// update printable state on canvas
if (type == ModelVolumeType::MODEL_PART) canvas->update_instance_printable_state_for_object(object_idx);
obj_list->selection_changed();
// Now is valid text volume selected open emboss gizmo
GLGizmosManager &manager = canvas->get_gizmos_manager();
if (manager.get_current_type() != GLGizmosManager::Emboss)
manager.open_gizmo(GLGizmosManager::Emboss);
// redraw scene
canvas->reload_scene(true);
}
ModelVolume *priv::get_volume(ModelObjectPtrs &objects,
const ObjectID &volume_id)
{
for (ModelObject *obj : objects)
for (ModelVolume *vol : obj->volumes)
if (vol->id() == volume_id) return vol;
return nullptr;
};
OrthoProject priv::create_projection_for_cut(
Transform3d tr,
double shape_scale,
const std::pair<float, float> &z_range)
{
double min_z = z_range.first - priv::safe_extension;
double max_z = z_range.second + priv::safe_extension;
assert(min_z < max_z);
// range between min and max value
double projection_size = max_z - min_z;
Matrix3d transformation_for_vector = tr.linear();
// Projection must be negative value.
// System of text coordinate
// X .. from left to right
// Y .. from bottom to top
// Z .. from text to eye
Vec3d untransformed_direction(0., 0., projection_size);
Vec3d project_direction = transformation_for_vector * untransformed_direction;
// Projection is in direction from far plane
tr.translate(Vec3d(0., 0., min_z));
tr.scale(shape_scale);
return OrthoProject(tr, project_direction);
}
OrthoProject3d priv::create_emboss_projection(
bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut)
{
// Offset of clossed side to model
const float surface_offset = 1e-3f; // [in mm]
float
front_move = (is_outside) ? emboss : surface_offset,
back_move = -((is_outside) ? surface_offset : emboss);
its_transform(cut, tr.pretranslate(Vec3d(0., 0., front_move)));
Vec3d from_front_to_back(0., 0., back_move - front_move);
return OrthoProject3d(from_front_to_back);
}
// input can't be const - cache of font
TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2, std::function<bool()> was_canceled)
{
const TextConfiguration &tc = input1.text_configuration;
const char *text = tc.text.c_str();
const FontProp &fp = tc.style.prop;
ExPolygons shapes = text2shapes(input1.font_file, text, fp, was_canceled);
if (shapes.empty() || shapes.front().contour.empty())
throw JobException(_u8L("Font doesn't have any shape for given text.").c_str());
if (was_canceled()) return {};
// Define alignment of text - left, right, center, top bottom, ....
BoundingBox bb = get_extents(shapes);
Point projection_center = bb.center();
for (ExPolygon &shape : shapes) shape.translate(-projection_center);
bb.translate(-projection_center);
const FontFile &ff = *input1.font_file.font_file;
double shape_scale = get_shape_scale(fp, ff);
const SurfaceVolumeData::ModelSources &sources = input2.sources;
const SurfaceVolumeData::ModelSource *biggest = nullptr;
size_t biggest_count = 0;
// convert index from (s)ources to (i)ndexed (t)riangle (s)ets
std::vector<size_t> s_to_itss(sources.size(), std::numeric_limits<size_t>::max());
std::vector<indexed_triangle_set> itss;
itss.reserve(sources.size());
for (const SurfaceVolumeData::ModelSource &s : sources) {
Transform3d mesh_tr_inv = s.tr.inverse();
Transform3d cut_projection_tr = mesh_tr_inv * input2.text_tr;
std::pair<float, float> z_range{0., 1.};
OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range);
// copy only part of source model
indexed_triangle_set its = its_cut_AoI(s.mesh->its, bb, cut_projection);
if (its.indices.empty()) continue;
if (biggest_count < its.vertices.size()) {
biggest_count = its.vertices.size();
biggest = &s;
}
s_to_itss[&s - &sources.front()] = itss.size();
itss.emplace_back(std::move(its));
}
if (itss.empty()) throw JobException(_u8L("There is no volume in projection direction.").c_str());
Transform3d tr_inv = biggest->tr.inverse();
size_t itss_index = s_to_itss[biggest - &sources.front()];
BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]);
for (const SurfaceVolumeData::ModelSource &s : sources) {
if (&s == biggest) continue;
size_t itss_index = s_to_itss[&s - &sources.front()];
if (itss_index == std::numeric_limits<size_t>::max()) continue;
Transform3d tr = s.tr * tr_inv;
indexed_triangle_set &its = itss[itss_index];
its_transform(its, tr);
BoundingBoxf3 bb = bounding_box(its);
mesh_bb.merge(bb);
}
// tr_inv = transformation of mesh inverted
Transform3d cut_projection_tr = tr_inv * input2.text_tr;
Transform3d emboss_tr = cut_projection_tr.inverse();
BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr);
std::pair<float, float> z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()};
OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range);
float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension);
// Use CGAL to cut surface from triangle mesh
SurfaceCut cut = cut_surface(shapes, itss, cut_projection, projection_ratio);
if (cut.empty()) throw JobException(_u8L("There is no valid surface for text projection.").c_str());
if (was_canceled()) return {};
// !! Projection needs to transform cut
OrthoProject3d projection = create_emboss_projection(input2.is_outside, fp.emboss, emboss_tr, cut);
indexed_triangle_set new_its = cut2model(cut, projection);
assert(!new_its.empty());
if (was_canceled()) return {};
return TriangleMesh(std::move(new_its));
}
bool priv::process(std::exception_ptr &eptr) {
if (!eptr) return false;
try {
std::rethrow_exception(eptr);
} catch (priv::JobException &e) {
create_message(e.what());
eptr = nullptr;
}
return true;
}
#include <wx/msgdlg.h>
void priv::create_message(const std::string &message) {
wxMessageBox(wxString(message), _L("Issue during embossing the text."),
wxOK | wxICON_WARNING);
}

View file

@ -0,0 +1,237 @@
#ifndef slic3r_EmbossJob_hpp_
#define slic3r_EmbossJob_hpp_
#include <atomic>
#include <memory>
#include <string>
#include <libslic3r/Emboss.hpp>
//#include <libslic3r/ModelVolumeType.hpp>
#include "slic3r/Utils/RaycastManager.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "Job.hpp"
namespace Slic3r {
class ModelVolume;
class TriangleMesh;
}
namespace Slic3r::GUI::Emboss {
/// <summary>
/// Base data holder for embossing
/// </summary>
struct DataBase
{
// Keep pointer on Data of font (glyph shapes)
Slic3r::Emboss::FontFileWithCache font_file;
// font item is not used for create object
TextConfiguration text_configuration;
// new volume name created from text
std::string volume_name;
};
/// <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 : 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;
};
/// <summary>
/// Create new TextVolume on the surface of ModelObject
/// Should not be stopped
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
/// </summary>
class CreateVolumeJob : public Job
{
DataCreateVolume m_input;
TriangleMesh m_result;
public:
CreateVolumeJob(DataCreateVolume&& input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
/// <summary>
/// Hold neccessary data to create ModelObject in job
/// Object is placed on bed under screen coor
/// OR to center of scene when it is out of bed shape
/// </summary>
struct DataCreateObject : public DataBase
{
// define position on screen where to create object
Vec2d screen_coor;
// projection property
Camera camera;
// shape of bed in case of create volume on bed
std::vector<Vec2d> bed_shape;
};
/// <summary>
/// Create new TextObject on the platter
/// Should not be stopped
/// </summary>
class CreateObjectJob : public Job
{
DataCreateObject m_input;
TriangleMesh m_result;
Transform3d m_transformation;
public:
CreateObjectJob(DataCreateObject&& input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
/// <summary>
/// Hold neccessary data to update embossed text object in job
/// </summary>
struct DataUpdate : public DataBase
{
// unique identifier of volume to change
ObjectID volume_id;
// flag that job is canceled
// for time after process.
std::shared_ptr<std::atomic<bool>> cancel;
};
/// <summary>
/// Update text shape in existing text volume
/// Predict that there is only one runnig(not canceled) instance of it
/// </summary>
class UpdateJob : public Job
{
DataUpdate m_input;
TriangleMesh m_result;
public:
// move params to private variable
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="text_configuration">Parametric description of volume</param>
/// <param name="volume_name">Name of volume</param>
static void update_volume(ModelVolume *volume,
TriangleMesh &&mesh,
const TextConfiguration &text_configuration,
const std::string &volume_name);
};
struct SurfaceVolumeData
{
// Transformation of text volume inside of object
Transform3d text_tr;
// Define projection move
// True (raised) .. move outside from surface
// False (engraved).. move into object
bool is_outside;
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 create(cut) volume from surface object in job
/// </summary>
struct CreateSurfaceVolumeData : public DataBase, public SurfaceVolumeData{
// define embossed volume type
ModelVolumeType volume_type;
// parent ModelObject index where to create volume
ObjectID object_id;
};
/// <summary>
/// Cut surface from object and create cutted volume
/// Should not be stopped
/// </summary>
class CreateSurfaceVolumeJob : public Job
{
CreateSurfaceVolumeData m_input;
TriangleMesh m_result;
public:
CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
/// <summary>
/// Hold neccessary data to update embossed text object in job
/// </summary>
struct UpdateSurfaceVolumeData : public DataUpdate, public SurfaceVolumeData{};
/// <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 = {});
/// <summary>
/// Copied triangles from object to be able create mesh for cut surface from
/// </summary>
/// <param name="text_volume">Define text in object</param>
/// <returns>Source data for cut surface from</returns>
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume);
/// <summary>
/// Update text volume to use surface from object
/// </summary>
class UpdateSurfaceVolumeJob : public Job
{
UpdateSurfaceVolumeData m_input;
TriangleMesh m_result;
public:
// move params to private variable
UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
} // namespace Slic3r::GUI
#endif // slic3r_EmbossJob_hpp_

View file

@ -2,6 +2,7 @@
#define PLATERWORKER_HPP
#include <map>
#include <chrono>
#include "Worker.hpp"
#include "BusyCursorJob.hpp"
@ -24,6 +25,7 @@ class PlaterWorker: public Worker {
class PlaterJob : public Job {
std::unique_ptr<Job> m_job;
Plater *m_plater;
long long m_process_duration; // [ms]
public:
void process(Ctl &c) override
@ -55,12 +57,27 @@ class PlaterWorker: public Worker {
} 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);

View file

@ -202,8 +202,14 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
if (m_id == 0)
m_id = m_id_provider.allocate_id();
std::string name = "!!Ntfctn" + std::to_string(m_id);
int flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse |
ImGuiWindowFlags_NoFocusOnAppearing;
if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
if (imgui.begin(name, flags)) {
ImVec2 win_size = ImGui::GetWindowSize();
render_left_sign(imgui);

View file

@ -111,6 +111,8 @@ enum class NotificationType
// Give user advice to simplify object with big amount of triangles
// Contains ObjectID for closing when object is deleted
SimplifySuggestion,
// Change of text will change font to similar one on.
UnknownFont,
// information about netfabb is finished repairing model (blocking proccess)
NetfabbFinished,
// Short meesage to fill space between start and finish of export

View file

@ -74,12 +74,14 @@ const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
const wxString& sub_obj_name,
Slic3r::ModelVolumeType type,
const bool is_text_volume,
const wxString& extruder,
const int idx/* = -1*/) :
m_parent(parent),
m_name(sub_obj_name),
m_type(itVolume),
m_volume_type(type),
m_is_text_volume(is_text_volume),
m_idx(idx),
m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : "")
{
@ -330,6 +332,7 @@ static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType roo
ObjectDataViewModel::ObjectDataViewModel()
{
m_volume_bmps = MenuFactory::get_volume_bitmaps();
m_text_volume_bmps = MenuFactory::get_text_volume_bitmaps();
m_warning_bmp = *get_bmp_bundle(WarningIcon);
m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon);
m_lock_bmp = *get_bmp_bundle(LockIcon);
@ -352,7 +355,7 @@ void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node)
bool is_volume_node = vol_type >= 0;
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) : *m_volume_bmps.at(vol_type)) : m_empty_bmp);
return;
}
@ -373,7 +376,7 @@ void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node)
if (node->has_lock())
bmps.emplace_back(&m_lock_bmp);
if (is_volume_node)
bmps.emplace_back(m_volume_bmps[vol_type]);
bmps.emplace_back(node->is_text_volume() ? m_text_volume_bmps[vol_type] : m_volume_bmps[vol_type]);
bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps);
}
@ -409,6 +412,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent
const wxString &name,
const int volume_idx,
const Slic3r::ModelVolumeType volume_type,
const bool is_text_volume,
const std::string& warning_icon_name,
const wxString& extruder)
{
@ -420,7 +424,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent
if (insert_position < 0)
insert_position = get_root_idx(root, itInstanceRoot);
const auto node = new ObjectDataViewModelNode(root, name, volume_type, extruder, volume_idx);
const auto node = new ObjectDataViewModelNode(root, name, volume_type, is_text_volume, extruder, volume_idx);
UpdateBitmapForNode(node, warning_icon_name, root->has_lock() && volume_type < ModelVolumeType::PARAMETER_MODIFIER);
insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position);
@ -769,8 +773,12 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
// get index of the last VolumeItem in CildrenList
size_t vol_idx = GetItemIndexForFirstVolume(node_parent);
// delete this last volume
ObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx);
// if last volume is text then don't delete it
if (last_child_node->is_text_volume())
return parent;
// delete this last volume
DeleteSettings(wxDataViewItem(last_child_node));
node_parent->GetChildren().Remove(last_child_node);
node_parent->m_volumes_cnt = 0;
@ -1616,22 +1624,6 @@ void ObjectDataViewModel::UpdateSettingsDigest(const wxDataViewItem &item,
ItemChanged(item);
}
void ObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType volume_type)
{
if (!item.IsOk() || GetItemType(item) != itVolume)
return;
ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID());
node->SetVolumeType(volume_type);
node->SetBitmap(*m_volume_bmps[int(volume_type)]);
if (volume_type != Slic3r::ModelVolumeType::MODEL_PART && volume_type != Slic3r::ModelVolumeType::PARAMETER_MODIFIER)
node->SetExtruder(""); // hide extruder
else if (node->GetExtruder().IsEmpty())
node->SetExtruder("default"); // show extruder ans set it to default
node->UpdateExtruderAndColorIcon();
ItemChanged(item);
}
ModelVolumeType ObjectDataViewModel::GetVolumeType(const wxDataViewItem& item)
{
if (!item.IsOk() || GetItemType(item) != itVolume)
@ -1684,6 +1676,7 @@ wxDataViewItem ObjectDataViewModel::SetObjectPrintableState(
void ObjectDataViewModel::UpdateBitmaps()
{
m_volume_bmps = MenuFactory::get_volume_bitmaps();
m_text_volume_bmps = MenuFactory::get_text_volume_bitmaps();
m_warning_bmp = *get_bmp_bundle(WarningIcon);
m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon);
m_lock_bmp = *get_bmp_bundle(LockIcon);

View file

@ -84,6 +84,7 @@ class ObjectDataViewModelNode
std::string m_action_icon_name = "";
ModelVolumeType m_volume_type{ -1 };
bool m_is_text_volume{ false };
InfoItemType m_info_item_type {InfoItemType::Undef};
public:
@ -101,6 +102,7 @@ public:
ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
const wxString& sub_obj_name,
Slic3r::ModelVolumeType type,
const bool is_text_volume,
const wxString& extruder,
const int idx = -1 );
@ -191,7 +193,7 @@ public:
InfoItemType GetInfoItemType() const { return m_info_item_type; }
void SetIdx(const int& idx);
int GetIdx() const { return m_idx; }
ModelVolumeType GetVolumeType() { return m_volume_type; }
ModelVolumeType GetVolumeType() const { return m_volume_type; }
t_layer_height_range GetLayerRange() const { return m_layer_range; }
wxString GetExtruder() { return m_extruder; }
PrintIndicator IsPrintable() const { return m_printable; }
@ -235,6 +237,7 @@ 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; }
void sys_color_changed();
#ifndef NDEBUG
@ -261,6 +264,7 @@ class ObjectDataViewModel :public wxDataViewModel
{
std::vector<ObjectDataViewModelNode*> m_objects;
std::vector<wxBitmapBundle*> m_volume_bmps;
std::vector<wxBitmapBundle*> m_text_volume_bmps;
std::map<InfoItemType, wxBitmapBundle*> m_info_bmps;
wxBitmapBundle m_empty_bmp;
wxBitmapBundle m_warning_bmp;
@ -281,6 +285,7 @@ public:
const wxString &name,
const int volume_idx,
const Slic3r::ModelVolumeType volume_type,
const bool is_text_volume,
const std::string& warning_icon_name,
const wxString& extruder);
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
@ -379,7 +384,6 @@ public:
void UpdateObjectPrintable(wxDataViewItem parent_item);
void UpdateInstancesPrintable(wxDataViewItem parent_item);
void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type);
ModelVolumeType GetVolumeType(const wxDataViewItem &item);
wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx,
int subobj_idx = -1,
@ -387,11 +391,9 @@ public:
wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item);
void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; }
// Rescale bitmaps for existing Items
// Rescale bitmaps for existing Items
void UpdateBitmaps();
wxBitmapBundle GetVolumeIcon(const Slic3r::ModelVolumeType vol_type,
const std::string& warning_icon_name = std::string());
void AddWarningIcon(const wxDataViewItem& item, const std::string& warning_name);
void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
void UpdateWarningIcon(const wxDataViewItem& item, const std::string& warning_name);

View file

@ -4505,19 +4505,29 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
#else
const bool is_part = selection.is_single_volume() || selection.is_single_modifier();
#endif // ENABLE_WORLD_COORDINATE
menu = is_some_full_instances ? menus.object_menu() :
is_part ? menus.part_menu() : menus.multi_selection_menu();
if (is_some_full_instances)
menu = menus.object_menu();
else if (is_part)
menu = selection.is_single_text() ? menus.text_part_menu() : menus.part_menu();
else
menu = menus.multi_selection_menu();
}
}
if (q != nullptr && menu) {
Vec2d mouse_position = evt.data.first;
wxPoint position(static_cast<int>(mouse_position.x()),
static_cast<int>(mouse_position.y()));
#ifdef __linux__
// For some reason on Linux the menu isn't displayed if position is specified
// (even though the position is sane).
q->PopupMenu(menu);
#else
q->PopupMenu(menu, (int)evt.data.first.x(), (int)evt.data.first.y());
// For some reason on Linux the menu isn't displayed if position is
// specified (even though the position is sane).
position = wxDefaultPosition;
#endif
GLCanvas3D &canvas = *q->canvas3D();
canvas.apply_retina_scale(mouse_position);
canvas.set_popup_menu_position(mouse_position);
q->PopupMenu(menu, position);
canvas.clear_popup_menu_position();
}
}
@ -4934,6 +4944,10 @@ bool Plater::priv::can_increase_instances() const
|| q->canvas3D()->get_gizmos_manager().is_in_editing_mode())
return false;
// Disallow arrange and add instance when emboss gizmo is opend
// Prevent strobo effect during editing emboss parameters.
if (q->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss) return false;
const int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) &&
!sidebar->obj_list()->has_selected_cut_object();
@ -4963,7 +4977,9 @@ bool Plater::priv::can_split_to_volumes() const
bool Plater::priv::can_arrange() const
{
return !model.objects.empty() && m_worker.is_idle();
if (model.objects.empty() && m_worker.is_idle()) return false;
if (q->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss) return false;
return true;
}
bool Plater::priv::can_layers_editing() const
@ -7244,7 +7260,7 @@ bool Plater::PopupMenu(wxMenu *menu, const wxPoint& pos)
SuppressBackgroundProcessingUpdate sbpu;
// When tracking a pop-up menu, postpone error messages from the slicing result.
m_tracking_popup_menu = true;
bool out = this->wxPanel::PopupMenu(menu, pos);
bool out = this->wxPanel::PopupMenu(menu, pos);
m_tracking_popup_menu = false;
if (! m_tracking_popup_menu_error_message.empty()) {
// Don't know whether the CallAfter is necessary, but it should not hurt.
@ -7262,6 +7278,7 @@ void Plater::bring_instance_forward()
wxMenu* Plater::object_menu() { return p->menus.object_menu(); }
wxMenu* Plater::part_menu() { return p->menus.part_menu(); }
wxMenu* Plater::text_part_menu() { return p->menus.text_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

@ -463,6 +463,7 @@ public:
// get same Plater/ObjectList menus
wxMenu* object_menu();
wxMenu* part_menu();
wxMenu* text_part_menu();
wxMenu* sla_object_menu();
wxMenu* default_menu();
wxMenu* instance_menu();

File diff suppressed because it is too large Load diff

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