Merge branch 'fs_svg_SPE-1517_'

This commit is contained in:
Lukas Matena 2023-10-18 16:24:33 +02:00
commit 6e206d8c9e
86 changed files with 8239 additions and 3704 deletions

View File

@ -1,13 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="add_x5F_modifer">
<path fill="#808080" d="M10.98,9.78c-0.29,0-0.52,0.23-0.52,0.52v2.09v1.04c0,0.29-0.23,0.52-0.52,0.52H2.62
c-0.29,0-0.53-0.24-0.53-0.53L2.04,6.12c0-0.14,0.05-0.27,0.15-0.37c0.1-0.1,0.23-0.15,0.37-0.15l3.19,0v0
c0.29,0,0.52-0.23,0.52-0.52S6.04,4.55,5.75,4.55H3.66c-0.01,0-0.01,0-0.02,0l-1.08,0c-0.42,0-0.81,0.16-1.11,0.46
C1.16,5.31,1,5.71,1,6.13l0.04,7.31C1.05,14.3,1.75,15,2.62,15h7.31c0.86,0,1.57-0.7,1.57-1.57v-1.04V10.3
C11.5,10.01,11.27,9.78,10.98,9.78z"/>
<circle fill="#ED6B21" cx="11" cy="5" r="3.5"/>
</g>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 16,3 C 16,1 15,0 13,0 H 6 C 4,0 3,1 3,3 V 8 H 4 V 3 C 4,1.7 4.7,1 6,1 h 7 c 1.3,0 2,0.7 2,2 v 7 c 0,1.3 -0.7,2 -2,2 H 8 v 1 h 5 c 2,0 3,-1 3,-3 z" />
<path fill="#ed6b21" d="M 3.5 9 C 1.5741139 9 0 10.574114 0 12.5 C 0 14.425886 1.5741139 16 3.5 16 C 5.4258861 16 7 14.425886 7 12.5 C 7 10.574114 5.4258861 9 3.5 9 z M 3.5 10.199219 C 4.7773592 10.199219 5.8007813 11.222641 5.8007812 12.5 C 5.8007812 12.667974 5.7816922 12.830839 5.7480469 12.988281 L 3.0117188 10.251953 C 3.1691607 10.218308 3.3320257 10.199219 3.5 10.199219 z M 2.4863281 10.433594 L 5.5664062 13.513672 C 5.3371871 13.983352 4.9544913 14.362231 4.4804688 14.583984 L 1.4160156 11.521484 C 1.6376653 11.046724 2.0161177 10.663072 2.4863281 10.433594 z M 1.2421875 12.054688 L 3.9472656 14.757812 C 3.8024888 14.786013 3.6534389 14.800781 3.5 14.800781 C 2.2226408 14.800781 1.1992188 13.777359 1.1992188 12.5 C 1.1992188 12.347226 1.2142247 12.198881 1.2421875 12.054688 z " />
</svg>

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,15 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="add_x5F_part">
<g>
<path fill="#ED6B21" d="M14,5.5H8C7.72,5.5,7.5,5.28,7.5,5S7.72,4.5,8,4.5h6c0.28,0,0.5,0.22,0.5,0.5S14.28,5.5,14,5.5z"/>
</g>
<path fill="#808080" d="M10.98,9.78c-0.29,0-0.52,0.23-0.52,0.52v2.09v1.04c0,0.29-0.23,0.52-0.52,0.52H2.62
c-0.29,0-0.53-0.24-0.53-0.53L2.04,6.12c0-0.14,0.05-0.27,0.15-0.37c0.1-0.1,0.23-0.15,0.37-0.15l3.19,0v0
c0.29,0,0.52-0.23,0.52-0.52S6.04,4.55,5.75,4.55H3.66c-0.01,0-0.01,0-0.02,0l-1.08,0c-0.42,0-0.81,0.16-1.11,0.46
C1.16,5.31,1,5.71,1,6.13l0.04,7.31C1.05,14.3,1.75,15,2.62,15h7.31c0.86,0,1.57-0.7,1.57-1.57v-1.04V10.3
C11.5,10.01,11.27,9.78,10.98,9.78z"/>
</g>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 16,3 C 16,1 15,0 13,0 H 6 C 4,0 3,1 3,3 V 8 H 4 V 3 C 4,1.7 4.7,1 6,1 h 7 c 1.3,0 2,0.7 2,2 v 7 c 0,1.3 -0.7,2 -2,2 H 8 v 1 h 5 c 2,0 3,-1 3,-3 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 6 C 6.5,13 7,13 7,12.5 7,12 6.5,12 6,12 Z" />
</svg>

Before

Width:  |  Height:  |  Size: 975 B

After

Width:  |  Height:  |  Size: 388 B

View File

@ -1,19 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="add_x5F_part">
<g>
<path fill="#ED6B21" d="M11,8.5c-0.28,0-0.5-0.22-0.5-0.5V2c0-0.28,0.22-0.5,0.5-0.5s0.5,0.22,0.5,0.5v6
C11.5,8.28,11.28,8.5,11,8.5z"/>
</g>
<g>
<path fill="#ED6B21" d="M14,5.5H8C7.72,5.5,7.5,5.28,7.5,5S7.72,4.5,8,4.5h6c0.28,0,0.5,0.22,0.5,0.5S14.28,5.5,14,5.5z"/>
</g>
<path fill="#808080" d="M10.98,9.78c-0.29,0-0.52,0.23-0.52,0.52v2.09v1.04c0,0.29-0.23,0.52-0.52,0.52H2.62
c-0.29,0-0.53-0.24-0.53-0.53L2.04,6.12c0-0.14,0.05-0.27,0.15-0.37c0.1-0.1,0.23-0.15,0.37-0.15l3.19,0v0
c0.29,0,0.52-0.23,0.52-0.52S6.04,4.55,5.75,4.55H3.66c-0.01,0-0.01,0-0.02,0l-1.08,0c-0.42,0-0.81,0.16-1.11,0.46
C1.16,5.31,1,5.71,1,6.13l0.04,7.31C1.05,14.3,1.75,15,2.62,15h7.31c0.86,0,1.57-0.7,1.57-1.57v-1.04V10.3
C11.5,10.01,11.27,9.78,10.98,9.78z"/>
</g>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 16,3 C 16,1 15,0 13,0 H 6 C 4,0 3,1 3,3 V 8 H 4 V 3 C 4,1.7 4.7,1 6,1 h 7 c 1.3,0 2,0.7 2,2 v 7 c 0,1.3 -0.7,2 -2,2 H 8 v 1 h 5 c 2,0 3,-1 3,-3 z" />
<path fill="#ed6b21" d="M 3.5,9 C 3,9 3,9.5 3,10 v 2 H 1 C 0.5,12 0,12 0,12.5 0,13 0.5,13 1,13 h 2 v 2 c 0,0.5 0,1 0.5,1 C 4,16 4,15.5 4,15 V 13 H 6 C 6.5,13 7,13 7,12.5 7,12 6.5,12 6,12 H 4 V 10 C 4,9.5 4,9 3.5,9 Z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 491 B

View File

@ -1,4 +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 " />
<path fill="#808080" d="m 4,0 v 4 h 4 v 10 h 4 V 4 h 4 V 0 Z m 1.121094,1 h 1.414062 l 2,2 H 7.115235 Z M 7.242188,1 H 8.65625 L 11,3.34375 v 1.4140625 z m 2.121093,0 h 1.408205 l 1.998045,1.998 H 11.35547 Z m 2.121094,0 h 1.414063 l 1.992186,2 h -1.414061 z m 2.121094,0 H 15 V 2.3964375 Z M 5,1.59175 6.408203,3 H 5 Z m 4,1.8789062 2,1.9941407 V 6.8788594 L 9,4.8847188 Z M 9,5.59175 11,7.5858906 V 9 L 9,7 Z m 0,2.1210938 2,2.0000002 v 1.414063 L 9,9.126906 Z m 0,2.1210942 2,2 V 13 H 10.761152 L 9,11.248 Z M 9,12 10,13 H 9 Z" />
<path fill="#ed6b21" d="M 3.5 9 C 1.5741139 9 0 10.574114 0 12.5 C 0 14.425886 1.5741139 16 3.5 16 C 5.4258861 16 7 14.425886 7 12.5 C 7 10.574114 5.4258861 9 3.5 9 z M 3.5 10.199219 C 4.7773592 10.199219 5.8007813 11.222641 5.8007812 12.5 C 5.8007812 12.667974 5.7816922 12.830839 5.7480469 12.988281 L 3.0117188 10.251953 C 3.1691607 10.218308 3.3320257 10.199219 3.5 10.199219 z M 2.4863281 10.433594 L 5.5664062 13.513672 C 5.3371871 13.983352 4.9544913 14.362231 4.4804688 14.583984 L 1.4160156 11.521484 C 1.6376653 11.046724 2.0161177 10.663072 2.4863281 10.433594 z M 1.2421875 12.054688 L 3.9472656 14.757812 C 3.8024888 14.786013 3.6534389 14.800781 3.5 14.800781 C 2.2226408 14.800781 1.1992188 13.777359 1.1992188 12.5 C 1.1992188 12.347226 1.2142247 12.198881 1.2421875 12.054688 z " />
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,4 +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" />
<path fill="#808080" d="m 5,14 v 2 H 10.293 11 V 15.293 6 h 5 V 0 H 0 v 6 h 5 v 5 l 1,0 V 5 L 4,3 H 1.5 V 4 H 3.583984 L 4.587891,5 H 1 V 1 h 14 v 3.293 l -1,-1 V 1.5 H 13 V 3 H 8 V 13 H 7 v 1 h 1.293 l 1,1 H 6 V 14 Z M 9.707,4 h 3.586 l 1,1 H 10.707 Z M 9,4.707 l 1,1 v 8.586 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 6 C 6.5,13 7,13 7,12.5 7,12 6.5,12 6,12 Z" />
</svg>

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 503 B

View File

@ -1,4 +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" />
<path fill="#808080" d="M 6,11 V 7.5 H 5 V 11 Z M 5,13.5 7.53125,16 H 12 V 7 h 4 V 2.5 L 13.625,0 H 1 V 4.7128906 L 3.419922,7 H 7 v 7.257812 l -1,-1 z M 2.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 3.5,9 C 3,9 3,9.5 3,10 v 2 H 1 C 0.5,12 0,12 0,12.5 0,13 0.5,13 1,13 h 2 v 2 c 0,0.5 0,1 0.5,1 C 4,16 4,15.5 4,15 V 13 H 6 C 6.5,13 7,13 7,12.5 7,12 6.5,12 6,12 H 4 V 10 C 4,9.5 4,9 3.5,9 Z" />
</svg>

Before

Width:  |  Height:  |  Size: 583 B

After

Width:  |  Height:  |  Size: 592 B

4
resources/icons/burn.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 10.324265,12.637091 C 9.7126185,11.801826 9.4455652,11.319769 9.3860031,10.943437 9.3387131,10.644653 9.2775165,10.67725 9.032793,11.131535 8.8565876,11.45863 8.806364,11.904627 8.9008009,12.303679 8.330577,11.894019 7.6015175,10.031363 7.7096999,9.1284851 5.381808,10.598103 4.7271155,13.939753 5,16 2.9751051,14.608227 1.2851951,13.046842 1.3078808,10.57598 1.3305665,8.1051179 3.8432395,6.4490194 3.8697024,4.1713523 3.8727624,3.3745518 3.8421277,3.3793755 4.5118024,4.0701844 5.3014406,5.073513 5.8791344,5.964439 5.6521467,7.0731381 6.1486239,6.623741 6.3341015,5.940722 6.5401623,5.4070849 6.8689362,3.4222537 6.7021829,1.5642078 6,0 c 2.5998224,0.67409817 4.550997,3.5888298 4.623306,5.6887828 l 9.3e-4,0.4921817 C 11.260843,5.1624272 11.966175,4.3725405 13,4 12.774748,6.4272659 14.752236,8.539235 14.683585,10.819702 14.614934,13.100169 13.178608,15.41894 11,16 11.750123,14.792953 10.874074,13.393198 10.324265,12.637091 Z" />
<path fill="#ed6b21" d="M 9.032793,11.131535 C 8.8565876,11.45863 8.806364,11.904627 8.9008009,12.303679 8.2684185,12.02479 7.7175594,10.368983 7.6751289,9.4545414 7.7066738,9.2878597 7.7222018,9.1411316 7.7097002,9.1284851 8.5239771,6.6708553 5.893441,7.0819045 6.5401623,5.4070849 6.8689362,3.4222537 6.7021829,1.5642078 6,0 c 2.5998224,0.67409817 4.550997,3.5888298 4.623306,5.6887828 l 9.3e-4,0.4921817 c -0.04706,1.5271028 -0.04706,2.5643146 -1.591443,4.9505705 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,10 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="question">
<path fill="none" stroke="#ED6B21" stroke-width="2" stroke-linecap="round" stroke-miterlimit="10" d="M4,5c0,0,0,0,0-1s1-2,2-2
s3,0,4,0c2,0,2,2,2,3v1c0,1-1,2-2,2S9,8,9,8s1,0,0,0s-2,1-2,2s0,1,0,1"/>
<circle fill="#ED6B21" cx="7" cy="14" r="1"/>
</g>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#ED6B21" d="M 6,1 C 4.5,1 3,2.5 3,4 V 5 C 3,5.5 3.5,6 4,6 4.5,6 5,5.5 5,5 V 4 C 5,3.5 5.5,3 6,3 h 4 c 0.5,0 1,0.5 1,1 v 2 c 0,0.5 -0.5,1 -1,1 H 9 C 7.5,7 6,8.5 6,10 v 1 c 0,0.5 0.5,1 1,1 0.5,0 1,-0.5 1,-1 V 10 C 8,9.5 8.5,9 9,9 h 1 c 1.5,0 3,-1.5 3,-3 l 0,-2 C 13,2.5 11.5,1 10,1 Z"/>
<circle fill="#ED6B21" cx="7" cy="14" r="1" />
</svg>

Before

Width:  |  Height:  |  Size: 615 B

After

Width:  |  Height:  |  Size: 439 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 6,0 0,16 H 6 Z M 7,0 V 3 H 8 V 0 Z M 7,4 V 5 H 8 V 4 Z M 5,5.515625 V 15 H 1.4550781 Z M 7,6 v 4 H 8 V 6 Z m 0,5 v 1 h 1 v -1 z m 0,2 v 3 h 1 v -3 z" />
<path fill="#ed6b21" d="m 9,0 6,16 H 9 Z m 1,5.515625 V 15 h 3.545765 z" />
</svg>

After

Width:  |  Height:  |  Size: 350 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 16,6 0,0 v 6 z m 0,1 h -3 v 1 h 3 z m -4,0 h -1 v 1 h 1 z M 10.484375,5 H 1 V 1.4550781 Z M 10,7 H 6 v 1 h 4 z M 5,7 H 4 V 8 H 5 Z M 3,7 H 0 v 1 h 3 z" />
<path fill="#ed6b21" d="M 16,9 0,15 V 9 Z m -5.515625,1 H 1 v 3.545765 z" />
</svg>

After

Width:  |  Height:  |  Size: 353 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#ed6b21" d="m 0.80996126,4.2067338 c 0,0 1.45662384,-2.2486082 3.76076774,-3.45466511 2.3041438,-1.2060569 5.1439072,-0.85800021 7.005512,0.31082271 1.861604,1.1704417 2.488671,2.1563326 2.488671,2.1563326 l 1.603592,-0.9146606 c 0,0 0.331496,-0.1651245 0.331496,0.2185472 v 5.7388883 c 0,0 0,0.5115624 -0.387017,0.3286302 C 15.288019,8.4368366 11.764034,6.4326776 10.655236,5.801319 10.046132,5.5293491 10.581752,5.309183 10.581752,5.309183 l 1.548071,-0.8855209 c 0,0 -0.883446,-1.1056871 -2.1751379,-1.6917175 C 8.5715456,2.0147859 7.2765874,1.9289859 5.6909576,2.5279672 4.6572773,2.9181146 3.4390694,3.9185752 2.5621557,5.3966019 Z" />
<path fill="#808080" d="m 15.19004,11.792751 c 0,0 -1.456624,2.248608 -3.760768,3.454665 C 9.125128,16.453472 6.2853646,16.107035 4.4237602,14.936593 2.5621557,13.766151 1.935089,12.78026 1.935089,12.78026 l -1.60359274,0.913042 c 0,0 -0.3314962496,0.165125 -0.3314962496,-0.218547 V 7.7342477 c 0,0 0,-0.5115623 0.3870177796,-0.3286303 0.32496429,0.1537925 3.84894891,2.1579516 4.95774671,2.7893106 0.609104,0.271969 0.073485,0.492136 0.073485,0.492136 l -1.5480712,0.88552 c 0,0 0.8834456,1.105688 2.1751379,1.691718 1.3831395,0.720396 2.6780976,0.806196 4.2637278,0.207215 1.033681,-0.390147 2.251888,-1.390608 3.128802,-2.868635 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#ed6b21" d="M 3.5 9 C 1.5741139 9 0 10.574114 0 12.5 C 0 14.425886 1.5741139 16 3.5 16 C 5.4258861 16 7 14.425886 7 12.5 C 7 10.574114 5.4258861 9 3.5 9 z M 3.5 10.199219 C 4.7773592 10.199219 5.8007813 11.222641 5.8007812 12.5 C 5.8007812 12.667974 5.7816922 12.830839 5.7480469 12.988281 L 3.0117188 10.251953 C 3.1691607 10.218308 3.3320257 10.199219 3.5 10.199219 z M 2.4863281 10.433594 L 5.5664062 13.513672 C 5.3371871 13.983352 4.9544913 14.362231 4.4804688 14.583984 L 1.4160156 11.521484 C 1.6376653 11.046724 2.0161177 10.663072 2.4863281 10.433594 z M 1.2421875 12.054688 L 3.9472656 14.757812 C 3.8024888 14.786013 3.6534389 14.800781 3.5 14.800781 C 2.2226408 14.800781 1.1992188 13.777359 1.1992188 12.5 C 1.1992188 12.347226 1.2142247 12.198881 1.2421875 12.054688 z "/>
<path fill="#808080" d="M 6,0 C 4,0 3,1 3,3 V 8 H 4 V 3 C 4,1.7 4.7,1 6,1 h 7 c 1.3,0 2,0.7 2,2 h 1 C 16,1 15,0 13,0 Z M 6,4 5,5 V 6 L 6,7 H 7 V 8 H 5 V 9 H 7 L 8,8 V 7 L 7,6 H 6 V 5 H 8 V 4 Z m 3,0 v 4 h 1 V 4 Z m 1,4 v 1 h 1 V 8 Z m 1,0 h 1 V 4 H 11 Z M 14.5,4 C 13.5,4 13,4.6 13,5.5 v 2 C 13,8.4 13.7,9 14.5,9 H 16 V 7 H 15 V 8 H 14.5 C 14.3,8 14,7.8 14,7.5 v -2 C 14,5 14.3,5 14.5,5 H 16 V 4 Z m 0.5,6 c 0,1.3 -0.7,2 -2,2 H 8 v 1 h 5 c 2,0 3,-1 3,-3 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#ed6b21" d="M 1,12 C 0.5,12 0,12 0,12.5 0,13 0.5,13 1,13 H 6 C 6.5,13 7,13 7,12.5 7,12 6.5,12 6,12 Z" />
<path fill="#808080" d="M 6,0 C 4,0 3,1 3,3 V 8 H 4 V 3 C 4,1.7 4.7,1 6,1 h 7 c 1.3,0 2,0.7 2,2 h 1 C 16,1 15,0 13,0 Z M 6,4 5,5 V 6 L 6,7 H 7 V 8 H 5 V 9 H 7 L 8,8 V 7 L 7,6 H 6 V 5 H 8 V 4 Z m 3,0 v 4 h 1 V 4 Z m 1,4 v 1 h 1 V 8 Z m 1,0 h 1 V 4 H 11 Z M 14.5,4 C 13.5,4 13,4.6 13,5.5 v 2 C 13,8.4 13.7,9 14.5,9 H 16 V 7 H 15 V 8 H 14.5 C 14.3,8 14,7.8 14,7.5 v -2 C 14,5 14.3,5 14.5,5 H 16 V 4 Z m 0.5,6 c 0,1.3 -0.7,2 -2,2 H 8 v 1 h 5 c 2,0 3,-1 3,-3 z" />
</svg>

After

Width:  |  Height:  |  Size: 671 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
<path fill="#808080" d="M 6,0 C 4,0 3,1 3,3 V 8 H 4 V 3 C 4,1.7 4.7,1 6,1 h 7 c 1.3,0 2,0.7 2,2 h 1 C 16,1 15,0 13,0 Z M 6,4 5,5 V 6 L 6,7 H 7 V 8 H 5 V 9 H 7 L 8,8 V 7 L 7,6 H 6 V 5 H 8 V 4 Z m 3,0 v 4 h 1 V 4 Z m 1,4 v 1 h 1 V 8 Z m 1,0 h 1 V 4 H 11 Z M 14.5,4 C 13.5,4 13,4.6 13,5.5 v 2 C 13,8.4 13.7,9 14.5,9 H 16 V 7 H 15 V 8 H 14.5 C 14.3,8 14,7.8 14,7.5 v -2 C 14,5 14.3,5 14.5,5 H 16 V 4 Z m 0.5,6 c 0,1.3 -0.7,2 -2,2 H 8 v 1 h 5 c 2,0 3,-1 3,-3 z" />
<path fill="#ed6b21" d="M 3.5,9 C 3,9 3,9.5 3,10 v 2 H 1 C 0.5,12 0,12 0,12.5 0,13 0.5,13 1,13 h 2 v 2 c 0,0.5 0,1 0.5,1 C 4,16 4,15.5 4,15 V 13 H 6 C 6.5,13 7,13 7,12.5 7,12 6.5,12 6,12 H 4 V 10 C 4,9.5 4,9 3.5,9 Z" />
</svg>

After

Width:  |  Height:  |  Size: 774 B

View File

@ -44,7 +44,6 @@ src/slic3r/GUI/GCodeViewer.cpp
src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp
src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
@ -62,6 +61,7 @@ src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp
src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
src/slic3r/GUI/GLCanvas3D.cpp
src/slic3r/GUI/GUI.cpp

View File

@ -121,45 +121,77 @@ inline std::tuple<int, int> coordinate_aligned_ray_hit_count(size_t
}
template<typename LineType, typename TreeType, typename VectorType>
inline std::vector<std::pair<VectorType, size_t>> get_intersections_with_line(size_t node_idx,
const TreeType &tree,
const std::vector<LineType> &lines,
const LineType &line,
const typename TreeType::BoundingBox &line_bb)
inline void insert_intersections_with_line(std::vector<std::pair<VectorType, size_t>> &result,
size_t node_idx,
const TreeType &tree,
const std::vector<LineType> &lines,
const LineType &line,
const typename TreeType::BoundingBox &line_bb)
{
const auto &node = tree.node(node_idx);
assert(node.is_valid());
if (node.is_leaf()) {
VectorType intersection_pt;
if (line_alg::intersection(line, lines[node.idx], &intersection_pt)) {
return {std::pair<VectorType, size_t>(intersection_pt, node.idx)};
} else {
return {};
result.emplace_back(intersection_pt, node.idx);
}
} else {
size_t left_node_idx = node_idx * 2 + 1;
size_t right_node_idx = left_node_idx + 1;
const auto &node_left = tree.node(left_node_idx);
const auto &node_right = tree.node(right_node_idx);
assert(node_left.is_valid());
assert(node_right.is_valid());
std::vector<std::pair<VectorType, size_t>> result;
if (node_left.bbox.intersects(line_bb)) {
std::vector<std::pair<VectorType, size_t>> intersections =
get_intersections_with_line<LineType, TreeType, VectorType>(left_node_idx, tree, lines, line, line_bb);
result.insert(result.end(), intersections.begin(), intersections.end());
}
if (node_right.bbox.intersects(line_bb)) {
std::vector<std::pair<VectorType, size_t>> intersections =
get_intersections_with_line<LineType, TreeType, VectorType>(right_node_idx, tree, lines, line, line_bb);
result.insert(result.end(), intersections.begin(), intersections.end());
}
return result;
return;
}
size_t left_node_idx = node_idx * 2 + 1;
size_t right_node_idx = left_node_idx + 1;
const auto &node_left = tree.node(left_node_idx);
const auto &node_right = tree.node(right_node_idx);
assert(node_left.is_valid());
assert(node_right.is_valid());
if (node_left.bbox.intersects(line_bb)) {
insert_intersections_with_line<LineType, TreeType, VectorType>(result, left_node_idx, tree, lines, line, line_bb);
}
if (node_right.bbox.intersects(line_bb)) {
insert_intersections_with_line<LineType, TreeType, VectorType>(result, right_node_idx, tree, lines, line, line_bb);
}
//// NOTE: Non recursive implementation - for my case was slower ;-(
// std::vector<size_t> node_indicies_for_check; // evaluation queue
// size_t approx_size = static_cast<size_t>(std::ceil(std::sqrt(tree.nodes().size())));
// node_indicies_for_check.reserve(approx_size);
// do {
// const auto &node = tree.node(node_index);
// assert(node.is_valid());
// if (node.is_leaf()) {
// VectorType intersection_pt;
// if (line_alg::intersection(line, lines[node.idx], &intersection_pt))
// result.emplace_back(intersection_pt, node.idx);
// node_index = 0;// clear next node
// } else {
// size_t left_node_idx = node_index * 2 + 1;
// size_t right_node_idx = left_node_idx + 1;
// const auto &node_left = tree.node(left_node_idx);
// const auto &node_right = tree.node(right_node_idx);
// assert(node_left.is_valid());
// assert(node_right.is_valid());
// // Set next node index
// node_index = 0; // clear next node
// if (node_left.bbox.intersects(line_bb))
// node_index = left_node_idx;
// if (node_right.bbox.intersects(line_bb)) {
// if (node_index == 0)
// node_index = right_node_idx;
// else
// node_indicies_for_check.push_back(right_node_idx); // store for later evaluation
// }
// }
// if (node_index == 0 && !node_indicies_for_check.empty()) {
// // no direct next node take one from queue
// node_index = node_indicies_for_check.back();
// node_indicies_for_check.pop_back();
// }
//} while (node_index != 0);
}
} // namespace detail
@ -277,7 +309,9 @@ inline std::vector<std::pair<VectorType, size_t>> get_intersections_with_line(co
auto line_bb = typename TreeType::BoundingBox(line.a, line.a);
line_bb.extend(line.b);
auto intersections = detail::get_intersections_with_line<LineType, TreeType, VectorType>(0, tree, lines, line, line_bb);
std::vector<std::pair<VectorType, size_t>> intersections; // result
detail::insert_intersections_with_line(intersections, 0, tree, lines, line, line_bb);
if (sorted) {
using Floating =
typename std::conditional<std::is_floating_point<typename LineType::Scalar>::value, typename LineType::Scalar, double>::type;
@ -293,7 +327,6 @@ inline std::vector<std::pair<VectorType, size_t>> get_intersections_with_line(co
intersections[i] = points_with_sq_distance[i].second;
}
}
return intersections;
}

View File

@ -69,9 +69,11 @@ set(SLIC3R_SOURCES
ElephantFootCompensation.hpp
Emboss.cpp
Emboss.hpp
EmbossShape.hpp
enum_bitmask.hpp
ExPolygon.cpp
ExPolygon.hpp
ExPolygonSerialize.hpp
ExPolygonsIndex.cpp
ExPolygonsIndex.hpp
Extruder.cpp

View File

@ -282,8 +282,8 @@ bool has_duplicate_points(const ClipperLib::PolyTree &polytree)
// Offset CCW contours outside, CW contours (holes) inside.
// Don't calculate union of the output paths.
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
template<typename PathsProvider>
static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType endType = ClipperLib::etClosedPolygon)
{
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
@ -375,11 +375,11 @@ inline ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &inpu
return PolyTreeToExPolygons(clipper_union<ClipperLib::PolyTree>(input, do_union ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd));
}
template<typename PathsProvider, ClipperLib::EndType endType = ClipperLib::etClosedPolygon>
static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit)
template<typename PathsProvider>
static ClipperLib::Paths raw_offset_polyline(PathsProvider &&paths, float offset, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type = ClipperLib::etOpenButt)
{
assert(offset > 0);
return raw_offset<PathsProvider, ClipperLib::etOpenButt>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit);
return raw_offset<PathsProvider>(std::forward<PathsProvider>(paths), offset, joinType, miterLimit, end_type);
}
template<class TResult, typename PathsProvider>
@ -432,10 +432,17 @@ Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, Cli
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ return PolyTreeToExPolygons(offset_paths<ClipperLib::PolyTree>(ClipperUtils::PolygonsProvider(polygons), delta, joinType, miterLimit)); }
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit))); }
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit))); }
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit, end_type))); }
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit, end_type))); }
Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type, double miter_limit){
assert(line_width > 1.f); return to_polygons(clipper_union<ClipperLib::Paths>(
raw_offset(ClipperUtils::SinglePathProvider(polygon.points), line_width/2, join_type, miter_limit, ClipperLib::etClosedLine)));}
Polygons contour_to_polygons(const Polygons &polygons, const float line_width, ClipperLib::JoinType join_type, double miter_limit){
assert(line_width > 1.f); return to_polygons(clipper_union<ClipperLib::Paths>(
raw_offset(ClipperUtils::PolygonsProvider(polygons), line_width/2, join_type, miter_limit, ClipperLib::etClosedLine)));}
// returns number of expolygons collected (0 or 1).
static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out)

View File

@ -34,6 +34,9 @@ class BoundingBox;
static constexpr const float ClipperSafetyOffset = 10.f;
static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter;
static constexpr const Slic3r::ClipperLib::EndType DefaultEndType = Slic3r::ClipperLib::etOpenButt;
//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2.
// Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill.
// However such a high limit causes issues with large positive or negative offsets, where a sharp corner
@ -341,8 +344,8 @@ Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, Clipp
// offset Polylines
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
@ -354,6 +357,10 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
// convert stroke to path by offsetting of contour
Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit);
Polygons contour_to_polygons(const Polygons &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit);
inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); }
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); }

View File

