Merge remote-tracking branch 'remotes/origin/fs_emboss'
2555
resources/data/embossed_text.obj
Normal file
4
resources/icons/add_text_modifier.svg
Normal 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 |
4
resources/icons/add_text_negative.svg
Normal 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 |
4
resources/icons/add_text_part.svg
Normal 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 |
4
resources/icons/make_bold.svg
Normal 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 |
4
resources/icons/make_italic.svg
Normal 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 |
4
resources/icons/make_unbold.svg
Normal 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 |
4
resources/icons/make_unitalic.svg
Normal 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 |
|
@ -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 |
|
@ -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)
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
76
src/imgui/imgui_stdlib.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
|
||||
// This is also an example of how you may wrap your own similar types.
|
||||
|
||||
// Compatibility:
|
||||
// - std::string support is only guaranteed to work from C++11.
|
||||
// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture)
|
||||
|
||||
// Changelog:
|
||||
// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_stdlib.h"
|
||||
|
||||
struct InputTextCallback_UserData
|
||||
{
|
||||
std::string* Str;
|
||||
ImGuiInputTextCallback ChainCallback;
|
||||
void* ChainCallbackUserData;
|
||||
};
|
||||
|
||||
static int InputTextCallback(ImGuiInputTextCallbackData* data)
|
||||
{
|
||||
InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData;
|
||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
|
||||
{
|
||||
// Resize string callback
|
||||
// If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want.
|
||||
std::string* str = user_data->Str;
|
||||
IM_ASSERT(data->Buf == str->c_str());
|
||||
str->resize(data->BufTextLen);
|
||||
data->Buf = (char*)str->c_str();
|
||||
}
|
||||
else if (user_data->ChainCallback)
|
||||
{
|
||||
// Forward to user callback, if any
|
||||
data->UserData = user_data->ChainCallbackUserData;
|
||||
return user_data->ChainCallback(data);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
||||
{
|
||||
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
|
||||
flags |= ImGuiInputTextFlags_CallbackResize;
|
||||
|
||||
InputTextCallback_UserData cb_user_data;
|
||||
cb_user_data.Str = str;
|
||||
cb_user_data.ChainCallback = callback;
|
||||
cb_user_data.ChainCallbackUserData = user_data;
|
||||
return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
|
||||
}
|
||||
|
||||
bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
||||
{
|
||||
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
|
||||
flags |= ImGuiInputTextFlags_CallbackResize;
|
||||
|
||||
InputTextCallback_UserData cb_user_data;
|
||||
cb_user_data.Str = str;
|
||||
cb_user_data.ChainCallback = callback;
|
||||
cb_user_data.ChainCallbackUserData = user_data;
|
||||
return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data);
|
||||
}
|
||||
|
||||
bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
||||
{
|
||||
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
|
||||
flags |= ImGuiInputTextFlags_CallbackResize;
|
||||
|
||||
InputTextCallback_UserData cb_user_data;
|
||||
cb_user_data.Str = str;
|
||||
cb_user_data.ChainCallback = callback;
|
||||
cb_user_data.ChainCallbackUserData = user_data;
|
||||
return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
|
||||
}
|
22
src/imgui/imgui_stdlib.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
|
||||
// This is also an example of how you may wrap your own similar types.
|
||||
|
||||
// Compatibility:
|
||||
// - std::string support is only guaranteed to work from C++11.
|
||||
// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture)
|
||||
|
||||
// Changelog:
|
||||
// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ImGui
|
||||
{
|
||||
// ImGui::InputText() with std::string
|
||||
// Because text input needs dynamic resizing, we need to setup a callback to grow the capacity
|
||||
IMGUI_API bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
|
||||
IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
|
||||
IMGUI_API bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 ¢er) const;
|
||||
void rotate(double angle) { (*this) = this->rotated(angle); }
|
||||
void rotate(double angle, const Point ¢er) { (*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);
|
||||
|
|
|
@ -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
74
src/libslic3r/CutSurface.hpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#ifndef slic3r_CutSurface_hpp_
|
||||
#define slic3r_CutSurface_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <admesh/stl.h> // indexed_triangle_set
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Emboss.hpp" // IProjection
|
||||
|
||||
namespace Slic3r{
|
||||
|
||||
/// <summary>
|
||||
/// Represents cutted surface from object
|
||||
/// Extend index triangle set by outlines
|
||||
/// </summary>
|
||||
struct SurfaceCut : public indexed_triangle_set
|
||||
{
|
||||
// vertex indices(index to mesh vertices)
|
||||
using Index = unsigned int;
|
||||
using Contour = std::vector<Index>;
|
||||
using Contours = std::vector<Contour>;
|
||||
// list of circulated open surface
|
||||
Contours contours;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Cut surface shape from models.
|
||||
/// </summary>
|
||||
/// <param name="shapes">Multiple shape to cut from model</param>
|
||||
/// <param name="models">Multi mesh to cut, need to be in same coordinate system</param>
|
||||
/// <param name="projection">Define transformation 2d shape into 3d</param>
|
||||
/// <param name="projection_ratio">Define ideal ratio between front and back projection to cut
|
||||
/// 0 .. means use closest to front projection
|
||||
/// 1 .. means use closest to back projection
|
||||
/// value from <0, 1>
|
||||
/// </param>
|
||||
/// <returns>Cutted surface from model</returns>
|
||||
SurfaceCut cut_surface(const ExPolygons &shapes,
|
||||
const std::vector<indexed_triangle_set> &models,
|
||||
const Emboss::IProjection &projection,
|
||||
float projection_ratio);
|
||||
|
||||
/// <summary>
|
||||
/// Create model from surface cuts by projection
|
||||
/// </summary>
|
||||
/// <param name="cut">Surface from model with outlines</param>
|
||||
/// <param name="projection">Way of emboss</param>
|
||||
/// <returns>Mesh</returns>
|
||||
indexed_triangle_set cut2model(const SurfaceCut &cut,
|
||||
const Emboss::IProject3d &projection);
|
||||
|
||||
/// <summary>
|
||||
/// Separate (A)rea (o)f (I)nterest .. AoI from model
|
||||
/// NOTE: Only 2d filtration, do not filtrate by Z coordinate
|
||||
/// </summary>
|
||||
/// <param name="its">Input model</param>
|
||||
/// <param name="bb">Bounding box to project into space</param>
|
||||
/// <param name="projection">Define tranformation of BB into space</param>
|
||||
/// <returns>Triangles lay at least partialy inside of projected Bounding box</returns>
|
||||
indexed_triangle_set its_cut_AoI(const indexed_triangle_set &its,
|
||||
const BoundingBox &bb,
|
||||
const Emboss::IProjection &projection);
|
||||
|
||||
/// <summary>
|
||||
/// Separate triangles by mask
|
||||
/// </summary>
|
||||
/// <param name="its">Input model</param>
|
||||
/// <param name="mask">Mask - same size as its::indices</param>
|
||||
/// <returns>Copy of indices by mask(with their vertices)</returns>
|
||||
indexed_triangle_set its_mask(const indexed_triangle_set &its, const std::vector<bool> &mask);
|
||||
|
||||
bool corefine_test(const std::string &model_path, const std::string &shape_path);
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_CutSurface_hpp_
|
1294
src/libslic3r/Emboss.cpp
Normal file
349
src/libslic3r/Emboss.hpp
Normal 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_
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
82
src/libslic3r/ExPolygonsIndex.cpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
#include "ExPolygonsIndex.hpp"
|
||||
using namespace Slic3r;
|
||||
|
||||
// IMPROVE: use one dimensional vector for polygons offset with searching by std::lower_bound
|
||||
ExPolygonsIndices::ExPolygonsIndices(const ExPolygons &shapes)
|
||||
{
|
||||
// prepare offsets
|
||||
m_offsets.reserve(shapes.size());
|
||||
uint32_t offset = 0;
|
||||
for (const ExPolygon &shape : shapes) {
|
||||
assert(!shape.contour.points.empty());
|
||||
std::vector<uint32_t> shape_offsets;
|
||||
shape_offsets.reserve(shape.holes.size() + 1);
|
||||
shape_offsets.push_back(offset);
|
||||
offset += shape.contour.points.size();
|
||||
for (const Polygon &hole: shape.holes) {
|
||||
shape_offsets.push_back(offset);
|
||||
offset += hole.points.size();
|
||||
}
|
||||
m_offsets.push_back(std::move(shape_offsets));
|
||||
}
|
||||
m_count = offset;
|
||||
}
|
||||
|
||||
uint32_t ExPolygonsIndices::cvt(const ExPolygonsIndex &id) const
|
||||
{
|
||||
assert(id.expolygons_index < m_offsets.size());
|
||||
const std::vector<uint32_t> &shape_offset = m_offsets[id.expolygons_index];
|
||||
assert(id.polygon_index < shape_offset.size());
|
||||
uint32_t res = shape_offset[id.polygon_index] + id.point_index;
|
||||
assert(res < m_count);
|
||||
return res;
|
||||
}
|
||||
|
||||
ExPolygonsIndex ExPolygonsIndices::cvt(uint32_t index) const
|
||||
{
|
||||
assert(index < m_count);
|
||||
ExPolygonsIndex result{0, 0, 0};
|
||||
// find expolygon index
|
||||
auto fn = [](const std::vector<uint32_t> &offsets, uint32_t index) { return offsets[0] < index; };
|
||||
auto it = std::lower_bound(m_offsets.begin() + 1, m_offsets.end(), index, fn);
|
||||
result.expolygons_index = it - m_offsets.begin();
|
||||
if (it == m_offsets.end() || it->at(0) != index) --result.expolygons_index;
|
||||
|
||||
// find polygon index
|
||||
const std::vector<uint32_t> &shape_offset = m_offsets[result.expolygons_index];
|
||||
auto it2 = std::lower_bound(shape_offset.begin() + 1, shape_offset.end(), index);
|
||||
result.polygon_index = it2 - shape_offset.begin();
|
||||
if (it2 == shape_offset.end() || *it2 != index) --result.polygon_index;
|
||||
|
||||
// calculate point index
|
||||
uint32_t polygon_offset = shape_offset[result.polygon_index];
|
||||
assert(index >= polygon_offset);
|
||||
result.point_index = index - polygon_offset;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ExPolygonsIndices::is_last_point(const ExPolygonsIndex &id) const {
|
||||
assert(id.expolygons_index < m_offsets.size());
|
||||
const std::vector<uint32_t> &shape_offset = m_offsets[id.expolygons_index];
|
||||
assert(id.polygon_index < shape_offset.size());
|
||||
uint32_t index = shape_offset[id.polygon_index] + id.point_index;
|
||||
assert(index < m_count);
|
||||
// next index
|
||||
uint32_t next_point_index = index + 1;
|
||||
uint32_t next_poly_index = id.polygon_index + 1;
|
||||
uint32_t next_expoly_index = id.expolygons_index + 1;
|
||||
// is last expoly?
|
||||
if (next_expoly_index == m_offsets.size()) {
|
||||
// is last expoly last poly?
|
||||
if (next_poly_index == shape_offset.size())
|
||||
return next_point_index == m_count;
|
||||
} else {
|
||||
// (not last expoly) is expoly last poly?
|
||||
if (next_poly_index == shape_offset.size())
|
||||
return next_point_index == m_offsets[next_expoly_index][0];
|
||||
}
|
||||
// Not last polygon in expolygon
|
||||
return next_point_index == shape_offset[next_poly_index];
|
||||
}
|
||||
|
||||
uint32_t ExPolygonsIndices::get_count() const { return m_count; }
|
74
src/libslic3r/ExPolygonsIndex.hpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#ifndef slic3r_ExPolygonsIndex_hpp_
|
||||
#define slic3r_ExPolygonsIndex_hpp_
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
namespace Slic3r {
|
||||
|
||||
/// <summary>
|
||||
/// Index into ExPolygons
|
||||
/// Identify expolygon, its contour (or hole) and point
|
||||
/// </summary>
|
||||
struct ExPolygonsIndex
|
||||
{
|
||||
// index of ExPolygons
|
||||
uint32_t expolygons_index;
|
||||
|
||||
// index of Polygon
|
||||
// 0 .. contour
|
||||
// N .. hole[N-1]
|
||||
uint32_t polygon_index;
|
||||
|
||||
// index of point in polygon
|
||||
uint32_t point_index;
|
||||
|
||||
bool is_contour() const { return polygon_index == 0; }
|
||||
bool is_hole() const { return polygon_index != 0; }
|
||||
uint32_t hole_index() const { return polygon_index - 1; }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Keep conversion from ExPolygonsIndex to Index and vice versa
|
||||
/// ExPolygonsIndex .. contour(or hole) point from ExPolygons
|
||||
/// Index .. continous number
|
||||
///
|
||||
/// index is used to address lines and points as result from function
|
||||
/// Slic3r::to_lines, Slic3r::to_points
|
||||
/// </summary>
|
||||
class ExPolygonsIndices
|
||||
{
|
||||
std::vector<std::vector<uint32_t>> m_offsets;
|
||||
// for check range of index
|
||||
uint32_t m_count; // count of points
|
||||
public:
|
||||
ExPolygonsIndices(const ExPolygons &shapes);
|
||||
|
||||
/// <summary>
|
||||
/// Convert to one index number
|
||||
/// </summary>
|
||||
/// <param name="id">Compose of adress into expolygons</param>
|
||||
/// <returns>Index</returns>
|
||||
uint32_t cvt(const ExPolygonsIndex &id) const;
|
||||
|
||||
/// <summary>
|
||||
/// Separate to multi index
|
||||
/// </summary>
|
||||
/// <param name="index">adress into expolygons</param>
|
||||
/// <returns></returns>
|
||||
ExPolygonsIndex cvt(uint32_t index) const;
|
||||
|
||||
/// <summary>
|
||||
/// Check whether id is last point in polygon
|
||||
/// </summary>
|
||||
/// <param name="id">Identify point in expolygon</param>
|
||||
/// <returns>True when id is last point in polygon otherwise false</returns>
|
||||
bool is_last_point(const ExPolygonsIndex &id) const;
|
||||
|
||||
/// <summary>
|
||||
/// Count of points in expolygons
|
||||
/// </summary>
|
||||
/// <returns>Count of points in expolygons</returns>
|
||||
uint32_t get_count() const;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_ExPolygonsIndex_hpp_
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
170
src/libslic3r/IntersectionPoints.cpp
Normal 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
|
16
src/libslic3r/IntersectionPoints.hpp
Normal 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_
|
|
@ -210,6 +210,7 @@ public:
|
|||
static const constexpr int Dim = 2;
|
||||
using Scalar = Vec2d::Scalar;
|
||||
};
|
||||
using Linesf = std::vector<Linef>;
|
||||
|
||||
class Linef3
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
16
src/libslic3r/ModelVolumeType.hpp
Normal 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_ */
|
83
src/libslic3r/NSVGUtils.cpp
Normal 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);
|
||||
}
|
34
src/libslic3r/NSVGUtils.hpp
Normal 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_
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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); }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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_
|
||||
|
|
239
src/libslic3r/TextConfiguration.hpp
Normal 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
|
@ -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
|
@ -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_
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
328
src/libslic3r/Triangulation.cpp
Normal 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;
|
||||
}
|
72
src/libslic3r/Triangulation.hpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#ifndef libslic3r_Triangulation_hpp_
|
||||
#define libslic3r_Triangulation_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Triangulation
|
||||
{
|
||||
public:
|
||||
Triangulation() = delete;
|
||||
|
||||
// define oriented connection of 2 vertices(defined by its index)
|
||||
using HalfEdge = std::pair<uint32_t, uint32_t>;
|
||||
using HalfEdges = std::vector<HalfEdge>;
|
||||
using Indices = std::vector<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_
|
|
@ -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__
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -944,7 +944,34 @@ std::string xml_escape(std::string text, bool is_marked/* = false*/)
|
|||
case '\'': replacement = "'"; break;
|
||||
case '&': replacement = "&"; break;
|
||||
case '<': replacement = is_marked ? "<" :"<"; break;
|
||||
case '>': replacement = is_marked ? ">" :">"; break;
|
||||
case '>': replacement = is_marked ? ">" : ">"; 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 = """; break;
|
||||
case '&': replacement = "&"; break;
|
||||
case '<': replacement = "<"; break;
|
||||
case '\r': replacement = "
"; break;
|
||||
case '\n': replacement = "
"; break;
|
||||
case '\t': replacement = "	"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ()
|
||||
|
|
|
@ -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
|
||||
|
|
123
src/slic3r/GUI/CameraUtils.cpp
Normal 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());
|
||||
}
|
58
src/slic3r/GUI/CameraUtils.hpp
Normal 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_ */
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 } },
|
||||
|
|
|
@ -62,6 +62,7 @@ enum FileType
|
|||
FT_GCODE,
|
||||
FT_MODEL,
|
||||
FT_PROJECT,
|
||||
FT_FONTS,
|
||||
FT_GALLERY,
|
||||
|
||||
FT_INI,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
3438
src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
Normal file
360
src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp
Normal 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_
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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();
|
||||
|
|
|
@ -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_
|
|
@ -937,4 +937,4 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const
|
|||
#endif // ENABLE_WORLD_COORDINATE
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
} // namespace Slic3r
|
|
@ -125,4 +125,4 @@ private:
|
|||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoScale_hpp_
|
||||
#endif // slic3r_GLGizmoScale_hpp_
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -79,8 +79,9 @@ public:
|
|||
FdmSupports,
|
||||
Seam,
|
||||
MmuSegmentation,
|
||||
Simplify,
|
||||
Measure,
|
||||
Emboss,
|
||||
Simplify,
|
||||
Undefined
|
||||
};
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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; }
|
||||
|
|
144
src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp
Normal 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();
|
||||
}
|
75
src/slic3r/GUI/Jobs/CreateFontNameImageJob.hpp
Normal 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_
|
164
src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp
Normal 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);
|
||||
}
|
36
src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp
Normal 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_
|
791
src/slic3r/GUI/Jobs/EmbossJob.cpp
Normal 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);
|
||||
}
|
237
src/slic3r/GUI/Jobs/EmbossJob.hpp
Normal 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_
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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();
|
||||
|
|