@ -453,16 +453,21 @@ ProjectionDistances choose_best_distance(
/// </summary>
/// <param name="best_distances">For each point selected closest distance</param>
/// <param name="patches">All patches</param>
/// <param name="shapes">All patches</param>
/// <param name="shapes">Shape to cut</param>
/// <param name="shapes_bb">Bound of shapes</param>
/// <param name="s2i"></param>
/// <param name="cutAOIs"></param>
/// <param name="meshes"></param>
/// <param name="projection"></param>
/// <returns>Mask of used patch</returns>
std::vector<bool> select_patches(const ProjectionDistances &best_distances,
const SurfacePatches &patches,
const ExPolygons &shapes,
const ExPolygonsIndices &s2i,
const VCutAOIs &cutAOIs,
const CutMeshes &meshes,
const Project &projection);
const ExPolygons &shapes,
const BoundingBox &shapes_bb,
const ExPolygonsIndices &s2i,
const VCutAOIs &cutAOIs,
const CutMeshes &meshes,
const Project &projection);
/// <summary>
/// Merge two surface cuts together
@ -605,8 +610,7 @@ SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes,
// Use only outline points
// for each point select best projection
priv::ProjectionDistances best_projection = priv::choose_best_distance(distances, shapes, start, s2i, patches);
std::vector<bool> use_patch = priv::select_patches(best_projection, patches,
shapes, s2i,model_cuts, cgal_models, projection);
std::vector<bool> use_patch = priv::select_patches(best_projection, patches, shapes, shapes_bb, s2i, model_cuts, cgal_models, projection);
SurfaceCut result = merge_patches(patches, use_patch);
//*/
@ -1917,6 +1921,26 @@ uint32_t priv::find_closest_point_index(const Point &p,
const std::vector<bool> &mask)
{
SearchData sd = create_search_data(shapes, mask);
if (sd.tree.nodes().size() == 0){
// no lines in expolygon, check whether exist point to start
double closest_square_distance = INFINITY;
uint32_t closest_id = -1;
for (uint32_t i = 0; i < mask.size(); i++)
if (mask[i]){
ExPolygonsIndex ei = s2i.cvt(i);
const Point& s_p = ei.is_contour()?
shapes[ei.expolygons_index].contour[ei.point_index]:
shapes[ei.expolygons_index].holes[ei.hole_index()][ei.point_index];
double square_distance = (p - s_p).cast<double>().squaredNorm();
if (closest_id >= mask.size() ||
closest_square_distance > square_distance) {
closest_id = i;
closest_square_distance = square_distance;
}
}
assert(closest_id < mask.size());
return closest_id;
}
size_t line_idx = std::numeric_limits<size_t>::max();
Vec2d hit_point;
Vec2d p_d = p.cast<double>();
@ -2222,7 +2246,11 @@ priv::ProjectionDistances priv::choose_best_distance(
// Select point from shapes(text contour) which is closest to center (all in 2d)
uint32_t unfinished_index = find_closest_point_index(start, shapes, s2i, mask_distances);
assert(unfinished_index < s2i.get_count());
if (unfinished_index >= s2i.get_count())
// no point to select
return result;
#ifdef DEBUG_OUTPUT_DIR
Connections connections;
connections.reserve(shapes.size());
@ -3198,15 +3226,17 @@ bool priv::is_over_whole_expoly(const CutAOI &cutAOI,
std::vector<bool> priv::select_patches(const ProjectionDistances &best_distances,
const SurfacePatches &patches,
const ExPolygons &shapes,
const ExPolygonsIndices &s2i,
const VCutAOIs &cutAOIs,
const CutMeshes &meshes,
const Project &projection)
const ExPolygons &shapes,
const BoundingBox &shapes_bb,
const ExPolygonsIndices &s2i,
const VCutAOIs &cutAOIs,
const CutMeshes &meshes,
const Project &projection)
{
// extension to cover numerical mistake made by back projection patch from 3d to 2d
const float extend_delta = 5.f / Emboss::SHAPE_SCALE; // [Font points scaled by Emboss::SHAPE_SCALE]
// Calculated as one percent of average size(width and height)
Point s = shapes_bb.size();
const float extend_delta = (s.x() + s.y())/ float(2 * 100);
// vector of patches for shape
std::vector<std::vector<uint32_t>> used_shapes_patches(shapes.size());

File diff suppressed because it is too large Load Diff

View File

@ -12,21 +12,25 @@
#include <admesh/stl.h> // indexed_triangle_set
#include "Polygon.hpp"
#include "ExPolygon.hpp"
#include "EmbossShape.hpp" // ExPolygonsWithIds
#include "BoundingBox.hpp"
#include "TextConfiguration.hpp"
namespace Slic3r {
// Extend expolygons with information whether it was successfull healed
struct HealedExPolygons{
ExPolygons expolygons;
bool is_healed;
operator ExPolygons&() { return expolygons; }
};
/// <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>
@ -156,20 +160,23 @@ namespace Emboss
/// <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, const std::function<bool()> &was_canceled = []() {return false;});
std::vector<ExPolygons> text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function<bool()>& was_canceled = []() {return false;});
HealedExPolygons text2shapes (FontFileWithCache &font, const char *text, const FontProp &font_prop, const std::function<bool()> &was_canceled = []() {return false;});
ExPolygonsWithIds text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function<bool()>& was_canceled = []() {return false;});
const unsigned ENTER_UNICODE = static_cast<unsigned>('\n');
/// Sum of character '\n'
unsigned get_count_lines(const std::wstring &ws);
unsigned get_count_lines(const std::string &text);
unsigned get_count_lines(const ExPolygonsWithIds &shape);
/// <summary>
/// Fix duplicit points and self intersections in polygons.
/// Also try to reduce amount of points and remove useless polygon parts
/// </summary>
/// <param name="precision">Define wanted precision of shape after heal</param>
/// <returns>Healed shapes</returns>
ExPolygons heal_shape(const Polygons &shape);
/// <param name="is_non_zero">Fill type ClipperLib::pftNonZero for overlapping otherwise </param>
/// <param name="max_iteration">Look at heal_expolygon()::max_iteration</param>
/// <returns>Healed shapes with flag is fully healed</returns>
HealedExPolygons heal_polygons(const Polygons &shape, bool is_non_zero = true, unsigned max_iteration = 10);
/// <summary>
/// NOTE: call Slic3r::union_ex before this call
@ -183,7 +190,7 @@ namespace Emboss
/// <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);
bool heal_expolygons(ExPolygons &shape, unsigned max_iteration = 10);
/// <summary>
/// Divide line segments in place near to point
@ -199,10 +206,9 @@ namespace Emboss
/// <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="angle">Z-rotation as angle to Y axis</param>
/// <param name="distance">Z-move as surface distance</param>
/// <param name="transformation">In / Out transformation to modify by property</param>
void apply_transformation(const FontProp &font_prop, Transform3d &transformation);
void apply_transformation(const std::optional<float> &angle, const std::optional<float> &distance, Transform3d &transformation);
/// <summary>
@ -230,7 +236,7 @@ namespace Emboss
/// <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);
double get_text_shape_scale(const FontProp &fp, const FontFile &ff);
/// <summary>
/// getter of font info by collection defined in prop
@ -245,7 +251,7 @@ namespace Emboss
/// </summary>
/// <param name="font">Infos for collections</param>
/// <param name="prop">Collection index + Additional line gap</param>
/// <returns>Line height with spacing in ExPolygon size</returns>
/// <returns>Line height with spacing in scaled font points (same as ExPolygons)</returns>
int get_line_height(const FontFile &font, const FontProp &prop);
/// <summary>
@ -338,7 +344,7 @@ namespace Emboss
class ProjectZ : public IProjection
{
public:
ProjectZ(double depth) : m_depth(depth) {}
explicit ProjectZ(double depth) : m_depth(depth) {}
// Inherited via IProject
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override;
Vec3d project(const Vec3d &point) const override;
@ -462,5 +468,15 @@ namespace Emboss
std::vector<double> calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon);
} // namespace Emboss
///////////////////////
// Move to ExPolygonsWithIds Utils
void translate(ExPolygonsWithIds &e, const Point &p);
BoundingBox get_extents(const ExPolygonsWithIds &e);
void center(ExPolygonsWithIds &e);
HealedExPolygons union_ex(const ExPolygonsWithIds &shapes, unsigned max_heal_iteration);
// delta .. safe offset before union (use as boolean close)
// NOTE: remove unprintable spaces between neighbor curves (made by linearization of curve)
HealedExPolygons union_with_delta(const ExPolygonsWithIds &shapes, float delta, unsigned max_heal_iteration);
} // namespace Slic3r
#endif // slic3r_Emboss_hpp_

View File

@ -0,0 +1,134 @@
#ifndef slic3r_EmbossShape_hpp_
#define slic3r_EmbossShape_hpp_
#include <string>
#include <optional>
#include <memory> // unique_ptr
#include <cereal/cereal.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/optional.hpp>
#include <cereal/archives/binary.hpp>
#include "Point.hpp" // Transform3d
#include "ExPolygon.hpp"
#include "ExPolygonSerialize.hpp"
#include "nanosvg/nanosvg.h" // NSVGimage
namespace Slic3r {
struct EmbossProjection
{
// Emboss depth, Size in local Z direction
double depth = 1.; // [in loacal mm]
// NOTE: User should see and modify mainly world size not local
// Flag that result volume use surface cutted from source objects
bool use_surface = false;
// enum class Align {
// left,
// right,
// center,
// top_left,
// top_right,
// top_center,
// bottom_left,
// bottom_right,
// bottom_center
// };
//// change pivot of volume
//// When not set, center is used and is not stored
// std::optional<Align> align;
// compare TextStyle
bool operator==(const EmbossProjection &other) const {
return depth == other.depth && use_surface == other.use_surface;
}
// undo / redo stack recovery
template<class Archive> void serialize(Archive &ar) { ar(depth, use_surface); }
};
// Help structure to identify expolygons grups
// e.g. emboss -> per glyph -> identify character
struct ExPolygonsWithId
{
// Identificator for shape
// In text it separate letters and the name is unicode value of letter
// Is svg it is id of path
unsigned id;
// shape defined by integer point contain only lines
// Curves are converted to sequence of lines
ExPolygons expoly;
// flag whether expolygons are fully healed(without duplication)
bool is_healed = true;
};
using ExPolygonsWithIds = std::vector<ExPolygonsWithId>;
/// <summary>
/// Contain plane shape information to be able emboss it and edit it
/// </summary>
struct EmbossShape
{
// shapes to to emboss separately over surface
ExPolygonsWithIds shapes_with_ids;
// scale of shape, multiplier to get 3d point in mm from integer shape
double scale = SCALING_FACTOR;
// Define how to emboss shape
EmbossProjection projection;
// !!! Volume stored in .3mf has transformed vertices.
// (baked transformation into vertices position)
// Only place for fill this is when load from .3mf
// This is correction for volume transformation
// Stored_Transform3d * fix_3mf_tr = Transform3d_before_store_to_3mf
std::optional<Slic3r::Transform3d> fix_3mf_tr;
struct SvgFile {
// File(.svg) path on local computer
// When empty can't reload from disk
std::string path;
// File path into .3mf(.zip)
// When empty svg is not stored into .3mf file yet.
// and will create dialog to delete private data on save.
std::string path_in_3mf;
// Loaded svg file data.
// !!! It is not serialized on undo/redo stack
std::shared_ptr<NSVGimage> image = nullptr;
// Loaded string data from file
std::shared_ptr<std::string> file_data = nullptr;
};
SvgFile svg_file;
// flag whether during cration of union expolygon final shape was fully correct
// correct mean without selfintersection and duplicate(double) points
bool is_healed = true;
// undo / redo stack recovery
template<class Archive> void save(Archive &ar) const
{
ar(shapes_with_ids, scale, projection, svg_file.path, svg_file.path_in_3mf);
cereal::save(ar, fix_3mf_tr);
}
template<class Archive> void load(Archive &ar)
{
ar(shapes_with_ids, scale, projection, svg_file.path, svg_file.path_in_3mf);
cereal::load(ar, fix_3mf_tr);
}
};
} // namespace Slic3r
// Serialization through the Cereal library
namespace cereal {
template<class Archive> void serialize(Archive &ar, Slic3r::ExPolygonsWithId &o) { ar(o.id, o.expoly); }
}; // namespace cereal
#endif // slic3r_EmbossShape_hpp_

View File

@ -469,6 +469,24 @@ bool has_duplicate_points(const ExPolygons &expolys)
#endif
}
bool remove_same_neighbor(ExPolygons &expolygons)
{
if (expolygons.empty())
return false;
bool remove_from_holes = false;
bool remove_from_contour = false;
for (ExPolygon &expoly : expolygons) {
remove_from_contour |= remove_same_neighbor(expoly.contour);
remove_from_holes |= remove_same_neighbor(expoly.holes);
}
// Removing of expolygons without contour
if (remove_from_contour)
expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(),
[](const ExPolygon &p) { return p.contour.points.size() <= 2; }),
expolygons.end());
return remove_from_holes || remove_from_contour;
}
bool remove_sticks(ExPolygon &poly)
{
return remove_sticks(poly.contour) || remove_sticks(poly.holes);

View File

@ -383,6 +383,11 @@ inline Points to_points(const ExPolygon &expoly)
return out;
}
inline void translate(ExPolygons &expolys, const Point &p) {
for (ExPolygon &expoly : expolys)
expoly.translate(p);
}
inline void polygons_append(Polygons &dst, const ExPolygon &src)
{
dst.reserve(dst.size() + src.holes.size() + 1);
@ -472,6 +477,9 @@ std::vector<BoundingBox> get_extents_vector(const ExPolygons &polygons);
bool has_duplicate_points(const ExPolygon &expoly);
bool has_duplicate_points(const ExPolygons &expolys);
// Return True when erase some otherwise False.
bool remove_same_neighbor(ExPolygons &expolys);
bool remove_sticks(ExPolygon &poly);
void keep_largest_contour_only(ExPolygons &polygons);

View File

@ -0,0 +1,28 @@
#ifndef slic3r_ExPolygonSerialize_hpp_
#define slic3r_ExPolygonSerialize_hpp_
#include "ExPolygon.hpp"
#include "Point.hpp" // Cereal serialization of Point
#include <cereal/cereal.hpp>
#include <cereal/types/vector.hpp>
/// <summary>
/// External Cereal serialization of ExPolygons
/// </summary>
// Serialization through the Cereal library
#include <cereal/access.hpp>
namespace cereal {
template<class Archive>
void serialize(Archive &archive, Slic3r::Polygon &polygon) {
archive(polygon.points);
}
template<class Archive>
void serialize(Archive &archive, Slic3r::ExPolygon &expoly) {
archive(expoly.contour, expoly.holes);
}
} // namespace Slic3r
#endif // slic3r_ExPolygonSerialize_hpp_

View File

@ -41,6 +41,10 @@ namespace pt = boost::property_tree;
#include "miniz_extension.hpp"
#include "TextConfiguration.hpp"
#include "EmbossShape.hpp"
#include "ExPolygonSerialize.hpp"
#include "NSVGUtils.hpp"
#include <fast_float/fast_float.h>
@ -162,12 +166,8 @@ static constexpr const char *FONT_DESCRIPTOR_TYPE_ATTR = "font_descriptor_type";
static constexpr const char *CHAR_GAP_ATTR = "char_gap";
static constexpr const char *LINE_GAP_ATTR = "line_gap";
static constexpr const char *LINE_HEIGHT_ATTR = "line_height";
static constexpr const char *DEPTH_ATTR = "depth";
static constexpr const char *USE_SURFACE_ATTR = "use_surface";
static constexpr const char *BOLDNESS_ATTR = "boldness";
static constexpr const char *SKEW_ATTR = "skew";
static constexpr const char *DISTANCE_ATTR = "distance";
static constexpr const char *ANGLE_ATTR = "angle";
static constexpr const char *PER_GLYPH_ATTR = "per_glyph";
static constexpr const char *HORIZONTAL_ALIGN_ATTR = "horizontal";
static constexpr const char *VERTICAL_ALIGN_ATTR = "vertical";
@ -178,6 +178,19 @@ static constexpr const char *FONT_FACE_NAME_ATTR = "face_name";
static constexpr const char *FONT_STYLE_ATTR = "style";
static constexpr const char *FONT_WEIGHT_ATTR = "weight";
// Store / load of EmbossShape
static constexpr const char *SHAPE_TAG = "slic3rpe:shape";
static constexpr const char *SHAPE_SCALE_ATTR = "scale";
static constexpr const char *UNHEALED_ATTR = "unhealed";
static constexpr const char *SVG_FILE_PATH_ATTR = "filepath";
static constexpr const char *SVG_FILE_PATH_IN_3MF_ATTR = "filepath3mf";
// EmbossProjection
static constexpr const char *DEPTH_ATTR = "depth";
static constexpr const char *USE_SURFACE_ATTR = "use_surface";
// static constexpr const char *FIX_TRANSFORMATION_ATTR = "transform";
const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
const char* VALID_OBJECT_TYPES[] =
{
@ -424,6 +437,7 @@ namespace Slic3r {
MetadataList metadata;
RepairedMeshErrors mesh_stats;
std::optional<TextConfiguration> text_configuration;
std::optional<EmbossShape> shape_configuration;
VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id)
: first_triangle_id(first_triangle_id)
, last_triangle_id(last_triangle_id)
@ -461,7 +475,7 @@ namespace Slic3r {
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;
using PathToEmbossShapeFileMap = std::map<std::string, std::shared_ptr<std::string>>;
// Version of the 3mf file
unsigned int m_version;
bool m_check_version;
@ -491,6 +505,7 @@ namespace Slic3r {
IdToLayerConfigRangesMap m_layer_config_ranges;
IdToSlaSupportPointsMap m_sla_support_points;
IdToSlaDrainHolesMap m_sla_drain_holes;
PathToEmbossShapeFileMap m_path_to_emboss_shape_files;
std::string m_curr_metadata_name;
std::string m_curr_characters;
std::string m_name;
@ -517,6 +532,7 @@ namespace Slic3r {
}
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions);
bool _is_svg_shape_file(const std::string &filename) const;
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
@ -528,6 +544,7 @@ namespace Slic3r {
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename);
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
void _extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat);
// handlers to parse the .model file
void _handle_start_model_xml_element(const char* name, const char** attributes);
@ -578,6 +595,7 @@ namespace Slic3r {
bool _handle_end_metadata();
bool _handle_start_text_configuration(const char** attributes, unsigned int num_attributes);
bool _handle_start_shape_configuration(const char **attributes, unsigned int num_attributes);
bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
@ -754,6 +772,9 @@ namespace Slic3r {
add_error("Archive does not contain a valid model config");
return false;
}
}
else if (_is_svg_shape_file(name)) {
_extract_embossed_svg_shape_file(name, archive, stat);
}
}
}
@ -929,6 +950,10 @@ namespace Slic3r {
return true;
}
bool _3MF_Importer::_is_svg_shape_file(const std::string &name) const {
return boost::starts_with(name, MODEL_FOLDER) && boost::ends_with(name, ".svg");
}
bool _3MF_Importer::_extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
{
if (stat.m_uncomp_size == 0) {
@ -1361,6 +1386,29 @@ namespace Slic3r {
}
}
void _3MF_Importer::_extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat){
assert(m_path_to_emboss_shape_files.find(filename) == m_path_to_emboss_shape_files.end());
auto file = std::make_unique<std::string>(stat.m_uncomp_size, '\0');
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void *) file->data(), stat.m_uncomp_size, 0);
if (res == 0) {
add_error("Error while reading svg shape for emboss");
return;
}
// store for case svg is loaded before volume
m_path_to_emboss_shape_files[filename] = std::move(file);
// find embossed volume, for case svg is loaded after volume
for (const ModelObject* object : m_model->objects)
for (ModelVolume *volume : object->volumes) {
std::optional<EmbossShape> &es = volume->emboss_shape;
if (!es.has_value())
continue;
if (filename.compare(es->svg_file.path_in_3mf) == 0)
es->svg_file.file_data = m_path_to_emboss_shape_files[filename];
}
}
bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
{
if (stat.m_uncomp_size == 0) {
@ -1560,6 +1608,8 @@ namespace Slic3r {
res = _handle_start_config_volume_mesh(attributes, num_attributes);
else if (::strcmp(METADATA_TAG, name) == 0)
res = _handle_start_config_metadata(attributes, num_attributes);
else if (::strcmp(SHAPE_TAG, name) == 0)
res = _handle_start_shape_configuration(attributes, num_attributes);
else if (::strcmp(TEXT_TAG, name) == 0)
res = _handle_start_text_configuration(attributes, num_attributes);
@ -1912,8 +1962,15 @@ namespace Slic3r {
{
public:
TextConfigurationSerialization() = delete;
using TypeToName = boost::bimap<EmbossStyle::Type, std::string_view>;
static const TypeToName type_to_name;
static const boost::bimap<EmbossStyle::Type, std::string_view> type_to_name;
using HorizontalAlignToName = boost::bimap<FontProp::HorizontalAlign, std::string_view>;
static const HorizontalAlignToName horizontal_align_to_name;
using VerticalAlignToName = boost::bimap<FontProp::VerticalAlign, std::string_view>;
static const VerticalAlignToName vertical_align_to_name;
static EmbossStyle::Type get_type(std::string_view type) {
const auto& to_type = TextConfigurationSerialization::type_to_name.right;
@ -1932,24 +1989,68 @@ namespace Slic3r {
}
static void to_xml(std::stringstream &stream, const TextConfiguration &tc);
static void create_fix_and_store(std::stringstream &stream, TextConfiguration tc, const ModelVolume& volume);
static std::optional<TextConfiguration> read(const char **attributes, unsigned int num_attributes);
static EmbossShape read_old(const char **attributes, unsigned int num_attributes);
};
bool _3MF_Importer::_handle_start_text_configuration(const char **attributes, unsigned int num_attributes)
{
IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id);
if (object == m_objects_metadata.end()) {
add_error("Cannot assign volume mesh to a valid object");
add_error("Can not assign volume mesh to a valid object");
return false;
}
if (object->second.volumes.empty()) {
add_error("Cannot assign mesh to a valid volume");
add_error("Can not assign mesh to a valid volume");
return false;
}
ObjectMetadata::VolumeMetadata& volume = object->second.volumes.back();
volume.text_configuration = TextConfigurationSerialization::read(attributes, num_attributes);
return volume.text_configuration.has_value();
if (!volume.text_configuration.has_value())
return false;
// Is 3mf version with shapes?
if (volume.shape_configuration.has_value())
return true;
// Back compatibility for 3mf version without shapes
volume.shape_configuration = TextConfigurationSerialization::read_old(attributes, num_attributes);
return true;
}
// Definition of read/write method for EmbossShape
static void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive);
static std::optional<EmbossShape> read_emboss_shape(const char **attributes, unsigned int num_attributes);
bool _3MF_Importer::_handle_start_shape_configuration(const char **attributes, unsigned int num_attributes)
{
IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id);
if (object == m_objects_metadata.end()) {
add_error("Can not assign volume mesh to a valid object");
return false;
}
auto &volumes = object->second.volumes;
if (volumes.empty()) {
add_error("Can not assign mesh to a valid volume");
return false;
}
ObjectMetadata::VolumeMetadata &volume = volumes.back();
volume.shape_configuration = read_emboss_shape(attributes, num_attributes);
if (!volume.shape_configuration.has_value())
return false;
// Fill svg file content into shape_configuration
EmbossShape::SvgFile &svg = volume.shape_configuration->svg_file;
const std::string &path = svg.path_in_3mf;
if (path.empty()) // do not contain svg file
return true;
auto it = m_path_to_emboss_shape_files.find(path);
if (it == m_path_to_emboss_shape_files.end())
return true; // svg file is not loaded yet
svg.file_data = it->second;
return true;
}
bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
@ -2232,22 +2333,11 @@ namespace Slic3r {
volume->supported_facets.shrink_to_fit();
volume->seam_facets.shrink_to_fit();
volume->mmu_segmentation_facets.shrink_to_fit();
auto &tc = volume_data.text_configuration;
if (tc.has_value()) {
if (auto &es = volume_data.shape_configuration; es.has_value())
volume->emboss_shape = std::move(es);
if (auto &tc = volume_data.text_configuration; tc.has_value())
volume->text_configuration = std::move(tc);
//// Transformation before store to 3mf
//const Transform3d &pre_trmat = *tc->fix_3mf_tr;
//// Cannot use source tranformation
//// When store transformed againg to 3mf it is not modified !!!
//// const Transform3d &pre_trmat = volume->source.transform.get_matrix();
//// create fix transformation
//assert(tc->fix_3mf_tr.has_value());
//volume->text_configuration->fix_3mf_tr =
// pre_trmat.inverse() *
// volume->get_transformation().get_matrix();
}
// apply the remaining volume's metadata
for (const Metadata& metadata : volume_data.metadata) {
@ -2379,6 +2469,7 @@ namespace Slic3r {
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64);
static void add_transformation(std::stringstream &stream, const Transform3d &tr);
private:
void _publish(Model &model);
bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data);
bool _add_content_types_file_to_archive(mz_zip_archive& archive);
bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data);
@ -3322,11 +3413,14 @@ namespace Slic3r {
for (const std::string& key : volume->config.keys()) {
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n";
}
// stores volume's text data
const auto &tc = volume->text_configuration;
if (tc.has_value())
TextConfigurationSerialization::create_fix_and_store(stream, *tc, *volume);
if (const std::optional<EmbossShape> &es = volume->emboss_shape;
es.has_value())
to_xml(stream, *es, *volume, archive);
if (const std::optional<TextConfiguration> &tc = volume->text_configuration;
tc.has_value())
TextConfigurationSerialization::to_xml(stream, *tc);
// stores mesh's statistics
const RepairedMeshErrors& stats = volume->mesh().stats().repaired_errors;
@ -3497,27 +3591,74 @@ bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config,
return res;
}
namespace{
// Conversion with bidirectional map
// F .. first, S .. second
template<typename F, typename S>
F bimap_cvt(const boost::bimap<F, S> &bmap, S s, const F & def_value) {
const auto &map = bmap.right;
auto found_item = map.find(s);
// only for back and forward compatibility
assert(found_item != map.end());
if (found_item == map.end())
return def_value;
return found_item->second;
}
template<typename F, typename S>
S bimap_cvt(const boost::bimap<F, S> &bmap, F f, const S &def_value)
{
const auto &map = bmap.left;
auto found_item = map.find(f);
// only for back and forward compatibility
assert(found_item != map.end());
if (found_item == map.end())
return def_value;
return found_item->second;
}
} // namespace
/// <summary>
/// TextConfiguration serialization
/// </summary>
using TypeToName = boost::bimap<EmbossStyle::Type, std::string_view>;
const TypeToName TextConfigurationSerialization::type_to_name =
boost::assign::list_of<TypeToName::relation>
const TextConfigurationSerialization::TypeToName TextConfigurationSerialization::type_to_name =
boost::assign::list_of<TypeToName::relation>
(EmbossStyle::Type::file_path, "file_name")
(EmbossStyle::Type::wx_win_font_descr, "wxFontDescriptor_Windows")
(EmbossStyle::Type::wx_lin_font_descr, "wxFontDescriptor_Linux")
(EmbossStyle::Type::wx_mac_font_descr, "wxFontDescriptor_MacOsX");
const TextConfigurationSerialization::HorizontalAlignToName TextConfigurationSerialization::horizontal_align_to_name =
boost::assign::list_of<HorizontalAlignToName::relation>
(FontProp::HorizontalAlign::left, "left")
(FontProp::HorizontalAlign::center, "center")
(FontProp::HorizontalAlign::right, "right");
const TextConfigurationSerialization::VerticalAlignToName TextConfigurationSerialization::vertical_align_to_name =
boost::assign::list_of<VerticalAlignToName::relation>
(FontProp::VerticalAlign::top, "top")
(FontProp::VerticalAlign::center, "middle")
(FontProp::VerticalAlign::bottom, "bottom");
void TextConfigurationSerialization::to_xml(std::stringstream &stream, const TextConfiguration &tc)
{
stream << " <" << TEXT_TAG << " ";
stream << TEXT_DATA_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(tc.text) << "\" ";
// font item
const EmbossStyle &fi = tc.style;
stream << STYLE_NAME_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(fi.name) << "\" ";
stream << FONT_DESCRIPTOR_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(fi.path) << "\" ";
stream << FONT_DESCRIPTOR_TYPE_ATTR << "=\"" << TextConfigurationSerialization::get_name(fi.type) << "\" ";
const EmbossStyle &style = tc.style;
stream << STYLE_NAME_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(style.name) << "\" ";
stream << FONT_DESCRIPTOR_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(style.path) << "\" ";
constexpr std::string_view dafault_type{"undefined"};
std::string_view style_type = bimap_cvt(type_to_name, style.type, dafault_type);
stream << FONT_DESCRIPTOR_TYPE_ATTR << "=\"" << style_type << "\" ";
// font property
const FontProp &fp = tc.style.prop;
@ -3527,21 +3668,14 @@ void TextConfigurationSerialization::to_xml(std::stringstream &stream, const Tex
stream << LINE_GAP_ATTR << "=\"" << *fp.line_gap << "\" ";
stream << LINE_HEIGHT_ATTR << "=\"" << fp.size_in_mm << "\" ";
stream << DEPTH_ATTR << "=\"" << fp.emboss << "\" ";
if (fp.use_surface)
stream << USE_SURFACE_ATTR << "=\"" << 1 << "\" ";
if (fp.boldness.has_value())
stream << BOLDNESS_ATTR << "=\"" << *fp.boldness << "\" ";
if (fp.skew.has_value())
stream << SKEW_ATTR << "=\"" << *fp.skew << "\" ";
if (fp.distance.has_value())
stream << DISTANCE_ATTR << "=\"" << *fp.distance << "\" ";
if (fp.angle.has_value())
stream << ANGLE_ATTR << "=\"" << *fp.angle << "\" ";
if (fp.per_glyph)
stream << PER_GLYPH_ATTR << "=\"" << 1 << "\" ";
stream << HORIZONTAL_ALIGN_ATTR << "=\"" << static_cast<int>(fp.align.first) << "\" ";
stream << VERTICAL_ALIGN_ATTR << "=\"" << static_cast<int>(fp.align.second) << "\" ";
stream << HORIZONTAL_ALIGN_ATTR << "=\"" << bimap_cvt(horizontal_align_to_name, fp.align.first, dafault_type) << "\" ";
stream << VERTICAL_ALIGN_ATTR << "=\"" << bimap_cvt(vertical_align_to_name, fp.align.second, dafault_type) << "\" ";
if (fp.collection_number.has_value())
stream << COLLECTION_NUMBER_ATTR << "=\"" << *fp.collection_number << "\" ";
// font descriptor
@ -3554,52 +3688,51 @@ void TextConfigurationSerialization::to_xml(std::stringstream &stream, const Tex
if (fp.weight.has_value())
stream << FONT_WEIGHT_ATTR << "=\"" << *fp.weight << "\" ";
// FIX of baked transformation
assert(tc.fix_3mf_tr.has_value());
stream << TRANSFORM_ATTR << "=\"";
_3MF_Exporter::add_transformation(stream, *tc.fix_3mf_tr);
stream << "\" ";
stream << "/>\n"; // end TEXT_TAG
}
namespace {
void TextConfigurationSerialization::create_fix_and_store(
std::stringstream &stream, TextConfiguration tc, const ModelVolume &volume)
{
const auto& vertices = volume.mesh().its.vertices;
assert(!vertices.empty());
if (vertices.empty()) {
to_xml(stream, tc);
return;
FontProp::HorizontalAlign read_horizontal_align(const char **attributes, unsigned int num_attributes, const TextConfigurationSerialization::HorizontalAlignToName& horizontal_align_to_name){
std::string horizontal_align_str = get_attribute_value_string(attributes, num_attributes, HORIZONTAL_ALIGN_ATTR);
// Back compatibility
// PS 2.6.0 do not have align
if (horizontal_align_str.empty())
return FontProp::HorizontalAlign::center;
// Back compatibility
// PS 2.6.1 store indices(0|1|2) instead of text for align
if (horizontal_align_str.length() == 1) {
int horizontal_align_int = 0;
if(boost::spirit::qi::parse(horizontal_align_str.c_str(), horizontal_align_str.c_str() + 1, boost::spirit::qi::int_, horizontal_align_int))
return static_cast<FontProp::HorizontalAlign>(horizontal_align_int);
}
// IMPROVE: check if volume was modified (translated, rotated OR scaled)
// when no change do not calculate transformation only store original fix matrix
// Create transformation used after load actual stored volume
const Transform3d &actual_trmat = volume.get_transformation().get_matrix();
Vec3d min = actual_trmat * vertices.front().cast<double>();
Vec3d max = min;
for (const Vec3f &v : vertices) {
Vec3d vd = actual_trmat * v.cast<double>();
for (size_t i = 0; i < 3; ++i) {
if (min[i] > vd[i]) min[i] = vd[i];
if (max[i] < vd[i]) max[i] = vd[i];
}
}
Vec3d center = (max + min) / 2;
Transform3d post_trmat = Transform3d::Identity();
post_trmat.translate(center);
Transform3d fix_trmat = actual_trmat.inverse() * post_trmat;
if (!tc.fix_3mf_tr.has_value()) {
tc.fix_3mf_tr = fix_trmat;
} else if (!fix_trmat.isApprox(Transform3d::Identity(), 1e-5)) {
tc.fix_3mf_tr = *tc.fix_3mf_tr * fix_trmat;
}
to_xml(stream, tc);
return bimap_cvt(horizontal_align_to_name, std::string_view(horizontal_align_str), FontProp::HorizontalAlign::center);
}
FontProp::VerticalAlign read_vertical_align(const char **attributes, unsigned int num_attributes, const TextConfigurationSerialization::VerticalAlignToName& vertical_align_to_name){
std::string vertical_align_str = get_attribute_value_string(attributes, num_attributes, VERTICAL_ALIGN_ATTR);
// Back compatibility
// PS 2.6.0 do not have align
if (vertical_align_str.empty())
return FontProp::VerticalAlign::center;
// Back compatibility
// PS 2.6.1 store indices(0|1|2) instead of text for align
if (vertical_align_str.length() == 1) {
int vertical_align_int = 0;
if(boost::spirit::qi::parse(vertical_align_str.c_str(), vertical_align_str.c_str() + 1, boost::spirit::qi::int_, vertical_align_int))
return static_cast<FontProp::VerticalAlign>(vertical_align_int);
}
return bimap_cvt(vertical_align_to_name, std::string_view(vertical_align_str), FontProp::VerticalAlign::center);
}
} // namespace
std::optional<TextConfiguration> TextConfigurationSerialization::read(const char **attributes, unsigned int num_attributes)
{
FontProp fp;
@ -3613,28 +3746,17 @@ std::optional<TextConfiguration> TextConfigurationSerialization::read(const char
float skew = get_attribute_value_float(attributes, num_attributes, SKEW_ATTR);
if (std::fabs(skew) > std::numeric_limits<float>::epsilon())
fp.skew = skew;
float distance = get_attribute_value_float(attributes, num_attributes, DISTANCE_ATTR);
if (std::fabs(distance) > std::numeric_limits<float>::epsilon())
fp.distance = distance;
int use_surface = get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR);
if (use_surface == 1) fp.use_surface = true;
float angle = get_attribute_value_float(attributes, num_attributes, ANGLE_ATTR);
if (std::fabs(angle) > std::numeric_limits<float>::epsilon())
fp.angle = angle;
int per_glyph = get_attribute_value_int(attributes, num_attributes, PER_GLYPH_ATTR);
if (per_glyph == 1) fp.per_glyph = true;
int horizontal = get_attribute_value_int(attributes, num_attributes, HORIZONTAL_ALIGN_ATTR);
int vertical = get_attribute_value_int(attributes, num_attributes, VERTICAL_ALIGN_ATTR);
fp.align = FontProp::Align(
static_cast<FontProp::HorizontalAlign>(horizontal),
static_cast<FontProp::VerticalAlign>(vertical));
fp.align = FontProp::Align(
read_horizontal_align(attributes, num_attributes, horizontal_align_to_name),
read_vertical_align(attributes, num_attributes, vertical_align_to_name));
int collection_number = get_attribute_value_int(attributes, num_attributes, COLLECTION_NUMBER_ATTR);
if (collection_number > 0) fp.collection_number = static_cast<unsigned int>(collection_number);
fp.size_in_mm = get_attribute_value_float(attributes, num_attributes, LINE_HEIGHT_ATTR);
fp.emboss = get_attribute_value_float(attributes, num_attributes, DEPTH_ATTR);
std::string family = get_attribute_value_string(attributes, num_attributes, FONT_FAMILY_ATTR);
if (!family.empty()) fp.family = family;
@ -3648,10 +3770,123 @@ std::optional<TextConfiguration> TextConfigurationSerialization::read(const char
std::string style_name = get_attribute_value_string(attributes, num_attributes, STYLE_NAME_ATTR);
std::string font_descriptor = get_attribute_value_string(attributes, num_attributes, FONT_DESCRIPTOR_ATTR);
std::string type_str = get_attribute_value_string(attributes, num_attributes, FONT_DESCRIPTOR_TYPE_ATTR);
EmbossStyle::Type type = TextConfigurationSerialization::get_type(type_str);
EmbossStyle fi{ style_name, std::move(font_descriptor), type, std::move(fp) };
EmbossStyle::Type type = bimap_cvt(type_to_name, std::string_view{type_str}, EmbossStyle::Type::undefined);
std::string text = get_attribute_value_string(attributes, num_attributes, TEXT_DATA_ATTR);
EmbossStyle es{style_name, std::move(font_descriptor), type, std::move(fp)};
return TextConfiguration{std::move(es), std::move(text)};
}
EmbossShape TextConfigurationSerialization::read_old(const char **attributes, unsigned int num_attributes)
{
EmbossShape es;
std::string fix_tr_mat_str = get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR);
if (!fix_tr_mat_str.empty())
es.fix_3mf_tr = get_transform_from_3mf_specs_string(fix_tr_mat_str);
if (get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR) == 1)
es.projection.use_surface = true;
es.projection.depth = get_attribute_value_float(attributes, num_attributes, DEPTH_ATTR);
int use_surface = get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR);
if (use_surface == 1)
es.projection.use_surface = true;
return es;
}
namespace {
Transform3d create_fix(const std::optional<Transform3d> &prev, const ModelVolume &volume)
{
// IMPROVE: check if volume was modified (translated, rotated OR scaled)
// when no change do not calculate transformation only store original fix matrix
// Create transformation used after load actual stored volume
const Transform3d &actual_trmat = volume.get_matrix();
const auto &vertices = volume.mesh().its.vertices;
Vec3d min = actual_trmat * vertices.front().cast<double>();
Vec3d max = min;
for (const Vec3f &v : vertices) {
Vec3d vd = actual_trmat * v.cast<double>();
for (size_t i = 0; i < 3; ++i) {
if (min[i] > vd[i])
min[i] = vd[i];
if (max[i] < vd[i])
max[i] = vd[i];
}
}
Vec3d center = (max + min) / 2;
Transform3d post_trmat = Transform3d::Identity();
post_trmat.translate(center);
Transform3d fix_trmat = actual_trmat.inverse() * post_trmat;
if (!prev.has_value())
return fix_trmat;
// check whether fix somehow differ previous
if (fix_trmat.isApprox(Transform3d::Identity(), 1e-5))
return *prev;
return *prev * fix_trmat;
}
bool to_xml(std::stringstream &stream, const EmbossShape::SvgFile &svg, const ModelVolume &volume, mz_zip_archive &archive){
if (svg.path_in_3mf.empty())
return true; // EmbossedText OR unwanted store .svg file into .3mf (protection of copyRight)
if (!svg.path.empty())
stream << SVG_FILE_PATH_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path) << "\" ";
stream << SVG_FILE_PATH_IN_3MF_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path_in_3mf) << "\" ";
const std::string &file_data = *svg.file_data;
return mz_zip_writer_add_mem(&archive, svg.path_in_3mf.c_str(),
(const void *) file_data.c_str(), file_data.size(), MZ_DEFAULT_COMPRESSION);
}
} // namespace
void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive)
{
stream << " <" << SHAPE_TAG << " ";
if(!to_xml(stream, es.svg_file, volume, archive))
BOOST_LOG_TRIVIAL(warning) << "Can't write svg file defiden embossed shape into 3mf";
stream << SHAPE_SCALE_ATTR << "=\"" << es.scale << "\" ";
if (!es.is_healed)
stream << UNHEALED_ATTR << "=\"" << 1 << "\" ";
// projection
const EmbossProjection &p = es.projection;
stream << DEPTH_ATTR << "=\"" << p.depth << "\" ";
if (p.use_surface)
stream << USE_SURFACE_ATTR << "=\"" << 1 << "\" ";
// FIX of baked transformation
Transform3d fix = create_fix(es.fix_3mf_tr, volume);
stream << TRANSFORM_ATTR << "=\"";
_3MF_Exporter::add_transformation(stream, fix);
stream << "\" ";
stream << "/>\n"; // end SHAPE_TAG
}
std::optional<EmbossShape> read_emboss_shape(const char **attributes, unsigned int num_attributes) {
double scale = get_attribute_value_float(attributes, num_attributes, SHAPE_SCALE_ATTR);
int unhealed = get_attribute_value_int(attributes, num_attributes, UNHEALED_ATTR);
bool is_healed = unhealed != 1;
EmbossProjection projection;
projection.depth = get_attribute_value_float(attributes, num_attributes, DEPTH_ATTR);
if (is_approx(projection.depth, 0.))
projection.depth = 10.;
int use_surface = get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR);
if (use_surface == 1)
projection.use_surface = true;
std::optional<Transform3d> fix_tr_mat;
std::string fix_tr_mat_str = get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR);
@ -3659,7 +3894,12 @@ std::optional<TextConfiguration> TextConfigurationSerialization::read(const char
fix_tr_mat = get_transform_from_3mf_specs_string(fix_tr_mat_str);
}
return TextConfiguration{std::move(fi), std::move(text), std::move(fix_tr_mat)};
std::string file_path = get_attribute_value_string(attributes, num_attributes, SVG_FILE_PATH_ATTR);
std::string file_path_3mf = get_attribute_value_string(attributes, num_attributes, SVG_FILE_PATH_IN_3MF_ATTR);
ExPolygonsWithIds shapes; // TODO: need to implement
EmbossShape::SvgFile svg{file_path, file_path_3mf};
return EmbossShape{shapes, scale, std::move(projection), std::move(fix_tr_mat), std::move(svg), is_healed};
}

View File

@ -316,6 +316,13 @@ template<typename T> T angle_to_0_2PI(T angle)
return angle;
}
template<typename T> void to_range_pi_pi(T &angle){
if (angle > T(PI) || angle <= -T(PI)) {
int count = static_cast<int>(std::round(angle / (2 * PI)));
angle -= static_cast<T>(count * 2 * PI);
assert(angle <= T(PI) && angle > -T(PI));
}
}
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval);

View File

@ -3,173 +3,47 @@
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "IntersectionPoints.hpp"
#include <libslic3r/AABBTreeLines.hpp>
//#define USE_CGAL_SWEEP_LINE
#ifdef USE_CGAL_SWEEP_LINE
//NOTE: using CGAL SweepLines is slower !!! (example in git history)
#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)
namespace {
using namespace Slic3r;
IntersectionsLines compute_intersections(const Lines &lines)
{
return Slic3r::Vec2d(CGAL::to_double(p.x()), CGAL::to_double(p.y()));
}
if (lines.size() < 3)
return {};
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;
}
auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
IntersectionsLines result;
for (uint32_t li = 0; li < lines.size()-1; ++li) {
const Line &l = lines[li];
auto intersections = AABBTreeLines::get_intersections_with_line<false, Point, Line>(lines, tree, l);
for (const auto &[p, node_index] : intersections) {
if (node_index - 1 <= li)
continue;
if (const Line &l_ = lines[node_index];
l_.a == l.a ||
l_.a == l.b ||
l_.b == l.a ||
l_.b == l.b )
// it is duplicit point not intersection
continue;
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);
}
// NOTE: fix AABBTree to compute intersection with double preccission!!
Vec2d intersection_point = p.cast<double>();
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 {
//FIXME O(n^2) complexity!
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.overlap(bb_) &&
l.intersection(l_, &i))
pts.push_back(i.cast<double>());
result.push_back(IntersectionLines{li, static_cast<uint32_t>(node_index), intersection_point});
}
}
return pts;
return result;
}
} // namespace priv
} // namespace
Slic3r::Pointfs Slic3r::intersection_points(const Lines &lines)
{
return priv::compute_intersections(lines);
namespace Slic3r {
IntersectionsLines get_intersections(const Lines &lines) { return compute_intersections(lines); }
IntersectionsLines get_intersections(const Polygon &polygon) { return compute_intersections(to_lines(polygon)); }
IntersectionsLines get_intersections(const Polygons &polygons) { return compute_intersections(to_lines(polygons)); }
IntersectionsLines get_intersections(const ExPolygon &expolygon) { return compute_intersections(to_lines(expolygon)); }
IntersectionsLines get_intersections(const ExPolygons &expolygons) { return compute_intersections(to_lines(expolygons)); }
}
Slic3r::Pointfs Slic3r::intersection_points(const Polygon &polygon)
{
return priv::compute_intersections(to_lines(polygon));
}
Slic3r::Pointfs Slic3r::intersection_points(const Polygons &polygons)
{
return priv::compute_intersections(to_lines(polygons));
}
Slic3r::Pointfs Slic3r::intersection_points(const ExPolygon &expolygon)
{
return priv::compute_intersections(to_lines(expolygon));
}
Slic3r::Pointfs Slic3r::intersection_points(const ExPolygons &expolygons)
{
return priv::compute_intersections(to_lines(expolygons));
}
#endif // USE_CGAL_SWEEP_LINE

View File

@ -9,13 +9,19 @@
namespace Slic3r {
struct IntersectionLines {
uint32_t line_index1;
uint32_t line_index2;
Vec2d intersection;
};
using IntersectionsLines = std::vector<IntersectionLines>;
// collect all intersecting points
//FIXME O(n^2) complexity!
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);
IntersectionsLines get_intersections(const Lines &lines);
IntersectionsLines get_intersections(const Polygon &polygon);
IntersectionsLines get_intersections(const Polygons &polygons);
IntersectionsLines get_intersections(const ExPolygon &expolygon);
IntersectionsLines get_intersections(const ExPolygons &expolygons);
} // namespace Slic3r
#endif // slic3r_IntersectionPoints_hpp_

View File

@ -26,6 +26,7 @@
#include "CustomGCode.hpp"
#include "enum_bitmask.hpp"
#include "TextConfiguration.hpp"
#include "EmbossShape.hpp"
#include <map>
#include <memory>
@ -829,6 +830,10 @@ public:
// Contain information how to re-create volume
std::optional<TextConfiguration> text_configuration;
// Is set only when volume is Embossed Shape
// Contain 2d information about embossed shape to be editabled
std::optional<EmbossShape> emboss_shape;
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; }
ModelVolumeType type() const { return m_type; }
@ -840,6 +845,7 @@ public:
bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; }
bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; }
bool is_text() const { return text_configuration.has_value(); }
bool is_svg() const { return emboss_shape.has_value() && !text_configuration.has_value(); }
bool is_the_only_one_part() const; // behave like an object
t_model_material_id material_id() const { return m_material_id; }
void reset_extra_facets();
@ -998,8 +1004,7 @@ private:
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets),
cut_info(other.cut_info),
text_configuration(other.text_configuration)
cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
{
assert(this->id().valid());
assert(this->config.id().valid());
@ -1020,8 +1025,7 @@ 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),
text_configuration(other.text_configuration)
cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
{
assert(this->id().valid());
assert(this->config.id().valid());
@ -1069,6 +1073,7 @@ private:
cereal::load_by_value(ar, mmu_segmentation_facets);
cereal::load_by_value(ar, config);
cereal::load(ar, text_configuration);
cereal::load(ar, emboss_shape);
assert(m_mesh);
if (has_convex_hull) {
cereal::load_optional(ar, m_convex_hull);
@ -1086,6 +1091,7 @@ private:
cereal::save_by_value(ar, mmu_segmentation_facets);
cereal::save_by_value(ar, config);
cereal::save(ar, text_configuration);
cereal::save(ar, emboss_shape);
if (has_convex_hull)
cereal::save_optional(ar, m_convex_hull);
}

View File

@ -3,87 +3,522 @@
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "NSVGUtils.hpp"
#include <array>
#include <charconv> // to_chars
#include <boost/nowide/iostream.hpp>
#include "ClipperUtils.hpp"
#include "Emboss.hpp" // heal for shape
using namespace Slic3r;
namespace {
using namespace Slic3r; // Polygon
// see function nsvg__lineTo(NSVGparser* p, float x, float y)
bool is_line(const float *p, float precision = 1e-4f);
// convert curve in path to lines
struct LinesPath{
Polygons polygons;
Polylines polylines; };
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams &param);
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
} // namespace
// inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez
// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335
void NSVGUtils::flatten_cubic_bez(Polygon &polygon,
float tessTol,
Vec2f p1,
Vec2f p2,
Vec2f p3,
Vec2f p4,
int level)
namespace Slic3r {
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams &param)
{
Vec2f p12 = (p1 + p2) * 0.5f;
Vec2f p23 = (p2 + p3) * 0.5f;
Vec2f p34 = (p3 + p4) * 0.5f;
Vec2f p123 = (p12 + p23) * 0.5f;
ExPolygonsWithIds result;
size_t shape_id = 0;
for (NSVGshape *shape_ptr = image.shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next, ++shape_id) {
const NSVGshape &shape = *shape_ptr;
if (!(shape.flags & NSVG_FLAGS_VISIBLE))
continue;
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());
bool is_fill_used = shape.fill.type != NSVG_PAINT_NONE;
bool is_stroke_used =
shape.stroke.type != NSVG_PAINT_NONE &&
shape.strokeWidth > 1e-5f;
if (!is_fill_used && !is_stroke_used)
continue;
const LinesPath lines_path = linearize_path(shape.paths, param);
if (is_fill_used) {
unsigned unique_id = static_cast<unsigned>(2 * shape_id);
HealedExPolygons expoly = fill_to_expolygons(lines_path, shape, param);
result.push_back({unique_id, expoly.expolygons, expoly.is_healed});
}
if (is_stroke_used) {
unsigned unique_id = static_cast<unsigned>(2 * shape_id + 1);
HealedExPolygons expoly = stroke_to_expolygons(lines_path, shape, param);
result.push_back({unique_id, expoly.expolygons, expoly.is_healed});
}
}
// SVG is used as centered
// Do not disturb user by settings of pivot position
center(result);
return result;
}
Polygons to_polygons(const NSVGimage &image, const NSVGLineParams &param)
{
Polygons result;
for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
if (!(shape->flags & NSVG_FLAGS_VISIBLE))
continue;
if (shape->fill.type == NSVG_PAINT_NONE)
continue;
const LinesPath lines_path = linearize_path(shape->paths, param);
polygons_append(result, lines_path.polygons);
// close polyline to create polygon
polygons_append(result, to_polygons(lines_path.polylines));
}
return result;
}
void bounds(const NSVGimage &image, Vec2f& min, Vec2f &max)
{
for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next)
for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
if (min.x() > path->bounds[0])
min.x() = path->bounds[0];
if (min.y() > path->bounds[1])
min.y() = path->bounds[1];
if (max.x() < path->bounds[2])
max.x() = path->bounds[2];
if (max.y() < path->bounds[3])
max.y() = path->bounds[3];
}
}
NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, float dpi)
{
NSVGimage *image = ::nsvgParseFromFile(filename.c_str(), units, dpi);
return {image, &nsvgDelete};
}
std::unique_ptr<std::string> read_from_disk(const std::string &path)
{
boost::nowide::ifstream fs{path};
if (!fs.is_open())
return nullptr;
std::stringstream ss;
ss << fs.rdbuf();
return std::make_unique<std::string>(ss.str());
}
NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units, float dpi){
// NOTE: nsvg parser consume data from input(char *)
size_t size = file_data.size();
// file data could be big, so it is allocated on heap
std::unique_ptr<char[]> data_copy(new char[size+1]);
memcpy(data_copy.get(), file_data.c_str(), size);
data_copy[size] = '\0'; // data for nsvg must be null terminated
NSVGimage *image = ::nsvgParse(data_copy.get(), units, dpi);
return {image, &nsvgDelete};
}
size_t get_shapes_count(const NSVGimage &image)
{
size_t count = 0;
for (NSVGshape * s = image.shapes; s != NULL; s = s->next)
++count;
return count;
}
//void save(const NSVGimage &image, std::ostream &data)
//{
// data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>";
//
// // tl .. top left
// Vec2f tl(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
// // br .. bottom right
// Vec2f br(std::numeric_limits<float>::min(), std::numeric_limits<float>::min());
// bounds(image, tl, br);
//
// tl.x() = std::floor(tl.x());
// tl.y() = std::floor(tl.y());
//
// br.x() = std::ceil(br.x());
// br.y() = std::ceil(br.y());
// Vec2f s = br - tl;
// Point size = s.cast<Point::coord_type>();
//
// data << "<svg xmlns=\"http://www.w3.org/2000/svg\" "
// << "width=\"" << size.x() << "mm\" "
// << "height=\"" << size.y() << "mm\" "
// << "viewBox=\"0 0 " << size.x() << " " << size.y() << "\" >\n";
// data << "<!-- Created with PrusaSlicer (https://www.prusa3d.com/prusaslicer/) -->\n";
//
// std::array<char, 128> buffer;
// auto write_point = [&tl, &buffer](std::string &d, const float *p) {
// float x = p[0] - tl.x();
// float y = p[1] - tl.y();
// auto to_string = [&buffer](float f) -> std::string {
// auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), f);
// if (ec != std::errc{})
// return "0";
// return std::string(buffer.data(), ptr);
// };
// d += to_string(x) + "," + to_string(y) + " ";
// };
//
// for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
// enum struct Type { move, line, curve, close }; // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
// Type type = Type::move;
// std::string d = "M "; // move on start point
// for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
// if (path->npts <= 1)
// continue;
//
// if (type == Type::close) {
// type = Type::move;
// // NOTE: After close must be a space
// d += " M "; // move on start point
// }
// write_point(d, path->pts);
// size_t path_size = static_cast<size_t>(path->npts - 1);
//
// if (path->closed) {
// // Do not use last point in path it is duplicit
// if (path->npts <= 4)
// continue;
// path_size = static_cast<size_t>(path->npts - 4);
// }
//
// for (size_t i = 0; i < path_size; i += 3) {
// const float *p = &path->pts[i * 2];
// if (!::is_line(p)) {
// if (type != Type::curve) {
// type = Type::curve;
// d += "C "; // start sequence of triplets defining curves
// }
// write_point(d, &p[2]);
// write_point(d, &p[4]);
// } else {
//
// if (type != Type::line) {
// type = Type::line;
// d += "L "; // start sequence of line points
// }
// }
// write_point(d, &p[6]);
// }
// if (path->closed) {
// type = Type::close;
// d += "Z"; // start sequence of line points
// }
// }
// if (type != Type::close) {
// //type = Type::close;
// d += "Z"; // closed path
// }
// data << "<path fill=\"#D2D2D2\" d=\"" << d << "\" />\n";
// }
// data << "</svg>\n";
//}
//
//bool save(const NSVGimage &image, const std::string &svg_file_path)
//{
// std::ofstream file{svg_file_path};
// if (!file.is_open())
// return false;
// save(image, file);
// return true;
//}
} // namespace Slic3r
namespace {
using namespace Slic3r; // Polygon + Vec2f
Point::coord_type to_coor(float val, double scale) { return static_cast<Point::coord_type>(std::round(val * scale)); }
bool need_flattening(float tessTol, const Vec2f &p1, const Vec2f &p2, const Vec2f &p3, const Vec2f &p4) {
// f .. first
// s .. second
auto det = [](const Vec2f &f, const Vec2f &s) {
return std::fabs(f.x() * s.y() - f.y() * s.x());
};
Vec2f pd = (p4 - p1);
Vec2f pd2 = (p2 - p4);
float d2 = det(pd2, pd);
Vec2f pd3 = (p3 - p4);
float d3 = det(pd3, pd);
float d23 = d2 + d3;
if ((d23 * d23) < tessTol * (pd.x() * pd.x() + pd.y() * pd.y())) {
polygon.points.emplace_back(p4.x(), p4.y());
return (d23 * d23) >= tessTol * pd.squaredNorm();
}
// see function nsvg__lineTo(NSVGparser* p, float x, float y)
bool is_line(const float *p, float precision){
//Vec2f p1(p[0], p[1]);
//Vec2f p2(p[2], p[3]);
//Vec2f p3(p[4], p[5]);
//Vec2f p4(p[6], p[7]);
float dx_3 = (p[6] - p[0]) / 3.f;
float dy_3 = (p[7] - p[1]) / 3.f;
return
is_approx(p[2], p[0] + dx_3, precision) &&
is_approx(p[4], p[6] - dx_3, precision) &&
is_approx(p[3], p[1] + dy_3, precision) &&
is_approx(p[5], p[7] - dy_3, precision);
}
/// <summary>
/// Convert cubic curve to lines
/// Inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez
/// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335
/// </summary>
/// <param name="polygon">Result points</param>
/// <param name="tessTol">Tesselation tolerance</param>
/// <param name="p1">Curve point</param>
/// <param name="p2">Curve point</param>
/// <param name="p3">Curve point</param>
/// <param name="p4">Curve point</param>
/// <param name="level">Actual depth of recursion</param>
void flatten_cubic_bez(Points &points, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level)
{
if (!need_flattening(tessTol, p1, p2, p3, p4)) {
Point::coord_type x = static_cast<Point::coord_type>(std::round(p4.x()));
Point::coord_type y = static_cast<Point::coord_type>(std::round(p4.y()));
points.emplace_back(x, y);
return;
}
--level;
if (level == 0) return;
if (level == 0)
return;
Vec2f p12 = (p1 + p2) * 0.5f;
Vec2f p23 = (p2 + p3) * 0.5f;
Vec2f p34 = (p3 + p4) * 0.5f;
Vec2f p123 = (p12 + p23) * 0.5f;
Vec2f p234 = (p23 + p34) * 0.5f;
Vec2f p1234 = (p123 + p234) * 0.5f;
flatten_cubic_bez(polygon, tessTol, p1, p12, p123, p1234, level);
flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level);
flatten_cubic_bez(points, tessTol, p1, p12, p123, p1234, level);
flatten_cubic_bez(points, tessTol, p1234, p234, p34, p4, level);
}
Polygons NSVGUtils::to_polygons(NSVGimage *image, float tessTol, int max_level)
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams &param)
{
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();
LinesPath result;
Polygons &polygons = result.polygons;
Polylines &polylines = result.polylines;
// multiple use of allocated memmory for points between paths
Points points;
for (NSVGpath *path = first_path; path != NULL; path = path->next) {
// Flatten path
Point::coord_type x = to_coor(path->pts[0], param.scale);
Point::coord_type y = to_coor(path->pts[1], param.scale);
points.emplace_back(x, y);
size_t path_size = (path->npts > 1) ? static_cast<size_t>(path->npts - 1) : 0;
for (size_t i = 0; i < path_size; i += 3) {
const float *p = &path->pts[i * 2];
if (is_line(p)) {
// point p4
Point::coord_type xx = to_coor(p[6], param.scale);
Point::coord_type yy = to_coor(p[7], param.scale);
points.emplace_back(xx, yy);
continue;
}
Vec2f p1(p[0], p[1]);
Vec2f p2(p[2], p[3]);
Vec2f p3(p[4], p[5]);
Vec2f p4(p[6], p[7]);
flatten_cubic_bez(points, param.tesselation_tolerance,
p1 * param.scale, p2 * param.scale, p3 * param.scale, p4 * param.scale,
param.max_level);
}
assert(!points.empty());
if (points.empty())
continue;
if (param.is_y_negative)
for (Point &p : points)
p.y() = -p.y();
if (path->closed) {
polygons.emplace_back(points);
} else {
polylines.emplace_back(points);
}
// prepare for new path - recycle alocated memory
points.clear();
}
remove_same_neighbor(polygons);
remove_same_neighbor(polylines);
return result;
}
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param)
{
Polygons fill = lines_path.polygons; // copy
// close polyline to create polygon
polygons_append(fill, to_polygons(lines_path.polylines));
if (fill.empty())
return {};
// if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_NONZERO)
bool is_non_zero = true;
if (shape.fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD)
is_non_zero = false;
return Emboss::heal_polygons(fill, is_non_zero, param.max_heal_iteration);
}
struct DashesParam{
// first dash length
float dash_length = 1.f; // scaled
// is current dash .. true
// is current space .. false
bool is_line = true;
// current index to array
unsigned char dash_index = 0;
static constexpr size_t max_dash_array_size = 8; // limitation of nanosvg strokeDashArray
std::array<float, max_dash_array_size> dash_array; // scaled
unsigned char dash_count = 0; // count of values in array
explicit DashesParam(const NSVGshape &shape, double scale) :
dash_count(shape.strokeDashCount)
{
assert(dash_count > 0);
assert(dash_count <= max_dash_array_size); // limitation of nanosvg strokeDashArray
for (size_t i = 0; i < dash_count; ++i)
dash_array[i] = static_cast<float>(shape.strokeDashArray[i] * scale);
// Figure out dash offset.
float all_dash_length = 0;
for (unsigned char j = 0; j < dash_count; ++j)
all_dash_length += dash_array[j];
if (dash_count%2 == 1) // (shape.strokeDashCount & 1)
all_dash_length *= 2.0f;
// Find location inside pattern
float dash_offset = fmodf(static_cast<float>(shape.strokeDashOffset * scale), all_dash_length);
if (dash_offset < 0.0f)
dash_offset += all_dash_length;
while (dash_offset > dash_array[dash_index]) {
dash_offset -= dash_array[dash_index];
dash_index = (dash_index + 1) % shape.strokeDashCount;
is_line = !is_line;
}
dash_length = dash_array[dash_index] - dash_offset;
}
};
Polylines to_dashes(const Polyline &polyline, const DashesParam& param)
{
Polylines dashes;
Polyline dash; // cache for one dash in dashed line
Point prev_point;
bool is_line = param.is_line;
unsigned char dash_index = param.dash_index;
float dash_length = param.dash_length; // current rest of dash distance
for (const Point &point : polyline.points) {
if (&point == &polyline.points.front()) {
// is first point
prev_point = point; // copy
continue;
}
Point diff = point - prev_point;
float line_segment_length = diff.cast<float>().norm();
while (dash_length < line_segment_length) {
// Calculate intermediate point
float d = dash_length / line_segment_length;
Point move_point = diff * d;
Point intermediate = prev_point + move_point;
// add Dash in stroke
if (is_line) {
if (dash.empty()) {
dashes.emplace_back(Points{prev_point, intermediate});
} else {
dash.append(prev_point);
dash.append(intermediate);
dashes.push_back(dash);
dash.clear();
}
}
diff -= move_point;
line_segment_length -= dash_length;
prev_point = intermediate;
// Advance dash pattern
is_line = !is_line;
dash_index = (dash_index + 1) % param.dash_count;
dash_length = param.dash_array[dash_index];
}
if (!polygon.empty())
polygons.push_back(polygon);
if (is_line)
dash.append(prev_point);
dash_length -= line_segment_length;
prev_point = point; // copy
}
return polygons;
// add last dash
if (is_line){
assert(!dash.empty());
dash.append(prev_point); // prev_point == polyline.points.back()
dashes.push_back(dash);
}
return dashes;
}
ExPolygons NSVGUtils::to_ExPolygons(NSVGimage *image,
float tessTol,
int max_level)
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param)
{
Polygons polygons = to_polygons(image, tessTol, max_level);
// convert stroke to polygon
ClipperLib::JoinType join_type = ClipperLib::JoinType::jtSquare;
switch (static_cast<NSVGlineJoin>(shape.strokeLineJoin)) {
case NSVGlineJoin::NSVG_JOIN_BEVEL: join_type = ClipperLib::JoinType::jtSquare; break;
case NSVGlineJoin::NSVG_JOIN_MITER: join_type = ClipperLib::JoinType::jtMiter; break;
case NSVGlineJoin::NSVG_JOIN_ROUND: join_type = ClipperLib::JoinType::jtRound; break;
}
// Fix Y axis
for (Polygon &polygon : polygons)
for (Point &p : polygon.points) p.y() *= -1;
double mitter = shape.miterLimit * param.scale;
if (join_type == ClipperLib::JoinType::jtRound) {
// mitter is used as ArcTolerance
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
mitter = std::pow(param.tesselation_tolerance, 1/3.);
}
float stroke_width = static_cast<float>(shape.strokeWidth * param.scale);
return Slic3r::union_ex(polygons);
}
ClipperLib::EndType end_type = ClipperLib::EndType::etOpenButt;
switch (static_cast<NSVGlineCap>(shape.strokeLineCap)) {
case NSVGlineCap::NSVG_CAP_BUTT: end_type = ClipperLib::EndType::etOpenButt; break;
case NSVGlineCap::NSVG_CAP_ROUND: end_type = ClipperLib::EndType::etOpenRound; break;
case NSVGlineCap::NSVG_CAP_SQUARE: end_type = ClipperLib::EndType::etOpenSquare; break;
}
Polygons result;
if (shape.strokeDashCount > 0) {
DashesParam params(shape, param.scale);
Polylines dashes;
for (const Polyline &polyline : lines_path.polylines)
polylines_append(dashes, to_dashes(polyline, params));
for (const Polygon &polygon : lines_path.polygons)
polylines_append(dashes, to_dashes(to_polyline(polygon), params));
result = offset(dashes, stroke_width / 2, join_type, mitter, end_type);
} else {
result = contour_to_polygons(lines_path.polygons, stroke_width, join_type, mitter);
polygons_append(result, offset(lines_path.polylines, stroke_width / 2, join_type, mitter, end_type));
}
bool is_non_zero = true;
return Emboss::heal_polygons(result, is_non_zero, param.max_heal_iteration);
}
} // namespace

View File

@ -5,34 +5,81 @@
#ifndef slic3r_NSVGUtils_hpp_
#define slic3r_NSVGUtils_hpp_
#include <memory>
#include <string>
#include <sstream>
#include "Polygon.hpp"
#include "ExPolygon.hpp"
#include "EmbossShape.hpp" // ExPolygonsWithIds
#include "nanosvg/nanosvg.h" // load SVG file
// Helper function to work with nano svg
namespace Slic3r {
// Helper function to work with nano svg
class NSVGUtils
/// <summary>
/// Paramreters for conversion curve from SVG to lines in Polygon
/// </summary>
struct NSVGLineParams
{
public:
NSVGUtils() = delete;
// Smaller will divide curve to more lines
// NOTE: Value is in image scale
double tesselation_tolerance = 10.f;
// 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);
// Maximal depth of recursion for conversion curve to lines
int max_level = 10;
// Multiplicator of point coors
// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer --> Slic3r::Point
double scale = 1. / SCALING_FACTOR;
// Flag wether y is negative, when true than y coor is multiplied by -1
bool is_y_negative = true;
// Is used only with rounded Stroke
double arc_tolerance = 1.;
// Maximal count of heal iteration
unsigned max_heal_iteration = 10;
explicit NSVGLineParams(double tesselation_tolerance):
tesselation_tolerance(tesselation_tolerance),
arc_tolerance(std::pow(tesselation_tolerance, 1/3.))
{}
};
/// <summary>
/// Convert .svg opened by nanoSvg to shapes stored in expolygons with ids
/// </summary>
/// <param name="image">Parsed svg file by NanoSvg</param>
/// <param name="tesselation_tolerance">Smaller will divide curve to more lines
/// NOTE: Value is in image scale</param>
/// <param name="max_level">Maximal depth for conversion curve to lines</param>
/// <param name="scale">Multiplicator of point coors
/// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer</param>
/// <returns>Shapes from svg image - fill + stroke</returns>
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams &param);
// help functions - prepare to be tested
/// <param name="is_y_negative">Flag is y negative, when true than y coor is multiplied by -1</param>
Polygons to_polygons(const NSVGimage &image, const NSVGLineParams &param);
void bounds(const NSVGimage &image, Vec2f &min, Vec2f &max);
// read text data from file
std::unique_ptr<std::string> read_from_disk(const std::string &path);
using NSVGimage_ptr = std::unique_ptr<NSVGimage, void (*)(NSVGimage*)>;
NSVGimage_ptr nsvgParseFromFile(const std::string &svg_file_path, const char *units = "mm", float dpi = 96.0f);
NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units = "mm", float dpi = 96.0f);
/// <summary>
/// Iterate over shapes and calculate count
/// </summary>
/// <param name="image">Contain pointer to first shape</param>
/// <returns>Count of shapes</returns>
size_t get_shapes_count(const NSVGimage &image);
//void save(const NSVGimage &image, std::ostream &data);
//bool save(const NSVGimage &image, const std::string &svg_file_path);
} // namespace Slic3r
#endif // slic3r_NSVGUtils_hpp_

View File

@ -163,6 +163,21 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t);
/// <returns>Is positive determinant</returns>
inline bool has_reflection(const Transform3d &transform) { return transform.matrix().determinant() < 0; }
/// <summary>
/// Getter on base of transformation matrix
/// </summary>
/// <param name="index">column index</param>
/// <param name="transform">source transformation</param>
/// <returns>Base of transformation matrix</returns>
inline const Vec3d get_base(unsigned index, const Transform3d &transform) { return transform.linear().col(index); }
inline const Vec3d get_x_base(const Transform3d &transform) { return get_base(0, transform); }
inline const Vec3d get_y_base(const Transform3d &transform) { return get_base(1, transform); }
inline const Vec3d get_z_base(const Transform3d &transform) { return get_base(2, transform); }
inline const Vec3d get_base(unsigned index, const Transform3d::LinearPart &transform) { return transform.col(index); }
inline const Vec3d get_x_base(const Transform3d::LinearPart &transform) { return get_base(0, transform); }
inline const Vec3d get_y_base(const Transform3d::LinearPart &transform) { return get_base(1, transform); }
inline const Vec3d get_z_base(const Transform3d::LinearPart &transform) { return get_base(2, transform); }
template<int N, class T> using Vec = Eigen::Matrix<T, N, 1, Eigen::DontAlign, N, 1>;
class Point : public Vec2crd
@ -559,6 +574,27 @@ inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base)
inline Point align_to_grid(Point coord, Point spacing, Point base)
{ return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); }
// MinMaxLimits
template<typename T> struct MinMax { T min; T max;};
template<typename T>
static bool apply(std::optional<T> &val, const MinMax<T> &limit) {
if (!val.has_value()) return false;
return apply<T>(*val, limit);
}
template<typename T>
static bool apply(T &val, const MinMax<T> &limit)
{
if (val > limit.max) {
val = limit.max;
return true;
}
if (val < limit.min) {
val = limit.min;
return true;
}
return false;
}
} // namespace Slic3r
// start Boost

View File

@ -448,6 +448,38 @@ bool has_duplicate_points(const Polygons &polys)
#endif
}
bool remove_same_neighbor(Polygon &polygon)
{
Points &points = polygon.points;
if (points.empty())
return false;
auto last = std::unique(points.begin(), points.end());
// remove first and last neighbor duplication
if (const Point &last_point = *(last - 1); last_point == points.front()) {
--last;
}
// no duplicits
if (last == points.end())
return false;
points.erase(last, points.end());
return true;
}
bool remove_same_neighbor(Polygons &polygons)
{
if (polygons.empty())
return false;
bool exist = false;
for (Polygon &polygon : polygons)
exist |= remove_same_neighbor(polygon);
// remove empty polygons
polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const Polygon &p) { return p.points.size() <= 2; }), polygons.end());
return exist;
}
static inline bool is_stick(const Point &p1, const Point &p2, const Point &p3)
{
Point v1 = p2 - p1;

View File

@ -119,6 +119,10 @@ inline bool has_duplicate_points(Polygon &&poly) { return has_duplicate_poi
inline bool has_duplicate_points(const Polygon &poly) { return has_duplicate_points(poly.points); }
bool has_duplicate_points(const Polygons &polys);
// Return True when erase some otherwise False.
bool remove_same_neighbor(Polygon &polygon);
bool remove_same_neighbor(Polygons &polygons);
inline double total_length(const Polygons &polylines) {
double total = 0;
for (Polygons::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
@ -253,6 +257,18 @@ inline Polylines to_polylines(Polygons &&polys)
return polylines;
}
// close polyline to polygon (connect first and last point in polyline)
inline Polygons to_polygons(const Polylines &polylines)
{
Polygons out;
out.reserve(polylines.size());
for (const Polyline &polyline : polylines) {
if (polyline.size())
out.emplace_back(polyline.points);
}
return out;
}
inline Polygons to_polygons(const VecOfPoints &paths)
{
Polygons out;

View File

@ -214,6 +214,33 @@ BoundingBox get_extents(const Polylines &polylines)
return bb;
}
// Return True when erase some otherwise False.
bool remove_same_neighbor(Polyline &polyline) {
Points &points = polyline.points;
if (points.empty())
return false;
auto last = std::unique(points.begin(), points.end());
// no duplicits
if (last == points.end())
return false;
points.erase(last, points.end());
return true;
}
bool remove_same_neighbor(Polylines &polylines){
if (polylines.empty())
return false;
bool exist = false;
for (Polyline &polyline : polylines)
exist |= remove_same_neighbor(polyline);
// remove empty polylines
polylines.erase(std::remove_if(polylines.begin(), polylines.end(), [](const Polyline &p) { return p.points.size() <= 1; }), polylines.end());
return exist;
}
const Point& leftmost_point(const Polylines &polylines)
{
if (polylines.empty())

View File

@ -99,6 +99,10 @@ inline bool operator!=(const Polyline &lhs, const Polyline &rhs) { return lhs.po
extern BoundingBox get_extents(const Polyline &polyline);
extern BoundingBox get_extents(const Polylines &polylines);
// Return True when erase some otherwise False.
bool remove_same_neighbor(Polyline &polyline);
bool remove_same_neighbor(Polylines &polylines);
inline double total_length(const Polylines &polylines) {
double total = 0;
for (const Polyline &pl : polylines)

View File

@ -30,15 +30,6 @@ struct FontProp
// 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
@ -49,17 +40,6 @@ struct FontProp
// 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]
// Angle of rotation around emboss direction (Z axis)
// It is calculate on the fly from volume world transformation
// only StyleManager keep actual value for comparision with style
// When not set value is zero and is not stored
std::optional<float> angle; // [in radians] form -Pi to Pi
// Parameter for True Type Font collections
// Select index of font in collection
std::optional<unsigned int> collection_number;
@ -74,7 +54,7 @@ struct FontProp
// change pivot of text
// When not set, center is used and is not stored
Align align = Align(HorizontalAlign::center, VerticalAlign::center);
//////
// Duplicit data to wxFontDescriptor
// used for store/load .3mf file
@ -96,54 +76,38 @@ struct FontProp
/// </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), per_glyph(false)
FontProp(float line_height = 10.f) : size_in_mm(line_height), per_glyph(false)
{}
bool operator==(const FontProp& other) const {
return
char_gap == other.char_gap &&
line_gap == other.line_gap &&
use_surface == other.use_surface &&
per_glyph == other.per_glyph &&
align == other.align &&
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);
is_approx(skew, other.skew);
}
// undo / redo stack recovery
template<class Archive> void save(Archive &ar) const
{
ar(emboss, use_surface, size_in_mm, per_glyph, align.first, align.second);
ar(size_in_mm, per_glyph, align.first, align.second);
cereal::save(ar, char_gap);
cereal::save(ar, line_gap);
cereal::save(ar, boldness);
cereal::save(ar, skew);
cereal::save(ar, 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, per_glyph, align.first, align.second);
ar(size_in_mm, per_glyph, align.first, align.second);
cereal::load(ar, char_gap);
cereal::load(ar, line_gap);
cereal::load(ar, boldness);
cereal::load(ar, skew);
cereal::load(ar, 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);
}
};
@ -197,9 +161,7 @@ struct EmbossStyle
}
// undo / redo stack recovery
template<class Archive> void serialize(Archive &ar){
ar(name, path, type, prop);
}
template<class Archive> void serialize(Archive &ar){ ar(name, path, type, prop); }
};
// Emboss style name inside vector is unique
@ -220,21 +182,8 @@ struct TextConfiguration
// 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);
}
template<class Archive> void serialize(Archive &ar) { ar(style, text); }
};
} // namespace Slic3r

View File

@ -65,7 +65,7 @@ inline bool has_self_intersection(
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();
return !get_intersections(lines).empty();
}
} // namespace priv

View File

@ -76,6 +76,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoSeam.hpp
GUI/Gizmos/GLGizmoSimplify.cpp
GUI/Gizmos/GLGizmoSimplify.hpp
GUI/Gizmos/GLGizmoSVG.cpp
GUI/Gizmos/GLGizmoSVG.hpp
GUI/Gizmos/GLGizmoMmuSegmentation.cpp
GUI/Gizmos/GLGizmoMmuSegmentation.hpp
GUI/Gizmos/GLGizmoMeasure.cpp
@ -274,8 +276,6 @@ set(SLIC3R_GUI_SOURCES
Utils/Duet.hpp
Utils/EmbossStyleManager.cpp
Utils/EmbossStyleManager.hpp
Utils/EmbossStylesSerializable.cpp
Utils/EmbossStylesSerializable.hpp
Utils/FlashAir.cpp
Utils/FlashAir.hpp
Utils/FontConfigHelp.cpp

View File

@ -3939,7 +3939,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
type == GLGizmosManager::EType::Move ||
type == GLGizmosManager::EType::Rotate ||
type == GLGizmosManager::EType::Scale ||
type == GLGizmosManager::EType::Emboss) ) {
type == GLGizmosManager::EType::Emboss||
type == GLGizmosManager::EType::Svg) ) {
for (int hover_volume_id : m_hover_volume_idxs) {
const GLVolume &hover_gl_volume = *m_volumes.volumes[hover_volume_id];
int object_idx = hover_gl_volume.object_idx();
@ -3948,12 +3949,20 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
int hover_volume_idx = hover_gl_volume.volume_idx();
if (hover_volume_idx < 0 || static_cast<size_t>(hover_volume_idx) >= hover_object->volumes.size()) continue;
const ModelVolume* hover_volume = hover_object->volumes[hover_volume_idx];
if (!hover_volume->text_configuration.has_value()) continue;
m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id});
if (type != GLGizmosManager::EType::Emboss)
m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss);
wxGetApp().obj_list()->update_selections();
return;
if (hover_volume->text_configuration.has_value()) {
m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id});
if (type != GLGizmosManager::EType::Emboss)
m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss);
wxGetApp().obj_list()->update_selections();
return;
} else if (hover_volume->emboss_shape.has_value()) {
m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id});
if (type != GLGizmosManager::EType::Svg)
m_gizmos.open_gizmo(GLGizmosManager::EType::Svg);
wxGetApp().obj_list()->update_selections();
return;
}
}
}
@ -7892,10 +7901,10 @@ const ModelVolume *get_model_volume(const GLVolume &v, const Model &model)
return ret;
}
const ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects)
ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects)
{
for (const ModelObject *obj : objects)
for (const ModelVolume *vol : obj->volumes)
for (ModelVolume *vol : obj->volumes)
if (vol->id() == volume_id)
return vol;
return nullptr;

View File

@ -1126,7 +1126,7 @@ private:
};
const ModelVolume *get_model_volume(const GLVolume &v, const Model &model);
const ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects);
ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects);
ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects);
ModelVolume *get_model_volume(const GLVolume &v, const ModelObject &object);

View File

@ -18,6 +18,7 @@
#include "Selection.hpp"
#include "format.hpp"
#include "Gizmos/GLGizmoEmboss.hpp"
#include "Gizmos/GLGizmoSVG.hpp"
#include <boost/algorithm/string.hpp>
#include "slic3r/Utils/FixModelByWin10.hpp"
@ -174,6 +175,12 @@ static const constexpr std::array<std::pair<const char *, const char *>, 3> TEXT
{L("Add negative text"), "add_text_negative" }, // ~ModelVolumeType::NEGATIVE_VOLUME
{L("Add text modifier"), "add_text_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
}};
// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
static const constexpr std::array<std::pair<const char *, const char *>, 3> SVG_VOLUME_ICONS{{
{L("Add SVG part"), "svg_part"}, // ~ModelVolumeType::MODEL_PART
{L("Add negative SVG"), "svg_negative"}, // ~ModelVolumeType::NEGATIVE_VOLUME
{L("Add SVG modifier"), "svg_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
}};
static Plater* plater()
{
@ -445,7 +452,7 @@ std::vector<wxBitmapBundle*> MenuFactory::get_volume_bitmaps()
{
std::vector<wxBitmapBundle*> volume_bmps;
volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size());
for (auto item : ADD_VOLUME_MENU_ITEMS)
for (const auto& item : ADD_VOLUME_MENU_ITEMS)
volume_bmps.push_back(get_bmp_bundle(item.second));
return volume_bmps;
}
@ -454,7 +461,16 @@ 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)
for (const auto& item : TEXT_VOLUME_ICONS)
volume_bmps.push_back(get_bmp_bundle(item.second));
return volume_bmps;
}
std::vector<wxBitmapBundle*> MenuFactory::get_svg_volume_bitmaps()
{
std::vector<wxBitmapBundle *> volume_bmps;
volume_bmps.reserve(SVG_VOLUME_ICONS.size());
for (const auto &item : SVG_VOLUME_ICONS)
volume_bmps.push_back(get_bmp_bundle(item.second));
return volume_bmps;
}
@ -491,6 +507,7 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty
}
append_menu_item_add_text(sub_menu, type);
append_menu_item_add_svg(sub_menu, type);
if (mode >= comAdvanced) {
sub_menu->AppendSeparator();
@ -501,42 +518,57 @@ 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;
static void append_menu_itemm_add_(const wxString& name, GLGizmosManager::EType gizmo_type, wxMenu *menu, ModelVolumeType type, bool is_submenu_item) {
auto add_ = [type, gizmo_type](const wxCommandEvent & /*unnamed*/) {
const GLCanvas3D *canvas = plater()->canvas3D();
const GLGizmosManager &mng = canvas->get_gizmos_manager();
GLGizmoBase *gizmo_base = mng.get_gizmo(gizmo_type);
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 (gizmo_type == GLGizmosManager::Emboss) {
auto emboss = dynamic_cast<GLGizmoEmboss *>(gizmo_base);
assert(emboss != nullptr);
if (emboss == nullptr) return;
if (screen_position.has_value()) {
emboss->create_volume(volume_type, *screen_position);
} else {
emboss->create_volume(volume_type);
}
} else if (gizmo_type == GLGizmosManager::Svg) {
auto svg = dynamic_cast<GLGizmoSVG *>(gizmo_base);
assert(svg != nullptr);
if (svg == nullptr) return;
if (screen_position.has_value()) {
svg->create_volume(volume_type, *screen_position);
} else {
svg->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");
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 = wxString(is_submenu_item ? "" : _(ADD_VOLUME_MENU_ITEMS[int(type)].first) + ": ") + name;
menu->AppendSeparator();
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);
append_menu_item(menu, wxID_ANY, item_name, "", add_, icon_name, menu);
}
}
void MenuFactory::append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item/* = true*/){
append_menu_itemm_add_(_L("Text"), GLGizmosManager::Emboss, menu, type, is_submenu_item);
}
void MenuFactory::append_menu_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item /* = true*/){
append_menu_itemm_add_(_L("SVG"), GLGizmosManager::Svg, menu, type, is_submenu_item);
}
void MenuFactory::append_menu_items_add_volume(MenuType menu_type)
{
wxMenu* menu = menu_type == mtObjectFFF ? &m_object_menu : menu_type == mtObjectSLA ? &m_sla_object_menu : nullptr;
@ -989,15 +1021,18 @@ void MenuFactory::append_menu_item_edit_text(wxMenu *menu)
wxString name = _L("Edit text");
auto can_edit_text = []() {
if (plater() != nullptr) {
const Selection& sel = plater()->get_selection();
if (sel.volumes_count() == 1) {
const GLVolume* gl_vol = sel.get_first_volume();
const ModelVolume* vol = plater()->model().objects[gl_vol->object_idx()]->volumes[gl_vol->volume_idx()];
return vol->text_configuration.has_value();
}
}
return false;
if (plater() == nullptr)
return false;
const Selection& selection = plater()->get_selection();
if (selection.volumes_count() != 1)
return false;
const GLVolume* gl_volume = selection.get_first_volume();
if (gl_volume == nullptr)
return false;
const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects);
if (volume == nullptr)
return false;
return volume->is_text();
};
if (menu != &m_text_part_menu) {
@ -1009,7 +1044,7 @@ void MenuFactory::append_menu_item_edit_text(wxMenu *menu)
}
wxString description = _L("Ability to change text, font, size, ...");
std::string icon = "";
std::string icon = "cog";
auto open_emboss = [](const wxCommandEvent &) {
GLGizmosManager &mng = plater()->canvas3D()->get_gizmos_manager();
if (mng.get_current_type() == GLGizmosManager::Emboss)
@ -1019,6 +1054,43 @@ void MenuFactory::append_menu_item_edit_text(wxMenu *menu)
append_menu_item(menu, wxID_ANY, name, description, open_emboss, icon, nullptr, can_edit_text, m_parent);
}
void MenuFactory::append_menu_item_edit_svg(wxMenu *menu)
{
wxString name = _L("Edit SVG");
auto can_edit_svg = []() {
if (plater() == nullptr)
return false;
const Selection& selection = plater()->get_selection();
if (selection.volumes_count() != 1)
return false;
const GLVolume* gl_volume = selection.get_first_volume();
if (gl_volume == nullptr)
return false;
const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects);
if (volume == nullptr)
return false;
return volume->is_svg();
};
if (menu != &m_svg_part_menu) {
const int menu_item_id = menu->FindItem(name);
if (menu_item_id != wxNOT_FOUND)
menu->Destroy(menu_item_id);
if (!can_edit_svg())
return;
}
wxString description = _L("Change SVG source file, projection, size, ...");
std::string icon = "cog";
auto open_svg = [](const wxCommandEvent &) {
GLGizmosManager &mng = plater()->canvas3D()->get_gizmos_manager();
if (mng.get_current_type() == GLGizmosManager::Svg)
mng.open_gizmo(GLGizmosManager::Svg); // close() and reopen - move to be visible
mng.open_gizmo(GLGizmosManager::Svg);
};
append_menu_item(menu, wxID_ANY, name, description, open_svg, icon, nullptr, can_edit_svg, m_parent);
}
MenuFactory::MenuFactory()
{
for (int i = 0; i < mtCount; i++) {
@ -1118,8 +1190,20 @@ 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_delete(menu);
append_menu_item_fix_through_winsdk(menu);
append_menu_item_simplify(menu);
append_immutable_part_menu_items(menu);
}
void MenuFactory::create_svg_part_menu()
{
wxMenu* menu = &m_svg_part_menu;
append_menu_item_edit_svg(menu);
append_menu_item_delete(menu);
append_menu_item_fix_through_winsdk(menu);
append_menu_item_simplify(menu);
@ -1143,6 +1227,7 @@ void MenuFactory::init(wxWindow* parent)
create_common_object_menu(&m_sla_object_menu);
create_part_menu();
create_text_part_menu();
create_svg_part_menu();
create_instance_menu();
}
@ -1165,6 +1250,7 @@ wxMenu* MenuFactory::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);
append_menu_item_edit_svg(&m_object_menu);
return &m_object_menu;
}
@ -1176,6 +1262,7 @@ wxMenu* MenuFactory::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);
append_menu_item_edit_svg(&m_object_menu);
return &m_sla_object_menu;
}
@ -1196,6 +1283,12 @@ wxMenu* MenuFactory::text_part_menu()
return &m_text_part_menu;
}
wxMenu *MenuFactory::svg_part_menu()
{
append_mutable_part_menu_items(&m_svg_part_menu);
return &m_svg_part_menu;
}
wxMenu* MenuFactory::instance_menu()
{
return &m_instance_menu;

View File

@ -39,6 +39,7 @@ class MenuFactory
public:
static std::vector<wxBitmapBundle*> get_volume_bitmaps();
static std::vector<wxBitmapBundle*> get_text_volume_bitmaps();
static std::vector<wxBitmapBundle*> get_svg_volume_bitmaps();
MenuFactory();
~MenuFactory() = default;
@ -56,6 +57,7 @@ public:
wxMenu* sla_object_menu();
wxMenu* part_menu();
wxMenu* text_part_menu();
wxMenu* svg_part_menu();
wxMenu* instance_menu();
wxMenu* layer_menu();
wxMenu* multi_selection_menu();
@ -72,6 +74,7 @@ private:
MenuWithSeparators m_object_menu;
MenuWithSeparators m_part_menu;
MenuWithSeparators m_text_part_menu;
MenuWithSeparators m_svg_part_menu;
MenuWithSeparators m_sla_object_menu;
MenuWithSeparators m_default_menu;
MenuWithSeparators m_instance_menu;
@ -87,10 +90,12 @@ private:
void append_mutable_part_menu_items(wxMenu* menu);
void create_part_menu();
void create_text_part_menu();
void create_svg_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_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item = true);
void append_menu_items_add_volume(MenuType type);
wxMenuItem* append_menu_item_layers_editing(wxMenu* menu);
wxMenuItem* append_menu_item_settings(wxMenu* menu);
@ -112,6 +117,7 @@ private:
// 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_item_edit_svg(wxMenu *menu);
void append_menu_items_instance_manipulation(wxMenu *menu);
void update_menu_items_instance_manipulation(MenuType type);
void append_menu_items_split(wxMenu *menu);

View File

@ -1040,7 +1040,11 @@ void ObjectList::show_context_menu(const bool evt_context_menu)
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();
const ModelVolume *volume = object(obj_idx)->volumes[vol_idx];
menu = volume->is_text() ? plater->text_part_menu() :
volume->is_svg() ? plater->svg_part_menu() :
plater->part_menu();
}
else
menu = type & itInstance ? plater->instance_menu() :
@ -1779,12 +1783,7 @@ void ObjectList::load_shape_object_from_gallery(const wxArrayString& input_files
wxGetApp().mainframe->update_title();
}
void ObjectList::load_mesh_object(
const TriangleMesh & mesh,
const std::string & name,
bool center,
const TextConfiguration *text_config /* = nullptr*/,
const Transform3d * transformation /* = nullptr*/)
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const std::string &name, bool center)
{
PlaterAfterLoadAutoArrange plater_after_load_auto_arrange;
// Add mesh to model as a new object
@ -1794,7 +1793,6 @@ void ObjectList::load_mesh_object(
check_model_ids_validity(model);
#endif /* _DEBUG */
std::vector<size_t> object_idxs;
ModelObject* new_object = model.add_object();
new_object->name = name;
new_object->add_instance(); // each object should have at list one instance
@ -1802,31 +1800,24 @@ void ObjectList::load_mesh_object(
ModelVolume* new_volume = new_object->add_volume(mesh);
new_object->sort_volumes(wxGetApp().app_config->get_bool("order_volumes"));
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();
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());
}
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();
object_idxs.push_back(model.objects.size() - 1);
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */
paste_objects_into_list(object_idxs);
paste_objects_into_list({model.objects.size() - 1});
#ifdef _DEBUG
check_model_ids_validity(model);
@ -2999,6 +2990,7 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st
volume_idx,
volume->type(),
volume->is_text(),
volume->is_svg(),
get_warning_icon_name(volume->mesh().stats()),
extruder2str(volume->config.has("extruder") ? volume->config.extruder() : 0));
add_settings_item(vol_item, &volume->config.get());
@ -4275,7 +4267,8 @@ void ObjectList::change_part_type()
types.emplace_back(ModelVolumeType::PARAMETER_MODIFIER);
}
if (!volume->text_configuration.has_value()) {
// is not embossed(SVG or Text)
if (!volume->emboss_shape.has_value()) {
for (const wxString& name : { _L("Support Blocker"), _L("Support Enforcer") })
names.Add(name);
for (const ModelVolumeType type_id : { ModelVolumeType::SUPPORT_BLOCKER, ModelVolumeType::SUPPORT_ENFORCER })

View File

@ -31,7 +31,6 @@ 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:
@ -260,8 +259,7 @@ 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 std::string &name, bool center = true,
const TextConfiguration* text_config = nullptr, const Transform3d* transformation = nullptr);
void load_mesh_object(const TriangleMesh &mesh, const std::string &name, bool center = true);
bool del_object(const int obj_idx);
bool del_subobject_item(wxDataViewItem& item);
void del_settings_from_config(const wxDataViewItem& parent_item);

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
#include "GLGizmoRotate.hpp"
#include "slic3r/GUI/IconManager.hpp"
#include "slic3r/GUI/SurfaceDrag.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/I18N.hpp" // TODO: not needed
#include "slic3r/GUI/TextLines.hpp"
#include "slic3r/Utils/RaycastManager.hpp"
#include "slic3r/Utils/EmbossStyleManager.hpp"
@ -20,7 +20,6 @@
#include "libslic3r/Emboss.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/TextConfiguration.hpp"
#include <imgui/imgui.h>
@ -37,20 +36,20 @@ namespace Slic3r::GUI {
class GLGizmoEmboss : public GLGizmoBase
{
public:
GLGizmoEmboss(GLCanvas3D& parent);
explicit 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);
bool 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);
bool create_volume(ModelVolumeType volume_type);
/// <summary>
/// Handle pressing of shortcut
@ -65,6 +64,14 @@ public:
/// <returns>True on success start job otherwise False</returns>
bool do_mirror(size_t axis);
/// <summary>
/// Call on change inside of object conatining projected volume
/// </summary>
/// <param name="job_cancel">Way to stop re_emboss job</param>
/// <returns>True on success otherwise False</returns>
static bool re_emboss(const ModelVolume &text, std::shared_ptr<std::atomic<bool>> job_cancel = nullptr);
protected:
bool on_init() override;
std::string on_get_name() const override;
@ -90,10 +97,11 @@ protected:
/// <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() const override { return _u8L("Embossing actions"); }
bool wants_enter_leave_snapshots() const override;
std::string get_gizmo_entering_text() const override;
std::string get_gizmo_leaving_text() const override;
std::string get_action_snapshot_name() const override;
private:
void volume_transformation_changing();
void volume_transformation_changed();
@ -111,7 +119,6 @@ private:
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();
@ -120,8 +127,6 @@ private:
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_line();
void draw_font_list();
void draw_height(bool use_inch);
@ -129,8 +134,6 @@ private:
// call after set m_style_manager.get_style().prop.size_in_mm
bool set_height();
// call after set m_style_manager.get_style().prop.emboss
bool set_depth();
bool draw_italic_button();
bool draw_bold_button();
@ -138,30 +141,25 @@ private:
bool select_facename(const wxString& facename);
void do_translate(const Vec3d& relative_move);
void do_rotate(float relative_z_angle);
bool rev_input_mm(const std::string &name, float &value, const float *default_value,
const std::string &undo_tooltip, float step, float step_fast, const char *format,
bool use_inch, const std::optional<float>& scale);
template<typename T> bool rev_input_mm(const std::string &name, T &value, const T *default_value,
const std::string &undo_tooltip, T step, T step_fast, const char *format, bool use_inch, const std::optional<float>& scale) const;
/// <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);
template<typename T> bool rev_input(const std::string &name, T &value, const T *default_value,
const std::string &undo_tooltip, T step, T step_fast, const char *format, ImGuiInputTextFlags flags = 0) const;
bool rev_checkbox(const std::string &name, bool &value, const bool* default_value, const std::string &undo_tooltip) const;
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);
const std::string &undo_tooltip, int v_min, int v_max, const std::string &format, const wxString &tooltip) const;
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);
const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip) const;
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);
const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip) const;
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) const;
bool m_should_set_minimal_windows_size = false;
void set_minimal_window_size(bool is_advance_edit_style);
@ -173,71 +171,13 @@ private:
void on_mouse_change_selection(const wxMouseEvent &mouse_event);
// When open text loaded from .3mf it could be written with unknown font
bool m_is_unknown_font;
bool m_is_unknown_font = false;
void create_notification_not_valid_font(const TextConfiguration& tc);
void create_notification_not_valid_font(const std::string& text);
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
{
// Detect invalid config values when change monitor DPI
double screen_scale;
float main_toolbar_height;
// 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 height_of_volume_type_selector = 0.f;
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 indent = 0.f;
float input_offset = 0.f;
float advanced_input_offset = 0.f;
float lock_offset = 0.f;
ImVec2 text_size;
// maximal size of face name image
Vec2i face_name_size = Vec2i(100, 0);
float face_name_texture_offset_x = 0.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 font;
std::string height;
std::string depth;
// advanced
std::string use_surface;
std::string per_glyph;
std::string alignment;
std::string char_gap;
std::string line_gap;
std::string boldness;
std::string skew_ration;
std::string from_surface;
std::string rotation;
std::string collection;
};
Translations translations;
};
std::optional<const GuiCfg> m_gui_cfg;
static GuiCfg create_gui_configuration();
struct GuiCfg;
std::unique_ptr<const GuiCfg> m_gui_cfg;
// Is open tree with advanced options
bool m_is_advanced_edit_style = false;
@ -251,62 +191,9 @@ private:
// Keep information about stored styles and loaded actual style to compare with
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;
bool has_truncated_names = 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
static constexpr 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)
// This variable must exist until no CreateFontImageJob is running
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;
// filtration pattern
std::string search = "";
std::vector<bool> hide; // result of filtration
} m_face_names;
static bool store(const Facenames &facenames);
static bool load(Facenames &facenames);
static void init_face_names(Facenames &facenames);
static void init_truncated_names(Facenames &face_names, float max_width);
// pImpl to hide implementation of FaceNames to .cpp file
struct Facenames; // forward declaration
std::unique_ptr<Facenames> m_face_names;
// Text to emboss
std::string m_text; // Sequence of Unicode UTF8 symbols
@ -316,7 +203,7 @@ private:
// current selected volume
// NOTE: Be carefull could be uninitialized (removed from Model)
ModelVolume *m_volume;
ModelVolume *m_volume = nullptr;
// When work with undo redo stack there could be situation that
// m_volume point to unexisting volume so One need also objectID
@ -326,7 +213,7 @@ private:
bool m_text_contain_unknown_glyph = false;
// cancel for previous update of volume to cancel finalize part
std::shared_ptr<std::atomic<bool>> m_job_cancel;
std::shared_ptr<std::atomic<bool>> m_job_cancel = nullptr;
// Keep information about curvature of text line around surface
TextLinesModel m_text_lines;
@ -340,8 +227,8 @@ private:
// Keep data about dragging only during drag&drop
std::optional<SurfaceDrag> m_surface_drag;
// TODO: it should be accessible by other gizmo too.
// May be move to plater?
// Keep old scene triangle data in AABB trees,
// all the time it need actualize before use.
RaycastManager m_raycast_manager;
// For text on scaled objects

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,200 @@
#ifndef slic3r_GLGizmoSVG_hpp_
#define slic3r_GLGizmoSVG_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/SurfaceDrag.hpp"
#include "slic3r/GUI/GLTexture.hpp"
#include "slic3r/Utils/RaycastManager.hpp"
#include "slic3r/GUI/IconManager.hpp"
#include <optional>
#include <memory>
#include <atomic>
#include "libslic3r/Emboss.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Model.hpp"
#include <imgui/imgui.h>
#include <GL/glew.h>
namespace Slic3r{
class ModelVolume;
enum class ModelVolumeType : int;
}
namespace Slic3r::GUI {
struct Texture{
unsigned id{0};
unsigned width{0};
unsigned height{0};
};
class GLGizmoSVG : public GLGizmoBase
{
public:
explicit GLGizmoSVG(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>
/// <returns>True on succesfull start creation otherwise False</returns>
bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); // first open file dialog
/// <summary>
/// Create new text without given position
/// </summary>
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
/// <returns>True on succesfull start creation otherwise False</returns>
bool create_volume(ModelVolumeType volume_type); // first open file dialog
/// <summary>
/// Create volume from already selected svg file
/// </summary>
/// <param name="svg_file">File path</param>
/// <param name="mouse_pos">Position on screen where to create volume</param>
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
/// <returns>True on succesfull start creation otherwise False</returns>
bool create_volume(std::string_view svg_file, const Vec2d &mouse_pos, ModelVolumeType volume_type = ModelVolumeType::MODEL_PART);
bool create_volume(std::string_view svg_file, ModelVolumeType volume_type = ModelVolumeType::MODEL_PART);
/// <summary>
/// Check whether volume is object containing only emboss volume
/// </summary>
/// <param name="volume">Pointer to volume</param>
/// <returns>True when object otherwise False</returns>
static bool is_svg_object(const ModelVolume &volume);
/// <summary>
/// Check whether volume has emboss data
/// </summary>
/// <param name="volume">Pointer to volume</param>
/// <returns>True when constain emboss data otherwise False</returns>
static bool is_svg(const ModelVolume &volume);
protected:
bool on_init() override;
std::string on_get_name() const override;
void on_render() override;
void on_register_raycasters_for_picking() override;
void on_unregister_raycasters_for_picking() override;
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 data_changed(bool is_serializing) override; // selection changed
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;
std::string get_gizmo_entering_text() const override;
std::string get_gizmo_leaving_text() const override;
std::string get_action_snapshot_name() const override;
private:
void set_volume_by_selection();
void reset_volume();
// create volume from text - main functionality
bool process();
void close();
void draw_window();
void draw_preview();
void draw_filename();
void draw_depth();
void draw_size();
void draw_use_surface();
void draw_distance();
void draw_rotation();
void draw_mirroring();
void draw_model_type();
// process mouse event
bool on_mouse_for_rotation(const wxMouseEvent &mouse_event);
bool on_mouse_for_translate(const wxMouseEvent &mouse_event);
struct GuiCfg;
std::unique_ptr<const GuiCfg> m_gui_cfg;
// actual selected only one volume - with emboss data
ModelVolume *m_volume = nullptr;
// Is used to edit eboss and send changes to job
// Inside volume is current state of shape WRT Volume
EmbossShape m_volume_shape; // copy from m_volume for edit
// same index as volumes in
std::vector<std::string> m_shape_warnings;
// When work with undo redo stack there could be situation that
// m_volume point to unexisting volume so One need also objectID
ObjectID m_volume_id;
// cancel for previous update of volume to cancel finalize part
std::shared_ptr<std::atomic<bool>> m_job_cancel = nullptr;
// Rotation gizmo
GLGizmoRotate m_rotate_gizmo;
std::optional<float> m_angle;
std::optional<float> m_distance;
// Value is set only when dragging rotation to calculate actual angle
std::optional<float> m_rotate_start_angle;
// TODO: it should be accessible by other gizmo too.
// May be move to plater?
RaycastManager m_raycast_manager;
// When true keep up vector otherwise relative rotation
bool m_keep_up = true;
// Keep size aspect ratio when True.
bool m_keep_ratio = true;
// setted only when wanted to use - not all the time
std::optional<ImVec2> m_set_window_offset;
// Keep data about dragging only during drag&drop
std::optional<SurfaceDrag> m_surface_drag;
// For volume on scaled objects
std::optional<float> m_scale_width;
std::optional<float> m_scale_height;
std::optional<float> m_scale_depth;
void calculate_scale();
float get_scale_for_tolerance();
// keep SVG data rendered on GPU
Texture m_texture;
// bounding box of shape
// Note: Scaled mm to int value by m_volume_shape.scale
BoundingBox m_shape_bb;
std::string m_filename_preview;
IconManager m_icon_manager;
IconManager::Icons m_icons;
// only temporary solution
static const std::string M_ICON_FILENAME;
};
} // namespace Slic3r::GUI
#endif // slic3r_GLGizmoSVG_hpp_

View File

@ -41,7 +41,7 @@ static void call_after_if_active(std::function<void()> fn, GUI_App* app = &wxGet
});
}
static std::set<ObjectID> get_volume_ids(const Selection &selection)
static std::set<ObjectID> get_selected_volume_ids(const Selection &selection)
{
const Selection::IndicesList &volume_ids = selection.get_volume_idxs();
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
@ -68,20 +68,6 @@ static std::set<ObjectID> get_volume_ids(const Selection &selection)
return result;
}
// return ModelVolume from selection by object id
static ModelVolume *get_volume(const ObjectID &id, const Selection &selection) {
const Selection::IndicesList &volume_ids = selection.get_volume_idxs();
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
for (auto volume_id : volume_ids) {
const GLVolume *selected_volume = selection.get_volume(volume_id);
const GLVolume::CompositeID &cid = selected_volume->composite_id;
ModelObject *obj = model_objects[cid.object_id];
ModelVolume *volume = obj->volumes[cid.volume_id];
if (id == volume->id()) return volume;
}
return nullptr;
}
static std::string create_volumes_name(const std::set<ObjectID>& ids, const Selection &selection){
assert(!ids.empty());
std::string name;
@ -92,7 +78,7 @@ static std::string create_volumes_name(const std::set<ObjectID>& ids, const Sele
else
name += " + ";
const ModelVolume *volume = get_volume(id, selection);
const ModelVolume *volume = get_selected_volume(id, selection);
assert(volume != nullptr);
name += volume->name;
}
@ -185,7 +171,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
{
create_gui_cfg();
const Selection &selection = m_parent.get_selection();
auto act_volume_ids = get_volume_ids(selection);
auto act_volume_ids = get_selected_volume_ids(selection);
if (act_volume_ids.empty()) {
stop_worker_thread_request();
close();
@ -474,7 +460,7 @@ void GLGizmoSimplify::process()
const Selection& selection = m_parent.get_selection();
State::Data its;
for (const auto &id : m_volume_ids) {
const ModelVolume *volume = get_volume(id, selection);
const ModelVolume *volume = get_selected_volume(id, selection);
its[id] = std::make_unique<indexed_triangle_set>(volume->mesh().its); // copy
}
@ -552,7 +538,7 @@ void GLGizmoSimplify::apply_simplify() {
for (const auto &item: m_state.result) {
const ObjectID &id = item.first;
const indexed_triangle_set &its = *item.second;
ModelVolume *volume = get_volume(id, selection);
ModelVolume *volume = get_selected_volume(id, selection);
assert(volume != nullptr);
ModelObject *obj = volume->get_object();
@ -728,7 +714,7 @@ void GLGizmoSimplify::on_render()
const Selection & selection = m_parent.get_selection();
// Check that the GLVolume still belongs to the ModelObject we work on.
if (m_volume_ids != get_volume_ids(selection)) return;
if (m_volume_ids != get_selected_volume_ids(selection)) return;
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
const Selection::IndicesList &volume_idxs = selection.get_volume_idxs();

View File

@ -27,6 +27,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSVG.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoMeasure.hpp"
#include "libslic3r/format.hpp"
@ -114,6 +115,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9));
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 10));
m_gizmos.emplace_back(new GLGizmoEmboss(m_parent));
m_gizmos.emplace_back(new GLGizmoSVG(m_parent));
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent));
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));

View File

@ -83,6 +83,7 @@ public:
MmuSegmentation,
Measure,
Emboss,
Svg,
Simplify,
Undefined
};

View File

@ -1,6 +1,15 @@
#include "IconManager.hpp"
#include <cmath>
#include <boost/log/trivial.hpp>
#include "nanosvg/nanosvg.h"
#include "nanosvg/nanosvgrast.h"
#include "libslic3r/Utils.hpp" // ScopeGuard
#include "3DScene.hpp" // glsafe
#include "GL/glew.h"
#define STB_RECT_PACK_IMPLEMENTATION
#include "imgui/imstb_rectpack.h" // distribute rectangles
using namespace Slic3r::GUI;
@ -14,16 +23,198 @@ static void draw_transparent_icon(const IconManager::Icon &icon); // only help f
IconManager::~IconManager() {
priv::clear(m_icons);
// release opengl texture is made in ~GLTexture()
if (m_id != 0)
glsafe(::glDeleteTextures(1, &m_id));
}
std::vector<IconManager::Icons> IconManager::init(const InitTypes &input)
namespace {
NSVGimage *parse_file(const char * filepath) {
FILE *fp = boost::nowide::fopen(filepath, "rb");
assert(fp != nullptr);
if (fp == nullptr)
return nullptr;
Slic3r::ScopeGuard sg([fp]() { fclose(fp); });
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// Note: +1 is for null termination
auto data_ptr = std::make_unique<char[]>(size+1);
data_ptr[size] = '\0'; // Must be null terminated.
size_t readed_size = fread(data_ptr.get(), 1, size, fp);
assert(readed_size == size);
if (readed_size != size)
return nullptr;
return nsvgParse(data_ptr.get(), "px", 96.0f);
}
void subdata(unsigned char *data, size_t data_stride, const std::vector<unsigned char> &data2, size_t data2_row) {
assert(data_stride >= data2_row);
for (size_t data2_offset = 0, data_offset = 0;
data2_offset < data2.size();
data2_offset += data2_row, data_offset += data_stride)
::memcpy((void *)(data + data_offset), (const void *)(data2.data() + data2_offset), data2_row);
}
}
IconManager::Icons IconManager::init(const InitTypes &input)
{
BOOST_LOG_TRIVIAL(error) << "Not implemented yet";
return {};
assert(!input.empty());
if (input.empty())
return {};
// TODO: remove in future
if (m_id != 0) {
glsafe(::glDeleteTextures(1, &m_id));
m_id = 0;
}
int total_surface = 0;
for (const InitType &i : input)
total_surface += i.size.x * i.size.y;
const int surface_sqrt = (int)sqrt((float)total_surface) + 1;
// Start packing
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
const int TEX_HEIGHT_MAX = 1024 * 32;
int width = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512;
int num_nodes = width;
std::vector<stbrp_node> nodes(num_nodes);
stbrp_context context;
stbrp_init_target(&context, width, TEX_HEIGHT_MAX, nodes.data(), num_nodes);
ImVector<stbrp_rect> pack_rects;
pack_rects.resize(input.size());
memset(pack_rects.Data, 0, (size_t) pack_rects.size_in_bytes());
for (size_t i = 0; i < input.size(); i++) {
const ImVec2 &size = input[i].size;
assert(size.x > 1);
assert(size.y > 1);
pack_rects[i].w = size.x;
pack_rects[i].h = size.y;
}
int pack_rects_res = stbrp_pack_rects(&context, &pack_rects[0], pack_rects.Size);
assert(pack_rects_res == 1);
if (pack_rects_res != 1)
return {};
ImVec2 tex_size(width, width);
for (const stbrp_rect &rect : pack_rects) {
float x = rect.x + rect.w;
float y = rect.y + rect.h;
if(x > tex_size.x) tex_size.x = x;
if(y > tex_size.y) tex_size.y = y;
}
Icons result(input.size());
for (int i = 0; i < pack_rects.Size; i++) {
const stbrp_rect &rect = pack_rects[i];
assert(rect.was_packed);
if (!rect.was_packed)
return {};
ImVec2 tl(rect.x / tex_size.x, rect.y / tex_size.y);
ImVec2 br((rect.x + rect.w) / tex_size.x, (rect.y + rect.h) / tex_size.y);
assert(input[i].size.x == rect.w);
assert(input[i].size.y == rect.h);
Icon icon = {input[i].size, tl, br};
result[i] = std::make_shared<Icon>(std::move(icon));
}
NSVGrasterizer *rast = nsvgCreateRasterizer();
assert(rast != nullptr);
if (rast == nullptr)
return {};
ScopeGuard sg_rast([rast]() { ::nsvgDeleteRasterizer(rast); });
int channels = 4;
int n_pixels = tex_size.x * tex_size.y;
// store data for whole texture
std::vector<unsigned char> data(n_pixels * channels, {0});
// initialize original index locations
std::vector<size_t> idx(input.size());
std::iota(idx.begin(), idx.end(), 0);
// Group same filename by sort inputs
// sort indexes based on comparing values in input
std::sort(idx.begin(), idx.end(), [&input](size_t i1, size_t i2) { return input[i1].filepath < input[i2].filepath; });
for (size_t j: idx) {
const InitType &i = input[j];
if (i.filepath.empty())
continue; // no file path only reservation of space for texture
assert(boost::filesystem::exists(i.filepath));
if (!boost::filesystem::exists(i.filepath))
continue;
assert(boost::algorithm::iends_with(i.filepath, ".svg"));
if (!boost::algorithm::iends_with(i.filepath, ".svg"))
continue;
NSVGimage *image = parse_file(i.filepath.c_str());
assert(image != nullptr);
if (image == nullptr)
return {};
ScopeGuard sg_image([image]() { ::nsvgDelete(image); });
float svg_scale = i.size.y / image->height;
// scale should be same in both directions
assert(is_approx(svg_scale, i.size.y / image->width));
const stbrp_rect &rect = pack_rects[j];
int n_pixels = rect.w * rect.h;
std::vector<unsigned char> icon_data(n_pixels * channels, {0});
::nsvgRasterize(rast, image, 0, 0, svg_scale, icon_data.data(), i.size.x, i.size.y, i.size.x * channels);
// makes white or gray only data in icon
if (i.type == RasterType::white_only_data ||
i.type == RasterType::gray_only_data) {
unsigned char value = (i.type == RasterType::white_only_data) ? 255 : 127;
for (size_t k = 0; k < icon_data.size(); k += channels)
if (icon_data[k] != 0 || icon_data[k + 1] != 0 || icon_data[k + 2] != 0) {
icon_data[k] = value;
icon_data[k + 1] = value;
icon_data[k + 2] = value;
}
}
int start_offset = (rect.y*tex_size.x + rect.x) * channels;
int data_stride = tex_size.x * channels;
subdata(data.data() + start_offset, data_stride, icon_data, rect.w * channels);
}
if (m_id != 0)
glsafe(::glDeleteTextures(1, &m_id));
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint) m_id));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) tex_size.x, (GLsizei) tex_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*) data.data()));
// bind no texture
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
for (const auto &i : result)
i->tex_id = m_id;
return result;
}
std::vector<IconManager::Icons> IconManager::init(const std::vector<std::string> &file_paths, const ImVec2 &size, RasterType type)
{
assert(!file_paths.empty());
assert(size.x >= 1);
assert(size.x < 256*16);
// TODO: remove in future
if (!m_icons.empty()) {
// not first initialization
@ -159,6 +350,7 @@ void priv::draw_transparent_icon(const IconManager::Icon &icon)
draw(icon_px);
}
#include "imgui/imgui_internal.h" //ImGuiWindow
namespace Slic3r::GUI {
void draw(const IconManager::Icon &icon, const ImVec2 &size, const ImVec4 &tint_col, const ImVec4 &border_col)
@ -179,7 +371,10 @@ void draw(const IconManager::Icon &icon, const ImVec2 &size, const ImVec4 &tint_
bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover)
{
// check of hover
float cursor_x = ImGui::GetCursorPosX();
ImGuiWindow *window = ImGui::GetCurrentWindow();
float cursor_x = ImGui::GetCursorPosX()
- window->DC.GroupOffset.x
- window->DC.ColumnsOffset.x;
priv::draw_transparent_icon(icon);
ImGui::SameLine(cursor_x);
if (ImGui::IsItemHovered()) {

View File

@ -70,10 +70,10 @@ public:
/// Initialize raster texture on GPU with given images
/// NOTE: Have to be called after OpenGL initialization
/// </summary>
/// <param name="input">Define files and its </param>
/// <param name="input">Define files and its size with rasterization</param>
/// <returns>Rasterized icons stored on GPU,
/// Same size and order as input, each item of vector is set of texture in order by RasterType</returns>
VIcons init(const InitTypes &input);
Icons init(const InitTypes &input);
/// <summary>
/// Initialize multiple icons with same settings for size and type
@ -96,6 +96,8 @@ public:
private:
// keep data stored on GPU
GLTexture m_icons_texture;
unsigned int m_id{ 0 };
Icons m_icons;
};

View File

@ -673,8 +673,9 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.4f, 0.4f, 0.4f, 1.0f });
int frame_padding = style.ItemSpacing.y / 2; // keep same line height for input and slider
const ImTextureID tex_id = io.Fonts->TexID;
if (image_button(tex_id, size, uv0, uv1, -1, ImVec4(0.0, 0.0, 0.0, 0.0), ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags_PressedOnClick)) {
if (image_button(tex_id, size, uv0, uv1, frame_padding, ImVec4(0.0, 0.0, 0.0, 0.0), ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags_PressedOnClick)) {
if (!slider_editing)
ImGui::SetKeyboardFocusHere(-1);
else
@ -1398,6 +1399,48 @@ bool ImGuiWrapper::slider_optional_int(const char *label,
} else return false;
}
std::optional<ImVec2> ImGuiWrapper::change_window_position(const char *window_name, bool try_to_fix) {
ImGuiWindow *window = ImGui::FindWindowByName(window_name);
// is window just created
if (window == NULL)
return {};
// position of window on screen
ImVec2 position = window->Pos;
ImVec2 size = window->SizeFull;
// screen size
ImVec2 screen = ImGui::GetMainViewport()->Size;
std::optional<ImVec2> output_window_offset;
if (position.x < 0) {
if (position.y < 0)
// top left
output_window_offset = ImVec2(0, 0);
else
// only left
output_window_offset = ImVec2(0, position.y);
} else if (position.y < 0) {
// only top
output_window_offset = ImVec2(position.x, 0);
} else if (screen.x < (position.x + size.x)) {
if (screen.y < (position.y + size.y))
// right bottom
output_window_offset = ImVec2(screen.x - size.x, screen.y - size.y);
else
// only right
output_window_offset = ImVec2(screen.x - size.x, position.y);
} else if (screen.y < (position.y + size.y)) {
// only bottom
output_window_offset = ImVec2(position.x, screen.y - size.y);
}
if (!try_to_fix && output_window_offset.has_value())
output_window_offset = ImVec2(-1, -1); // Put on default position
return output_window_offset;
}
void ImGuiWrapper::left_inputs() {
ImGui::ClearActiveID();
}

View File

@ -158,6 +158,15 @@ public:
// Extended function ImGuiWrapper::slider_float to work with std::optional<int>, when value == def_val than optional release its value
bool slider_optional_int(const char* label, std::optional<int> &v, int v_min, int v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true, int def_val = 0);
/// <summary>
/// Change position of imgui window
/// </summary>
/// <param name="window_name">ImGui identifier of window</param>
/// <param name="output_window_offset">[output] optional </param>
/// <param name="try_to_fix">When True Only move to be full visible otherwise reset position</param>
/// <returns>New offset of window for function ImGui::SetNextWindowPos</returns>
static std::optional<ImVec2> change_window_position(const char *window_name, bool try_to_fix);
/// <summary>
/// Use ImGui internals to unactivate (lose focus) in input.
/// When input is activ it can't change value by application.

View File

@ -176,10 +176,11 @@ bool BoostThreadWorker::wait_for_idle(unsigned timeout_ms)
bool BoostThreadWorker::push(std::unique_ptr<Job> job)
{
if (job)
m_input_queue.push(JobEntry{std::move(job)});
if (!job)
return false;
return bool{job};
m_input_queue.push(JobEntry{std::move(job)});
return true;
}
}} // namespace Slic3r::GUI

View File

@ -78,7 +78,7 @@ void CreateFontImageJob::process(Ctl &ctl)
// normalize height of font
BoundingBox bounding_box;
for (ExPolygon &shape : shapes)
for (const ExPolygon &shape : shapes)
bounding_box.merge(BoundingBox(shape.contour.points));
if (bounding_box.size().x() < 1 || bounding_box.size().y() < 1) {
m_input.cancel->store(true);

View File

@ -51,8 +51,7 @@ void CreateFontStyleImagesJob::process(Ctl &ctl)
for (ExPolygon &shape : shapes) shape.translate(-bounding_box.min);
// calculate conversion from FontPoint to screen pixels by size of font
const FontFile::Info &info = get_font_info(*item.font.font_file, item.prop);
double scale = item.prop.size_in_mm / info.unit_per_em * SHAPE_SCALE * m_input.ppm;
double scale = get_text_shape_scale(item.prop, *item.font.font_file);
scales[index] = scale;
//double scale = font_prop.size_in_mm * SCALING_FACTOR;

File diff suppressed because it is too large Load Diff

View File

@ -9,42 +9,80 @@
#include <memory>
#include <string>
#include <libslic3r/Emboss.hpp>
#include "slic3r/Utils/RaycastManager.hpp"
#include <libslic3r/EmbossShape.hpp> // ExPolygonsWithIds
#include "libslic3r/Point.hpp" // Transform3d
#include "libslic3r/ObjectID.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/TextLines.hpp"
#include "Job.hpp"
// forward declarations
namespace Slic3r {
class ModelVolume;
class TriangleMesh;
}
class ModelVolume;
enum class ModelVolumeType : int;
class BuildVolume;
namespace GUI {
class RaycastManager;
class Plater;
class GLCanvas3D;
class Worker;
class Selection;
}}
namespace Slic3r::GUI::Emboss {
/// <summary>
/// Base data holder for embossing
/// Base data hold data for create emboss shape
/// </summary>
struct DataBase
class 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;
public:
DataBase(const std::string& volume_name, std::shared_ptr<std::atomic<bool>> cancel)
: volume_name(volume_name), cancel(std::move(cancel)) {}
DataBase(const std::string& volume_name, std::shared_ptr<std::atomic<bool>> cancel, EmbossShape&& shape)
: volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape)){}
DataBase(DataBase &&) = default;
virtual ~DataBase() = default;
/// <summary>
/// Create shape
/// e.g. Text extract glyphs from font
/// Not 'const' function because it could modify shape
/// </summary>
virtual EmbossShape& create_shape() { return shape; };
/// <summary>
/// Write data how to reconstruct shape to volume
/// </summary>
/// <param name="volume">Data object for store emboss params</param>
virtual void write(ModelVolume &volume) const;
// Define projection move
// True (raised) .. move outside from surface
// False (engraved).. move into object
bool is_outside;
// True (raised) .. move outside from surface (MODEL_PART)
// False (engraved).. move into object (NEGATIVE_VOLUME)
bool is_outside = true;
// Define per letter projection on one text line
// [optional] It is not used when empty
Slic3r::Emboss::TextLines text_lines = {};
// [optional] Define distance for surface
// It is used only for flat surface (not cutted)
// Position of Zero(not set value) differ for MODEL_PART and NEGATIVE_VOLUME
std::optional<float> from_surface;
// new volume name
std::string volume_name;
// flag that job is canceled
// for time after process.
std::shared_ptr<std::atomic<bool>> cancel;
// Define per letter projection on one text line
// [optional] It is not used when empty
Slic3r::Emboss::TextLines text_lines;
// shape to emboss
EmbossShape shape;
};
/// <summary>
@ -63,60 +101,16 @@ struct DataCreateVolume : public DataBase
// 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;
};
using DataBasePtr = std::unique_ptr<DataBase>;
/// <summary>
/// Hold neccessary data to update embossed text object in job
/// </summary>
struct DataUpdate : public DataBase
struct DataUpdate
{
// Hold data about shape
DataBasePtr base;
// unique identifier of volume to change
ObjectID volume_id;
};
@ -132,7 +126,7 @@ class UpdateJob : public Job
public:
// move params to private variable
UpdateJob(DataUpdate &&input);
explicit UpdateJob(DataUpdate &&input);
/// <summary>
/// Create new embossed volume by m_input data and store to m_result
@ -154,18 +148,14 @@ public:
/// </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,
std::string_view volume_name);
/// <param name="base">Data to write into volume</param>
static void update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base);
};
struct SurfaceVolumeData
{
// Transformation of text volume inside of object
Transform3d text_tr;
// Transformation of volume inside of object
Transform3d transform;
struct ModelSource
{
@ -178,66 +168,98 @@ struct SurfaceVolumeData
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;
TriangleMesh m_result;
public:
// move params to private variable
UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input);
explicit UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
/// <summary>
/// Copied triangles from object to be able create mesh for cut surface from
/// </summary>
/// <param name="volume">Define embossed volume</param>
/// <returns>Source data for cut surface from</returns>
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume);
/// <summary>
/// shorten params for start_crate_volume functions
/// </summary>
struct CreateVolumeParams
{
GLCanvas3D &canvas;
// Direction of ray into scene
const Camera &camera;
// To put new object on the build volume
const BuildVolume &build_volume;
// used to emplace job for execution
Worker &worker;
// New created volume type
ModelVolumeType volume_type;
// Contain AABB trees from scene
RaycastManager &raycaster;
// Define which gizmo open on the success
unsigned char gizmo; // GLGizmosManager::EType
// Volume define object to add new volume
const GLVolume *gl_volume;
// Wanted additionl move in Z(emboss) direction of new created volume
std::optional<float> distance = {};
// Wanted additionl rotation around Z of new created volume
std::optional<float> angle = {};
};
/// <summary>
/// Create new volume on position of mouse cursor
/// </summary>
/// <param name="plater_ptr">canvas + camera + bed shape + </param>
/// <param name="data">Shape of emboss</param>
/// <param name="volume_type">New created volume type</param>
/// <param name="raycaster">Knows object in scene</param>
/// <param name="gizmo">Define which gizmo open on the success - enum GLGizmosManager::EType</param>
/// <param name="mouse_pos">Define position where to create volume</param>
/// <param name="distance">Wanted additionl move in Z(emboss) direction of new created volume</param>
/// <param name="angle">Wanted additionl rotation around Z of new created volume</param>
/// <returns>True on success otherwise False</returns>
bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos);
/// <summary>
/// Same as previous function but without mouse position
/// Need to suggest position or put near the selection
/// </summary>
bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr data);
/// <summary>
/// Start job for update embossed volume
/// </summary>
/// <param name="data">define update data</param>
/// <param name="volume">Volume to be updated</param>
/// <param name="selection">Keep model and gl_volumes - when start use surface volume must be selected</param>
/// <param name="raycaster">Could cast ray to scene</param>
/// <returns>True when start job otherwise false</returns>
bool start_update_volume(DataUpdate &&data, const ModelVolume &volume, const Selection &selection, RaycastManager &raycaster);
} // namespace Slic3r::GUI
#endif // slic3r_EmbossJob_hpp_

View File

@ -1392,10 +1392,10 @@ void MainFrame::init_menubar_as_editor()
append_submenu(fileMenu, export_menu, wxID_ANY, _L("&Export"), "");
wxMenu* convert_menu = new wxMenu();
append_menu_item(convert_menu, wxID_ANY, _L("Convert ascii G-code to &binary") + dots, _L("Convert a G-code file from ascii to binary format"),
append_menu_item(convert_menu, wxID_ANY, _L("Convert ASCII G-code to &binary") + dots, _L("Convert a G-code file from ASCII to binary format"),
[this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->convert_gcode_to_binary(); }, "convert_file", nullptr,
[]() { return true; }, this);
append_menu_item(convert_menu, wxID_ANY, _L("Convert binary G-code to &ascii") + dots, _L("Convert a G-code file from binary to ascii format"),
append_menu_item(convert_menu, wxID_ANY, _L("Convert binary G-code to &ASCII") + dots, _L("Convert a G-code file from binary to ASCII format"),
[this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->convert_gcode_to_ascii(); }, "convert_file", nullptr,
[]() { return true; }, this);
append_submenu(fileMenu, convert_menu, wxID_ANY, _L("&Convert"), "");

View File

@ -80,6 +80,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* pare
const wxString& sub_obj_name,
Slic3r::ModelVolumeType type,
const bool is_text_volume,
const bool is_svg_volume,
const wxString& extruder,
const int idx/* = -1*/) :
m_parent(parent),
@ -87,6 +88,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* pare
m_type(itVolume),
m_volume_type(type),
m_is_text_volume(is_text_volume),
m_is_svg_volume(is_svg_volume),
m_idx(idx),
m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : "")
{
@ -338,6 +340,7 @@ ObjectDataViewModel::ObjectDataViewModel()
{
m_volume_bmps = MenuFactory::get_volume_bitmaps();
m_text_volume_bmps = MenuFactory::get_text_volume_bitmaps();
m_svg_volume_bmps = MenuFactory::get_svg_volume_bitmaps();
m_warning_bmp = *get_bmp_bundle(WarningIcon);
m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon);
m_lock_bmp = *get_bmp_bundle(LockIcon);
@ -360,7 +363,10 @@ 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 ? (node->is_text_volume() ? *m_text_volume_bmps.at(vol_type) : *m_volume_bmps.at(vol_type)) : m_empty_bmp);
node->SetBitmap(is_volume_node ? (
node->is_text_volume() ? *m_text_volume_bmps.at(vol_type) :
node->is_svg_volume() ? *m_svg_volume_bmps.at(vol_type) :
*m_volume_bmps.at(vol_type)) : m_empty_bmp);
return;
}
@ -381,7 +387,10 @@ void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node)
if (node->has_lock())
bmps.emplace_back(&m_lock_bmp);
if (is_volume_node)
bmps.emplace_back(node->is_text_volume() ? m_text_volume_bmps[vol_type] : m_volume_bmps[vol_type]);
bmps.emplace_back(
node->is_text_volume() ? m_text_volume_bmps[vol_type] :
node->is_svg_volume() ? m_svg_volume_bmps[vol_type] :
m_volume_bmps[vol_type]);
bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps);
}
@ -418,6 +427,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent
const int volume_idx,
const Slic3r::ModelVolumeType volume_type,
const bool is_text_volume,
const bool is_svg_volume,
const std::string& warning_icon_name,
const wxString& extruder)
{
@ -429,7 +439,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, is_text_volume, extruder, volume_idx);
const auto node = new ObjectDataViewModelNode(root, name, volume_type, is_text_volume, is_svg_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);
@ -1688,6 +1698,7 @@ void ObjectDataViewModel::UpdateBitmaps()
{
m_volume_bmps = MenuFactory::get_volume_bitmaps();
m_text_volume_bmps = MenuFactory::get_text_volume_bitmaps();
m_svg_volume_bmps = MenuFactory::get_svg_volume_bitmaps();
m_warning_bmp = *get_bmp_bundle(WarningIcon);
m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon);
m_lock_bmp = *get_bmp_bundle(LockIcon);

View File

@ -89,6 +89,7 @@ class ObjectDataViewModelNode
std::string m_action_icon_name = "";
ModelVolumeType m_volume_type{ -1 };
bool m_is_text_volume{ false };
bool m_is_svg_volume{false};
InfoItemType m_info_item_type {InfoItemType::Undef};
public:
@ -107,6 +108,7 @@ public:
const wxString& sub_obj_name,
Slic3r::ModelVolumeType type,
const bool is_text_volume,
const bool is_svg_volume,
const wxString& extruder,
const int idx = -1 );
@ -242,6 +244,7 @@ public:
bool update_settings_digest(const std::vector<std::string>& categories);
int volume_type() const { return int(m_volume_type); }
bool is_text_volume() const { return m_is_text_volume; }
bool is_svg_volume() const { return m_is_svg_volume; }
void sys_color_changed();
#ifndef NDEBUG
@ -268,7 +271,8 @@ class ObjectDataViewModel :public wxDataViewModel
{
std::vector<ObjectDataViewModelNode*> m_objects;
std::vector<wxBitmapBundle*> m_volume_bmps;
std::vector<wxBitmapBundle*> m_text_volume_bmps;
std::vector<wxBitmapBundle *> m_text_volume_bmps;
std::vector<wxBitmapBundle *> m_svg_volume_bmps;
std::map<InfoItemType, wxBitmapBundle*> m_info_bmps;
wxBitmapBundle m_empty_bmp;
wxBitmapBundle m_warning_bmp;
@ -290,6 +294,7 @@ public:
const int volume_idx,
const Slic3r::ModelVolumeType volume_type,
const bool is_text_volume,
const bool is_svg_volume,
const std::string& warning_icon_name,
const wxString& extruder);
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);

View File

@ -127,6 +127,7 @@
#include "MsgDialog.hpp"
#include "ProjectDirtyStateManager.hpp"
#include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification
#include "Gizmos/GLGizmoSVG.hpp" // Drop SVG file
#include "Gizmos/GLGizmoCut.hpp"
#include "FileArchiveDialog.hpp"
@ -1641,6 +1642,29 @@ private:
Plater& m_plater;
};
namespace {
bool emboss_svg(Plater& plater, const wxString &svg_file, const Vec2d& mouse_drop_position)
{
std::string svg_file_str = into_u8(svg_file);
GLCanvas3D* canvas = plater.canvas3D();
if (canvas == nullptr)
return false;
auto base_svg = canvas->get_gizmos_manager().get_gizmo(GLGizmosManager::Svg);
if (base_svg == nullptr)
return false;
GLGizmoSVG* svg = dynamic_cast<GLGizmoSVG *>(base_svg);
if (svg == nullptr)
return false;
// Refresh hover state to find surface point under mouse
wxMouseEvent evt(wxEVT_MOTION);
evt.SetPosition(wxPoint(mouse_drop_position.x(), mouse_drop_position.y()));
canvas->on_mouse(evt); // call render where is call GLCanvas3D::_picking_pass()
return svg->create_volume(svg_file_str, mouse_drop_position, ModelVolumeType::MODEL_PART);
}
}
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
{
#ifdef WIN32
@ -1652,6 +1676,20 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
m_mainframe.select_tab(size_t(0));
if (wxGetApp().is_editor())
m_plater.select_view_3D("3D");
// When only one .svg file is dropped on scene
if (filenames.size() == 1) {
const wxString &filename = filenames.Last();
const wxString file_extension = filename.substr(filename.length() - 4);
if (file_extension.CmpNoCase(".svg") == 0) {
const wxPoint offset = m_plater.GetPosition();
Vec2d mouse_position(x - offset.x, y - offset.y);
// Scale for retina displays
const GLCanvas3D *canvas = m_plater.canvas3D();
canvas->apply_retina_scale(mouse_position);
return emboss_svg(m_plater, filename, mouse_position);
}
}
bool res = m_plater.load_files(filenames);
m_mainframe.update_title();
return res;
@ -4471,9 +4509,13 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
const bool is_part = selection.is_single_volume_or_modifier() && ! selection.is_any_connector();
if (is_some_full_instances)
menu = printer_technology == ptSLA ? menus.sla_object_menu() : menus.object_menu();
else if (is_part)
menu = selection.is_single_text() ? menus.text_part_menu() : menus.part_menu();
else
else if (is_part) {
const GLVolume* gl_volume = selection.get_first_volume();
const ModelVolume *model_volume = get_model_volume(*gl_volume, selection.get_model()->objects);
menu = (model_volume != nullptr && model_volume->is_text()) ? menus.text_part_menu() :
(model_volume != nullptr && model_volume->is_svg()) ? menus.svg_part_menu() :
menus.part_menu();
} else
menu = menus.multi_selection_menu();
// }
}
@ -6816,6 +6858,105 @@ void Plater::export_amf()
}
}
namespace {
std::string get_file_name(const std::string &file_path)
{
size_t pos_last_delimiter = file_path.find_last_of("/\\");
size_t pos_point = file_path.find_last_of('.');
size_t offset = pos_last_delimiter + 1;
size_t count = pos_point - pos_last_delimiter - 1;
return file_path.substr(offset, count);
}
using SvgFile = EmbossShape::SvgFile;
using SvgFiles = std::vector<SvgFile*>;
std::string create_unique_3mf_filepath(const std::string &file, const SvgFiles svgs)
{
// const std::string MODEL_FOLDER = "3D/"; // copy from file 3mf.cpp
std::string path_in_3mf = "3D/" + file + ".svg";
size_t suffix_number = 0;
bool is_unique = false;
do{
is_unique = true;
path_in_3mf = "3D/" + file + ((suffix_number++)? ("_" + std::to_string(suffix_number)) : "") + ".svg";
for (SvgFile *svgfile : svgs) {
if (svgfile->path_in_3mf.empty())
continue;
if (svgfile->path_in_3mf.compare(path_in_3mf) == 0) {
is_unique = false;
break;
}
}
} while (!is_unique);
return path_in_3mf;
}
bool set_by_local_path(SvgFile &svg, const SvgFiles& svgs)
{
// Try to find already used svg file
for (SvgFile *svg_ : svgs) {
if (svg_->path_in_3mf.empty())
continue;
if (svg.path.compare(svg_->path) == 0) {
svg.path_in_3mf = svg_->path_in_3mf;
return true;
}
}
return false;
}
/// <summary>
/// Function to secure private data before store to 3mf
/// </summary>
/// <param name="model">Data(also private) to clean before publishing</param>
void publish(Model &model) {
// SVG file publishing
bool exist_new = false;
SvgFiles svgfiles;
for (ModelObject *object: model.objects){
for (ModelVolume *volume : object->volumes) {
if (!volume->emboss_shape.has_value())
continue;
if (volume->text_configuration.has_value())
continue; // text dosen't have svg path
SvgFile* svg = &volume->emboss_shape->svg_file;
if (svg->path_in_3mf.empty())
exist_new = true;
svgfiles.push_back(svg);
}
}
if (exist_new){
MessageDialog dialog(nullptr,
_L("Are you sure you want to store original SVGs with their local paths into the 3MF file?\n "
"If you hit 'NO', all SVGs in the project will not be editable any more."),
_L("Private protection"), wxYES_NO | wxICON_QUESTION);
if (dialog.ShowModal() == wxID_NO){
for (ModelObject *object : model.objects)
for (ModelVolume *volume : object->volumes)
if (volume->emboss_shape.has_value())
volume->emboss_shape.reset();
}
}
for (SvgFile* svgfile : svgfiles){
if (!svgfile->path_in_3mf.empty())
continue; // already suggested path (previous save)
// create unique name for svgs, when local path differ
std::string filename = "unknown";
if (!svgfile->path.empty()) {
if (set_by_local_path(*svgfile, svgfiles))
continue;
// check whether original filename is already in:
filename = get_file_name(svgfile->path);
}
svgfile->path_in_3mf = create_unique_3mf_filepath(filename, svgfiles);
}
}
}
bool Plater::export_3mf(const boost::filesystem::path& output_path)
{
if (p->model.objects.empty()) {
@ -6836,6 +6977,10 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path)
if (!path.Lower().EndsWith(".3mf"))
return false;
// take care about private data stored into .3mf
// modify model
publish(p->model);
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
@ -7945,6 +8090,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::svg_part_menu() { return p->menus.svg_part_menu(); }
wxMenu* Plater::sla_object_menu() { return p->menus.sla_object_menu(); }
wxMenu* Plater::default_menu() { return p->menus.default_menu(); }
wxMenu* Plater::instance_menu() { return p->menus.instance_menu(); }

View File

@ -493,6 +493,7 @@ public:
wxMenu* object_menu();
wxMenu* part_menu();
wxMenu* text_part_menu();
wxMenu* svg_part_menu();
wxMenu* sla_object_menu();
wxMenu* default_menu();
wxMenu* instance_menu();

View File

@ -3028,5 +3028,30 @@ const GLVolume *get_selected_gl_volume(const Selection &selection)
return selection.get_volume(volume_idx);
}
ModelVolume *get_selected_volume(const ObjectID &volume_id, const Selection &selection) {
const Selection::IndicesList &volume_ids = selection.get_volume_idxs();
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
for (auto id : volume_ids) {
const GLVolume *selected_volume = selection.get_volume(id);
const GLVolume::CompositeID &cid = selected_volume->composite_id;
ModelObject *obj = model_objects[cid.object_id];
ModelVolume *volume = obj->volumes[cid.volume_id];
if (volume_id == volume->id())
return volume;
}
return nullptr;
}
ModelVolume *get_volume(const ObjectID &volume_id, const Selection &selection) {
const ModelObjectPtrs &objects = selection.get_model()->objects;
for (const ModelObject *object : objects) {
for (ModelVolume *volume : object->volumes) {
if (volume->id() == volume_id)
return volume;
}
}
return nullptr;
}
} // namespace GUI
} // namespace Slic3r

View File

@ -18,6 +18,7 @@ class Shader;
class Model;
class ModelObject;
class ModelVolume;
class ObjectID;
class GLVolume;
class GLArrow;
class GLCurvedArrow;
@ -403,9 +404,12 @@ private:
const Transform3d& transform, const Vec3d& world_pivot);
};
ModelVolume *get_selected_volume(const Selection &selection);
ModelVolume *get_selected_volume (const Selection &selection);
const GLVolume *get_selected_gl_volume(const Selection &selection);
ModelVolume *get_selected_volume (const ObjectID &volume_id, const Selection &selection);
ModelVolume *get_volume (const ObjectID &volume_id, const Selection &selection);
} // namespace GUI
} // namespace Slic3r

View File

@ -12,8 +12,57 @@
#include "slic3r/GUI/I18N.hpp"
#include "libslic3r/Emboss.hpp"
namespace Slic3r::GUI {
using namespace Slic3r;
using namespace Slic3r::GUI;
namespace{
// Distance of embossed volume from surface to be represented as distance surface
// Maximal distance is also enlarge by size of emboss depth
constexpr Slic3r::MinMax<double> surface_distance_sq{1e-4, 10.}; // [in mm]
/// <summary>
/// Extract position of mouse from mouse event
/// </summary>
/// <param name="mouse_event">Event</param>
/// <returns>Position</returns>
Vec2d mouse_position(const wxMouseEvent &mouse_event);
/// <summary>
/// Start dragging
/// </summary>
/// <param name="mouse_pos"></param>
/// <param name="camera"></param>
/// <param name="surface_drag"></param>
/// <param name="canvas"></param>
/// <param name="raycast_manager"></param>
/// <param name="up_limit"></param>
/// <returns>True on success start otherwise false</returns>
bool start_dragging(const Vec2d &mouse_pos,
const Camera &camera,
std::optional<SurfaceDrag> &surface_drag,
GLCanvas3D &canvas,
RaycastManager &raycast_manager,
const std::optional<double> &up_limit);
/// <summary>
/// During dragging
/// </summary>
/// <param name="mouse_pos"></param>
/// <param name="camera"></param>
/// <param name="surface_drag"></param>
/// <param name="canvas"></param>
/// <param name="raycast_manager"></param>
/// <param name="up_limit"></param>
/// <returns></returns>
bool dragging(const Vec2d &mouse_pos,
const Camera &camera,
std::optional<SurfaceDrag> &surface_drag,
GLCanvas3D &canvas,
const RaycastManager &raycast_manager,
const std::optional<double> &up_limit);
}
namespace Slic3r::GUI {
// Calculate scale in world for check in debug
[[maybe_unused]] static std::optional<double> calc_scale(const Matrix3d &from, const Matrix3d &to, const Vec3d &dir)
{
@ -52,189 +101,16 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event,
if (mouse_event.Moving())
return false;
// detect start text dragging
if (mouse_event.LeftDown()) {
// selected volume
GLVolume *gl_volume = get_selected_gl_volume(canvas);
if (gl_volume == nullptr)
return false;
// is selected volume closest hovered?
const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes;
if (int hovered_idx = canvas.get_first_hover_volume_idx();
hovered_idx < 0)
return false;
else if (auto hovered_idx_ = static_cast<size_t>(hovered_idx);
hovered_idx_ >= gl_volumes.size() ||
gl_volumes[hovered_idx_] != gl_volume)
return false;
const ModelObjectPtrs &objects = canvas.get_model()->objects;
const ModelObject *object = get_model_object(*gl_volume, objects);
assert(object != nullptr);
if (object == nullptr)
return false;
const ModelInstance *instance = get_model_instance(*gl_volume, *object);
const ModelVolume *volume = get_model_volume(*gl_volume, *object);
assert(instance != nullptr && volume != nullptr);
if (object == nullptr || instance == nullptr || volume == nullptr)
return false;
// allowed drag&drop by canvas for object
if (volume->is_the_only_one_part())
return false;
const ModelVolumePtrs &volumes = object->volumes;
std::vector<size_t> allowed_volumes_id;
if (volumes.size() > 1) {
allowed_volumes_id.reserve(volumes.size() - 1);
for (auto &v : volumes) {
// skip actual selected object
if (v->id() == volume->id())
continue;
// drag only above part not modifiers or negative surface
if (!v->is_model_part())
continue;
allowed_volumes_id.emplace_back(v->id().id);
}
}
RaycastManager::AllowVolumes condition(std::move(allowed_volumes_id));
RaycastManager::Meshes meshes = create_meshes(canvas, condition);
// initialize raycasters
// INFO: It could slows down for big objects
// (may be move to thread and do not show drag until it finish)
raycast_manager.actualize(*instance, &condition, &meshes);
// wxCoord == int --> wx/types.h
Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
Vec2d mouse_pos = mouse_coord.cast<double>();
// world_matrix_fixed() without sla shift
Transform3d to_world = world_matrix_fixed(*gl_volume, objects);
// zero point of volume in world coordinate system
Vec3d volume_center = to_world.translation();
// screen coordinate of volume center
Vec2i coor = CameraUtils::project(camera, volume_center);
Vec2d mouse_offset = coor.cast<double>() - mouse_pos;
Vec2d mouse_offset_without_sla_shift = mouse_offset;
if (double sla_shift = gl_volume->get_sla_shift_z(); !is_approx(sla_shift, 0.)) {
Transform3d to_world_without_sla_move = instance->get_matrix() * volume->get_matrix();
if (volume->text_configuration.has_value() && volume->text_configuration->fix_3mf_tr.has_value())
to_world_without_sla_move = to_world_without_sla_move * (*volume->text_configuration->fix_3mf_tr);
// zero point of volume in world coordinate system
volume_center = to_world_without_sla_move.translation();
// screen coordinate of volume center
coor = CameraUtils::project(camera, volume_center);
mouse_offset_without_sla_shift = coor.cast<double>() - mouse_pos;
}
Transform3d volume_tr = gl_volume->get_volume_transformation().get_matrix();
if (volume->text_configuration.has_value()) {
const TextConfiguration &tc = *volume->text_configuration;
// fix baked transformation from .3mf store process
if (tc.fix_3mf_tr.has_value())
volume_tr = volume_tr * tc.fix_3mf_tr->inverse();
}
Transform3d instance_tr = instance->get_matrix();
Transform3d instance_tr_inv = instance_tr.inverse();
Transform3d world_tr = instance_tr * volume_tr;
std::optional<float> start_angle;
if (up_limit.has_value())
start_angle = Emboss::calc_up(world_tr, *up_limit);
surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv, gl_volume, condition, start_angle, true, mouse_offset_without_sla_shift};
// disable moving with object by mouse
canvas.enable_moving(false);
canvas.enable_picking(false);
return true;
}
if (mouse_event.LeftDown())
return start_dragging(mouse_position(mouse_event), camera, surface_drag, canvas, raycast_manager, up_limit);
// Dragging starts out of window
if (!surface_drag.has_value())
return false;
if (mouse_event.Dragging()) {
// wxCoord == int --> wx/types.h
Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
Vec2d mouse_pos = mouse_coord.cast<double>();
Vec2d offseted_mouse = mouse_pos + surface_drag->mouse_offset_without_sla_shift;
if (mouse_event.Dragging())
return dragging(mouse_position(mouse_event), camera, surface_drag, canvas, raycast_manager, up_limit);
std::optional<RaycastManager::Hit> hit = ray_from_camera(
raycast_manager, offseted_mouse, camera, &surface_drag->condition);
surface_drag->exist_hit = hit.has_value();
if (!hit.has_value()) {
// cross hair need redraw
canvas.set_as_dirty();
return true;
}
auto world_linear = surface_drag->world.linear();
// Calculate offset: transformation to wanted position
{
// Reset skew of the text Z axis:
// Project the old Z axis into a new Z axis, which is perpendicular to the old XY plane.
Vec3d old_z = world_linear.col(2);
Vec3d new_z = world_linear.col(0).cross(world_linear.col(1));
world_linear.col(2) = new_z * (old_z.dot(new_z) / new_z.squaredNorm());
}
Vec3d text_z_world = world_linear.col(2); // world_linear * Vec3d::UnitZ()
auto z_rotation = Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(text_z_world, hit->normal);
Transform3d world_new = z_rotation * surface_drag->world;
auto world_new_linear = world_new.linear();
// Fix direction of up vector to zero initial rotation
if(up_limit.has_value()){
Vec3d z_world = world_new_linear.col(2);
z_world.normalize();
Vec3d wanted_up = Emboss::suggest_up(z_world, *up_limit);
Vec3d y_world = world_new_linear.col(1);
auto y_rotation = Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(y_world, wanted_up);
world_new = y_rotation * world_new;
world_new_linear = world_new.linear();
}
// Edit position from right
Transform3d volume_new{Eigen::Translation<double, 3>(surface_drag->instance_inv * hit->position)};
volume_new.linear() = surface_drag->instance_inv.linear() * world_new_linear;
// Check that transformation matrix is valid transformation
assert(volume_new.matrix()(0, 0) == volume_new.matrix()(0, 0)); // Check valid transformation not a NAN
if (volume_new.matrix()(0, 0) != volume_new.matrix()(0, 0))
return true;
// Check that scale in world did not changed
assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitY()).has_value());
assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitZ()).has_value());
const ModelVolume *volume = get_model_volume(*surface_drag->gl_volume, canvas.get_model()->objects);
if (volume != nullptr && volume->text_configuration.has_value()) {
const TextConfiguration &tc = *volume->text_configuration;
// fix baked transformation from .3mf store process
if (tc.fix_3mf_tr.has_value())
volume_new = volume_new * (*tc.fix_3mf_tr);
// apply move in Z direction and rotation by up vector
Emboss::apply_transformation(surface_drag->start_angle, tc.style.prop.distance, volume_new);
}
// Update transformation for all instances
for (GLVolume *vol : canvas.get_volumes().volumes) {
if (vol->object_idx() != surface_drag->gl_volume->object_idx() || vol->volume_idx() != surface_drag->gl_volume->volume_idx())
continue;
vol->set_volume_transformation(volume_new);
}
canvas.set_as_dirty();
return true;
}
return false;
}
@ -257,12 +133,11 @@ std::optional<Vec3d> calc_surface_offset(const Selection &selection, RaycastMana
auto cond = RaycastManager::SkipVolume(volume->id().id);
raycast_manager.actualize(*instance, &cond);
Transform3d to_world = world_matrix_fixed(gl_volume, selection.get_model()->objects);
Vec3d point = to_world * Vec3d::Zero();
Vec3d direction = to_world.linear() * (-Vec3d::UnitZ());
Transform3d to_world = world_matrix_fixed(gl_volume, objects);
Vec3d point = to_world.translation();
Vec3d dir = -get_z_base(to_world);
// ray in direction of text projection(from volume zero to z-dir)
std::optional<RaycastManager::Hit> hit_opt = raycast_manager.closest_hit(point, direction, &cond);
std::optional<RaycastManager::Hit> hit_opt = raycast_manager.closest_hit(point, dir, &cond);
// Try to find closest point when no hit object in emboss direction
if (!hit_opt.has_value()) {
@ -297,6 +172,62 @@ std::optional<Vec3d> calc_surface_offset(const Selection &selection, RaycastMana
return offset_volume;
}
std::optional<float> calc_distance(const GLVolume &gl_volume, RaycastManager &raycaster, GLCanvas3D &canvas)
{
const ModelObject *object = get_model_object(gl_volume, canvas.get_model()->objects);
assert(object != nullptr);
if (object == nullptr)
return {};
const ModelInstance *instance = get_model_instance(gl_volume, *object);
const ModelVolume *volume = get_model_volume(gl_volume, *object);
assert(instance != nullptr && volume != nullptr);
if (object == nullptr || instance == nullptr || volume == nullptr)
return {};
if (volume->is_the_only_one_part())
return {};
RaycastManager::AllowVolumes condition = create_condition(object->volumes, volume->id());
RaycastManager::Meshes meshes = create_meshes(canvas, condition);
raycaster.actualize(*instance, &condition, &meshes);
return calc_distance(gl_volume, raycaster, &condition);
}
std::optional<float> calc_distance(const GLVolume &gl_volume, const RaycastManager &raycaster, const RaycastManager::ISkip *condition)
{
Transform3d w = gl_volume.world_matrix();
Vec3d p = w.translation();
Vec3d dir = -get_z_base(w);
auto hit_opt = raycaster.closest_hit(p, dir, condition);
if (!hit_opt.has_value())
return {};
const RaycastManager::Hit &hit = *hit_opt;
// NOTE: hit.squared_distance is in volume space not world
const Transform3d &tr = raycaster.get_transformation(hit.tr_key);
Vec3d hit_world = tr * hit.position;
Vec3d p_to_hit = hit_world - p;
double distance_sq = p_to_hit.squaredNorm();
// too small distance is calculated as zero distance
if (distance_sq < ::surface_distance_sq.min)
return {};
// check maximal distance
const BoundingBoxf3& bb = gl_volume.bounding_box();
double max_squared_distance = std::max(std::pow(2 * bb.size().z(), 2), ::surface_distance_sq.max);
if (distance_sq > max_squared_distance)
return {};
// calculate sign
float sign = (p_to_hit.dot(dir) > 0)? 1.f : -1.f;
// distiguish sign
return sign * static_cast<float>(sqrt(distance_sq));
}
Transform3d world_matrix_fixed(const GLVolume &gl_volume, const ModelObjectPtrs &objects)
{
Transform3d res = gl_volume.world_matrix();
@ -305,11 +236,11 @@ Transform3d world_matrix_fixed(const GLVolume &gl_volume, const ModelObjectPtrs
if (!mv)
return res;
const std::optional<TextConfiguration> &tc = mv->text_configuration;
if (!tc.has_value())
const std::optional<EmbossShape> &es = mv->emboss_shape;
if (!es.has_value())
return res;
const std::optional<Transform3d> &fix = tc->fix_3mf_tr;
const std::optional<Transform3d> &fix = es->fix_3mf_tr;
if (!fix.has_value())
return res;
@ -326,4 +257,329 @@ Transform3d world_matrix_fixed(const Selection &selection)
return world_matrix_fixed(*gl_volume, selection.get_model()->objects);
}
} // namespace Slic3r::GUI
void selection_transform(Selection &selection, const std::function<void()> &selection_transformation_fnc, const ModelVolume *volume)
{
GLVolume *gl_volume = selection.get_volume(*selection.get_volume_idxs().begin());
if (gl_volume == nullptr)
return selection_transformation_fnc();
if (volume == nullptr) {
volume = get_model_volume(*gl_volume, selection.get_model()->objects);
if (volume == nullptr)
return selection_transformation_fnc();
}
if (!volume->emboss_shape.has_value())
return selection_transformation_fnc();
const std::optional<Transform3d> &fix_tr = volume->emboss_shape->fix_3mf_tr;
if (!fix_tr.has_value())
return selection_transformation_fnc();
Transform3d volume_tr = gl_volume->get_volume_transformation().get_matrix();
gl_volume->set_volume_transformation(volume_tr * fix_tr->inverse());
selection.setup_cache();
selection_transformation_fnc();
volume_tr = gl_volume->get_volume_transformation().get_matrix();
gl_volume->set_volume_transformation(volume_tr * (*fix_tr));
selection.setup_cache();
}
bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas)
{
const Vec3d &cam_dir = camera.get_dir_forward();
Selection &sel = canvas.get_selection();
if (sel.is_empty())
return false;
// camera direction transformed into volume coordinate system
Transform3d to_world = world_matrix_fixed(sel);
Vec3d cam_dir_tr = to_world.inverse().linear() * cam_dir;
cam_dir_tr.normalize();
Vec3d emboss_dir(0., 0., -1.);
// check wether cam_dir is already used
if (is_approx(cam_dir_tr, emboss_dir))
return false;
assert(sel.get_volume_idxs().size() == 1);
GLVolume *gl_volume = sel.get_volume(*sel.get_volume_idxs().begin());
Transform3d vol_rot;
Transform3d vol_tr = gl_volume->get_volume_transformation().get_matrix();
// check whether cam_dir is opposit to emboss dir
if (is_approx(cam_dir_tr, -emboss_dir)) {
// rotate 180 DEG by y
vol_rot = Eigen::AngleAxis(M_PI_2, Vec3d(0., 1., 0.));
} else {
// calc params for rotation
Vec3d axe = emboss_dir.cross(cam_dir_tr);
axe.normalize();
double angle = std::acos(emboss_dir.dot(cam_dir_tr));
vol_rot = Eigen::AngleAxis(angle, axe);
}
Vec3d offset = vol_tr * Vec3d::Zero();
Vec3d offset_inv = vol_rot.inverse() * offset;
Transform3d res = vol_tr * Eigen::Translation<double, 3>(-offset) * vol_rot * Eigen::Translation<double, 3>(offset_inv);
// Transform3d res = vol_tr * vol_rot;
gl_volume->set_volume_transformation(Geometry::Transformation(res));
get_model_volume(*gl_volume, sel.get_model()->objects)->set_transformation(res);
return true;
}
void do_local_z_rotate(GLCanvas3D &canvas, double relative_angle)
{
Selection &selection = canvas.get_selection();
assert(!selection.is_empty());
if(selection.is_empty()) return;
assert(selection.is_single_full_object() || selection.is_single_volume());
if (!selection.is_single_full_object() && !selection.is_single_volume()) return;
// Fix angle for mirrored volume
bool is_mirrored = false;
const GLVolume* gl_volume = selection.get_first_volume();
if (gl_volume != nullptr) {
if (selection.is_single_full_object()) {
const ModelInstance *instance = get_model_instance(*gl_volume, selection.get_model()->objects);
if (instance != nullptr)
is_mirrored = has_reflection(instance->get_matrix());
} else {
// selection.is_single_volume()
const ModelVolume *volume = get_model_volume(*gl_volume, selection.get_model()->objects);
if (volume != nullptr)
is_mirrored = has_reflection(volume->get_matrix());
}
}
if (is_mirrored)
relative_angle *= -1;
selection.setup_cache();
auto selection_rotate_fnc = [&selection, &relative_angle](){
TransformationType transformation_type = selection.is_single_volume() ?
TransformationType::Local_Relative_Independent :
TransformationType::Instance_Relative_Independent;
selection.rotate(Vec3d(0., 0., relative_angle), transformation_type);
};
selection_transform(selection, selection_rotate_fnc);
std::string snapshot_name; // empty meand no store undo / redo
// NOTE: it use L instead of _L macro because prefix _ is appended
// inside function do_move
// snapshot_name = L("Set text rotation");
canvas.do_rotate(snapshot_name);
}
void do_local_z_move(GLCanvas3D &canvas, double relative_move) {
Selection &selection = canvas.get_selection();
assert(!selection.is_empty());
if (selection.is_empty()) return;
selection.setup_cache();
auto selection_translate_fnc = [&selection, relative_move]() {
Vec3d translate = Vec3d::UnitZ() * relative_move;
selection.translate(translate, TransformationType::Local);
};
selection_transform(selection, selection_translate_fnc);
std::string snapshot_name; // empty mean no store undo / redo
// NOTE: it use L instead of _L macro because prefix _ is appended inside
// function do_move
// snapshot_name = L("Set surface distance");
canvas.do_move(snapshot_name);
}
} // namespace Slic3r::GUI
// private implementation
namespace {
Vec2d mouse_position(const wxMouseEvent &mouse_event){
// wxCoord == int --> wx/types.h
Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
return mouse_coord.cast<double>();
}
bool start_dragging(const Vec2d &mouse_pos,
const Camera &camera,
std::optional<SurfaceDrag> &surface_drag,
GLCanvas3D &canvas,
RaycastManager &raycast_manager,
const std::optional<double>&up_limit)
{
// selected volume
GLVolume *gl_volume_ptr = get_selected_gl_volume(canvas);
if (gl_volume_ptr == nullptr)
return false;
const GLVolume &gl_volume = *gl_volume_ptr;
// is selected volume closest hovered?
const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes;
if (int hovered_idx = canvas.get_first_hover_volume_idx(); hovered_idx < 0)
return false;
else if (auto hovered_idx_ = static_cast<size_t>(hovered_idx);
hovered_idx_ >= gl_volumes.size() || gl_volumes[hovered_idx_] != gl_volume_ptr)
return false;
const ModelObjectPtrs &objects = canvas.get_model()->objects;
const ModelObject *object = get_model_object(gl_volume, objects);
assert(object != nullptr);
if (object == nullptr)
return false;
const ModelInstance *instance = get_model_instance(gl_volume, *object);
const ModelVolume *volume = get_model_volume(gl_volume, *object);
assert(instance != nullptr && volume != nullptr);
if (object == nullptr || instance == nullptr || volume == nullptr)
return false;
// allowed drag&drop by canvas for object
if (volume->is_the_only_one_part())
return false;
RaycastManager::AllowVolumes condition = create_condition(object->volumes, volume->id());
RaycastManager::Meshes meshes = create_meshes(canvas, condition);
// initialize raycasters
// INFO: It could slows down for big objects
// (may be move to thread and do not show drag until it finish)
raycast_manager.actualize(*instance, &condition, &meshes);
// world_matrix_fixed() without sla shift
Transform3d to_world = world_matrix_fixed(gl_volume, objects);
// zero point of volume in world coordinate system
Vec3d volume_center = to_world.translation();
// screen coordinate of volume center
Vec2i coor = CameraUtils::project(camera, volume_center);
Vec2d mouse_offset = coor.cast<double>() - mouse_pos;
Vec2d mouse_offset_without_sla_shift = mouse_offset;
if (double sla_shift = gl_volume.get_sla_shift_z(); !is_approx(sla_shift, 0.)) {
Transform3d to_world_without_sla_move = instance->get_matrix() * volume->get_matrix();
if (volume->emboss_shape.has_value() && volume->emboss_shape->fix_3mf_tr.has_value())
to_world_without_sla_move = to_world_without_sla_move * (*volume->emboss_shape->fix_3mf_tr);
// zero point of volume in world coordinate system
volume_center = to_world_without_sla_move.translation();
// screen coordinate of volume center
coor = CameraUtils::project(camera, volume_center);
mouse_offset_without_sla_shift = coor.cast<double>() - mouse_pos;
}
Transform3d volume_tr = gl_volume.get_volume_transformation().get_matrix();
// fix baked transformation from .3mf store process
if (const std::optional<EmbossShape> &es_opt = volume->emboss_shape; es_opt.has_value()) {
const std::optional<Slic3r::Transform3d> &fix = es_opt->fix_3mf_tr;
if (fix.has_value())
volume_tr = volume_tr * fix->inverse();
}
Transform3d instance_tr = instance->get_matrix();
Transform3d instance_tr_inv = instance_tr.inverse();
Transform3d world_tr = instance_tr * volume_tr;
std::optional<float> start_angle;
if (up_limit.has_value())
start_angle = Emboss::calc_up(world_tr, *up_limit);
std::optional<float> start_distance;
if (!volume->emboss_shape->projection.use_surface)
start_distance = calc_distance(gl_volume, raycast_manager, &condition);
surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv,
gl_volume_ptr, condition, start_angle,
start_distance, true, mouse_offset_without_sla_shift};
// disable moving with object by mouse
canvas.enable_moving(false);
canvas.enable_picking(false);
return true;
}
bool dragging(const Vec2d &mouse_pos,
const Camera &camera,
std::optional<SurfaceDrag> &surface_drag,
GLCanvas3D &canvas,
const RaycastManager &raycast_manager,
const std::optional<double> &up_limit)
{
Vec2d offseted_mouse = mouse_pos + surface_drag->mouse_offset_without_sla_shift;
std::optional<RaycastManager::Hit> hit = ray_from_camera(
raycast_manager, offseted_mouse, camera, &surface_drag->condition);
surface_drag->exist_hit = hit.has_value();
if (!hit.has_value()) {
// cross hair need redraw
canvas.set_as_dirty();
return true;
}
auto world_linear = surface_drag->world.linear();
// Calculate offset: transformation to wanted position
{
// Reset skew of the text Z axis:
// Project the old Z axis into a new Z axis, which is perpendicular to the old XY plane.
Vec3d old_z = world_linear.col(2);
Vec3d new_z = world_linear.col(0).cross(world_linear.col(1));
world_linear.col(2) = new_z * (old_z.dot(new_z) / new_z.squaredNorm());
}
Vec3d text_z_world = world_linear.col(2); // world_linear * Vec3d::UnitZ()
auto z_rotation = Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(text_z_world, hit->normal);
Transform3d world_new = z_rotation * surface_drag->world;
auto world_new_linear = world_new.linear();
// Fix direction of up vector to zero initial rotation
if(up_limit.has_value()){
Vec3d z_world = world_new_linear.col(2);
z_world.normalize();
Vec3d wanted_up = Emboss::suggest_up(z_world, *up_limit);
Vec3d y_world = world_new_linear.col(1);
auto y_rotation = Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(y_world, wanted_up);
world_new = y_rotation * world_new;
world_new_linear = world_new.linear();
}
// Edit position from right
Transform3d volume_new{Eigen::Translation<double, 3>(surface_drag->instance_inv * hit->position)};
volume_new.linear() = surface_drag->instance_inv.linear() * world_new_linear;
// Check that transformation matrix is valid transformation
assert(volume_new.matrix()(0, 0) == volume_new.matrix()(0, 0)); // Check valid transformation not a NAN
if (volume_new.matrix()(0, 0) != volume_new.matrix()(0, 0))
return true;
// Check that scale in world did not changed
assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitY()).has_value());
assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitZ()).has_value());
const ModelVolume *volume = get_model_volume(*surface_drag->gl_volume, canvas.get_model()->objects);
// fix baked transformation from .3mf store process
if (volume != nullptr && volume->emboss_shape.has_value()) {
const std::optional<Slic3r::Transform3d> &fix = volume->emboss_shape->fix_3mf_tr;
if (fix.has_value())
volume_new = volume_new * (*fix);
// apply move in Z direction and rotation by up vector
Emboss::apply_transformation(surface_drag->start_angle, surface_drag->start_distance, volume_new);
}
// Update transformation for all instances
for (GLVolume *vol : canvas.get_volumes().volumes) {
if (vol->object_idx() != surface_drag->gl_volume->object_idx() || vol->volume_idx() != surface_drag->gl_volume->volume_idx())
continue;
vol->set_volume_transformation(volume_new);
}
canvas.set_as_dirty();
return true;
}
} // namespace

View File

@ -5,9 +5,11 @@
#include "libslic3r/Point.hpp" // Vec2d, Transform3d
#include "slic3r/Utils/RaycastManager.hpp"
#include "wx/event.h" // wxMouseEvent
#include <functional>
namespace Slic3r {
class GLVolume;
class ModelVolume;
} // namespace Slic3r
namespace Slic3r::GUI {
@ -37,6 +39,9 @@ struct SurfaceDrag
// initial rotation in Z axis of volume
std::optional<float> start_angle;
// initial Z distance from surface
std::optional<float> start_distance;
// Flag whether coordinate hit some volume
bool exist_hit = true;
@ -44,6 +49,10 @@ struct SurfaceDrag
Vec2d mouse_offset_without_sla_shift;
};
// Limit direction of up vector on model
// Between side and top surface
constexpr double up_limit = 0.9;
/// <summary>
/// Mouse event handler, when move(drag&drop) volume over model surface
/// NOTE: Dragged volume has to be selected. And also has to be hovered on start of dragging.
@ -71,6 +80,16 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event,
/// <returns>Offset of volume in volume coordinate</returns>
std::optional<Vec3d> calc_surface_offset(const Selection &selection, RaycastManager &raycast_manager);
/// <summary>
/// Calculate distance by ray to surface of object in emboss direction
/// </summary>
/// <param name="gl_volume">Define embossed volume</param>
/// <param name="raycaster">Way to cast rays to object</param>
/// <param name="canvas">Contain model</param>
/// <returns>Calculated distance from surface</returns>
std::optional<float> calc_distance(const GLVolume &gl_volume, RaycastManager &raycaster, GLCanvas3D &canvas);
std::optional<float> calc_distance(const GLVolume &gl_volume, const RaycastManager &raycaster, const RaycastManager::ISkip *condition);
/// <summary>
/// Get transformation to world
/// - use fix after store to 3mf when exists
@ -89,5 +108,37 @@ Transform3d world_matrix_fixed(const GLVolume &gl_volume, const ModelObjectPtrs&
/// <returns>Fixed Transformation of selected volume in selection</returns>
Transform3d world_matrix_fixed(const Selection &selection);
/// <summary>
/// Wrap function around selection transformation to apply fix transformation
/// Fix transformation is needed because of (store/load) volume (to/from) 3mf
/// </summary>
/// <param name="selection">Selected gl volume will be modified</param>
/// <param name="selection_transformation_fnc">Function modified Selection transformation</param>
/// <param name="volume">Same as selected GLVolume, volume may(or may not) contain fix matrix,
/// when nullptr it is gathered from selection</param>
void selection_transform(Selection &selection, const std::function<void()>& selection_transformation_fnc, const ModelVolume *volume = nullptr);
/// <summary>
/// Apply camera direction for emboss direction
/// </summary>
/// <param name="camera">Define view vector</param>
/// <param name="canvas">Containe Selected ModelVolume to modify orientation</param>
/// <returns>True when apply change otherwise false</returns>
bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas);
/// <summary>
/// Rotation around z Axis(emboss direction)
/// </summary>
/// <param name="canvas">Selected volume for rotation</param>
/// <param name="relative_angle">Relative angle to rotate around emboss direction</param>
void do_local_z_rotate(GLCanvas3D &canvas, double relative_angle);
/// <summary>
/// Translation along local z Axis (emboss direction)
/// </summary>
/// <param name="canvas">Selected volume for translate</param>
/// <param name="relative_move">Relative move along emboss direction</param>
void do_local_z_move(GLCanvas3D &canvas, double relative_move);
} // namespace Slic3r::GUI
#endif // slic3r_SurfaceDrag_hpp_

View File

@ -217,25 +217,6 @@ GLModel::Geometry create_geometry(const TextLines &lines, float radius, bool is_
}
return geometry;
}
bool get_line_height_offset(const FontProp &fp, const FontFile &ff, double &line_height_mm, double &line_offset_mm)
{
double third_ascent_shape_size = get_font_info(ff, fp).ascent / 3.;
int line_height_shape_size = get_line_height(ff, fp); // In shape size
double scale = get_shape_scale(fp, ff);
line_offset_mm = third_ascent_shape_size * scale / SHAPE_SCALE;
line_height_mm = line_height_shape_size * scale;
if (line_height_mm < 0)
return false;
// fix for bad filled ascent in font file
if (line_offset_mm <= 0)
line_offset_mm = line_height_mm / 3;
return true;
}
} // namespace
void TextLinesModel::init(const Transform3d &text_tr,
@ -259,8 +240,9 @@ void TextLinesModel::init(const Transform3d &text_tr,
FontProp::VerticalAlign align = fp.align.second;
double line_height_mm, line_offset_mm;
if (!get_line_height_offset(fp, ff, line_height_mm, line_offset_mm))
double line_height_mm = calc_line_height_in_mm(ff, fp);
assert(line_height_mm > 0);
if (line_height_mm <= 0)
return;
m_model.reset();
@ -364,9 +346,9 @@ void TextLinesModel::render(const Transform3d &text_world)
shader->stop_using();
}
double TextLinesModel::calc_line_height(const Slic3r::Emboss::FontFile &ff, const FontProp &fp)
double TextLinesModel::calc_line_height_in_mm(const Slic3r::Emboss::FontFile &ff, const FontProp &fp)
{
int line_height = Slic3r::Emboss::get_line_height(ff, fp); // In shape size
double scale = Slic3r::Emboss::get_shape_scale(fp, ff);
double scale = Slic3r::Emboss::get_text_shape_scale(fp, ff);
return line_height * scale;
}

View File

@ -32,7 +32,7 @@ public:
void reset() { m_model.reset(); m_lines.clear(); }
const Slic3r::Emboss::TextLines &get_lines() const { return m_lines; }
static double calc_line_height(const Slic3r::Emboss::FontFile& ff, const FontProp& fp); // return lineheight in mm
static double calc_line_height_in_mm(const Slic3r::Emboss::FontFile& ff, const FontProp& fp); // return lineheight in mm
private:
Slic3r::Emboss::TextLines m_lines;

View File

@ -3,28 +3,24 @@
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "EmbossStyleManager.hpp"
#include <optional>
#include <GL/glew.h> // Imgui texture
#include <imgui/imgui_internal.h> // ImTextCharFromUtf8
#include "WxFontUtils.hpp"
#include "libslic3r/Utils.hpp" // ScopeGuard
#include <libslic3r/AppConfig.hpp>
#include <libslic3r/Utils.hpp> // ScopeGuard
#include "WxFontUtils.hpp"
#include "slic3r/GUI/3DScene.hpp" // ::glsafe
#include "slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp" // check of font ranges
#include "slic3r/Utils/EmbossStylesSerializable.hpp"
using namespace Slic3r;
using namespace Slic3r::Emboss;
using namespace Slic3r::GUI::Emboss;
StyleManager::StyleManager(const ImWchar *language_glyph_range, std::function<EmbossStyles()> create_default_styles)
: m_imgui_init_glyph_range(language_glyph_range)
, m_create_default_styles(create_default_styles)
, m_exist_style_images(false)
, m_temp_style_images(nullptr)
, m_app_config(nullptr)
, m_last_style_index(std::numeric_limits<size_t>::max())
StyleManager::StyleManager(const ImWchar *language_glyph_range, const std::function<EmbossStyles()>& create_default_styles)
: m_create_default_styles(create_default_styles)
, m_imgui_init_glyph_range(language_glyph_range)
{}
StyleManager::~StyleManager() {
@ -32,53 +28,66 @@ StyleManager::~StyleManager() {
free_style_images();
}
/// <summary>
/// For store/load emboss style to/from AppConfig
/// </summary>
namespace {
void store_style_index(AppConfig &cfg, size_t index);
::std::optional<size_t> load_style_index(const AppConfig &cfg);
StyleManager::Styles load_styles(const AppConfig &cfg);
void store_styles(AppConfig &cfg, const StyleManager::Styles &styles);
void make_unique_name(const StyleManager::Styles &styles, std::string &name);
} // namespace
void StyleManager::init(AppConfig *app_config)
{
assert(app_config != nullptr);
m_app_config = app_config;
EmbossStyles styles = (app_config != nullptr) ?
EmbossStylesSerializable::load_styles(*app_config) :
EmbossStyles{};
if (styles.empty())
styles = m_create_default_styles();
for (EmbossStyle &style : styles) {
make_unique_name(style.name);
m_style_items.push_back({style});
m_styles = ::load_styles(*app_config);
if (m_styles.empty()) {
// No styles loaded from ini file so use default
EmbossStyles styles = m_create_default_styles();
for (EmbossStyle &style : styles) {
::make_unique_name(m_styles, style.name);
m_styles.push_back({style});
}
}
std::optional<size_t> active_index_opt = (app_config != nullptr) ?
EmbossStylesSerializable::load_style_index(*app_config) :
::load_style_index(*app_config) :
std::optional<size_t>{};
size_t active_index = 0;
if (active_index_opt.has_value()) active_index = *active_index_opt;
if (active_index >= m_style_items.size()) active_index = 0;
if (active_index >= m_styles.size()) active_index = 0;
// find valid font item
if (load_style(active_index))
return; // style is loaded
// Try to fix that style can't be loaded
m_style_items.erase(m_style_items.begin() + active_index);
m_styles.erase(m_styles.begin() + active_index);
load_valid_style();
}
bool StyleManager::store_styles_to_app_config(bool use_modification,
bool store_active_index)
bool StyleManager::store_styles_to_app_config(bool use_modification, bool store_active_index)
{
assert(m_app_config != nullptr);
if (m_app_config == nullptr) return false;
if (use_modification) {
if (exist_stored_style()) {
// update stored item
m_style_items[m_style_cache.style_index].style = m_style_cache.style;
m_styles[m_style_cache.style_index] = m_style_cache.style;
} else {
// add new into stored list
EmbossStyle &style = m_style_cache.style;
make_unique_name(style.name);
::make_unique_name(m_styles, style.name);
m_style_cache.truncated_name.clear();
m_style_cache.style_index = m_style_items.size();
m_style_items.push_back({style});
m_style_cache.style_index = m_styles.size();
m_styles.push_back({style});
}
m_style_cache.stored_wx_font = m_style_cache.wx_font;
}
@ -88,30 +97,27 @@ bool StyleManager::store_styles_to_app_config(bool use_modification,
size_t style_index = exist_stored_style() ?
m_style_cache.style_index :
m_last_style_index;
EmbossStylesSerializable::store_style_index(*m_app_config, style_index);
store_style_index(*m_app_config, style_index);
}
EmbossStyles styles;
styles.reserve(m_style_items.size());
for (const Item &item : m_style_items) styles.push_back(item.style);
EmbossStylesSerializable::store_styles(*m_app_config, styles);
store_styles(*m_app_config, m_styles);
return true;
}
void StyleManager::add_style(const std::string &name) {
EmbossStyle& style = m_style_cache.style;
style.name = name;
make_unique_name(style.name);
m_style_cache.style_index = m_style_items.size();
::make_unique_name(m_styles, style.name);
m_style_cache.style_index = m_styles.size();
m_style_cache.stored_wx_font = m_style_cache.wx_font;
m_style_cache.truncated_name.clear();
m_style_items.push_back({style});
m_styles.push_back({style});
}
void StyleManager::swap(size_t i1, size_t i2) {
if (i1 >= m_style_items.size() ||
i2 >= m_style_items.size()) return;
std::swap(m_style_items[i1], m_style_items[i2]);
if (i1 >= m_styles.size() ||
i2 >= m_styles.size()) return;
std::swap(m_styles[i1], m_styles[i2]);
// fix selected index
if (!exist_stored_style()) return;
if (m_style_cache.style_index == i1) {
@ -135,7 +141,7 @@ void StyleManager::discard_style_changes() {
}
void StyleManager::erase(size_t index) {
if (index >= m_style_items.size()) return;
if (index >= m_styles.size()) return;
// fix selected index
if (exist_stored_style()) {
@ -144,15 +150,15 @@ void StyleManager::erase(size_t index) {
else if (index == i) i = std::numeric_limits<size_t>::max();
}
m_style_items.erase(m_style_items.begin() + index);
m_styles.erase(m_styles.begin() + index);
}
void StyleManager::rename(const std::string& name) {
m_style_cache.style.name = name;
m_style_cache.truncated_name.clear();
if (exist_stored_style()) {
Item &it = m_style_items[m_style_cache.style_index];
it.style.name = name;
Style &it = m_styles[m_style_cache.style_index];
it.name = name;
it.truncated_name.clear();
}
}
@ -160,28 +166,28 @@ void StyleManager::rename(const std::string& name) {
void StyleManager::load_valid_style()
{
// iterate over all known styles
while (!m_style_items.empty()) {
while (!m_styles.empty()) {
if (load_style(0))
return;
// can't load so erase it from list
m_style_items.erase(m_style_items.begin());
m_styles.erase(m_styles.begin());
}
// no one style is loadable
// set up default font list
EmbossStyles def_style = m_create_default_styles();
for (EmbossStyle &style : def_style) {
make_unique_name(style.name);
m_style_items.push_back({std::move(style)});
::make_unique_name(m_styles, style.name);
m_styles.push_back({std::move(style)});
}
// iterate over default styles
// There have to be option to use build in font
while (!m_style_items.empty()) {
while (!m_styles.empty()) {
if (load_style(0))
return;
// can't load so erase it from list
m_style_items.erase(m_style_items.begin());
m_styles.erase(m_styles.begin());
}
// This OS doesn't have TTF as default font,
@ -191,15 +197,15 @@ void StyleManager::load_valid_style()
bool StyleManager::load_style(size_t style_index)
{
if (style_index >= m_style_items.size()) return false;
if (!load_style(m_style_items[style_index].style)) return false;
if (style_index >= m_styles.size()) return false;
if (!load_style(m_styles[style_index])) return false;
m_style_cache.style_index = style_index;
m_style_cache.stored_wx_font = m_style_cache.wx_font; // copy
m_last_style_index = style_index;
return true;
}
bool StyleManager::load_style(const EmbossStyle &style) {
bool StyleManager::load_style(const Style &style) {
if (style.type == EmbossStyle::Type::file_path) {
std::unique_ptr<FontFile> font_ptr =
create_font_file(style.path.c_str());
@ -212,13 +218,13 @@ bool StyleManager::load_style(const EmbossStyle &style) {
m_style_cache.stored_wx_font = {};
return true;
}
if (style.type != WxFontUtils::get_actual_type()) return false;
if (style.type != WxFontUtils::get_current_type()) return false;
std::optional<wxFont> wx_font_opt = WxFontUtils::load_wxFont(style.path);
if (!wx_font_opt.has_value()) return false;
return load_style(style, *wx_font_opt);
}
bool StyleManager::load_style(const EmbossStyle &style, const wxFont &font)
bool StyleManager::load_style(const Style &style, const wxFont &font)
{
m_style_cache.style = style; // copy
@ -269,12 +275,19 @@ bool StyleManager::is_font_changed() const
return is_bold != is_stored_bold;
}
bool StyleManager::is_unique_style_name(const std::string &name) const {
for (const StyleManager::Style &style : m_styles)
if (style.name == name)
return false;
return true;
}
bool StyleManager::is_active_font() { return m_style_cache.font_file.has_value(); }
const EmbossStyle* StyleManager::get_stored_style() const
const StyleManager::Style *StyleManager::get_stored_style() const
{
if (m_style_cache.style_index >= m_style_items.size()) return nullptr;
return &m_style_items[m_style_cache.style_index].style;
if (m_style_cache.style_index >= m_styles.size()) return nullptr;
return &m_styles[m_style_cache.style_index];
}
void StyleManager::clear_glyphs_cache()
@ -286,25 +299,6 @@ void StyleManager::clear_glyphs_cache()
void StyleManager::clear_imgui_font() { m_style_cache.atlas.Clear(); }
#include "slic3r/GUI/TextLines.hpp"
double StyleManager::get_line_height()
{
assert(is_active_font());
if (!is_active_font())
return -1;
const auto &ffc = get_font_file_with_cache();
assert(ffc.has_value());
if (!ffc.has_value())
return -1;
const auto &ff_ptr = ffc.font_file;
assert(ff_ptr != nullptr);
if (ff_ptr == nullptr)
return -1;
const FontProp &fp = get_font_prop();
const FontFile &ff = *ff_ptr;
return TextLinesModel::calc_line_height(ff, fp);
}
ImFont *StyleManager::get_imgui_font()
{
if (!is_active_font()) return nullptr;
@ -321,44 +315,11 @@ ImFont *StyleManager::get_imgui_font()
return font;
}
const std::vector<StyleManager::Item> &StyleManager::get_styles() const{ return m_style_items; }
void StyleManager::make_unique_name(std::string &name)
{
auto is_unique = [&](const std::string &name) -> bool {
for (const Item &it : m_style_items)
if (it.style.name == name) return false;
return true;
};
// Style name can't be empty so default name is set
if (name.empty()) name = "Text style";
// When name is already unique, nothing need to be changed
if (is_unique(name)) return;
// when there is previous version of style name only find number
const char *prefix = " (";
const char suffix = ')';
auto pos = name.find_last_of(prefix);
if (name.c_str()[name.size() - 1] == suffix &&
pos != std::string::npos) {
// short name by ord number
name = name.substr(0, pos);
}
int order = 1; // start with value 2 to represents same font name
std::string new_name;
do {
new_name = name + prefix + std::to_string(++order) + suffix;
} while (!is_unique(new_name));
name = new_name;
}
const StyleManager::Styles &StyleManager::get_styles() const{ return m_styles; }
void StyleManager::init_trunc_names(float max_width) {
for (auto &s : m_style_items)
for (auto &s : m_styles)
if (s.truncated_name.empty()) {
std::string name = s.style.name;
std::string name = s.name;
ImGuiWrapper::escape_double_hash(name);
s.truncated_name = ImGuiWrapper::trunc(name, max_width);
}
@ -391,9 +352,9 @@ void StyleManager::init_style_images(const Vec2i &max_size,
StyleImagesData::Item &style = m_temp_style_images->styles[index];
// find style in font list and copy to it
for (auto &it : m_style_items) {
if (it.style.name != style.text ||
!(it.style.prop == style.prop))
for (auto &it : m_styles) {
if (it.name != style.text ||
!(it.prop == style.prop))
continue;
it.image = image;
break;
@ -410,9 +371,8 @@ void StyleManager::init_style_images(const Vec2i &max_size,
// create job for init images
m_temp_style_images = std::make_shared<StyleImagesData::StyleImages>();
StyleImagesData::Items styles;
styles.reserve(m_style_items.size());
for (const Item &item : m_style_items) {
const EmbossStyle &style = item.style;
styles.reserve(m_styles.size());
for (const Style &style : m_styles) {
std::optional<wxFont> wx_font_opt = WxFontUtils::load_wxFont(style.path);
if (!wx_font_opt.has_value()) continue;
std::unique_ptr<FontFile> font_file =
@ -439,7 +399,7 @@ void StyleManager::init_style_images(const Vec2i &max_size,
void StyleManager::free_style_images() {
if (!m_exist_style_images) return;
GLuint tex_id = 0;
for (Item &it : m_style_items) {
for (Style &it : m_styles) {
if (tex_id == 0 && it.image.has_value())
tex_id = (GLuint)(intptr_t) it.image->texture_id;
it.image.reset();
@ -551,10 +511,278 @@ bool StyleManager::set_wx_font(const wxFont &wx_font, std::unique_ptr<FontFile>
FontFileWithCache(std::move(font_file));
EmbossStyle &style = m_style_cache.style;
style.type = WxFontUtils::get_actual_type();
style.type = WxFontUtils::get_current_type();
// update string path
style.path = WxFontUtils::store_wxFont(wx_font);
WxFontUtils::update_property(style.prop, wx_font);
clear_imgui_font();
return true;
}
#include <libslic3r/AppConfig.hpp>
#include "WxFontUtils.hpp"
#include "fast_float/fast_float.h"
// StylesSerializable
namespace {
using namespace Slic3r;
using namespace Slic3r::GUI;
using Section = std::map<std::string,std::string>;
const std::string APP_CONFIG_FONT_NAME = "name";
const std::string APP_CONFIG_FONT_DESCRIPTOR = "descriptor";
const std::string APP_CONFIG_FONT_LINE_HEIGHT = "line_height";
const std::string APP_CONFIG_FONT_DEPTH = "depth";
const std::string APP_CONFIG_FONT_USE_SURFACE = "use_surface";
const std::string APP_CONFIG_FONT_BOLDNESS = "boldness";
const std::string APP_CONFIG_FONT_SKEW = "skew";
const std::string APP_CONFIG_FONT_DISTANCE = "distance";
const std::string APP_CONFIG_FONT_ANGLE = "angle";
const std::string APP_CONFIG_FONT_COLLECTION = "collection";
const std::string APP_CONFIG_FONT_CHAR_GAP = "char_gap";
const std::string APP_CONFIG_FONT_LINE_GAP = "line_gap";
const std::string APP_CONFIG_ACTIVE_FONT = "active_font";
std::string create_section_name(unsigned index)
{
return AppConfig::SECTION_EMBOSS_STYLE + ':' + std::to_string(index);
}
// check only existence of flag
bool read(const Section &section, const std::string &key, bool &value)
{
auto item = section.find(key);
if (item == section.end())
return false;
value = true;
return true;
}
bool read(const Section &section, const std::string &key, float &value)
{
auto item = section.find(key);
if (item == section.end())
return false;
const std::string &data = item->second;
if (data.empty())
return false;
float value_;
fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_);
// read only non zero value
if (fabs(value_) <= std::numeric_limits<float>::epsilon())
return false;
value = value_;
return true;
}
bool read(const Section &section, const std::string &key, std::optional<int> &value)
{
auto item = section.find(key);
if (item == section.end())
return false;
const std::string &data = item->second;
if (data.empty())
return false;
int value_ = std::atoi(data.c_str());
if (value_ == 0)
return false;
value = value_;
return true;
}
bool read(const Section &section, const std::string &key, std::optional<unsigned int> &value)
{
auto item = section.find(key);
if (item == section.end())
return false;
const std::string &data = item->second;
if (data.empty())
return false;
int value_ = std::atoi(data.c_str());
if (value_ <= 0)
return false;
value = static_cast<unsigned int>(value_);
return true;
}
bool read(const Section &section, const std::string &key, std::optional<float> &value)
{
auto item = section.find(key);
if (item == section.end())
return false;
const std::string &data = item->second;
if (data.empty())
return false;
float value_;
fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_);
// read only non zero value
if (fabs(value_) <= std::numeric_limits<float>::epsilon())
return false;
value = value_;
return true;
}
std::optional<StyleManager::Style> load_style(const Section &app_cfg_section)
{
auto path_it = app_cfg_section.find(APP_CONFIG_FONT_DESCRIPTOR);
if (path_it == app_cfg_section.end())
return {};
StyleManager::Style s;
EmbossProjection& ep = s.projection;
FontProp& fp = s.prop;
s.path = path_it->second;
s.type = WxFontUtils::get_current_type();
auto name_it = app_cfg_section.find(APP_CONFIG_FONT_NAME);
const std::string default_name = "font_name";
s.name = (name_it == app_cfg_section.end()) ? default_name : name_it->second;
read(app_cfg_section, APP_CONFIG_FONT_LINE_HEIGHT, fp.size_in_mm);
float depth = 1.;
read(app_cfg_section, APP_CONFIG_FONT_DEPTH, depth);
ep.depth = depth;
read(app_cfg_section, APP_CONFIG_FONT_USE_SURFACE, ep.use_surface);
read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness);
read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew);
read(app_cfg_section, APP_CONFIG_FONT_DISTANCE, s.distance);
read(app_cfg_section, APP_CONFIG_FONT_ANGLE, s.angle);
read(app_cfg_section, APP_CONFIG_FONT_COLLECTION, fp.collection_number);
read(app_cfg_section, APP_CONFIG_FONT_CHAR_GAP, fp.char_gap);
read(app_cfg_section, APP_CONFIG_FONT_LINE_GAP, fp.line_gap);
return s;
}
void store_style(AppConfig &cfg, const StyleManager::Style &s, unsigned index)
{
const EmbossProjection &ep = s.projection;
Section data;
data[APP_CONFIG_FONT_NAME] = s.name;
data[APP_CONFIG_FONT_DESCRIPTOR] = s.path;
const FontProp &fp = s.prop;
data[APP_CONFIG_FONT_LINE_HEIGHT] = std::to_string(fp.size_in_mm);
data[APP_CONFIG_FONT_DEPTH] = std::to_string(ep.depth);
if (ep.use_surface)
data[APP_CONFIG_FONT_USE_SURFACE] = "true";
if (fp.boldness.has_value())
data[APP_CONFIG_FONT_BOLDNESS] = std::to_string(*fp.boldness);
if (fp.skew.has_value())
data[APP_CONFIG_FONT_SKEW] = std::to_string(*fp.skew);
if (s.distance.has_value())
data[APP_CONFIG_FONT_DISTANCE] = std::to_string(*s.distance);
if (s.angle.has_value())
data[APP_CONFIG_FONT_ANGLE] = std::to_string(*s.angle);
if (fp.collection_number.has_value())
data[APP_CONFIG_FONT_COLLECTION] = std::to_string(*fp.collection_number);
if (fp.char_gap.has_value())
data[APP_CONFIG_FONT_CHAR_GAP] = std::to_string(*fp.char_gap);
if (fp.line_gap.has_value())
data[APP_CONFIG_FONT_LINE_GAP] = std::to_string(*fp.line_gap);
cfg.set_section(create_section_name(index), std::move(data));
}
void store_style_index(AppConfig &cfg, size_t index)
{
// store actual font index
// active font first index is +1 to correspond with section name
Section data;
data[APP_CONFIG_ACTIVE_FONT] = std::to_string(index);
cfg.set_section(AppConfig::SECTION_EMBOSS_STYLE, std::move(data));
}
std::optional<size_t> load_style_index(const AppConfig &cfg)
{
if (!cfg.has_section(AppConfig::SECTION_EMBOSS_STYLE))
return {};
auto section = cfg.get_section(AppConfig::SECTION_EMBOSS_STYLE);
auto it = section.find(APP_CONFIG_ACTIVE_FONT);
if (it == section.end())
return {};
size_t active_font = static_cast<size_t>(std::atoi(it->second.c_str()));
// order in config starts with number 1
return active_font - 1;
}
::StyleManager::Styles load_styles(const AppConfig &cfg)
{
StyleManager::Styles result;
// human readable index inside of config starts from 1 !!
unsigned index = 1;
std::string section_name = create_section_name(index);
while (cfg.has_section(section_name)) {
std::optional<StyleManager::Style> style_opt = load_style(cfg.get_section(section_name));
if (style_opt.has_value()) {
make_unique_name(result, style_opt->name);
result.emplace_back(*style_opt);
}
section_name = create_section_name(++index);
}
return result;
}
void store_styles(AppConfig &cfg, const StyleManager::Styles &styles)
{
EmbossStyle::Type current_type = WxFontUtils::get_current_type();
// store styles
unsigned index = 1;
for (const StyleManager::Style &style : styles) {
// skip file paths + fonts from other OS(loaded from .3mf)
assert(style.type == current_type);
if (style.type != current_type)
continue;
store_style(cfg, style, index);
++index;
}
// remove rest of font sections (after deletation)
std::string section_name = create_section_name(index);
while (cfg.has_section(section_name)) {
cfg.clear_section(section_name);
section_name = create_section_name(index);
++index;
}
}
void make_unique_name(const StyleManager::Styles& styles, std::string &name)
{
auto is_unique = [&styles](const std::string &name){
for (const StyleManager::Style &it : styles)
if (it.name == name) return false;
return true;
};
// Style name can't be empty so default name is set
if (name.empty()) name = "Text style";
// When name is already unique, nothing need to be changed
if (is_unique(name)) return;
// when there is previous version of style name only find number
const char *prefix = " (";
const char suffix = ')';
auto pos = name.find_last_of(prefix);
if (name.c_str()[name.size() - 1] == suffix &&
pos != std::string::npos) {
// short name by ord number
name = name.substr(0, pos);
}
int order = 1; // start with value 2 to represents same font name
std::string new_name;
do {
new_name = name + prefix + std::to_string(++order) + suffix;
} while (!is_unique(new_name));
name = new_name;
}
} // namespace

View File

@ -14,6 +14,8 @@
#include <GL/glew.h>
#include <libslic3r/BoundingBox.hpp>
#include <libslic3r/Emboss.hpp>
#include <libslic3r/TextConfiguration.hpp>
#include <libslic3r/EmbossShape.hpp>
#include <libslic3r/AppConfig.hpp>
namespace Slic3r::GUI::Emboss {
@ -26,11 +28,10 @@ namespace Slic3r::GUI::Emboss {
class StyleManager
{
friend class CreateFontStyleImagesJob; // access to StyleImagesData
public:
/// <param name="language_glyph_range">Character to load for imgui when initialize imgui font</param>
/// <param name="create_default_styles">Function to create default styles</param>
StyleManager(const ImWchar *language_glyph_range, std::function<EmbossStyles()> create_default_styles);
StyleManager(const ImWchar *language_glyph_range, const std::function<EmbossStyles()>& create_default_styles);
/// <summary>
/// Release imgui font and style images from GPU
@ -61,11 +62,11 @@ public:
void add_style(const std::string& name);
/// <summary>
/// Change order of style item in m_style_items.
/// Change order of style item in m_styles.
/// Fix selected font index when (i1 || i2) == m_font_selected
/// </summary>
/// <param name="i1">First index to m_style_items</param>
/// <param name="i2">Second index to m_style_items</param>
/// <param name="i1">First index to m_styles</param>
/// <param name="i2">Second index to m_styles</param>
void swap(size_t i1, size_t i2);
/// <summary>
@ -75,7 +76,7 @@ public:
void discard_style_changes();
/// <summary>
/// Remove style from m_style_items.
/// Remove style from m_styles.
/// Fix selected font index when index is under m_font_selected
/// </summary>
/// <param name="index">Index of style to be removed</param>
@ -96,13 +97,14 @@ public:
/// Change active font
/// When font not loaded roll back activ font
/// </summary>
/// <param name="font_index">New font index(from m_style_items range)</param>
/// <param name="font_index">New font index(from m_styles range)</param>
/// <returns>True on succes. False on fail load font</returns>
bool load_style(size_t font_index);
// load font style not stored in list
bool load_style(const EmbossStyle &style);
struct Style;
bool load_style(const Style &style);
// fastering load font on index by wxFont, ignore type and descriptor
bool load_style(const EmbossStyle &style, const wxFont &font);
bool load_style(const Style &style, const wxFont &font);
// clear actual selected glyphs cache
void clear_glyphs_cache();
@ -110,15 +112,11 @@ public:
// remove cached imgui font for actual selected font
void clear_imgui_font();
// calculate line height
// not const because access to font file which could be created.
double get_line_height(); /* const */
// getters for private data
const EmbossStyle *get_stored_style() const;
const Style *get_stored_style() const;
const EmbossStyle &get_style() const { return m_style_cache.style; }
EmbossStyle &get_style() { return m_style_cache.style; }
const Style &get_style() const { return m_style_cache.style; }
Style &get_style() { return m_style_cache.style; }
size_t get_style_index() const { return m_style_cache.style_index; }
std::string &get_truncated_name() { return m_style_cache.truncated_name; }
const ImFontAtlas &get_atlas() const { return m_style_cache.atlas; }
@ -139,6 +137,8 @@ public:
/// <returns></returns>
bool is_font_changed() const;
bool is_unique_style_name(const std::string &name) const;
/// <summary>
/// Setter on wx_font when changed
/// </summary>
@ -173,30 +173,49 @@ public:
void init_style_images(const Vec2i& max_size, const std::string &text);
void free_style_images();
struct Item;
// access to all managed font styles
const std::vector<Item> &get_styles() const;
const std::vector<Style> &get_styles() const;
/// <summary>
/// Describe image in GPU to show settings of style
/// </summary>
struct StyleImage
{
void* texture_id = 0; // GLuint
void* texture_id = nullptr; // GLuint
BoundingBox bounding_box;
ImVec2 tex_size, uv0, uv1;
Point offset = Point(0, 0);
StyleImage() = default;
ImVec2 tex_size;
ImVec2 uv0;
ImVec2 uv1;
Point offset = Point(0, 0);
};
/// <summary>
/// All connected with one style
/// keep temporary data and caches for style
/// </summary>
struct Item
struct Style : public EmbossStyle
{
// define font, style and other property of text
EmbossStyle style;
// Define how to emboss shape
EmbossProjection projection;
// 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]
// Angle of rotation around emboss direction (Z axis)
// It is calculate on the fly from volume world transformation
// only StyleManager keep actual value for comparision with style
// When not set value is zero and is not stored
std::optional<float> angle; // [in radians] form -Pi to Pi
bool operator==(const Style &other) const
{
return EmbossStyle::operator==(other) &&
projection == other.projection &&
distance == other.distance &&
angle == other.angle;
}
// cache for view font name with maximal width in imgui
std::string truncated_name;
@ -204,6 +223,7 @@ public:
// visualization of style
std::optional<StyleImage> image;
};
using Styles = std::vector<Style>;
// check if exist selected font style in manager
bool is_active_font();
@ -215,7 +235,10 @@ public:
static float get_imgui_font_size(const FontProp &prop, const Slic3r::Emboss::FontFile &file, double scale);
private:
// function to create default style list
std::function<EmbossStyles()> m_create_default_styles;
// keep language dependent glyph range
const ImWchar *m_imgui_init_glyph_range;
/// <summary>
/// Cache data from style to reduce amount of:
@ -241,22 +264,20 @@ private:
std::string truncated_name;
// actual used font item
EmbossStyle style = {};
Style style = {};
// cache for stored wx font to not create every frame
wxFont stored_wx_font = {};
// index into m_style_items
// index into m_styles
size_t style_index = std::numeric_limits<size_t>::max();
} m_style_cache;
void make_unique_name(std::string &name);
// Privat member
std::vector<Item> m_style_items;
AppConfig *m_app_config;
size_t m_last_style_index;
Styles m_styles;
AppConfig *m_app_config = nullptr;
size_t m_last_style_index = std::numeric_limits<size_t>::max();
/// <summary>
/// Keep data needed to create Font Style Images in Job
@ -295,12 +316,8 @@ private:
// pixel per milimeter (scaled DPI)
double ppm;
};
std::shared_ptr<StyleImagesData::StyleImages> m_temp_style_images;
bool m_exist_style_images;
// store all font GLImages
//ImFontAtlas m_imgui_font_atlas;
const ImWchar *m_imgui_init_glyph_range;
std::shared_ptr<StyleImagesData::StyleImages> m_temp_style_images = nullptr;
bool m_exist_style_images = false;
};
} // namespace Slic3r

View File

@ -1,205 +0,0 @@
///|/ Copyright (c) Prusa Research 2022 - 2023 Vojtěch Bubník @bubnikv, Filip Sykala @Jony01
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "EmbossStylesSerializable.hpp"
#include <libslic3r/AppConfig.hpp>
#include "WxFontUtils.hpp"
using namespace Slic3r;
using namespace Slic3r::GUI;
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_NAME = "name";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_DESCRIPTOR = "descriptor";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_LINE_HEIGHT = "line_height";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_DEPTH = "depth";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_USE_SURFACE = "use_surface";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_BOLDNESS = "boldness";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_SKEW = "skew";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_DISTANCE = "distance";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_ANGLE = "angle";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_COLLECTION = "collection";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_CHAR_GAP = "char_gap";
const std::string EmbossStylesSerializable::APP_CONFIG_FONT_LINE_GAP = "line_gap";
const std::string EmbossStylesSerializable::APP_CONFIG_ACTIVE_FONT = "active_font";
std::string EmbossStylesSerializable::create_section_name(unsigned index)
{
return AppConfig::SECTION_EMBOSS_STYLE + ':' + std::to_string(index);
}
// check only existence of flag
bool EmbossStylesSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, bool& value){
auto item = section.find(key);
if (item == section.end()) return false;
value = true;
return true;
}
#include "fast_float/fast_float.h"
bool EmbossStylesSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, float& value){
auto item = section.find(key);
if (item == section.end()) return false;
const std::string &data = item->second;
if (data.empty()) return false;
float value_;
fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_);
// read only non zero value
if (fabs(value_) <= std::numeric_limits<float>::epsilon()) return false;
value = value_;
return true;
}
bool EmbossStylesSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<int>& value){
auto item = section.find(key);
if (item == section.end()) return false;
const std::string &data = item->second;
if (data.empty()) return false;
int value_ = std::atoi(data.c_str());
if (value_ == 0) return false;
value = value_;
return true;
}
bool EmbossStylesSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<unsigned int>& value){
auto item = section.find(key);
if (item == section.end()) return false;
const std::string &data = item->second;
if (data.empty()) return false;
int value_ = std::atoi(data.c_str());
if (value_ <= 0) return false;
value = static_cast<unsigned int>(value_);
return true;
}
bool EmbossStylesSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<float>& value){
auto item = section.find(key);
if (item == section.end()) return false;
const std::string &data = item->second;
if (data.empty()) return false;
float value_;
fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_);
// read only non zero value
if (fabs(value_) <= std::numeric_limits<float>::epsilon()) return false;
value = value_;
return true;
}
std::optional<EmbossStyle> EmbossStylesSerializable::load_style(
const std::map<std::string, std::string> &app_cfg_section)
{
auto path_it = app_cfg_section.find(APP_CONFIG_FONT_DESCRIPTOR);
if (path_it == app_cfg_section.end()) return {};
const std::string &path = path_it->second;
auto name_it = app_cfg_section.find(APP_CONFIG_FONT_NAME);
const std::string default_name = "font_name";
const std::string &name =
(name_it == app_cfg_section.end()) ?
default_name : name_it->second;
FontProp fp;
read(app_cfg_section, APP_CONFIG_FONT_LINE_HEIGHT, fp.size_in_mm);
read(app_cfg_section, APP_CONFIG_FONT_DEPTH, fp.emboss);
read(app_cfg_section, APP_CONFIG_FONT_USE_SURFACE, fp.use_surface);
read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness);
read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew);
read(app_cfg_section, APP_CONFIG_FONT_DISTANCE, fp.distance);
read(app_cfg_section, APP_CONFIG_FONT_ANGLE, fp.angle);
read(app_cfg_section, APP_CONFIG_FONT_COLLECTION, fp.collection_number);
read(app_cfg_section, APP_CONFIG_FONT_CHAR_GAP, fp.char_gap);
read(app_cfg_section, APP_CONFIG_FONT_LINE_GAP, fp.line_gap);
EmbossStyle::Type type = WxFontUtils::get_actual_type();
return EmbossStyle{ name, path, type, fp };
}
void EmbossStylesSerializable::store_style(AppConfig & cfg,
const EmbossStyle &fi,
unsigned index)
{
std::map<std::string, std::string> data;
data[APP_CONFIG_FONT_NAME] = fi.name;
data[APP_CONFIG_FONT_DESCRIPTOR] = fi.path;
const FontProp &fp = fi.prop;
data[APP_CONFIG_FONT_LINE_HEIGHT] = std::to_string(fp.size_in_mm);
data[APP_CONFIG_FONT_DEPTH] = std::to_string(fp.emboss);
if (fp.use_surface)
data[APP_CONFIG_FONT_USE_SURFACE] = "true";
if (fp.boldness.has_value())
data[APP_CONFIG_FONT_BOLDNESS] = std::to_string(*fp.boldness);
if (fp.skew.has_value())
data[APP_CONFIG_FONT_SKEW] = std::to_string(*fp.skew);
if (fp.distance.has_value())
data[APP_CONFIG_FONT_DISTANCE] = std::to_string(*fp.distance);
if (fp.angle.has_value())
data[APP_CONFIG_FONT_ANGLE] = std::to_string(*fp.angle);
if (fp.collection_number.has_value())
data[APP_CONFIG_FONT_COLLECTION] = std::to_string(*fp.collection_number);
if (fp.char_gap.has_value())
data[APP_CONFIG_FONT_CHAR_GAP] = std::to_string(*fp.char_gap);
if (fp.line_gap.has_value())
data[APP_CONFIG_FONT_LINE_GAP] = std::to_string(*fp.line_gap);
cfg.set_section(create_section_name(index), std::move(data));
}
void EmbossStylesSerializable::store_style_index(AppConfig &cfg, unsigned index) {
// store actual font index
// active font first index is +1 to correspond with section name
std::map<std::string, std::string> data;
data[APP_CONFIG_ACTIVE_FONT] = std::to_string(index);
cfg.set_section(AppConfig::SECTION_EMBOSS_STYLE, std::move(data));
}
std::optional<size_t> EmbossStylesSerializable::load_style_index(const AppConfig &cfg)
{
if (!cfg.has_section(AppConfig::SECTION_EMBOSS_STYLE)) return {};
auto section = cfg.get_section(AppConfig::SECTION_EMBOSS_STYLE);
auto it = section.find(APP_CONFIG_ACTIVE_FONT);
if (it == section.end()) return {};
size_t active_font = static_cast<size_t>(std::atoi(it->second.c_str()));
// order in config starts with number 1
return active_font - 1;
}
EmbossStyles EmbossStylesSerializable::load_styles(const AppConfig &cfg)
{
EmbossStyles result;
// human readable index inside of config starts from 1 !!
unsigned index = 1;
std::string section_name = create_section_name(index);
while (cfg.has_section(section_name)) {
std::optional<EmbossStyle> style_opt = load_style(cfg.get_section(section_name));
if (style_opt.has_value()) result.emplace_back(*style_opt);
section_name = create_section_name(++index);
}
return result;
}
void EmbossStylesSerializable::store_styles(AppConfig &cfg, const EmbossStyles& styles)
{
// store styles
unsigned index = 1;
for (const EmbossStyle &style : styles) {
// skip file paths + fonts from other OS(loaded from .3mf)
assert(style.type == WxFontUtils::get_actual_type());
// if (style_opt.type != WxFontUtils::get_actual_type()) continue;
store_style(cfg, style, index++);
}
// remove rest of font sections (after deletation)
std::string section_name = create_section_name(index);
while (cfg.has_section(section_name)) {
cfg.clear_section(section_name);
section_name = create_section_name(++index);
}
}

View File

@ -1,62 +0,0 @@
///|/ Copyright (c) Prusa Research 2022 Filip Sykala @Jony01
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_EmbossStylesSerializable_hpp_
#define slic3r_EmbossStylesSerializable_hpp_
#include <string>
#include <map>
#include <optional>
#include <libslic3r/TextConfiguration.hpp> // EmbossStyles+EmbossStyle
namespace Slic3r {
class AppConfig;
}
namespace Slic3r::GUI {
/// <summary>
/// For store/load font list to/from AppConfig
/// </summary>
class EmbossStylesSerializable
{
static const std::string APP_CONFIG_FONT_NAME;
static const std::string APP_CONFIG_FONT_DESCRIPTOR;
static const std::string APP_CONFIG_FONT_LINE_HEIGHT;
static const std::string APP_CONFIG_FONT_DEPTH;
static const std::string APP_CONFIG_FONT_USE_SURFACE;
static const std::string APP_CONFIG_FONT_BOLDNESS;
static const std::string APP_CONFIG_FONT_SKEW;
static const std::string APP_CONFIG_FONT_DISTANCE;
static const std::string APP_CONFIG_FONT_ANGLE;
static const std::string APP_CONFIG_FONT_COLLECTION;
static const std::string APP_CONFIG_FONT_CHAR_GAP;
static const std::string APP_CONFIG_FONT_LINE_GAP;
static const std::string APP_CONFIG_ACTIVE_FONT;
public:
EmbossStylesSerializable() = delete;
static void store_style_index(AppConfig &cfg, unsigned index);
static std::optional<size_t> load_style_index(const AppConfig &cfg);
static EmbossStyles load_styles(const AppConfig &cfg);
static void store_styles(AppConfig &cfg, const EmbossStyles& styles);
private:
static std::string create_section_name(unsigned index);
static std::optional<EmbossStyle> load_style(const std::map<std::string, std::string> &app_cfg_section);
static void store_style(AppConfig &cfg, const EmbossStyle &style, unsigned index);
// TODO: move to app config like read from section
static bool read(const std::map<std::string, std::string>& section, const std::string& key, bool& value);
static bool read(const std::map<std::string, std::string>& section, const std::string& key, float& value);
static bool read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<int>& value);
static bool read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<unsigned int>& value);
static bool read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<float>& value);
};
} // namespace Slic3r
#endif // #define slic3r_EmbossStylesSerializable_hpp_

View File

@ -186,7 +186,8 @@ std::optional<RaycastManager::Hit> RaycastManager::closest_hit(const Vec3d &poin
std::vector<AABBMesh::hit_result> hits_neg = mesh->query_ray_hits(point_negative, -mesh_direction);
hits.insert(hits.end(), std::make_move_iterator(hits_neg.begin()), std::make_move_iterator(hits_neg.end()));
for (const AABBMesh::hit_result &hit : hits) {
double squared_distance = (mesh_point - hit.position()).squaredNorm();
Vec3d diff = mesh_point - hit.position();
double squared_distance = diff.squaredNorm();
if (closest.has_value() &&
closest->squared_distance < squared_distance)
continue;
@ -363,4 +364,23 @@ std::optional<RaycastManager::Hit> ray_from_camera(const RaycastManager &
return raycaster.first_hit(point, direction, skip);
}
RaycastManager::AllowVolumes create_condition(const ModelVolumePtrs &volumes, const ObjectID &disallowed_volume_id) {
std::vector<size_t> allowed_volumes_id;
if (volumes.size() > 1) {
allowed_volumes_id.reserve(volumes.size() - 1);
for (const ModelVolume *v : volumes) {
// drag only above part not modifiers or negative surface
if (!v->is_model_part())
continue;
// skip actual selected object
if (v->id() == disallowed_volume_id)
continue;
allowed_volumes_id.emplace_back(v->id().id);
}
}
return RaycastManager::AllowVolumes(allowed_volumes_id);
}
} // namespace Slic3r::GUI

View File

@ -171,6 +171,14 @@ std::optional<RaycastManager::Hit> ray_from_camera(const RaycastManager &
const Camera &camera,
const RaycastManager::ISkip *skip);
/// <summary>
/// Create condition to allowe only parts from volumes without one given
/// </summary>
/// <param name="volumes">List of allowed volumes included one which is dissalowed and non parts</param>
/// <param name="disallowed_volume_id">Disallowed volume</param>
/// <returns>Condition</returns>
RaycastManager::AllowVolumes create_condition(const ModelVolumePtrs &volumes, const ObjectID &disallowed_volume_id);
} // namespace Slic3r::GUI
#endif // slic3r_RaycastManager_hpp_

View File

@ -112,7 +112,7 @@ std::unique_ptr<Emboss::FontFile> WxFontUtils::create_font_file(const wxFont &fo
#endif
}
EmbossStyle::Type WxFontUtils::get_actual_type()
EmbossStyle::Type WxFontUtils::get_current_type()
{
#ifdef _WIN32
return EmbossStyle::Type::wx_win_font_descr;
@ -129,7 +129,7 @@ EmbossStyle WxFontUtils::create_emboss_style(const wxFont &font, const std::stri
{
std::string name_item = name.empty()? get_human_readable_name(font) : name;
std::string fontDesc = store_wxFont(font);
EmbossStyle::Type type = get_actual_type();
EmbossStyle::Type type = get_current_type();
// synchronize font property with actual font
FontProp font_prop;
@ -138,7 +138,6 @@ EmbossStyle WxFontUtils::create_emboss_style(const wxFont &font, const std::stri
// is approximately 0.0139 inch or 352.8 um. But it is too small, so I
// decide use point size as mm for emboss
font_prop.size_in_mm = font.GetPointSize(); // *0.3528f;
font_prop.emboss = font_prop.size_in_mm / 2.f;
WxFontUtils::update_property(font_prop, font);
return { name_item, fontDesc, type, font_prop };

View File

@ -28,7 +28,7 @@ public:
// os specific load of wxFont
static std::unique_ptr<Slic3r::Emboss::FontFile> create_font_file(const wxFont &font);
static EmbossStyle::Type get_actual_type();
static EmbossStyle::Type get_current_type();
static EmbossStyle create_emboss_style(const wxFont &font, const std::string& name = "");
static std::string get_human_readable_name(const wxFont &font);

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg">
<path fill="#ed6b21" d="m 68.34,199.06 c 0,6.2 -5.04,11.24 -11.24,11.24 l 0.03507,-11.27073 z" />
<path fill="#808080" d="m 68.34,199.06 5.608583,1.97096 -14.648722,13.67 L 57.1,210.3 c 6.2,0 11.24,-5.04 11.24,-11.24 z" />
</svg>

After

Width:  |  Height:  |  Size: 271 B

View File

@ -23,7 +23,8 @@ TEST_CASE("Cut character from surface", "[]")
Transform3d tr = Transform3d::Identity();
tr.translate(Vec3d(0., 0., -z_depth));
tr.scale(Emboss::SHAPE_SCALE);
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
tr.scale(text_shape_scale);
Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth));
auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
@ -158,7 +159,7 @@ TEST_CASE("CutSurface in 3mf", "[Emboss]")
FontProp fp = tc.style.prop;
ExPolygons shapes = Emboss::text2shapes(ff, tc.text.c_str(), fp);
double shape_scale = Emboss::get_shape_scale(fp, *ff.font_file);
double shape_scale = Emboss::get_text_shape_scale(fp, *ff.font_file);
Emboss::OrthoProject projection = create_projection_for_cut(
cut_projection_tr, shape_scale, get_extents(shapes), z_range);

View File

@ -195,15 +195,17 @@ TEST_CASE("Visualize glyph from font", "[Emboss]")
#endif // VISUALIZE
#include "test_utils.hpp"
#include "nanosvg/nanosvg.h" // load SVG file
#include "libslic3r/NSVGUtils.hpp"
#include <nanosvg/nanosvg.h> // load SVG file
#include <libslic3r/NSVGUtils.hpp>
#include <libslic3r/IntersectionPoints.hpp>
ExPolygons heal_and_check(const Polygons &polygons)
{
Pointfs intersections_prev = intersection_points(polygons);
IntersectionsLines intersections_prev = get_intersections(polygons);
Points polygons_points = to_points(polygons);
Points duplicits_prev = collect_duplicates(polygons_points);
ExPolygons shape = Emboss::heal_shape(polygons);
auto [shape, success] = Emboss::heal_polygons(polygons);
CHECK(success);
// Is default shape for unhealabled shape?
bool is_default_shape =
@ -213,7 +215,7 @@ ExPolygons heal_and_check(const Polygons &polygons)
shape.front().holes.front().points.size() == 4 ;
CHECK(!is_default_shape);
Pointfs intersections = intersection_points(shape);
IntersectionsLines intersections = get_intersections(shape);
Points shape_points = to_points(shape);
Points duplicits = collect_duplicates(shape_points);
//{
@ -244,7 +246,9 @@ void scale(Polygons &polygons, double multiplicator) {
Polygons load_polygons(const std::string &svg_file) {
std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + svg_file;
NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f);
Polygons polygons = NSVGUtils::to_polygons(image);
NSVGLineParams param{1000};
param.scale = 10.;
Polygons polygons = to_polygons(*image, param);
nsvgDelete(image);
return polygons;
}
@ -258,7 +262,9 @@ TEST_CASE("Heal of 'i' in ALIENATO.TTF", "[Emboss]")
auto a = heal_and_check(polygons);
Polygons scaled_shape = polygons; // copy
scale(scaled_shape, 1 / Emboss::SHAPE_SCALE);
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
scale(scaled_shape, 1 / text_shape_scale);
auto b = heal_and_check(scaled_shape);
// different scale
@ -282,12 +288,38 @@ TEST_CASE("Heal of 'm' in Allura_Script.ttf", "[Emboss]")
auto a = heal_and_check(polygons);
}
#include "libslic3r/NSVGUtils.hpp"
TEST_CASE("Heal of svg contour overlap", "[Emboss]") {
std::string svg_file = "contour_neighbor.svg";
auto image = nsvgParseFromFile(TEST_DATA_DIR PATH_SEPARATOR + svg_file, "mm");
NSVGLineParams param(1e10);
ExPolygonsWithIds shapes = create_shape_with_ids(*image, param);
Polygons polygons;
for (ExPolygonsWithId &shape : shapes)
polygons.push_back(shape.expoly.front().contour);
auto a = heal_and_check(polygons);
}
// Input contour is extracted from case above "contour_neighbor.svg" with trouble shooted scale
TEST_CASE("Heal of overlaping contour", "[Emboss]"){
// Extracted from svg:
Points contour{{2228926, 1543620}, {745002, 2065101}, {745002, 2065094}, {744990, 2065094}, {684487, 1466338},
{510999, 908378}, {236555, 403250}, {-126813, -37014}, {-567074, -400382}, {-1072201, -674822},
{-567074, -400378}, {-126813, -37010}, {236555, 403250}, {510999, 908382}, {684487, 1466346},
{744990, 2065105}, {-2219648, 2073234}, {-2228926, -908814}, {-1646879, -2073235}};
ExPolygons shapes = {ExPolygon{contour}};
CHECK(Emboss::heal_expolygons(shapes));
}
TEST_CASE("Heal of points close to line", "[Emboss]")
{
std::string file_name = "points_close_to_line.svg";
std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name;
NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f);
Polygons polygons = NSVGUtils::to_polygons(image);
NSVGLineParams param{1000};
param.scale = 1.;
Polygons polygons = to_polygons(*image, param);
nsvgDelete(image);
REQUIRE(polygons.size() == 1);
Polygon polygon = polygons.front();
@ -309,18 +341,19 @@ The other kids at school nicknamed him Ix,\n\
which in the language of Betelgeuse Five translates as\t\n\
\"boy who is not able satisfactorily to explain what a Hrung is,\n\
nor why it should choose to collapse on Betelgeuse Seven\".";
float line_height = 10.f, depth = 2.f;
float line_height = 10.f;
auto font = Emboss::create_font_file(font_path.c_str());
REQUIRE(font != nullptr);
Emboss::FontFileWithCache ffwc(std::move(font));
FontProp fp{line_height, depth};
FontProp fp{line_height};
auto was_canceled = []() { return false; };
ExPolygons shapes = Emboss::text2shapes(ffwc, text.c_str(), fp, was_canceled);
REQUIRE(!shapes.empty());
float depth = 2.f;
Emboss::ProjectZ projection(depth);
indexed_triangle_set its = Emboss::polygons2model(shapes, projection);
CHECK(!its.indices.empty());
@ -470,7 +503,8 @@ TEST_CASE("Cut surface", "[]")
Transform3d tr = Transform3d::Identity();
tr.translate(Vec3d(0., 0., -z_depth));
tr.scale(Emboss::SHAPE_SCALE);
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
tr.scale(text_shape_scale);
Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth));
auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
@ -493,25 +527,25 @@ TEST_CASE("Cut surface", "[]")
#include <sstream>
#include <cereal/cereal.hpp>
#include <cereal/archives/binary.hpp>
TEST_CASE("UndoRedo serialization", "[Emboss]")
TEST_CASE("UndoRedo TextConfiguration serialization", "[Emboss]")
{
TextConfiguration tc;
tc.text = "Dovede-li se člověk zasmát sám sobě, nevyjde ze smíchu po celý život.";
EmbossStyle& es = tc.style;
es.name = "Seneca";
es.path = "Simply the best";
es.type = EmbossStyle::Type::file_path;
FontProp &fp = es.prop;
fp.angle = 100.;
fp.distance = 10.;
fp.char_gap = 1;
fp.use_surface = true;
tc.fix_3mf_tr = Transform3d::Identity();
es.name = "Seneca";
es.path = "Simply the best";
es.type = EmbossStyle::Type::file_path;
FontProp &fp = es.prop;
fp.char_gap = 3;
fp.line_gap = 7;
fp.boldness = 2.3f;
fp.skew = 4.5f;
fp.collection_number = 13;
fp.size_in_mm= 6.7f;
std::stringstream ss; // any stream can be used
{
cereal::BinaryOutputArchive oarchive(ss); // Create an output archive
cereal::BinaryOutputArchive oarchive(ss); // Create an output archive
oarchive(tc);
} // archive goes out of scope, ensuring all contents are flushed
@ -522,7 +556,36 @@ TEST_CASE("UndoRedo serialization", "[Emboss]")
}
CHECK(tc.style == tc_loaded.style);
CHECK(tc.text == tc_loaded.text);
CHECK(tc.fix_3mf_tr.has_value() == tc_loaded.fix_3mf_tr.has_value());
}
#include "libslic3r/EmbossShape.hpp"
TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]")
{
EmbossShape emboss;
emboss.shapes_with_ids = {{0, {{{0, 0}, {10, 0}, {10, 10}, {0, 10}}, {{5, 5}, {6, 5}, {6, 6}, {5, 6}}}}};
emboss.scale = 2.;
emboss.projection.depth = 5.;
emboss.projection.use_surface = true;
emboss.fix_3mf_tr = Transform3d::Identity();
emboss.svg_file.path = "Everything starts somewhere, though many physicists disagree.\
But people have always been dimly aware of the problem with the start of things.\
They wonder how the snowplough driver gets to work,\
or how the makers of dictionaries look up the spelling of words.";
std::stringstream ss; // any stream can be used
{
cereal::BinaryOutputArchive oarchive(ss); // Create an output archive
oarchive(emboss);
} // archive goes out of scope, ensuring all contents are flushed
EmbossShape emboss_loaded;
{
cereal::BinaryInputArchive iarchive(ss); // Create an input archive
iarchive(emboss_loaded);
}
CHECK(emboss.shapes_with_ids.front().expoly == emboss_loaded.shapes_with_ids.front().expoly);
CHECK(emboss.scale == emboss_loaded.scale);
CHECK(emboss.projection.depth == emboss_loaded.projection.depth);
CHECK(emboss.projection.use_surface == emboss_loaded.projection.use_surface);
}

View File

@ -65,3 +65,99 @@ SCENARIO("Basics", "[ExPolygon]") {
}
}
}
#include <sstream>
#include <cereal/cereal.hpp>
#include <cereal/archives/binary.hpp>
#include "libslic3r/ExPolygonSerialize.hpp"
TEST_CASE("Serialization of expolygons", "[ExPolygon, Cereal, serialization]")
{
ExPolygons expolys{{
// expolygon 1 - without holes
{{0,0}, {10,0}, {10,10}, {0,10}}, // contour
// expolygon 2 - with rect 1px hole
{{{0,0}, {10,0}, {10,10}, {0,10}},
{{5, 5}, {6, 5}, {6, 6}, {5, 6}}}
}};
std::stringstream ss; // any stream can be used
{
cereal::BinaryOutputArchive oarchive(ss); // Create an output archive
oarchive(expolys);
} // archive goes out of scope, ensuring all contents are flushed
std::string data = ss.str();
CHECK(!data.empty());
ExPolygons expolys_loaded;
{
cereal::BinaryInputArchive iarchive(ss); // Create an input archive
iarchive(expolys_loaded);
}
CHECK(expolys == expolys_loaded);
}
#include <cereal/archives/json.hpp>
#include <regex>
// It is used to serialize expolygons into 3mf.
TEST_CASE("Serialization of expolygons to string", "[ExPolygon, Cereal, serialization]")
{
ExPolygons expolys{{
// expolygon 1 - without holes
{{0,0}, {10,0}, {10,10}, {0,10}}, // contour
// expolygon 2 - with rect 1px hole
{{{0,0}, {10,0}, {10,10}, {0,10}},
{{5, 5}, {6, 5}, {6, 6}, {5, 6}}}
}};
std::stringstream ss_out; // any stream can be used
{
cereal::JSONOutputArchive oarchive(ss_out); // Create an output archive
oarchive(expolys);
} // archive goes out of scope, ensuring all contents are flushed
//Simplify text representation of expolygons
std::string data = ss_out.str();
// Data contain this JSON string
//{
// "value0": [
// {
// "value0": {
// "value0":
// [{"value0": 0, "value1": 0}, {"value0": 10, "value1": 0}, {"value0": 10, "value1": 10}, {"value0": 0, "value1": 10}]
// },
// "value1": []
// },
// {
// "value0": {
// "value0":
// [{"value0": 0, "value1": 0}, {"value0": 10, "value1": 0}, {"value0": 10, "value1": 10}, {"value0": 0, "value1": 10}]
// },
// "value1": [{
// "value0":
// [{"value0": 5, "value1": 5}, {"value0": 6, "value1": 5}, {"value0": 6, "value1": 6}, {"value0": 5, "value1": 6}]
// }]
// }
// ]
//}
// Change JSON named object to JSON arrays(without name)
// RegEx for wihitespace = "[ \t\r\n\v\f]"
std::regex r("\"value[0-9]+\":|[ \t\r\n\v\f]");
std::string data_short = std::regex_replace(data, r , "");
std::replace(data_short.begin(), data_short.end(), '{', '[');
std::replace(data_short.begin(), data_short.end(), '}', ']');
CHECK(!data_short.empty());
// Cereal acceptable string
// [[[[[[0,0],[10,0],[10,10],[0,10]]],[]],[[[[0,0],[10,0],[10,10],[0,10]]],[[[[5,5],[6,5],[6,6],[5,6]]]]]]]
std::stringstream ss_in(data_short);
ExPolygons expolys_loaded;
{
cereal::JSONInputArchive iarchive(ss_in); // Create an input archive
iarchive(expolys_loaded);
}
CHECK(expolys == expolys_loaded);
}