mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into ys_ph_printers
This commit is contained in:
		
						commit
						a6dc3d37f5
					
				
					 78 changed files with 4309 additions and 2783 deletions
				
			
		
							
								
								
									
										10
									
								
								resources/icons/cancel.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								resources/icons/cancel.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
<?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="resin">
 | 
			
		||||
	<rect x="4" y="7" fill="#ED6B21" width="8" height="8"/>
 | 
			
		||||
	<path fill="none" stroke="#808080" stroke-linecap="round" stroke-miterlimit="10" d="M4.5,15h6.99c0.28,0,0.5-0.23,0.5-0.5V6
 | 
			
		||||
		c0-1-2-1-2-2s0-1,0-1h1V1.5C11,1.23,10.77,1,10.5,1H5.5C5.23,1,5,1.23,5,1.5V3h1v1c0,1-2,1-2,2v8.5C4,14.77,4.23,15,4.5,15z"/>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 671 B  | 
							
								
								
									
										81
									
								
								resources/icons/cross_focus_large.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								resources/icons/cross_focus_large.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,81 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
 | 
			
		||||
   sodipodi:docname="cross_megafocus.svg"
 | 
			
		||||
   xml:space="preserve"
 | 
			
		||||
   enable-background="new 0 0 16 16"
 | 
			
		||||
   viewBox="0 0 16 16"
 | 
			
		||||
   y="0px"
 | 
			
		||||
   x="0px"
 | 
			
		||||
   id="Layer_1"
 | 
			
		||||
   version="1.0"><metadata
 | 
			
		||||
   id="metadata16"><rdf:RDF><cc:Work
 | 
			
		||||
       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
 | 
			
		||||
         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
 | 
			
		||||
   id="defs14" /><sodipodi:namedview
 | 
			
		||||
   inkscape:current-layer="Layer_1"
 | 
			
		||||
   inkscape:window-maximized="1"
 | 
			
		||||
   inkscape:window-y="-9"
 | 
			
		||||
   inkscape:window-x="-9"
 | 
			
		||||
   inkscape:cy="8"
 | 
			
		||||
   inkscape:cx="8"
 | 
			
		||||
   inkscape:zoom="47.0625"
 | 
			
		||||
   showgrid="false"
 | 
			
		||||
   id="namedview12"
 | 
			
		||||
   inkscape:window-height="1721"
 | 
			
		||||
   inkscape:window-width="3200"
 | 
			
		||||
   inkscape:pageshadow="2"
 | 
			
		||||
   inkscape:pageopacity="0"
 | 
			
		||||
   guidetolerance="10"
 | 
			
		||||
   gridtolerance="10"
 | 
			
		||||
   objecttolerance="10"
 | 
			
		||||
   borderopacity="1"
 | 
			
		||||
   bordercolor="#666666"
 | 
			
		||||
   pagecolor="#ffffff" />
 | 
			
		||||
<g
 | 
			
		||||
   style="opacity:1;fill-opacity:1"
 | 
			
		||||
   transform="matrix(1.1,0,0,1.1,-0.8,-0.8)"
 | 
			
		||||
   id="cross">
 | 
			
		||||
	<g
 | 
			
		||||
   style="fill-opacity:1"
 | 
			
		||||
   id="g4">
 | 
			
		||||
		
 | 
			
		||||
			<line
 | 
			
		||||
   style="fill-opacity:1"
 | 
			
		||||
   id="line2"
 | 
			
		||||
   y2="14"
 | 
			
		||||
   x2="2"
 | 
			
		||||
   y1="2"
 | 
			
		||||
   x1="14"
 | 
			
		||||
   stroke-miterlimit="10"
 | 
			
		||||
   stroke-linecap="round"
 | 
			
		||||
   stroke-width="3"
 | 
			
		||||
   stroke="#ed6b21"
 | 
			
		||||
   fill="none" />
 | 
			
		||||
	</g>
 | 
			
		||||
	<g
 | 
			
		||||
   style="fill-opacity:1"
 | 
			
		||||
   id="g8">
 | 
			
		||||
		
 | 
			
		||||
			<line
 | 
			
		||||
   style="fill-opacity:1"
 | 
			
		||||
   id="line6"
 | 
			
		||||
   y2="14"
 | 
			
		||||
   x2="14"
 | 
			
		||||
   y1="2"
 | 
			
		||||
   x1="2"
 | 
			
		||||
   stroke-miterlimit="10"
 | 
			
		||||
   stroke-linecap="round"
 | 
			
		||||
   stroke-width="3"
 | 
			
		||||
   stroke="#ed6b21"
 | 
			
		||||
   fill="none" />
 | 
			
		||||
	</g>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 2 KiB  | 
							
								
								
									
										27
									
								
								resources/icons/ironing.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								resources/icons/ironing.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!-- Generator: Adobe Illustrator 24.2.1, 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="ironing">
 | 
			
		||||
	<g>
 | 
			
		||||
		<path fill="#ED6B21" d="M14,9.42H2c-0.39,0-0.71-0.32-0.71-0.71C1.29,7.08,2.07,4,5,4h8c0.33,0,0.61,0.22,0.69,0.54l1,4
 | 
			
		||||
			c0.05,0.21,0,0.44-0.13,0.61C14.42,9.32,14.22,9.42,14,9.42z M2.77,8h10.32l-0.65-2.58H5C3.39,5.42,2.91,7.03,2.77,8z"/>
 | 
			
		||||
	</g>
 | 
			
		||||
	<g>
 | 
			
		||||
		<path fill="#ED6B21" d="M13,5.42c-0.39,0-0.71-0.32-0.71-0.71v-1c0-1.18-0.99-1.29-1.3-1.29H6c-0.39,0-0.71-0.32-0.71-0.71
 | 
			
		||||
			S5.61,1,6,1h5c1.05,0,2.61,0.68,2.7,2.52c0,0.03,0,0.06,0,0.08v1.1C13.71,5.1,13.39,5.42,13,5.42z"/>
 | 
			
		||||
	</g>
 | 
			
		||||
	<g>
 | 
			
		||||
		<path fill="#808080" d="M14.65,15H1.35C1.16,15,1,14.84,1,14.65s0.16-0.35,0.35-0.35h13.29c0.2,0,0.35,0.16,0.35,0.35
 | 
			
		||||
			S14.84,15,14.65,15z"/>
 | 
			
		||||
	</g>
 | 
			
		||||
	<g>
 | 
			
		||||
		<path fill="#808080" d="M14.65,13H1.35C1.16,13,1,12.84,1,12.65s0.16-0.35,0.35-0.35h13.29c0.2,0,0.35,0.16,0.35,0.35
 | 
			
		||||
			S14.84,13,14.65,13z"/>
 | 
			
		||||
	</g>
 | 
			
		||||
	<g>
 | 
			
		||||
		<path fill="#808080" d="M14.65,11H1.35C1.16,11,1,10.84,1,10.65s0.16-0.35,0.35-0.35h13.29c0.2,0,0.35,0.16,0.35,0.35
 | 
			
		||||
			S14.84,11,14.65,11z"/>
 | 
			
		||||
	</g>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.3 KiB  | 
							
								
								
									
										72
									
								
								resources/icons/timer_dot.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								resources/icons/timer_dot.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,72 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:xlink="http://www.w3.org/1999/xlink"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
 | 
			
		||||
   sodipodi:docname="timer_dot.svg"
 | 
			
		||||
   xml:space="preserve"
 | 
			
		||||
   enable-background="new 0 0 16 16"
 | 
			
		||||
   viewBox="0 0 16 16"
 | 
			
		||||
   y="0px"
 | 
			
		||||
   x="0px"
 | 
			
		||||
   id="Layer_1"
 | 
			
		||||
   version="1.0"><metadata
 | 
			
		||||
   id="metadata11"><rdf:RDF><cc:Work
 | 
			
		||||
       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
 | 
			
		||||
         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
 | 
			
		||||
   id="defs9"><linearGradient
 | 
			
		||||
     id="linearGradient830"
 | 
			
		||||
     inkscape:collect="always"><stop
 | 
			
		||||
       id="stop826"
 | 
			
		||||
       offset="0"
 | 
			
		||||
       style="stop-color:#000000;stop-opacity:1;" /><stop
 | 
			
		||||
       id="stop828"
 | 
			
		||||
       offset="1"
 | 
			
		||||
       style="stop-color:#000000;stop-opacity:0;" /></linearGradient><radialGradient
 | 
			
		||||
     gradientUnits="userSpaceOnUse"
 | 
			
		||||
     r="3.5"
 | 
			
		||||
     fy="8"
 | 
			
		||||
     fx="8"
 | 
			
		||||
     cy="8"
 | 
			
		||||
     cx="8"
 | 
			
		||||
     id="radialGradient832"
 | 
			
		||||
     xlink:href="#linearGradient830"
 | 
			
		||||
     inkscape:collect="always" /></defs><sodipodi:namedview
 | 
			
		||||
   inkscape:document-rotation="0"
 | 
			
		||||
   inkscape:current-layer="Layer_1"
 | 
			
		||||
   inkscape:window-maximized="1"
 | 
			
		||||
   inkscape:window-y="-11"
 | 
			
		||||
   inkscape:window-x="-11"
 | 
			
		||||
   inkscape:cy="6.66147"
 | 
			
		||||
   inkscape:cx="7.0304602"
 | 
			
		||||
   inkscape:zoom="83.4386"
 | 
			
		||||
   showgrid="false"
 | 
			
		||||
   id="namedview7"
 | 
			
		||||
   inkscape:window-height="2066"
 | 
			
		||||
   inkscape:window-width="3840"
 | 
			
		||||
   inkscape:pageshadow="2"
 | 
			
		||||
   inkscape:pageopacity="0"
 | 
			
		||||
   guidetolerance="10"
 | 
			
		||||
   gridtolerance="10"
 | 
			
		||||
   objecttolerance="10"
 | 
			
		||||
   borderopacity="1"
 | 
			
		||||
   bordercolor="#666666"
 | 
			
		||||
   pagecolor="#ffffff" />
 | 
			
		||||
<g
 | 
			
		||||
   transform="matrix(0.7,0,0,0.7,2.4,2.4)"
 | 
			
		||||
   style="fill:#bf6637;fill-opacity:1;stroke:none;stroke-opacity:1"
 | 
			
		||||
   id="g4">
 | 
			
		||||
	<circle
 | 
			
		||||
   style="fill:#bf6637;fill-opacity:1;stroke:none;stroke-opacity:1"
 | 
			
		||||
   id="circle2"
 | 
			
		||||
   r="3"
 | 
			
		||||
   cy="8"
 | 
			
		||||
   cx="8" />
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 2.2 KiB  | 
							
								
								
									
										73
									
								
								resources/icons/timer_dot_empty.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								resources/icons/timer_dot_empty.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,73 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 | 
			
		||||
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:xlink="http://www.w3.org/1999/xlink"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   version="1.0"
 | 
			
		||||
   id="Layer_1"
 | 
			
		||||
   x="0px"
 | 
			
		||||
   y="0px"
 | 
			
		||||
   viewBox="0 0 16 16"
 | 
			
		||||
   enable-background="new 0 0 16 16"
 | 
			
		||||
   xml:space="preserve"
 | 
			
		||||
   sodipodi:docname="timer_dot_empty.svg"
 | 
			
		||||
   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
 | 
			
		||||
   id="metadata11"><rdf:RDF><cc:Work
 | 
			
		||||
       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
 | 
			
		||||
         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
 | 
			
		||||
   id="defs9"><linearGradient
 | 
			
		||||
     inkscape:collect="always"
 | 
			
		||||
     id="linearGradient830"><stop
 | 
			
		||||
       style="stop-color:#000000;stop-opacity:1;"
 | 
			
		||||
       offset="0"
 | 
			
		||||
       id="stop826" /><stop
 | 
			
		||||
       style="stop-color:#000000;stop-opacity:0;"
 | 
			
		||||
       offset="1"
 | 
			
		||||
       id="stop828" /></linearGradient><radialGradient
 | 
			
		||||
     inkscape:collect="always"
 | 
			
		||||
     xlink:href="#linearGradient830"
 | 
			
		||||
     id="radialGradient832"
 | 
			
		||||
     cx="8"
 | 
			
		||||
     cy="8"
 | 
			
		||||
     fx="8"
 | 
			
		||||
     fy="8"
 | 
			
		||||
     r="3.5"
 | 
			
		||||
     gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
 | 
			
		||||
   pagecolor="#ffffff"
 | 
			
		||||
   bordercolor="#666666"
 | 
			
		||||
   borderopacity="1"
 | 
			
		||||
   objecttolerance="10"
 | 
			
		||||
   gridtolerance="10"
 | 
			
		||||
   guidetolerance="10"
 | 
			
		||||
   inkscape:pageopacity="0"
 | 
			
		||||
   inkscape:pageshadow="2"
 | 
			
		||||
   inkscape:window-width="3840"
 | 
			
		||||
   inkscape:window-height="2066"
 | 
			
		||||
   id="namedview7"
 | 
			
		||||
   showgrid="false"
 | 
			
		||||
   inkscape:zoom="83.4386"
 | 
			
		||||
   inkscape:cx="7.0304602"
 | 
			
		||||
   inkscape:cy="6.66147"
 | 
			
		||||
   inkscape:window-x="-11"
 | 
			
		||||
   inkscape:window-y="-11"
 | 
			
		||||
   inkscape:window-maximized="1"
 | 
			
		||||
   inkscape:current-layer="Layer_1" />
 | 
			
		||||
<g
 | 
			
		||||
   id="g4"
 | 
			
		||||
   style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1"
 | 
			
		||||
   transform="matrix(0.7,0,0,0.7,2.4,2.4)">
 | 
			
		||||
	<circle
 | 
			
		||||
   cx="8"
 | 
			
		||||
   cy="8"
 | 
			
		||||
   r="3"
 | 
			
		||||
   id="circle2"
 | 
			
		||||
   style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" />
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 2.3 KiB  | 
| 
						 | 
				
			
			@ -113,7 +113,12 @@ namespace ImGui
 | 
			
		|||
    const char PrinterSlaIconMarker = 0x6; 
 | 
			
		||||
    const char FilamentIconMarker   = 0x7; 
 | 
			
		||||
    const char MaterialIconMarker   = 0x8;
 | 
			
		||||
 | 
			
		||||
	const char CloseIconMarker      = 0xB;
 | 
			
		||||
	const char CloseIconHoverMarker = 0xC;
 | 
			
		||||
	const char TimerDotMarker       = 0xE;
 | 
			
		||||
	const char TimerDotEmptyMarker  = 0xF;
 | 
			
		||||
	const char WarningMarker        = 0x10;
 | 
			
		||||
	const char ErrorMarker          = 0x11;
 | 
			
		||||
//    void MyFunction(const char* name, const MyMatrix44& v);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,661 +1,165 @@
 | 
			
		|||
                    GNU AFFERO GENERAL PUBLIC LICENSE
 | 
			
		||||
                       Version 3, 19 November 2007
 | 
			
		||||
    GNU LESSER GENERAL PUBLIC LICENSE
 | 
			
		||||
                       Version 3, 29 June 2007
 | 
			
		||||
 | 
			
		||||
 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 | 
			
		||||
 Everyone is permitted to copy and distribute verbatim copies
 | 
			
		||||
 of this license document, but changing it is not allowed.
 | 
			
		||||
 | 
			
		||||
                            Preamble
 | 
			
		||||
 | 
			
		||||
  The GNU Affero General Public License is a free, copyleft license for
 | 
			
		||||
software and other kinds of works, specifically designed to ensure
 | 
			
		||||
cooperation with the community in the case of network server software.
 | 
			
		||||
 | 
			
		||||
  The licenses for most software and other practical works are designed
 | 
			
		||||
to take away your freedom to share and change the works.  By contrast,
 | 
			
		||||
our General Public Licenses are intended to guarantee your freedom to
 | 
			
		||||
share and change all versions of a program--to make sure it remains free
 | 
			
		||||
software for all its users.
 | 
			
		||||
 | 
			
		||||
  When we speak of free software, we are referring to freedom, not
 | 
			
		||||
price.  Our General Public Licenses are designed to make sure that you
 | 
			
		||||
have the freedom to distribute copies of free software (and charge for
 | 
			
		||||
them if you wish), that you receive source code or can get it if you
 | 
			
		||||
want it, that you can change the software or use pieces of it in new
 | 
			
		||||
free programs, and that you know you can do these things.
 | 
			
		||||
 | 
			
		||||
  Developers that use our General Public Licenses protect your rights
 | 
			
		||||
with two steps: (1) assert copyright on the software, and (2) offer
 | 
			
		||||
you this License which gives you legal permission to copy, distribute
 | 
			
		||||
and/or modify the software.
 | 
			
		||||
 | 
			
		||||
  A secondary benefit of defending all users' freedom is that
 | 
			
		||||
improvements made in alternate versions of the program, if they
 | 
			
		||||
receive widespread use, become available for other developers to
 | 
			
		||||
incorporate.  Many developers of free software are heartened and
 | 
			
		||||
encouraged by the resulting cooperation.  However, in the case of
 | 
			
		||||
software used on network servers, this result may fail to come about.
 | 
			
		||||
The GNU General Public License permits making a modified version and
 | 
			
		||||
letting the public access it on a server without ever releasing its
 | 
			
		||||
source code to the public.
 | 
			
		||||
 | 
			
		||||
  The GNU Affero General Public License is designed specifically to
 | 
			
		||||
ensure that, in such cases, the modified source code becomes available
 | 
			
		||||
to the community.  It requires the operator of a network server to
 | 
			
		||||
provide the source code of the modified version running there to the
 | 
			
		||||
users of that server.  Therefore, public use of a modified version, on
 | 
			
		||||
a publicly accessible server, gives the public access to the source
 | 
			
		||||
code of the modified version.
 | 
			
		||||
 | 
			
		||||
  An older license, called the Affero General Public License and
 | 
			
		||||
published by Affero, was designed to accomplish similar goals.  This is
 | 
			
		||||
a different license, not a version of the Affero GPL, but Affero has
 | 
			
		||||
released a new version of the Affero GPL which permits relicensing under
 | 
			
		||||
this license.
 | 
			
		||||
 | 
			
		||||
  The precise terms and conditions for copying, distribution and
 | 
			
		||||
modification follow.
 | 
			
		||||
 | 
			
		||||
                       TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
  0. Definitions.
 | 
			
		||||
 | 
			
		||||
  "This License" refers to version 3 of the GNU Affero General Public License.
 | 
			
		||||
 | 
			
		||||
  "Copyright" also means copyright-like laws that apply to other kinds of
 | 
			
		||||
works, such as semiconductor masks.
 | 
			
		||||
 | 
			
		||||
  "The Program" refers to any copyrightable work licensed under this
 | 
			
		||||
License.  Each licensee is addressed as "you".  "Licensees" and
 | 
			
		||||
"recipients" may be individuals or organizations.
 | 
			
		||||
 | 
			
		||||
  To "modify" a work means to copy from or adapt all or part of the work
 | 
			
		||||
in a fashion requiring copyright permission, other than the making of an
 | 
			
		||||
exact copy.  The resulting work is called a "modified version" of the
 | 
			
		||||
earlier work or a work "based on" the earlier work.
 | 
			
		||||
 | 
			
		||||
  A "covered work" means either the unmodified Program or a work based
 | 
			
		||||
on the Program.
 | 
			
		||||
 | 
			
		||||
  To "propagate" a work means to do anything with it that, without
 | 
			
		||||
permission, would make you directly or secondarily liable for
 | 
			
		||||
infringement under applicable copyright law, except executing it on a
 | 
			
		||||
computer or modifying a private copy.  Propagation includes copying,
 | 
			
		||||
distribution (with or without modification), making available to the
 | 
			
		||||
public, and in some countries other activities as well.
 | 
			
		||||
 | 
			
		||||
  To "convey" a work means any kind of propagation that enables other
 | 
			
		||||
parties to make or receive copies.  Mere interaction with a user through
 | 
			
		||||
a computer network, with no transfer of a copy, is not conveying.
 | 
			
		||||
 | 
			
		||||
  An interactive user interface displays "Appropriate Legal Notices"
 | 
			
		||||
to the extent that it includes a convenient and prominently visible
 | 
			
		||||
feature that (1) displays an appropriate copyright notice, and (2)
 | 
			
		||||
tells the user that there is no warranty for the work (except to the
 | 
			
		||||
extent that warranties are provided), that licensees may convey the
 | 
			
		||||
work under this License, and how to view a copy of this License.  If
 | 
			
		||||
the interface presents a list of user commands or options, such as a
 | 
			
		||||
menu, a prominent item in the list meets this criterion.
 | 
			
		||||
 | 
			
		||||
  1. Source Code.
 | 
			
		||||
 | 
			
		||||
  The "source code" for a work means the preferred form of the work
 | 
			
		||||
for making modifications to it.  "Object code" means any non-source
 | 
			
		||||
form of a work.
 | 
			
		||||
 | 
			
		||||
  A "Standard Interface" means an interface that either is an official
 | 
			
		||||
standard defined by a recognized standards body, or, in the case of
 | 
			
		||||
interfaces specified for a particular programming language, one that
 | 
			
		||||
is widely used among developers working in that language.
 | 
			
		||||
 | 
			
		||||
  The "System Libraries" of an executable work include anything, other
 | 
			
		||||
than the work as a whole, that (a) is included in the normal form of
 | 
			
		||||
packaging a Major Component, but which is not part of that Major
 | 
			
		||||
Component, and (b) serves only to enable use of the work with that
 | 
			
		||||
Major Component, or to implement a Standard Interface for which an
 | 
			
		||||
implementation is available to the public in source code form.  A
 | 
			
		||||
"Major Component", in this context, means a major essential component
 | 
			
		||||
(kernel, window system, and so on) of the specific operating system
 | 
			
		||||
(if any) on which the executable work runs, or a compiler used to
 | 
			
		||||
produce the work, or an object code interpreter used to run it.
 | 
			
		||||
 | 
			
		||||
  The "Corresponding Source" for a work in object code form means all
 | 
			
		||||
the source code needed to generate, install, and (for an executable
 | 
			
		||||
work) run the object code and to modify the work, including scripts to
 | 
			
		||||
control those activities.  However, it does not include the work's
 | 
			
		||||
System Libraries, or general-purpose tools or generally available free
 | 
			
		||||
programs which are used unmodified in performing those activities but
 | 
			
		||||
which are not part of the work.  For example, Corresponding Source
 | 
			
		||||
includes interface definition files associated with source files for
 | 
			
		||||
the work, and the source code for shared libraries and dynamically
 | 
			
		||||
linked subprograms that the work is specifically designed to require,
 | 
			
		||||
such as by intimate data communication or control flow between those
 | 
			
		||||
subprograms and other parts of the work.
 | 
			
		||||
 | 
			
		||||
  The Corresponding Source need not include anything that users
 | 
			
		||||
can regenerate automatically from other parts of the Corresponding
 | 
			
		||||
Source.
 | 
			
		||||
 | 
			
		||||
  The Corresponding Source for a work in source code form is that
 | 
			
		||||
same work.
 | 
			
		||||
 | 
			
		||||
  2. Basic Permissions.
 | 
			
		||||
 | 
			
		||||
  All rights granted under this License are granted for the term of
 | 
			
		||||
copyright on the Program, and are irrevocable provided the stated
 | 
			
		||||
conditions are met.  This License explicitly affirms your unlimited
 | 
			
		||||
permission to run the unmodified Program.  The output from running a
 | 
			
		||||
covered work is covered by this License only if the output, given its
 | 
			
		||||
content, constitutes a covered work.  This License acknowledges your
 | 
			
		||||
rights of fair use or other equivalent, as provided by copyright law.
 | 
			
		||||
 | 
			
		||||
  You may make, run and propagate covered works that you do not
 | 
			
		||||
convey, without conditions so long as your license otherwise remains
 | 
			
		||||
in force.  You may convey covered works to others for the sole purpose
 | 
			
		||||
of having them make modifications exclusively for you, or provide you
 | 
			
		||||
with facilities for running those works, provided that you comply with
 | 
			
		||||
the terms of this License in conveying all material for which you do
 | 
			
		||||
not control copyright.  Those thus making or running the covered works
 | 
			
		||||
for you must do so exclusively on your behalf, under your direction
 | 
			
		||||
and control, on terms that prohibit them from making any copies of
 | 
			
		||||
your copyrighted material outside their relationship with you.
 | 
			
		||||
 | 
			
		||||
  Conveying under any other circumstances is permitted solely under
 | 
			
		||||
the conditions stated below.  Sublicensing is not allowed; section 10
 | 
			
		||||
makes it unnecessary.
 | 
			
		||||
 | 
			
		||||
  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
 | 
			
		||||
 | 
			
		||||
  No covered work shall be deemed part of an effective technological
 | 
			
		||||
measure under any applicable law fulfilling obligations under article
 | 
			
		||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
 | 
			
		||||
similar laws prohibiting or restricting circumvention of such
 | 
			
		||||
measures.
 | 
			
		||||
 | 
			
		||||
  When you convey a covered work, you waive any legal power to forbid
 | 
			
		||||
circumvention of technological measures to the extent such circumvention
 | 
			
		||||
is effected by exercising rights under this License with respect to
 | 
			
		||||
the covered work, and you disclaim any intention to limit operation or
 | 
			
		||||
modification of the work as a means of enforcing, against the work's
 | 
			
		||||
users, your or third parties' legal rights to forbid circumvention of
 | 
			
		||||
technological measures.
 | 
			
		||||
 | 
			
		||||
  4. Conveying Verbatim Copies.
 | 
			
		||||
 | 
			
		||||
  You may convey verbatim copies of the Program's source code as you
 | 
			
		||||
receive it, in any medium, provided that you conspicuously and
 | 
			
		||||
appropriately publish on each copy an appropriate copyright notice;
 | 
			
		||||
keep intact all notices stating that this License and any
 | 
			
		||||
non-permissive terms added in accord with section 7 apply to the code;
 | 
			
		||||
keep intact all notices of the absence of any warranty; and give all
 | 
			
		||||
recipients a copy of this License along with the Program.
 | 
			
		||||
 | 
			
		||||
  You may charge any price or no price for each copy that you convey,
 | 
			
		||||
and you may offer support or warranty protection for a fee.
 | 
			
		||||
 | 
			
		||||
  5. Conveying Modified Source Versions.
 | 
			
		||||
 | 
			
		||||
  You may convey a work based on the Program, or the modifications to
 | 
			
		||||
produce it from the Program, in the form of source code under the
 | 
			
		||||
terms of section 4, provided that you also meet all of these conditions:
 | 
			
		||||
 | 
			
		||||
    a) The work must carry prominent notices stating that you modified
 | 
			
		||||
    it, and giving a relevant date.
 | 
			
		||||
 | 
			
		||||
    b) The work must carry prominent notices stating that it is
 | 
			
		||||
    released under this License and any conditions added under section
 | 
			
		||||
    7.  This requirement modifies the requirement in section 4 to
 | 
			
		||||
    "keep intact all notices".
 | 
			
		||||
 | 
			
		||||
    c) You must license the entire work, as a whole, under this
 | 
			
		||||
    License to anyone who comes into possession of a copy.  This
 | 
			
		||||
    License will therefore apply, along with any applicable section 7
 | 
			
		||||
    additional terms, to the whole of the work, and all its parts,
 | 
			
		||||
    regardless of how they are packaged.  This License gives no
 | 
			
		||||
    permission to license the work in any other way, but it does not
 | 
			
		||||
    invalidate such permission if you have separately received it.
 | 
			
		||||
 | 
			
		||||
    d) If the work has interactive user interfaces, each must display
 | 
			
		||||
    Appropriate Legal Notices; however, if the Program has interactive
 | 
			
		||||
    interfaces that do not display Appropriate Legal Notices, your
 | 
			
		||||
    work need not make them do so.
 | 
			
		||||
 | 
			
		||||
  A compilation of a covered work with other separate and independent
 | 
			
		||||
works, which are not by their nature extensions of the covered work,
 | 
			
		||||
and which are not combined with it such as to form a larger program,
 | 
			
		||||
in or on a volume of a storage or distribution medium, is called an
 | 
			
		||||
"aggregate" if the compilation and its resulting copyright are not
 | 
			
		||||
used to limit the access or legal rights of the compilation's users
 | 
			
		||||
beyond what the individual works permit.  Inclusion of a covered work
 | 
			
		||||
in an aggregate does not cause this License to apply to the other
 | 
			
		||||
parts of the aggregate.
 | 
			
		||||
 | 
			
		||||
  6. Conveying Non-Source Forms.
 | 
			
		||||
 | 
			
		||||
  You may convey a covered work in object code form under the terms
 | 
			
		||||
of sections 4 and 5, provided that you also convey the
 | 
			
		||||
machine-readable Corresponding Source under the terms of this License,
 | 
			
		||||
in one of these ways:
 | 
			
		||||
 | 
			
		||||
    a) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by the
 | 
			
		||||
    Corresponding Source fixed on a durable physical medium
 | 
			
		||||
    customarily used for software interchange.
 | 
			
		||||
 | 
			
		||||
    b) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by a
 | 
			
		||||
    written offer, valid for at least three years and valid for as
 | 
			
		||||
    long as you offer spare parts or customer support for that product
 | 
			
		||||
    model, to give anyone who possesses the object code either (1) a
 | 
			
		||||
    copy of the Corresponding Source for all the software in the
 | 
			
		||||
    product that is covered by this License, on a durable physical
 | 
			
		||||
    medium customarily used for software interchange, for a price no
 | 
			
		||||
    more than your reasonable cost of physically performing this
 | 
			
		||||
    conveying of source, or (2) access to copy the
 | 
			
		||||
    Corresponding Source from a network server at no charge.
 | 
			
		||||
 | 
			
		||||
    c) Convey individual copies of the object code with a copy of the
 | 
			
		||||
    written offer to provide the Corresponding Source.  This
 | 
			
		||||
    alternative is allowed only occasionally and noncommercially, and
 | 
			
		||||
    only if you received the object code with such an offer, in accord
 | 
			
		||||
    with subsection 6b.
 | 
			
		||||
 | 
			
		||||
    d) Convey the object code by offering access from a designated
 | 
			
		||||
    place (gratis or for a charge), and offer equivalent access to the
 | 
			
		||||
    Corresponding Source in the same way through the same place at no
 | 
			
		||||
    further charge.  You need not require recipients to copy the
 | 
			
		||||
    Corresponding Source along with the object code.  If the place to
 | 
			
		||||
    copy the object code is a network server, the Corresponding Source
 | 
			
		||||
    may be on a different server (operated by you or a third party)
 | 
			
		||||
    that supports equivalent copying facilities, provided you maintain
 | 
			
		||||
    clear directions next to the object code saying where to find the
 | 
			
		||||
    Corresponding Source.  Regardless of what server hosts the
 | 
			
		||||
    Corresponding Source, you remain obligated to ensure that it is
 | 
			
		||||
    available for as long as needed to satisfy these requirements.
 | 
			
		||||
 | 
			
		||||
    e) Convey the object code using peer-to-peer transmission, provided
 | 
			
		||||
    you inform other peers where the object code and Corresponding
 | 
			
		||||
    Source of the work are being offered to the general public at no
 | 
			
		||||
    charge under subsection 6d.
 | 
			
		||||
 | 
			
		||||
  A separable portion of the object code, whose source code is excluded
 | 
			
		||||
from the Corresponding Source as a System Library, need not be
 | 
			
		||||
included in conveying the object code work.
 | 
			
		||||
 | 
			
		||||
  A "User Product" is either (1) a "consumer product", which means any
 | 
			
		||||
tangible personal property which is normally used for personal, family,
 | 
			
		||||
or household purposes, or (2) anything designed or sold for incorporation
 | 
			
		||||
into a dwelling.  In determining whether a product is a consumer product,
 | 
			
		||||
doubtful cases shall be resolved in favor of coverage.  For a particular
 | 
			
		||||
product received by a particular user, "normally used" refers to a
 | 
			
		||||
typical or common use of that class of product, regardless of the status
 | 
			
		||||
of the particular user or of the way in which the particular user
 | 
			
		||||
actually uses, or expects or is expected to use, the product.  A product
 | 
			
		||||
is a consumer product regardless of whether the product has substantial
 | 
			
		||||
commercial, industrial or non-consumer uses, unless such uses represent
 | 
			
		||||
the only significant mode of use of the product.
 | 
			
		||||
 | 
			
		||||
  "Installation Information" for a User Product means any methods,
 | 
			
		||||
procedures, authorization keys, or other information required to install
 | 
			
		||||
and execute modified versions of a covered work in that User Product from
 | 
			
		||||
a modified version of its Corresponding Source.  The information must
 | 
			
		||||
suffice to ensure that the continued functioning of the modified object
 | 
			
		||||
code is in no case prevented or interfered with solely because
 | 
			
		||||
modification has been made.
 | 
			
		||||
 | 
			
		||||
  If you convey an object code work under this section in, or with, or
 | 
			
		||||
specifically for use in, a User Product, and the conveying occurs as
 | 
			
		||||
part of a transaction in which the right of possession and use of the
 | 
			
		||||
User Product is transferred to the recipient in perpetuity or for a
 | 
			
		||||
fixed term (regardless of how the transaction is characterized), the
 | 
			
		||||
Corresponding Source conveyed under this section must be accompanied
 | 
			
		||||
by the Installation Information.  But this requirement does not apply
 | 
			
		||||
if neither you nor any third party retains the ability to install
 | 
			
		||||
modified object code on the User Product (for example, the work has
 | 
			
		||||
been installed in ROM).
 | 
			
		||||
 | 
			
		||||
  The requirement to provide Installation Information does not include a
 | 
			
		||||
requirement to continue to provide support service, warranty, or updates
 | 
			
		||||
for a work that has been modified or installed by the recipient, or for
 | 
			
		||||
the User Product in which it has been modified or installed.  Access to a
 | 
			
		||||
network may be denied when the modification itself materially and
 | 
			
		||||
adversely affects the operation of the network or violates the rules and
 | 
			
		||||
protocols for communication across the network.
 | 
			
		||||
 | 
			
		||||
  Corresponding Source conveyed, and Installation Information provided,
 | 
			
		||||
in accord with this section must be in a format that is publicly
 | 
			
		||||
documented (and with an implementation available to the public in
 | 
			
		||||
source code form), and must require no special password or key for
 | 
			
		||||
unpacking, reading or copying.
 | 
			
		||||
 | 
			
		||||
  7. Additional Terms.
 | 
			
		||||
 | 
			
		||||
  "Additional permissions" are terms that supplement the terms of this
 | 
			
		||||
License by making exceptions from one or more of its conditions.
 | 
			
		||||
Additional permissions that are applicable to the entire Program shall
 | 
			
		||||
be treated as though they were included in this License, to the extent
 | 
			
		||||
that they are valid under applicable law.  If additional permissions
 | 
			
		||||
apply only to part of the Program, that part may be used separately
 | 
			
		||||
under those permissions, but the entire Program remains governed by
 | 
			
		||||
this License without regard to the additional permissions.
 | 
			
		||||
 | 
			
		||||
  When you convey a copy of a covered work, you may at your option
 | 
			
		||||
remove any additional permissions from that copy, or from any part of
 | 
			
		||||
it.  (Additional permissions may be written to require their own
 | 
			
		||||
removal in certain cases when you modify the work.)  You may place
 | 
			
		||||
additional permissions on material, added by you to a covered work,
 | 
			
		||||
for which you have or can give appropriate copyright permission.
 | 
			
		||||
 | 
			
		||||
  Notwithstanding any other provision of this License, for material you
 | 
			
		||||
add to a covered work, you may (if authorized by the copyright holders of
 | 
			
		||||
that material) supplement the terms of this License with terms:
 | 
			
		||||
 | 
			
		||||
    a) Disclaiming warranty or limiting liability differently from the
 | 
			
		||||
    terms of sections 15 and 16 of this License; or
 | 
			
		||||
 | 
			
		||||
    b) Requiring preservation of specified reasonable legal notices or
 | 
			
		||||
    author attributions in that material or in the Appropriate Legal
 | 
			
		||||
    Notices displayed by works containing it; or
 | 
			
		||||
 | 
			
		||||
    c) Prohibiting misrepresentation of the origin of that material, or
 | 
			
		||||
    requiring that modified versions of such material be marked in
 | 
			
		||||
    reasonable ways as different from the original version; or
 | 
			
		||||
 | 
			
		||||
    d) Limiting the use for publicity purposes of names of licensors or
 | 
			
		||||
    authors of the material; or
 | 
			
		||||
 | 
			
		||||
    e) Declining to grant rights under trademark law for use of some
 | 
			
		||||
    trade names, trademarks, or service marks; or
 | 
			
		||||
 | 
			
		||||
    f) Requiring indemnification of licensors and authors of that
 | 
			
		||||
    material by anyone who conveys the material (or modified versions of
 | 
			
		||||
    it) with contractual assumptions of liability to the recipient, for
 | 
			
		||||
    any liability that these contractual assumptions directly impose on
 | 
			
		||||
    those licensors and authors.
 | 
			
		||||
 | 
			
		||||
  All other non-permissive additional terms are considered "further
 | 
			
		||||
restrictions" within the meaning of section 10.  If the Program as you
 | 
			
		||||
received it, or any part of it, contains a notice stating that it is
 | 
			
		||||
governed by this License along with a term that is a further
 | 
			
		||||
restriction, you may remove that term.  If a license document contains
 | 
			
		||||
a further restriction but permits relicensing or conveying under this
 | 
			
		||||
License, you may add to a covered work material governed by the terms
 | 
			
		||||
of that license document, provided that the further restriction does
 | 
			
		||||
not survive such relicensing or conveying.
 | 
			
		||||
 | 
			
		||||
  If you add terms to a covered work in accord with this section, you
 | 
			
		||||
must place, in the relevant source files, a statement of the
 | 
			
		||||
additional terms that apply to those files, or a notice indicating
 | 
			
		||||
where to find the applicable terms.
 | 
			
		||||
 | 
			
		||||
  Additional terms, permissive or non-permissive, may be stated in the
 | 
			
		||||
form of a separately written license, or stated as exceptions;
 | 
			
		||||
the above requirements apply either way.
 | 
			
		||||
 | 
			
		||||
  8. Termination.
 | 
			
		||||
 | 
			
		||||
  You may not propagate or modify a covered work except as expressly
 | 
			
		||||
provided under this License.  Any attempt otherwise to propagate or
 | 
			
		||||
modify it is void, and will automatically terminate your rights under
 | 
			
		||||
this License (including any patent licenses granted under the third
 | 
			
		||||
paragraph of section 11).
 | 
			
		||||
 | 
			
		||||
  However, if you cease all violation of this License, then your
 | 
			
		||||
license from a particular copyright holder is reinstated (a)
 | 
			
		||||
provisionally, unless and until the copyright holder explicitly and
 | 
			
		||||
finally terminates your license, and (b) permanently, if the copyright
 | 
			
		||||
holder fails to notify you of the violation by some reasonable means
 | 
			
		||||
prior to 60 days after the cessation.
 | 
			
		||||
 | 
			
		||||
  Moreover, your license from a particular copyright holder is
 | 
			
		||||
reinstated permanently if the copyright holder notifies you of the
 | 
			
		||||
violation by some reasonable means, this is the first time you have
 | 
			
		||||
received notice of violation of this License (for any work) from that
 | 
			
		||||
copyright holder, and you cure the violation prior to 30 days after
 | 
			
		||||
your receipt of the notice.
 | 
			
		||||
 | 
			
		||||
  Termination of your rights under this section does not terminate the
 | 
			
		||||
licenses of parties who have received copies or rights from you under
 | 
			
		||||
this License.  If your rights have been terminated and not permanently
 | 
			
		||||
reinstated, you do not qualify to receive new licenses for the same
 | 
			
		||||
material under section 10.
 | 
			
		||||
 | 
			
		||||
  9. Acceptance Not Required for Having Copies.
 | 
			
		||||
 | 
			
		||||
  You are not required to accept this License in order to receive or
 | 
			
		||||
run a copy of the Program.  Ancillary propagation of a covered work
 | 
			
		||||
occurring solely as a consequence of using peer-to-peer transmission
 | 
			
		||||
to receive a copy likewise does not require acceptance.  However,
 | 
			
		||||
nothing other than this License grants you permission to propagate or
 | 
			
		||||
modify any covered work.  These actions infringe copyright if you do
 | 
			
		||||
not accept this License.  Therefore, by modifying or propagating a
 | 
			
		||||
covered work, you indicate your acceptance of this License to do so.
 | 
			
		||||
 | 
			
		||||
  10. Automatic Licensing of Downstream Recipients.
 | 
			
		||||
 | 
			
		||||
  Each time you convey a covered work, the recipient automatically
 | 
			
		||||
receives a license from the original licensors, to run, modify and
 | 
			
		||||
propagate that work, subject to this License.  You are not responsible
 | 
			
		||||
for enforcing compliance by third parties with this License.
 | 
			
		||||
 | 
			
		||||
  An "entity transaction" is a transaction transferring control of an
 | 
			
		||||
organization, or substantially all assets of one, or subdividing an
 | 
			
		||||
organization, or merging organizations.  If propagation of a covered
 | 
			
		||||
work results from an entity transaction, each party to that
 | 
			
		||||
transaction who receives a copy of the work also receives whatever
 | 
			
		||||
licenses to the work the party's predecessor in interest had or could
 | 
			
		||||
give under the previous paragraph, plus a right to possession of the
 | 
			
		||||
Corresponding Source of the work from the predecessor in interest, if
 | 
			
		||||
the predecessor has it or can get it with reasonable efforts.
 | 
			
		||||
 | 
			
		||||
  You may not impose any further restrictions on the exercise of the
 | 
			
		||||
rights granted or affirmed under this License.  For example, you may
 | 
			
		||||
not impose a license fee, royalty, or other charge for exercise of
 | 
			
		||||
rights granted under this License, and you may not initiate litigation
 | 
			
		||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
 | 
			
		||||
any patent claim is infringed by making, using, selling, offering for
 | 
			
		||||
sale, or importing the Program or any portion of it.
 | 
			
		||||
 | 
			
		||||
  11. Patents.
 | 
			
		||||
 | 
			
		||||
  A "contributor" is a copyright holder who authorizes use under this
 | 
			
		||||
License of the Program or a work on which the Program is based.  The
 | 
			
		||||
work thus licensed is called the contributor's "contributor version".
 | 
			
		||||
 | 
			
		||||
  A contributor's "essential patent claims" are all patent claims
 | 
			
		||||
owned or controlled by the contributor, whether already acquired or
 | 
			
		||||
hereafter acquired, that would be infringed by some manner, permitted
 | 
			
		||||
by this License, of making, using, or selling its contributor version,
 | 
			
		||||
but do not include claims that would be infringed only as a
 | 
			
		||||
consequence of further modification of the contributor version.  For
 | 
			
		||||
purposes of this definition, "control" includes the right to grant
 | 
			
		||||
patent sublicenses in a manner consistent with the requirements of
 | 
			
		||||
this License.
 | 
			
		||||
 | 
			
		||||
  Each contributor grants you a non-exclusive, worldwide, royalty-free
 | 
			
		||||
patent license under the contributor's essential patent claims, to
 | 
			
		||||
make, use, sell, offer for sale, import and otherwise run, modify and
 | 
			
		||||
propagate the contents of its contributor version.
 | 
			
		||||
 | 
			
		||||
  In the following three paragraphs, a "patent license" is any express
 | 
			
		||||
agreement or commitment, however denominated, not to enforce a patent
 | 
			
		||||
(such as an express permission to practice a patent or covenant not to
 | 
			
		||||
sue for patent infringement).  To "grant" such a patent license to a
 | 
			
		||||
party means to make such an agreement or commitment not to enforce a
 | 
			
		||||
patent against the party.
 | 
			
		||||
 | 
			
		||||
  If you convey a covered work, knowingly relying on a patent license,
 | 
			
		||||
and the Corresponding Source of the work is not available for anyone
 | 
			
		||||
to copy, free of charge and under the terms of this License, through a
 | 
			
		||||
publicly available network server or other readily accessible means,
 | 
			
		||||
then you must either (1) cause the Corresponding Source to be so
 | 
			
		||||
available, or (2) arrange to deprive yourself of the benefit of the
 | 
			
		||||
patent license for this particular work, or (3) arrange, in a manner
 | 
			
		||||
consistent with the requirements of this License, to extend the patent
 | 
			
		||||
license to downstream recipients.  "Knowingly relying" means you have
 | 
			
		||||
actual knowledge that, but for the patent license, your conveying the
 | 
			
		||||
covered work in a country, or your recipient's use of the covered work
 | 
			
		||||
in a country, would infringe one or more identifiable patents in that
 | 
			
		||||
country that you have reason to believe are valid.
 | 
			
		||||
 | 
			
		||||
  If, pursuant to or in connection with a single transaction or
 | 
			
		||||
arrangement, you convey, or propagate by procuring conveyance of, a
 | 
			
		||||
covered work, and grant a patent license to some of the parties
 | 
			
		||||
receiving the covered work authorizing them to use, propagate, modify
 | 
			
		||||
or convey a specific copy of the covered work, then the patent license
 | 
			
		||||
you grant is automatically extended to all recipients of the covered
 | 
			
		||||
work and works based on it.
 | 
			
		||||
 | 
			
		||||
  A patent license is "discriminatory" if it does not include within
 | 
			
		||||
the scope of its coverage, prohibits the exercise of, or is
 | 
			
		||||
conditioned on the non-exercise of one or more of the rights that are
 | 
			
		||||
specifically granted under this License.  You may not convey a covered
 | 
			
		||||
work if you are a party to an arrangement with a third party that is
 | 
			
		||||
in the business of distributing software, under which you make payment
 | 
			
		||||
to the third party based on the extent of your activity of conveying
 | 
			
		||||
the work, and under which the third party grants, to any of the
 | 
			
		||||
parties who would receive the covered work from you, a discriminatory
 | 
			
		||||
patent license (a) in connection with copies of the covered work
 | 
			
		||||
conveyed by you (or copies made from those copies), or (b) primarily
 | 
			
		||||
for and in connection with specific products or compilations that
 | 
			
		||||
contain the covered work, unless you entered into that arrangement,
 | 
			
		||||
or that patent license was granted, prior to 28 March 2007.
 | 
			
		||||
 | 
			
		||||
  Nothing in this License shall be construed as excluding or limiting
 | 
			
		||||
any implied license or other defenses to infringement that may
 | 
			
		||||
otherwise be available to you under applicable patent law.
 | 
			
		||||
 | 
			
		||||
  12. No Surrender of Others' Freedom.
 | 
			
		||||
 | 
			
		||||
  If conditions are imposed on you (whether by court order, agreement or
 | 
			
		||||
otherwise) that contradict the conditions of this License, they do not
 | 
			
		||||
excuse you from the conditions of this License.  If you cannot convey a
 | 
			
		||||
covered work so as to satisfy simultaneously your obligations under this
 | 
			
		||||
License and any other pertinent obligations, then as a consequence you may
 | 
			
		||||
not convey it at all.  For example, if you agree to terms that obligate you
 | 
			
		||||
to collect a royalty for further conveying from those to whom you convey
 | 
			
		||||
the Program, the only way you could satisfy both those terms and this
 | 
			
		||||
License would be to refrain entirely from conveying the Program.
 | 
			
		||||
 | 
			
		||||
  13. Remote Network Interaction; Use with the GNU General Public License.
 | 
			
		||||
 | 
			
		||||
  Notwithstanding any other provision of this License, if you modify the
 | 
			
		||||
Program, your modified version must prominently offer all users
 | 
			
		||||
interacting with it remotely through a computer network (if your version
 | 
			
		||||
supports such interaction) an opportunity to receive the Corresponding
 | 
			
		||||
Source of your version by providing access to the Corresponding Source
 | 
			
		||||
from a network server at no charge, through some standard or customary
 | 
			
		||||
means of facilitating copying of software.  This Corresponding Source
 | 
			
		||||
shall include the Corresponding Source for any work covered by version 3
 | 
			
		||||
of the GNU General Public License that is incorporated pursuant to the
 | 
			
		||||
following paragraph.
 | 
			
		||||
 | 
			
		||||
  Notwithstanding any other provision of this License, you have
 | 
			
		||||
permission to link or combine any covered work with a work licensed
 | 
			
		||||
under version 3 of the GNU General Public License into a single
 | 
			
		||||
combined work, and to convey the resulting work.  The terms of this
 | 
			
		||||
License will continue to apply to the part which is the covered work,
 | 
			
		||||
but the work with which it is combined will remain governed by version
 | 
			
		||||
3 of the GNU General Public License.
 | 
			
		||||
 | 
			
		||||
  14. Revised Versions of this License.
 | 
			
		||||
 | 
			
		||||
  The Free Software Foundation may publish revised and/or new versions of
 | 
			
		||||
the GNU Affero General Public License from time to time.  Such new versions
 | 
			
		||||
will be similar in spirit to the present version, but may differ in detail to
 | 
			
		||||
address new problems or concerns.
 | 
			
		||||
 | 
			
		||||
  Each version is given a distinguishing version number.  If the
 | 
			
		||||
Program specifies that a certain numbered version of the GNU Affero General
 | 
			
		||||
Public License "or any later version" applies to it, you have the
 | 
			
		||||
option of following the terms and conditions either of that numbered
 | 
			
		||||
version or of any later version published by the Free Software
 | 
			
		||||
Foundation.  If the Program does not specify a version number of the
 | 
			
		||||
GNU Affero General Public License, you may choose any version ever published
 | 
			
		||||
by the Free Software Foundation.
 | 
			
		||||
 | 
			
		||||
  If the Program specifies that a proxy can decide which future
 | 
			
		||||
versions of the GNU Affero General Public License can be used, that proxy's
 | 
			
		||||
public statement of acceptance of a version permanently authorizes you
 | 
			
		||||
to choose that version for the Program.
 | 
			
		||||
 | 
			
		||||
  Later license versions may give you additional or different
 | 
			
		||||
permissions.  However, no additional obligations are imposed on any
 | 
			
		||||
author or copyright holder as a result of your choosing to follow a
 | 
			
		||||
later version.
 | 
			
		||||
 | 
			
		||||
  15. Disclaimer of Warranty.
 | 
			
		||||
 | 
			
		||||
  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
 | 
			
		||||
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
 | 
			
		||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
 | 
			
		||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
 | 
			
		||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 | 
			
		||||
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
 | 
			
		||||
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
 | 
			
		||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
 | 
			
		||||
 | 
			
		||||
  16. Limitation of Liability.
 | 
			
		||||
 | 
			
		||||
  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 | 
			
		||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
 | 
			
		||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
 | 
			
		||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
 | 
			
		||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
 | 
			
		||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
 | 
			
		||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
 | 
			
		||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
 | 
			
		||||
SUCH DAMAGES.
 | 
			
		||||
 | 
			
		||||
  17. Interpretation of Sections 15 and 16.
 | 
			
		||||
 | 
			
		||||
  If the disclaimer of warranty and limitation of liability provided
 | 
			
		||||
above cannot be given local legal effect according to their terms,
 | 
			
		||||
reviewing courts shall apply local law that most closely approximates
 | 
			
		||||
an absolute waiver of all civil liability in connection with the
 | 
			
		||||
Program, unless a warranty or assumption of liability accompanies a
 | 
			
		||||
copy of the Program in return for a fee.
 | 
			
		||||
 | 
			
		||||
                     END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
            How to Apply These Terms to Your New Programs
 | 
			
		||||
 | 
			
		||||
  If you develop a new program, and you want it to be of the greatest
 | 
			
		||||
possible use to the public, the best way to achieve this is to make it
 | 
			
		||||
free software which everyone can redistribute and change under these terms.
 | 
			
		||||
 | 
			
		||||
  To do so, attach the following notices to the program.  It is safest
 | 
			
		||||
to attach them to the start of each source file to most effectively
 | 
			
		||||
state the exclusion of warranty; and each file should have at least
 | 
			
		||||
the "copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
    <one line to give the program's name and a brief idea of what it does.>
 | 
			
		||||
    Copyright (C) <year>  <name of author>
 | 
			
		||||
 | 
			
		||||
    This program is free software: you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
    the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
    (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
    This program is distributed in the hope that it will be useful,
 | 
			
		||||
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
    GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
    You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
Also add information on how to contact you by electronic and paper mail.
 | 
			
		||||
 | 
			
		||||
  If your software can interact with users remotely through a computer
 | 
			
		||||
network, you should also make sure that it provides a way for users to
 | 
			
		||||
get its source.  For example, if your program is a web application, its
 | 
			
		||||
interface could display a "Source" link that leads users to an archive
 | 
			
		||||
of the code.  There are many ways you could offer source, and different
 | 
			
		||||
solutions will be better for different programs; see section 13 for the
 | 
			
		||||
specific requirements.
 | 
			
		||||
 | 
			
		||||
  You should also get your employer (if you work as a programmer) or school,
 | 
			
		||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
 | 
			
		||||
For more information on this, and how to apply and follow the GNU AGPL, see
 | 
			
		||||
<http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
  This version of the GNU Lesser General Public License incorporates
 | 
			
		||||
the terms and conditions of version 3 of the GNU General Public
 | 
			
		||||
License, supplemented by the additional permissions listed below.
 | 
			
		||||
 | 
			
		||||
  0. Additional Definitions.
 | 
			
		||||
 | 
			
		||||
  As used herein, "this License" refers to version 3 of the GNU Lesser
 | 
			
		||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
 | 
			
		||||
General Public License.
 | 
			
		||||
 | 
			
		||||
  "The Library" refers to a covered work governed by this License,
 | 
			
		||||
other than an Application or a Combined Work as defined below.
 | 
			
		||||
 | 
			
		||||
  An "Application" is any work that makes use of an interface provided
 | 
			
		||||
by the Library, but which is not otherwise based on the Library.
 | 
			
		||||
Defining a subclass of a class defined by the Library is deemed a mode
 | 
			
		||||
of using an interface provided by the Library.
 | 
			
		||||
 | 
			
		||||
  A "Combined Work" is a work produced by combining or linking an
 | 
			
		||||
Application with the Library.  The particular version of the Library
 | 
			
		||||
with which the Combined Work was made is also called the "Linked
 | 
			
		||||
Version".
 | 
			
		||||
 | 
			
		||||
  The "Minimal Corresponding Source" for a Combined Work means the
 | 
			
		||||
Corresponding Source for the Combined Work, excluding any source code
 | 
			
		||||
for portions of the Combined Work that, considered in isolation, are
 | 
			
		||||
based on the Application, and not on the Linked Version.
 | 
			
		||||
 | 
			
		||||
  The "Corresponding Application Code" for a Combined Work means the
 | 
			
		||||
object code and/or source code for the Application, including any data
 | 
			
		||||
and utility programs needed for reproducing the Combined Work from the
 | 
			
		||||
Application, but excluding the System Libraries of the Combined Work.
 | 
			
		||||
 | 
			
		||||
  1. Exception to Section 3 of the GNU GPL.
 | 
			
		||||
 | 
			
		||||
  You may convey a covered work under sections 3 and 4 of this License
 | 
			
		||||
without being bound by section 3 of the GNU GPL.
 | 
			
		||||
 | 
			
		||||
  2. Conveying Modified Versions.
 | 
			
		||||
 | 
			
		||||
  If you modify a copy of the Library, and, in your modifications, a
 | 
			
		||||
facility refers to a function or data to be supplied by an Application
 | 
			
		||||
that uses the facility (other than as an argument passed when the
 | 
			
		||||
facility is invoked), then you may convey a copy of the modified
 | 
			
		||||
version:
 | 
			
		||||
 | 
			
		||||
   a) under this License, provided that you make a good faith effort to
 | 
			
		||||
   ensure that, in the event an Application does not supply the
 | 
			
		||||
   function or data, the facility still operates, and performs
 | 
			
		||||
   whatever part of its purpose remains meaningful, or
 | 
			
		||||
 | 
			
		||||
   b) under the GNU GPL, with none of the additional permissions of
 | 
			
		||||
   this License applicable to that copy.
 | 
			
		||||
 | 
			
		||||
  3. Object Code Incorporating Material from Library Header Files.
 | 
			
		||||
 | 
			
		||||
  The object code form of an Application may incorporate material from
 | 
			
		||||
a header file that is part of the Library.  You may convey such object
 | 
			
		||||
code under terms of your choice, provided that, if the incorporated
 | 
			
		||||
material is not limited to numerical parameters, data structure
 | 
			
		||||
layouts and accessors, or small macros, inline functions and templates
 | 
			
		||||
(ten or fewer lines in length), you do both of the following:
 | 
			
		||||
 | 
			
		||||
   a) Give prominent notice with each copy of the object code that the
 | 
			
		||||
   Library is used in it and that the Library and its use are
 | 
			
		||||
   covered by this License.
 | 
			
		||||
 | 
			
		||||
   b) Accompany the object code with a copy of the GNU GPL and this license
 | 
			
		||||
   document.
 | 
			
		||||
 | 
			
		||||
  4. Combined Works.
 | 
			
		||||
 | 
			
		||||
  You may convey a Combined Work under terms of your choice that,
 | 
			
		||||
taken together, effectively do not restrict modification of the
 | 
			
		||||
portions of the Library contained in the Combined Work and reverse
 | 
			
		||||
engineering for debugging such modifications, if you also do each of
 | 
			
		||||
the following:
 | 
			
		||||
 | 
			
		||||
   a) Give prominent notice with each copy of the Combined Work that
 | 
			
		||||
   the Library is used in it and that the Library and its use are
 | 
			
		||||
   covered by this License.
 | 
			
		||||
 | 
			
		||||
   b) Accompany the Combined Work with a copy of the GNU GPL and this license
 | 
			
		||||
   document.
 | 
			
		||||
 | 
			
		||||
   c) For a Combined Work that displays copyright notices during
 | 
			
		||||
   execution, include the copyright notice for the Library among
 | 
			
		||||
   these notices, as well as a reference directing the user to the
 | 
			
		||||
   copies of the GNU GPL and this license document.
 | 
			
		||||
 | 
			
		||||
   d) Do one of the following:
 | 
			
		||||
 | 
			
		||||
       0) Convey the Minimal Corresponding Source under the terms of this
 | 
			
		||||
       License, and the Corresponding Application Code in a form
 | 
			
		||||
       suitable for, and under terms that permit, the user to
 | 
			
		||||
       recombine or relink the Application with a modified version of
 | 
			
		||||
       the Linked Version to produce a modified Combined Work, in the
 | 
			
		||||
       manner specified by section 6 of the GNU GPL for conveying
 | 
			
		||||
       Corresponding Source.
 | 
			
		||||
 | 
			
		||||
       1) Use a suitable shared library mechanism for linking with the
 | 
			
		||||
       Library.  A suitable mechanism is one that (a) uses at run time
 | 
			
		||||
       a copy of the Library already present on the user's computer
 | 
			
		||||
       system, and (b) will operate properly with a modified version
 | 
			
		||||
       of the Library that is interface-compatible with the Linked
 | 
			
		||||
       Version.
 | 
			
		||||
 | 
			
		||||
   e) Provide Installation Information, but only if you would otherwise
 | 
			
		||||
   be required to provide such information under section 6 of the
 | 
			
		||||
   GNU GPL, and only to the extent that such information is
 | 
			
		||||
   necessary to install and execute a modified version of the
 | 
			
		||||
   Combined Work produced by recombining or relinking the
 | 
			
		||||
   Application with a modified version of the Linked Version. (If
 | 
			
		||||
   you use option 4d0, the Installation Information must accompany
 | 
			
		||||
   the Minimal Corresponding Source and Corresponding Application
 | 
			
		||||
   Code. If you use option 4d1, you must provide the Installation
 | 
			
		||||
   Information in the manner specified by section 6 of the GNU GPL
 | 
			
		||||
   for conveying Corresponding Source.)
 | 
			
		||||
 | 
			
		||||
  5. Combined Libraries.
 | 
			
		||||
 | 
			
		||||
  You may place library facilities that are a work based on the
 | 
			
		||||
Library side by side in a single library together with other library
 | 
			
		||||
facilities that are not Applications and are not covered by this
 | 
			
		||||
License, and convey such a combined library under terms of your
 | 
			
		||||
choice, if you do both of the following:
 | 
			
		||||
 | 
			
		||||
   a) Accompany the combined library with a copy of the same work based
 | 
			
		||||
   on the Library, uncombined with any other library facilities,
 | 
			
		||||
   conveyed under the terms of this License.
 | 
			
		||||
 | 
			
		||||
   b) Give prominent notice with the combined library that part of it
 | 
			
		||||
   is a work based on the Library, and explaining where to find the
 | 
			
		||||
   accompanying uncombined form of the same work.
 | 
			
		||||
 | 
			
		||||
  6. Revised Versions of the GNU Lesser General Public License.
 | 
			
		||||
 | 
			
		||||
  The Free Software Foundation may publish revised and/or new versions
 | 
			
		||||
of the GNU Lesser General Public License from time to time. Such new
 | 
			
		||||
versions will be similar in spirit to the present version, but may
 | 
			
		||||
differ in detail to address new problems or concerns.
 | 
			
		||||
 | 
			
		||||
  Each version is given a distinguishing version number. If the
 | 
			
		||||
Library as you received it specifies that a certain numbered version
 | 
			
		||||
of the GNU Lesser General Public License "or any later version"
 | 
			
		||||
applies to it, you have the option of following the terms and
 | 
			
		||||
conditions either of that published version or of any later version
 | 
			
		||||
published by the Free Software Foundation. If the Library as you
 | 
			
		||||
received it does not specify a version number of the GNU Lesser
 | 
			
		||||
General Public License, you may choose any version of the GNU Lesser
 | 
			
		||||
General Public License ever published by the Free Software Foundation.
 | 
			
		||||
 | 
			
		||||
  If the Library as you received it specifies that a proxy can decide
 | 
			
		||||
whether future versions of the GNU Lesser General Public License shall
 | 
			
		||||
apply, that proxy's public statement of acceptance of any version is
 | 
			
		||||
permanent authorization for you to choose that version for the
 | 
			
		||||
Library.
 | 
			
		||||
| 
						 | 
				
			
			@ -207,12 +207,13 @@ add_library(libslic3r STATIC
 | 
			
		|||
    SimplifyMeshImpl.hpp
 | 
			
		||||
    SimplifyMesh.cpp
 | 
			
		||||
    MarchingSquares.hpp
 | 
			
		||||
    Optimizer.hpp
 | 
			
		||||
    ${OpenVDBUtils_SOURCES}
 | 
			
		||||
    SLA/Common.hpp
 | 
			
		||||
    SLA/Common.cpp
 | 
			
		||||
    SLA/Pad.hpp
 | 
			
		||||
    SLA/Pad.cpp
 | 
			
		||||
    SLA/SupportTreeBuilder.hpp
 | 
			
		||||
    SLA/SupportTreeMesher.hpp
 | 
			
		||||
    SLA/SupportTreeMesher.cpp
 | 
			
		||||
    SLA/SupportTreeBuildsteps.hpp
 | 
			
		||||
    SLA/SupportTreeBuildsteps.cpp
 | 
			
		||||
    SLA/SupportTreeBuilder.cpp
 | 
			
		||||
| 
						 | 
				
			
			@ -224,6 +225,7 @@ add_library(libslic3r STATIC
 | 
			
		|||
    SLA/Rotfinder.cpp
 | 
			
		||||
    SLA/BoostAdapter.hpp
 | 
			
		||||
    SLA/SpatIndex.hpp
 | 
			
		||||
    SLA/SpatIndex.cpp
 | 
			
		||||
    SLA/RasterBase.hpp
 | 
			
		||||
    SLA/RasterBase.cpp
 | 
			
		||||
    SLA/AGGRaster.hpp
 | 
			
		||||
| 
						 | 
				
			
			@ -239,8 +241,10 @@ add_library(libslic3r STATIC
 | 
			
		|||
    SLA/SupportPointGenerator.cpp
 | 
			
		||||
    SLA/Contour3D.hpp
 | 
			
		||||
    SLA/Contour3D.cpp
 | 
			
		||||
    SLA/EigenMesh3D.hpp
 | 
			
		||||
    SLA/IndexedMesh.hpp
 | 
			
		||||
    SLA/IndexedMesh.cpp
 | 
			
		||||
    SLA/Clustering.hpp
 | 
			
		||||
    SLA/Clustering.cpp
 | 
			
		||||
    SLA/ReprojectPointsOnMesh.hpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1878,10 +1878,11 @@ namespace Slic3r {
 | 
			
		|||
            volume->calculate_convex_hull();
 | 
			
		||||
 | 
			
		||||
            // recreate custom supports from previously loaded attribute
 | 
			
		||||
            assert(geometry.custom_supports.size() == triangles_count);
 | 
			
		||||
            for (unsigned i=0; i<triangles_count; ++i) {
 | 
			
		||||
                if (! geometry.custom_supports[i].empty())
 | 
			
		||||
                    volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[i]);
 | 
			
		||||
                size_t index = src_start_id/3 + i;
 | 
			
		||||
                assert(index < geometry.custom_supports.size());
 | 
			
		||||
                if (! geometry.custom_supports[index].empty())
 | 
			
		||||
                    volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // apply the remaining volume's metadata
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -686,6 +686,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
 | 
			
		|||
    std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; });
 | 
			
		||||
 | 
			
		||||
    std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print;
 | 
			
		||||
 | 
			
		||||
    // Merge numerically very close Z values.
 | 
			
		||||
    for (size_t i = 0; i < ordering.size();) {
 | 
			
		||||
        // Find the last layer with roughly the same print_z.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@
 | 
			
		|||
#define OPENVDBUTILS_HPP
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/TriangleMesh.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Contour3D.hpp>
 | 
			
		||||
#include <openvdb/openvdb.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										380
									
								
								src/libslic3r/Optimizer.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								src/libslic3r/Optimizer.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,380 @@
 | 
			
		|||
#ifndef NLOPTOPTIMIZER_HPP
 | 
			
		||||
#define NLOPTOPTIMIZER_HPP
 | 
			
		||||
 | 
			
		||||
#ifdef _MSC_VER
 | 
			
		||||
#pragma warning(push)
 | 
			
		||||
#pragma warning(disable: 4244)
 | 
			
		||||
#pragma warning(disable: 4267)
 | 
			
		||||
#endif
 | 
			
		||||
#include <nlopt.h>
 | 
			
		||||
#ifdef _MSC_VER
 | 
			
		||||
#pragma warning(pop)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <tuple>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <cassert>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r { namespace opt {
 | 
			
		||||
 | 
			
		||||
// A type to hold the complete result of the optimization.
 | 
			
		||||
template<size_t N> struct Result {
 | 
			
		||||
    int resultcode;
 | 
			
		||||
    std::array<double, N> optimum;
 | 
			
		||||
    double score;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// An interval of possible input values for optimization
 | 
			
		||||
class Bound {
 | 
			
		||||
    double m_min, m_max;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    Bound(double min = std::numeric_limits<double>::min(),
 | 
			
		||||
          double max = std::numeric_limits<double>::max())
 | 
			
		||||
        : m_min(min), m_max(max)
 | 
			
		||||
    {}
 | 
			
		||||
 | 
			
		||||
    double min() const noexcept { return m_min; }
 | 
			
		||||
    double max() const noexcept { return m_max; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Helper types for optimization function input and bounds
 | 
			
		||||
template<size_t N> using Input = std::array<double, N>;
 | 
			
		||||
template<size_t N> using Bounds = std::array<Bound, N>;
 | 
			
		||||
 | 
			
		||||
// A type for specifying the stop criteria. Setter methods can be concatenated
 | 
			
		||||
class StopCriteria {
 | 
			
		||||
 | 
			
		||||
    // If the absolute value difference between two scores.
 | 
			
		||||
    double m_abs_score_diff = std::nan("");
 | 
			
		||||
 | 
			
		||||
    // If the relative value difference between two scores.
 | 
			
		||||
    double m_rel_score_diff = std::nan("");
 | 
			
		||||
 | 
			
		||||
    // Stop if this value or better is found.
 | 
			
		||||
    double m_stop_score = std::nan("");
 | 
			
		||||
 | 
			
		||||
    // A predicate that if evaluates to true, the optimization should terminate
 | 
			
		||||
    // and the best result found prior to termination should be returned.
 | 
			
		||||
    std::function<bool()> m_stop_condition = [] { return false; };
 | 
			
		||||
 | 
			
		||||
    // The max allowed number of iterations.
 | 
			
		||||
    unsigned m_max_iterations = 0;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
    StopCriteria & abs_score_diff(double val)
 | 
			
		||||
    {
 | 
			
		||||
        m_abs_score_diff = val; return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    double abs_score_diff() const { return m_abs_score_diff; }
 | 
			
		||||
 | 
			
		||||
    StopCriteria & rel_score_diff(double val)
 | 
			
		||||
    {
 | 
			
		||||
        m_rel_score_diff = val; return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    double rel_score_diff() const { return m_rel_score_diff; }
 | 
			
		||||
 | 
			
		||||
    StopCriteria & stop_score(double val)
 | 
			
		||||
    {
 | 
			
		||||
        m_stop_score = val; return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    double stop_score() const { return m_stop_score; }
 | 
			
		||||
 | 
			
		||||
    StopCriteria & max_iterations(double val)
 | 
			
		||||
    {
 | 
			
		||||
        m_max_iterations = val; return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    double max_iterations() const { return m_max_iterations; }
 | 
			
		||||
 | 
			
		||||
    template<class Fn> StopCriteria & stop_condition(Fn &&cond)
 | 
			
		||||
    {
 | 
			
		||||
        m_stop_condition = cond; return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool stop_condition() { return m_stop_condition(); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Helper class to use optimization methods involving gradient.
 | 
			
		||||
template<size_t N> struct ScoreGradient {
 | 
			
		||||
    double score;
 | 
			
		||||
    std::optional<std::array<double, N>> gradient;
 | 
			
		||||
 | 
			
		||||
    ScoreGradient(double s, const std::array<double, N> &grad)
 | 
			
		||||
        : score{s}, gradient{grad}
 | 
			
		||||
    {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Helper to be used in static_assert.
 | 
			
		||||
template<class T> struct always_false { enum { value = false }; };
 | 
			
		||||
 | 
			
		||||
// Basic interface to optimizer object
 | 
			
		||||
template<class Method, class Enable = void> class Optimizer {
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
    Optimizer(const StopCriteria &)
 | 
			
		||||
    {
 | 
			
		||||
        static_assert (always_false<Method>::value,
 | 
			
		||||
                       "Optimizer unimplemented for given method!");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Optimizer<Method> &to_min() { return *this; }
 | 
			
		||||
    Optimizer<Method> &to_max() { return *this; }
 | 
			
		||||
    Optimizer<Method> &set_criteria(const StopCriteria &) { return *this; }
 | 
			
		||||
    StopCriteria get_criteria() const { return {}; };
 | 
			
		||||
 | 
			
		||||
    template<class Func, size_t N>
 | 
			
		||||
    Result<N> optimize(Func&& func,
 | 
			
		||||
                       const Input<N> &initvals,
 | 
			
		||||
                       const Bounds<N>& bounds) { return {}; }
 | 
			
		||||
 | 
			
		||||
    // optional for randomized methods:
 | 
			
		||||
    void seed(long /*s*/) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace detail {
 | 
			
		||||
 | 
			
		||||
// Helper types for NLopt algorithm selection in template contexts
 | 
			
		||||
template<nlopt_algorithm alg> struct NLoptAlg {};
 | 
			
		||||
 | 
			
		||||
// NLopt can combine multiple algorithms if one is global an other is a local
 | 
			
		||||
// method. This is how template specializations can be informed about this fact.
 | 
			
		||||
template<nlopt_algorithm gl_alg, nlopt_algorithm lc_alg = NLOPT_LN_NELDERMEAD>
 | 
			
		||||
struct NLoptAlgComb {};
 | 
			
		||||
 | 
			
		||||
template<class M> struct IsNLoptAlg {
 | 
			
		||||
    static const constexpr bool value = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<nlopt_algorithm a> struct IsNLoptAlg<NLoptAlg<a>> {
 | 
			
		||||
    static const constexpr bool value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<nlopt_algorithm a1, nlopt_algorithm a2>
 | 
			
		||||
struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
 | 
			
		||||
    static const constexpr bool value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<class M, class T = void>
 | 
			
		||||
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
 | 
			
		||||
 | 
			
		||||
// Helper to convert C style array to std::array. The copy should be optimized
 | 
			
		||||
// away with modern compilers.
 | 
			
		||||
template<size_t N, class T> auto to_arr(const T *a)
 | 
			
		||||
{
 | 
			
		||||
    std::array<T, N> r;
 | 
			
		||||
    std::copy(a, a + N, std::begin(r));
 | 
			
		||||
    return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<size_t N, class T> auto to_arr(const T (&a) [N])
 | 
			
		||||
{
 | 
			
		||||
    return to_arr<N>(static_cast<const T *>(a));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum class OptDir { MIN, MAX }; // Where to optimize
 | 
			
		||||
 | 
			
		||||
struct NLopt { // Helper RAII class for nlopt_opt
 | 
			
		||||
    nlopt_opt ptr = nullptr;
 | 
			
		||||
 | 
			
		||||
    template<class...A> explicit NLopt(A&&...a)
 | 
			
		||||
    {
 | 
			
		||||
        ptr = nlopt_create(std::forward<A>(a)...);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    NLopt(const NLopt&) = delete;
 | 
			
		||||
    NLopt(NLopt&&) = delete;
 | 
			
		||||
    NLopt& operator=(const NLopt&) = delete;
 | 
			
		||||
    NLopt& operator=(NLopt&&) = delete;
 | 
			
		||||
 | 
			
		||||
    ~NLopt() { nlopt_destroy(ptr); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<class Method> class NLoptOpt {};
 | 
			
		||||
 | 
			
		||||
// Optimizers based on NLopt.
 | 
			
		||||
template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> {
 | 
			
		||||
protected:
 | 
			
		||||
    StopCriteria m_stopcr;
 | 
			
		||||
    OptDir m_dir;
 | 
			
		||||
 | 
			
		||||
    template<class Fn> using TOptData =
 | 
			
		||||
        std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>;
 | 
			
		||||
 | 
			
		||||
    template<class Fn, size_t N>
 | 
			
		||||
    static double optfunc(unsigned n, const double *params,
 | 
			
		||||
                          double *gradient,
 | 
			
		||||
                          void *data)
 | 
			
		||||
    {
 | 
			
		||||
        assert(n >= N);
 | 
			
		||||
 | 
			
		||||
        auto tdata = static_cast<TOptData<Fn>*>(data);
 | 
			
		||||
 | 
			
		||||
        if (std::get<1>(*tdata)->m_stopcr.stop_condition())
 | 
			
		||||
            nlopt_force_stop(std::get<2>(*tdata));
 | 
			
		||||
 | 
			
		||||
        auto fnptr  = std::get<0>(*tdata);
 | 
			
		||||
        auto funval = to_arr<N>(params);
 | 
			
		||||
 | 
			
		||||
        double scoreval = 0.;
 | 
			
		||||
        using RetT = decltype((*fnptr)(funval));
 | 
			
		||||
        if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) {
 | 
			
		||||
            ScoreGradient<N> score = (*fnptr)(funval);
 | 
			
		||||
            for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i];
 | 
			
		||||
            scoreval = score.score;
 | 
			
		||||
        } else {
 | 
			
		||||
            scoreval = (*fnptr)(funval);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return scoreval;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template<size_t N>
 | 
			
		||||
    void set_up(NLopt &nl, const Bounds<N>& bounds)
 | 
			
		||||
    {
 | 
			
		||||
        std::array<double, N> lb, ub;
 | 
			
		||||
 | 
			
		||||
        for (size_t i = 0; i < N; ++i) {
 | 
			
		||||
            lb[i] = bounds[i].min();
 | 
			
		||||
            ub[i] = bounds[i].max();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        nlopt_set_lower_bounds(nl.ptr, lb.data());
 | 
			
		||||
        nlopt_set_upper_bounds(nl.ptr, ub.data());
 | 
			
		||||
 | 
			
		||||
        double abs_diff = m_stopcr.abs_score_diff();
 | 
			
		||||
        double rel_diff = m_stopcr.rel_score_diff();
 | 
			
		||||
        double stopval = m_stopcr.stop_score();
 | 
			
		||||
        if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff);
 | 
			
		||||
        if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff);
 | 
			
		||||
        if(!std::isnan(stopval))  nlopt_set_stopval(nl.ptr, stopval);
 | 
			
		||||
 | 
			
		||||
        if(this->m_stopcr.max_iterations() > 0)
 | 
			
		||||
            nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template<class Fn, size_t N>
 | 
			
		||||
    Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals)
 | 
			
		||||
    {
 | 
			
		||||
        Result<N> r;
 | 
			
		||||
 | 
			
		||||
        TOptData<Fn> data = std::make_tuple(&fn, this, nl.ptr);
 | 
			
		||||
 | 
			
		||||
        switch(m_dir) {
 | 
			
		||||
        case OptDir::MIN:
 | 
			
		||||
            nlopt_set_min_objective(nl.ptr, optfunc<Fn, N>, &data); break;
 | 
			
		||||
        case OptDir::MAX:
 | 
			
		||||
            nlopt_set_max_objective(nl.ptr, optfunc<Fn, N>, &data); break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        r.optimum = initvals;
 | 
			
		||||
        r.resultcode = nlopt_optimize(nl.ptr, r.optimum.data(), &r.score);
 | 
			
		||||
 | 
			
		||||
        return r;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
    template<class Func, size_t N>
 | 
			
		||||
    Result<N> optimize(Func&& func,
 | 
			
		||||
                       const Input<N> &initvals,
 | 
			
		||||
                       const Bounds<N>& bounds)
 | 
			
		||||
    {
 | 
			
		||||
        NLopt nl{alg, N};
 | 
			
		||||
        set_up(nl, bounds);
 | 
			
		||||
 | 
			
		||||
        return optimize(nl, std::forward<Func>(func), initvals);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {}
 | 
			
		||||
 | 
			
		||||
    void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
 | 
			
		||||
    const StopCriteria &get_criteria() const noexcept { return m_stopcr; }
 | 
			
		||||
    void set_dir(OptDir dir) noexcept { m_dir = dir; }
 | 
			
		||||
 | 
			
		||||
    void seed(long s) { nlopt_srand(s); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<nlopt_algorithm glob, nlopt_algorithm loc>
 | 
			
		||||
class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>>
 | 
			
		||||
{
 | 
			
		||||
    using Base = NLoptOpt<NLoptAlg<glob>>;
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
    template<class Fn, size_t N>
 | 
			
		||||
    Result<N> optimize(Fn&& f,
 | 
			
		||||
                       const Input<N> &initvals,
 | 
			
		||||
                       const Bounds<N>& bounds)
 | 
			
		||||
    {
 | 
			
		||||
        NLopt nl_glob{glob, N}, nl_loc{loc, N};
 | 
			
		||||
 | 
			
		||||
        Base::set_up(nl_glob, bounds);
 | 
			
		||||
        Base::set_up(nl_loc, bounds);
 | 
			
		||||
        nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
 | 
			
		||||
 | 
			
		||||
        return Base::optimize(nl_glob, std::forward<Fn>(f), initvals);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace detail;
 | 
			
		||||
 | 
			
		||||
// Optimizers based on NLopt.
 | 
			
		||||
template<class M> class Optimizer<M, detail::NLoptOnly<M>> {
 | 
			
		||||
    detail::NLoptOpt<M> m_opt;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
    Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; }
 | 
			
		||||
    Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; }
 | 
			
		||||
 | 
			
		||||
    template<class Func, size_t N>
 | 
			
		||||
    Result<N> optimize(Func&& func,
 | 
			
		||||
                       const Input<N> &initvals,
 | 
			
		||||
                       const Bounds<N>& bounds)
 | 
			
		||||
    {
 | 
			
		||||
        return m_opt.optimize(std::forward<Func>(func), initvals, bounds);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {}
 | 
			
		||||
 | 
			
		||||
    Optimizer &set_criteria(const StopCriteria &cr)
 | 
			
		||||
    {
 | 
			
		||||
        m_opt.set_criteria(cr); return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const StopCriteria &get_criteria() const { return m_opt.get_criteria(); }
 | 
			
		||||
 | 
			
		||||
    void seed(long s) { m_opt.seed(s); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
 | 
			
		||||
template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
 | 
			
		||||
template<size_t N> auto score_gradient(double s, const double (&grad)[N])
 | 
			
		||||
{
 | 
			
		||||
    return ScoreGradient<N>(s, detail::to_arr(grad));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Predefinded NLopt algorithms that are used in the codebase
 | 
			
		||||
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
 | 
			
		||||
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
 | 
			
		||||
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
 | 
			
		||||
 | 
			
		||||
// TODO: define others if needed...
 | 
			
		||||
 | 
			
		||||
// Helper defs for pre-crafted global and local optimizers that work well.
 | 
			
		||||
using DefaultGlobalOptimizer = Optimizer<AlgNLoptGenetic>;
 | 
			
		||||
using DefaultLocalOptimizer  = Optimizer<AlgNLoptSubplex>;
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::opt
 | 
			
		||||
 | 
			
		||||
#endif // NLOPTOPTIMIZER_HPP
 | 
			
		||||
| 
						 | 
				
			
			@ -60,10 +60,13 @@ inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2(
 | 
			
		|||
inline float   cross2(const Vec2f   &v1, const Vec2f   &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
 | 
			
		||||
inline double  cross2(const Vec2d   &v1, const Vec2d   &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
 | 
			
		||||
 | 
			
		||||
inline Vec2i32 to_2d(const Vec2i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); }
 | 
			
		||||
inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
 | 
			
		||||
inline Vec2f   to_2d(const Vec3f   &pt3) { return Vec2f  (pt3(0), pt3(1)); }
 | 
			
		||||
inline Vec2d   to_2d(const Vec3d   &pt3) { return Vec2d  (pt3(0), pt3(1)); }
 | 
			
		||||
template<class T, int N> Eigen::Matrix<T,  2, 1, Eigen::DontAlign>
 | 
			
		||||
to_2d(const Eigen::Matrix<T,  N, 1, Eigen::DontAlign> &ptN) { return {ptN(0), ptN(1)}; }
 | 
			
		||||
 | 
			
		||||
//inline Vec2i32 to_2d(const Vec3i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); }
 | 
			
		||||
//inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
 | 
			
		||||
//inline Vec2f   to_2d(const Vec3f   &pt3) { return Vec2f  (pt3(0), pt3(1)); }
 | 
			
		||||
//inline Vec2d   to_2d(const Vec3d   &pt3) { return Vec2d  (pt3(0), pt3(1)); }
 | 
			
		||||
 | 
			
		||||
inline Vec3d   to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); }
 | 
			
		||||
inline Vec3f   to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -498,6 +498,7 @@ const std::vector<std::string>& Preset::sla_print_options()
 | 
			
		|||
            "support_head_penetration",
 | 
			
		||||
            "support_head_width",
 | 
			
		||||
            "support_pillar_diameter",
 | 
			
		||||
            "support_small_pillar_diameter_percent",
 | 
			
		||||
            "support_max_bridges_on_pillar",
 | 
			
		||||
            "support_pillar_connection_mode",
 | 
			
		||||
            "support_buildplate_only",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2746,7 +2746,7 @@ void PrintConfigDef::init_sla_params()
 | 
			
		|||
    def->set_default_value(new ConfigOptionBool(true));
 | 
			
		||||
 | 
			
		||||
    def = this->add("support_head_front_diameter", coFloat);
 | 
			
		||||
    def->label = L("Support head front diameter");
 | 
			
		||||
    def->label = L("Pinhead front diameter");
 | 
			
		||||
    def->category = L("Supports");
 | 
			
		||||
    def->tooltip = L("Diameter of the pointing side of the head");
 | 
			
		||||
    def->sidetext = L("mm");
 | 
			
		||||
| 
						 | 
				
			
			@ -2755,7 +2755,7 @@ void PrintConfigDef::init_sla_params()
 | 
			
		|||
    def->set_default_value(new ConfigOptionFloat(0.4));
 | 
			
		||||
 | 
			
		||||
    def = this->add("support_head_penetration", coFloat);
 | 
			
		||||
    def->label = L("Support head penetration");
 | 
			
		||||
    def->label = L("Head penetration");
 | 
			
		||||
    def->category = L("Supports");
 | 
			
		||||
    def->tooltip = L("How much the pinhead has to penetrate the model surface");
 | 
			
		||||
    def->sidetext = L("mm");
 | 
			
		||||
| 
						 | 
				
			
			@ -2764,7 +2764,7 @@ void PrintConfigDef::init_sla_params()
 | 
			
		|||
    def->set_default_value(new ConfigOptionFloat(0.2));
 | 
			
		||||
 | 
			
		||||
    def = this->add("support_head_width", coFloat);
 | 
			
		||||
    def->label = L("Support head width");
 | 
			
		||||
    def->label = L("Pinhead width");
 | 
			
		||||
    def->category = L("Supports");
 | 
			
		||||
    def->tooltip = L("Width from the back sphere center to the front sphere center");
 | 
			
		||||
    def->sidetext = L("mm");
 | 
			
		||||
| 
						 | 
				
			
			@ -2774,7 +2774,7 @@ void PrintConfigDef::init_sla_params()
 | 
			
		|||
    def->set_default_value(new ConfigOptionFloat(1.0));
 | 
			
		||||
 | 
			
		||||
    def = this->add("support_pillar_diameter", coFloat);
 | 
			
		||||
    def->label = L("Support pillar diameter");
 | 
			
		||||
    def->label = L("Pillar diameter");
 | 
			
		||||
    def->category = L("Supports");
 | 
			
		||||
    def->tooltip = L("Diameter in mm of the support pillars");
 | 
			
		||||
    def->sidetext = L("mm");
 | 
			
		||||
| 
						 | 
				
			
			@ -2782,6 +2782,17 @@ void PrintConfigDef::init_sla_params()
 | 
			
		|||
    def->max = 15;
 | 
			
		||||
    def->mode = comSimple;
 | 
			
		||||
    def->set_default_value(new ConfigOptionFloat(1.0));
 | 
			
		||||
 | 
			
		||||
    def = this->add("support_small_pillar_diameter_percent", coPercent);
 | 
			
		||||
    def->label = L("Small pillar diameter percent");
 | 
			
		||||
    def->category = L("Supports");
 | 
			
		||||
    def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter "
 | 
			
		||||
                     "which are used in problematic areas where a normal pilla cannot fit.");
 | 
			
		||||
    def->sidetext = L("%");
 | 
			
		||||
    def->min = 1;
 | 
			
		||||
    def->max = 100;
 | 
			
		||||
    def->mode = comExpert;
 | 
			
		||||
    def->set_default_value(new ConfigOptionPercent(50));
 | 
			
		||||
    
 | 
			
		||||
    def = this->add("support_max_bridges_on_pillar", coInt);
 | 
			
		||||
    def->label = L("Max bridges on a pillar");
 | 
			
		||||
| 
						 | 
				
			
			@ -2794,7 +2805,7 @@ void PrintConfigDef::init_sla_params()
 | 
			
		|||
    def->set_default_value(new ConfigOptionInt(3));
 | 
			
		||||
 | 
			
		||||
    def = this->add("support_pillar_connection_mode", coEnum);
 | 
			
		||||
    def->label = L("Support pillar connection mode");
 | 
			
		||||
    def->label = L("Pillar connection mode");
 | 
			
		||||
    def->tooltip = L("Controls the bridge type between two neighboring pillars."
 | 
			
		||||
                     " Can be zig-zag, cross (double zig-zag) or dynamic which"
 | 
			
		||||
                     " will automatically switch between the first two depending"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1031,6 +1031,10 @@ public:
 | 
			
		|||
 | 
			
		||||
    // Radius in mm of the support pillars.
 | 
			
		||||
    ConfigOptionFloat support_pillar_diameter /*= 0.8*/;
 | 
			
		||||
 | 
			
		||||
    // The percentage of smaller pillars compared to the normal pillar diameter
 | 
			
		||||
    // which are used in problematic areas where a normal pilla cannot fit.
 | 
			
		||||
    ConfigOptionPercent support_small_pillar_diameter_percent;
 | 
			
		||||
    
 | 
			
		||||
    // How much bridge (supporting another pinhead) can be placed on a pillar.
 | 
			
		||||
    ConfigOptionInt   support_max_bridges_on_pillar;
 | 
			
		||||
| 
						 | 
				
			
			@ -1155,6 +1159,7 @@ protected:
 | 
			
		|||
        OPT_PTR(support_head_penetration);
 | 
			
		||||
        OPT_PTR(support_head_width);
 | 
			
		||||
        OPT_PTR(support_pillar_diameter);
 | 
			
		||||
        OPT_PTR(support_small_pillar_diameter_percent);
 | 
			
		||||
        OPT_PTR(support_max_bridges_on_pillar);
 | 
			
		||||
        OPT_PTR(support_pillar_connection_mode);
 | 
			
		||||
        OPT_PTR(support_buildplate_only);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,9 @@
 | 
			
		|||
#ifndef SLA_BOOSTADAPTER_HPP
 | 
			
		||||
#define SLA_BOOSTADAPTER_HPP
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/Point.hpp>
 | 
			
		||||
#include <libslic3r/BoundingBox.hpp>
 | 
			
		||||
 | 
			
		||||
#include <boost/geometry.hpp>
 | 
			
		||||
 | 
			
		||||
namespace boost {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										152
									
								
								src/libslic3r/SLA/Clustering.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								src/libslic3r/SLA/Clustering.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,152 @@
 | 
			
		|||
#include "Clustering.hpp"
 | 
			
		||||
#include "boost/geometry/index/rtree.hpp"
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/SLA/SpatIndex.hpp>
 | 
			
		||||
#include <libslic3r/SLA/BoostAdapter.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r { namespace sla {
 | 
			
		||||
 | 
			
		||||
namespace bgi = boost::geometry::index;
 | 
			
		||||
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
 | 
			
		||||
{
 | 
			
		||||
    return e1.second < e2.second;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ClusteredPoints cluster(Index3D &sindex,
 | 
			
		||||
                        unsigned max_points,
 | 
			
		||||
                        std::function<std::vector<PointIndexEl>(
 | 
			
		||||
                            const Index3D &, const PointIndexEl &)> qfn)
 | 
			
		||||
{
 | 
			
		||||
    using Elems = std::vector<PointIndexEl>;
 | 
			
		||||
 | 
			
		||||
    // Recursive function for visiting all the points in a given distance to
 | 
			
		||||
    // each other
 | 
			
		||||
    std::function<void(Elems&, Elems&)> group =
 | 
			
		||||
        [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
 | 
			
		||||
    {
 | 
			
		||||
        for(auto& p : pts) {
 | 
			
		||||
            std::vector<PointIndexEl> tmp = qfn(sindex, p);
 | 
			
		||||
 | 
			
		||||
            std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
 | 
			
		||||
 | 
			
		||||
            Elems newpts;
 | 
			
		||||
            std::set_difference(tmp.begin(), tmp.end(),
 | 
			
		||||
                                cluster.begin(), cluster.end(),
 | 
			
		||||
                                std::back_inserter(newpts), cmp_ptidx_elements);
 | 
			
		||||
 | 
			
		||||
            int c = max_points && newpts.size() + cluster.size() > max_points?
 | 
			
		||||
                        int(max_points - cluster.size()) : int(newpts.size());
 | 
			
		||||
 | 
			
		||||
            cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
 | 
			
		||||
            std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
 | 
			
		||||
 | 
			
		||||
            if(!newpts.empty() && (!max_points || cluster.size() < max_points))
 | 
			
		||||
                group(newpts, cluster);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    std::vector<Elems> clusters;
 | 
			
		||||
    for(auto it = sindex.begin(); it != sindex.end();) {
 | 
			
		||||
        Elems cluster = {};
 | 
			
		||||
        Elems pts = {*it};
 | 
			
		||||
        group(pts, cluster);
 | 
			
		||||
 | 
			
		||||
        for(auto& c : cluster) sindex.remove(c);
 | 
			
		||||
        it = sindex.begin();
 | 
			
		||||
 | 
			
		||||
        clusters.emplace_back(cluster);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ClusteredPoints result;
 | 
			
		||||
    for(auto& cluster : clusters) {
 | 
			
		||||
        result.emplace_back();
 | 
			
		||||
        for(auto c : cluster) result.back().emplace_back(c.second);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
 | 
			
		||||
                                           const PointIndexEl& p,
 | 
			
		||||
                                           double dist,
 | 
			
		||||
                                           unsigned max_points)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
 | 
			
		||||
    sindex.query(
 | 
			
		||||
        bgi::nearest(p.first, max_points),
 | 
			
		||||
        std::back_inserter(tmp)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    for(auto it = tmp.begin(); it < tmp.end(); ++it)
 | 
			
		||||
        if((p.first - it->first).norm() > dist) it = tmp.erase(it);
 | 
			
		||||
 | 
			
		||||
    return tmp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace
 | 
			
		||||
 | 
			
		||||
// Clustering a set of points by the given criteria
 | 
			
		||||
ClusteredPoints cluster(
 | 
			
		||||
    const std::vector<unsigned>& indices,
 | 
			
		||||
    std::function<Vec3d(unsigned)> pointfn,
 | 
			
		||||
    double dist,
 | 
			
		||||
    unsigned max_points)
 | 
			
		||||
{
 | 
			
		||||
    // A spatial index for querying the nearest points
 | 
			
		||||
    Index3D sindex;
 | 
			
		||||
 | 
			
		||||
    // Build the index
 | 
			
		||||
    for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
 | 
			
		||||
 | 
			
		||||
    return cluster(sindex, max_points,
 | 
			
		||||
                   [dist, max_points](const Index3D& sidx, const PointIndexEl& p)
 | 
			
		||||
                   {
 | 
			
		||||
                       return distance_queryfn(sidx, p, dist, max_points);
 | 
			
		||||
                   });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Clustering a set of points by the given criteria
 | 
			
		||||
ClusteredPoints cluster(
 | 
			
		||||
    const std::vector<unsigned>& indices,
 | 
			
		||||
    std::function<Vec3d(unsigned)> pointfn,
 | 
			
		||||
    std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
 | 
			
		||||
    unsigned max_points)
 | 
			
		||||
{
 | 
			
		||||
    // A spatial index for querying the nearest points
 | 
			
		||||
    Index3D sindex;
 | 
			
		||||
 | 
			
		||||
    // Build the index
 | 
			
		||||
    for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
 | 
			
		||||
 | 
			
		||||
    return cluster(sindex, max_points,
 | 
			
		||||
                   [max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
 | 
			
		||||
                   {
 | 
			
		||||
                       std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
 | 
			
		||||
                       sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
 | 
			
		||||
                                      return predicate(p, e);
 | 
			
		||||
                                  }), std::back_inserter(tmp));
 | 
			
		||||
                       return tmp;
 | 
			
		||||
                   });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ClusteredPoints cluster(const Eigen::MatrixXd& pts, double dist, unsigned max_points)
 | 
			
		||||
{
 | 
			
		||||
    // A spatial index for querying the nearest points
 | 
			
		||||
    Index3D sindex;
 | 
			
		||||
 | 
			
		||||
    // Build the index
 | 
			
		||||
    for(Eigen::Index i = 0; i < pts.rows(); i++)
 | 
			
		||||
        sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
 | 
			
		||||
 | 
			
		||||
    return cluster(sindex, max_points,
 | 
			
		||||
                   [dist, max_points](const Index3D& sidx, const PointIndexEl& p)
 | 
			
		||||
                   {
 | 
			
		||||
                       return distance_queryfn(sidx, p, dist, max_points);
 | 
			
		||||
                   });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::sla
 | 
			
		||||
| 
						 | 
				
			
			@ -2,7 +2,8 @@
 | 
			
		|||
#define SLA_CLUSTERING_HPP
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/Point.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SpatIndex.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r { namespace sla {
 | 
			
		||||
| 
						 | 
				
			
			@ -16,7 +17,7 @@ ClusteredPoints cluster(const std::vector<unsigned>& indices,
 | 
			
		|||
                        double dist,
 | 
			
		||||
                        unsigned max_points);
 | 
			
		||||
 | 
			
		||||
ClusteredPoints cluster(const PointSet& points,
 | 
			
		||||
ClusteredPoints cluster(const Eigen::MatrixXd& points,
 | 
			
		||||
                        double dist,
 | 
			
		||||
                        unsigned max_points);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -26,5 +27,56 @@ ClusteredPoints cluster(
 | 
			
		|||
    std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
 | 
			
		||||
    unsigned max_points);
 | 
			
		||||
 | 
			
		||||
}}
 | 
			
		||||
// This function returns the position of the centroid in the input 'clust'
 | 
			
		||||
// vector of point indices.
 | 
			
		||||
template<class DistFn, class PointFn>
 | 
			
		||||
long cluster_centroid(const ClusterEl &clust, PointFn pointfn, DistFn df)
 | 
			
		||||
{
 | 
			
		||||
    switch(clust.size()) {
 | 
			
		||||
    case 0: /* empty cluster */ return -1;
 | 
			
		||||
    case 1: /* only one element */ return 0;
 | 
			
		||||
    case 2: /* if two elements, there is no center */ return 0;
 | 
			
		||||
    default: ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The function works by calculating for each point the average distance
 | 
			
		||||
    // from all the other points in the cluster. We create a selector bitmask of
 | 
			
		||||
    // the same size as the cluster. The bitmask will have two true bits and
 | 
			
		||||
    // false bits for the rest of items and we will loop through all the
 | 
			
		||||
    // permutations of the bitmask (combinations of two points). Get the
 | 
			
		||||
    // distance for the two points and add the distance to the averages.
 | 
			
		||||
    // The point with the smallest average than wins.
 | 
			
		||||
 | 
			
		||||
    // The complexity should be O(n^2) but we will mostly apply this function
 | 
			
		||||
    // for small clusters only (cca 3 elements)
 | 
			
		||||
 | 
			
		||||
    std::vector<bool> sel(clust.size(), false);   // create full zero bitmask
 | 
			
		||||
    std::fill(sel.end() - 2, sel.end(), true);    // insert the two ones
 | 
			
		||||
    std::vector<double> avgs(clust.size(), 0.0);  // store the average distances
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
        std::array<size_t, 2> idx;
 | 
			
		||||
        for(size_t i = 0, j = 0; i < clust.size(); i++)
 | 
			
		||||
            if(sel[i]) idx[j++] = i;
 | 
			
		||||
 | 
			
		||||
        double d = df(pointfn(clust[idx[0]]),
 | 
			
		||||
                      pointfn(clust[idx[1]]));
 | 
			
		||||
 | 
			
		||||
        // add the distance to the sums for both associated points
 | 
			
		||||
        for(auto i : idx) avgs[i] += d;
 | 
			
		||||
 | 
			
		||||
        // now continue with the next permutation of the bitmask with two 1s
 | 
			
		||||
    } while(std::next_permutation(sel.begin(), sel.end()));
 | 
			
		||||
 | 
			
		||||
    // Divide by point size in the cluster to get the average (may be redundant)
 | 
			
		||||
    for(auto& a : avgs) a /= clust.size();
 | 
			
		||||
 | 
			
		||||
    // get the lowest average distance and return the index
 | 
			
		||||
    auto minit = std::min_element(avgs.begin(), avgs.end());
 | 
			
		||||
    return long(minit - avgs.begin());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::sla
 | 
			
		||||
 | 
			
		||||
#endif // CLUSTERING_HPP
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,27 +0,0 @@
 | 
			
		|||
#ifndef SLA_COMMON_HPP
 | 
			
		||||
#define SLA_COMMON_HPP
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <numeric>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <Eigen/Geometry>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
    
 | 
			
		||||
// Typedefs from Point.hpp
 | 
			
		||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
 | 
			
		||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
 | 
			
		||||
typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i;
 | 
			
		||||
typedef Eigen::Matrix<int, 4, 1, Eigen::DontAlign> Vec4i;
 | 
			
		||||
 | 
			
		||||
namespace sla {
 | 
			
		||||
 | 
			
		||||
using PointSet = Eigen::MatrixXd;
 | 
			
		||||
 | 
			
		||||
} // namespace sla
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif // SLASUPPORTTREE_HPP
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
#include <libslic3r/SLA/Contour3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/IndexedMesh.hpp>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/Format/objparser.hpp>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ Contour3D::Contour3D(TriangleMesh &&trmesh)
 | 
			
		|||
    faces3.swap(trmesh.its.indices);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Contour3D::Contour3D(const EigenMesh3D &emesh) {
 | 
			
		||||
Contour3D::Contour3D(const IndexedMesh &emesh) {
 | 
			
		||||
    points.reserve(emesh.vertices().size());
 | 
			
		||||
    faces3.reserve(emesh.indices().size());
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,16 @@
 | 
			
		|||
#ifndef SLA_CONTOUR3D_HPP
 | 
			
		||||
#define SLA_CONTOUR3D_HPP
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/TriangleMesh.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r { namespace sla {
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
class EigenMesh3D;
 | 
			
		||||
// Used for quads (TODO: remove this, and convert quads to triangles in OpenVDBUtils)
 | 
			
		||||
using Vec4i = Eigen::Matrix<int, 4, 1, Eigen::DontAlign>;
 | 
			
		||||
 | 
			
		||||
namespace sla {
 | 
			
		||||
 | 
			
		||||
class IndexedMesh;
 | 
			
		||||
 | 
			
		||||
/// Dumb vertex mesh consisting of triangles (or) quads. Capable of merging with
 | 
			
		||||
/// other meshes of this type and converting to and from other mesh formats.
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +22,7 @@ struct Contour3D {
 | 
			
		|||
    Contour3D() = default;
 | 
			
		||||
    Contour3D(const TriangleMesh &trmesh);
 | 
			
		||||
    Contour3D(TriangleMesh &&trmesh);
 | 
			
		||||
    Contour3D(const EigenMesh3D  &emesh);
 | 
			
		||||
    Contour3D(const IndexedMesh  &emesh);
 | 
			
		||||
    
 | 
			
		||||
    Contour3D& merge(const Contour3D& ctr);
 | 
			
		||||
    Contour3D& merge(const Pointf3s& triangles);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,11 +3,10 @@
 | 
			
		|||
#include <libslic3r/OpenVDBUtils.hpp>
 | 
			
		||||
#include <libslic3r/TriangleMesh.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Hollowing.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Contour3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
 | 
			
		||||
#include <libslic3r/SLA/IndexedMesh.hpp>
 | 
			
		||||
#include <libslic3r/ClipperUtils.hpp>
 | 
			
		||||
#include <libslic3r/SimplifyMesh.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SupportTreeMesher.hpp>
 | 
			
		||||
 | 
			
		||||
#include <boost/log/trivial.hpp>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +159,7 @@ bool DrainHole::get_intersections(const Vec3f& s, const Vec3f& dir,
 | 
			
		|||
    const Eigen::ParametrizedLine<float, 3> ray(s, dir.normalized());
 | 
			
		||||
 | 
			
		||||
    for (size_t i=0; i<2; ++i)
 | 
			
		||||
        out[i] = std::make_pair(sla::EigenMesh3D::hit_result::infty(), Vec3d::Zero());
 | 
			
		||||
        out[i] = std::make_pair(sla::IndexedMesh::hit_result::infty(), Vec3d::Zero());
 | 
			
		||||
 | 
			
		||||
    const float sqr_radius = pow(radius, 2.f);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@
 | 
			
		|||
#define SLA_HOLLOWING_HPP
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Contour3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/JobController.hpp>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,187 +1,18 @@
 | 
			
		|||
#include <cmath>
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Concurrency.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SpatIndex.hpp>
 | 
			
		||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Contour3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Clustering.hpp>
 | 
			
		||||
#include "IndexedMesh.hpp"
 | 
			
		||||
#include "Concurrency.hpp"
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/AABBTreeIndirect.hpp>
 | 
			
		||||
#include <libslic3r/TriangleMesh.hpp>
 | 
			
		||||
 | 
			
		||||
// for concave hull merging decisions
 | 
			
		||||
#include <libslic3r/SLA/BoostAdapter.hpp>
 | 
			
		||||
#include "boost/geometry/index/rtree.hpp"
 | 
			
		||||
 | 
			
		||||
#ifdef _MSC_VER
 | 
			
		||||
#pragma warning(push)
 | 
			
		||||
#pragma warning(disable: 4244)
 | 
			
		||||
#pragma warning(disable: 4267)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <igl/remove_duplicate_vertices.h>
 | 
			
		||||
#include <numeric>
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3R_HOLE_RAYCASTER
 | 
			
		||||
  #include <libslic3r/SLA/Hollowing.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Hollowing.hpp>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace Slic3r { namespace sla {
 | 
			
		||||
 | 
			
		||||
#ifdef _MSC_VER
 | 
			
		||||
#pragma warning(pop)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
namespace sla {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* **************************************************************************
 | 
			
		||||
 * PointIndex implementation
 | 
			
		||||
 * ************************************************************************** */
 | 
			
		||||
 | 
			
		||||
class PointIndex::Impl {
 | 
			
		||||
public:
 | 
			
		||||
    using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
 | 
			
		||||
                                                     boost::geometry::index::rstar<16, 4> /* ? */ >;
 | 
			
		||||
    
 | 
			
		||||
    BoostIndex m_store;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
PointIndex::PointIndex(): m_impl(new Impl()) {}
 | 
			
		||||
PointIndex::~PointIndex() {}
 | 
			
		||||
 | 
			
		||||
PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
 | 
			
		||||
PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
 | 
			
		||||
 | 
			
		||||
PointIndex& PointIndex::operator=(const PointIndex &cpy)
 | 
			
		||||
{
 | 
			
		||||
    m_impl.reset(new Impl(*cpy.m_impl));
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PointIndex& PointIndex::operator=(PointIndex &&cpy)
 | 
			
		||||
{
 | 
			
		||||
    m_impl.swap(cpy.m_impl);
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PointIndex::insert(const PointIndexEl &el)
 | 
			
		||||
{
 | 
			
		||||
    m_impl->m_store.insert(el);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PointIndex::remove(const PointIndexEl& el)
 | 
			
		||||
{
 | 
			
		||||
    return m_impl->m_store.remove(el) == 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<PointIndexEl>
 | 
			
		||||
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
 | 
			
		||||
{
 | 
			
		||||
    namespace bgi = boost::geometry::index;
 | 
			
		||||
    
 | 
			
		||||
    std::vector<PointIndexEl> ret;
 | 
			
		||||
    m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
 | 
			
		||||
{
 | 
			
		||||
    namespace bgi = boost::geometry::index;
 | 
			
		||||
    std::vector<PointIndexEl> ret; ret.reserve(k);
 | 
			
		||||
    m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t PointIndex::size() const
 | 
			
		||||
{
 | 
			
		||||
    return m_impl->m_store.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
 | 
			
		||||
{
 | 
			
		||||
    for(auto& el : m_impl->m_store) fn(el);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
 | 
			
		||||
{
 | 
			
		||||
    for(const auto &el : m_impl->m_store) fn(el);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* **************************************************************************
 | 
			
		||||
 * BoxIndex implementation
 | 
			
		||||
 * ************************************************************************** */
 | 
			
		||||
 | 
			
		||||
class BoxIndex::Impl {
 | 
			
		||||
public:
 | 
			
		||||
    using BoostIndex = boost::geometry::index::
 | 
			
		||||
        rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
 | 
			
		||||
    
 | 
			
		||||
    BoostIndex m_store;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
BoxIndex::BoxIndex(): m_impl(new Impl()) {}
 | 
			
		||||
BoxIndex::~BoxIndex() {}
 | 
			
		||||
 | 
			
		||||
BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
 | 
			
		||||
BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
 | 
			
		||||
 | 
			
		||||
BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
 | 
			
		||||
{
 | 
			
		||||
    m_impl.reset(new Impl(*cpy.m_impl));
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
 | 
			
		||||
{
 | 
			
		||||
    m_impl.swap(cpy.m_impl);
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BoxIndex::insert(const BoxIndexEl &el)
 | 
			
		||||
{
 | 
			
		||||
    m_impl->m_store.insert(el);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BoxIndex::remove(const BoxIndexEl& el)
 | 
			
		||||
{
 | 
			
		||||
    return m_impl->m_store.remove(el) == 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
 | 
			
		||||
                                        BoxIndex::QueryType qt)
 | 
			
		||||
{
 | 
			
		||||
    namespace bgi = boost::geometry::index;
 | 
			
		||||
    
 | 
			
		||||
    std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
 | 
			
		||||
    
 | 
			
		||||
    switch (qt) {
 | 
			
		||||
    case qtIntersects:
 | 
			
		||||
        m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
 | 
			
		||||
        break;
 | 
			
		||||
    case qtWithin:
 | 
			
		||||
        m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t BoxIndex::size() const
 | 
			
		||||
{
 | 
			
		||||
    return m_impl->m_store.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
 | 
			
		||||
{
 | 
			
		||||
    for(auto& el : m_impl->m_store) fn(el);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* ****************************************************************************
 | 
			
		||||
 * EigenMesh3D implementation
 | 
			
		||||
 * ****************************************************************************/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EigenMesh3D::AABBImpl {
 | 
			
		||||
class IndexedMesh::AABBImpl {
 | 
			
		||||
private:
 | 
			
		||||
    AABBTreeIndirect::Tree3f m_tree;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -189,7 +20,7 @@ public:
 | 
			
		|||
    void init(const TriangleMesh& tm)
 | 
			
		||||
    {
 | 
			
		||||
        m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
 | 
			
		||||
                    tm.its.vertices, tm.its.indices);
 | 
			
		||||
            tm.its.vertices, tm.its.indices);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void intersect_ray(const TriangleMesh& tm,
 | 
			
		||||
| 
						 | 
				
			
			@ -215,9 +46,9 @@ public:
 | 
			
		|||
        size_t idx_unsigned = 0;
 | 
			
		||||
        Vec3d closest_vec3d(closest);
 | 
			
		||||
        double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
 | 
			
		||||
                          tm.its.vertices,
 | 
			
		||||
                          tm.its.indices,
 | 
			
		||||
                          m_tree, point, idx_unsigned, closest_vec3d);
 | 
			
		||||
            tm.its.vertices,
 | 
			
		||||
            tm.its.indices,
 | 
			
		||||
            m_tree, point, idx_unsigned, closest_vec3d);
 | 
			
		||||
        i = int(idx_unsigned);
 | 
			
		||||
        closest = closest_vec3d;
 | 
			
		||||
        return dist;
 | 
			
		||||
| 
						 | 
				
			
			@ -226,72 +57,71 @@ public:
 | 
			
		|||
 | 
			
		||||
static const constexpr double MESH_EPS = 1e-6;
 | 
			
		||||
 | 
			
		||||
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh)
 | 
			
		||||
IndexedMesh::IndexedMesh(const TriangleMesh& tmesh)
 | 
			
		||||
    : m_aabb(new AABBImpl()), m_tm(&tmesh)
 | 
			
		||||
{
 | 
			
		||||
    auto&& bb = tmesh.bounding_box();
 | 
			
		||||
    m_ground_level += bb.min(Z);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Build the AABB accelaration tree
 | 
			
		||||
    m_aabb->init(tmesh);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EigenMesh3D::~EigenMesh3D() {}
 | 
			
		||||
IndexedMesh::~IndexedMesh() {}
 | 
			
		||||
 | 
			
		||||
EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
 | 
			
		||||
IndexedMesh::IndexedMesh(const IndexedMesh &other):
 | 
			
		||||
    m_tm(other.m_tm), m_ground_level(other.m_ground_level),
 | 
			
		||||
    m_aabb( new AABBImpl(*other.m_aabb) ) {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
 | 
			
		||||
IndexedMesh &IndexedMesh::operator=(const IndexedMesh &other)
 | 
			
		||||
{
 | 
			
		||||
    m_tm = other.m_tm;
 | 
			
		||||
    m_ground_level = other.m_ground_level;
 | 
			
		||||
    m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default;
 | 
			
		||||
IndexedMesh &IndexedMesh::operator=(IndexedMesh &&other) = default;
 | 
			
		||||
 | 
			
		||||
EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default;
 | 
			
		||||
IndexedMesh::IndexedMesh(IndexedMesh &&other) = default;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const std::vector<Vec3f>& EigenMesh3D::vertices() const
 | 
			
		||||
const std::vector<Vec3f>& IndexedMesh::vertices() const
 | 
			
		||||
{
 | 
			
		||||
    return m_tm->its.vertices;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const std::vector<Vec3i>& EigenMesh3D::indices()  const
 | 
			
		||||
const std::vector<Vec3i>& IndexedMesh::indices()  const
 | 
			
		||||
{
 | 
			
		||||
    return m_tm->its.indices;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const Vec3f& EigenMesh3D::vertices(size_t idx) const
 | 
			
		||||
const Vec3f& IndexedMesh::vertices(size_t idx) const
 | 
			
		||||
{
 | 
			
		||||
    return m_tm->its.vertices[idx];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const Vec3i& EigenMesh3D::indices(size_t idx) const
 | 
			
		||||
const Vec3i& IndexedMesh::indices(size_t idx) const
 | 
			
		||||
{
 | 
			
		||||
    return m_tm->its.indices[idx];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Vec3d EigenMesh3D::normal_by_face_id(int face_id) const {
 | 
			
		||||
Vec3d IndexedMesh::normal_by_face_id(int face_id) const {
 | 
			
		||||
    return m_tm->stl.facet_start[face_id].normal.cast<double>();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EigenMesh3D::hit_result
 | 
			
		||||
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
 | 
			
		||||
IndexedMesh::hit_result
 | 
			
		||||
IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
 | 
			
		||||
{
 | 
			
		||||
    assert(is_approx(dir.norm(), 1.));
 | 
			
		||||
    igl::Hit hit;
 | 
			
		||||
| 
						 | 
				
			
			@ -319,13 +149,13 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
 | 
			
		|||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<EigenMesh3D::hit_result>
 | 
			
		||||
EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
 | 
			
		||||
std::vector<IndexedMesh::hit_result>
 | 
			
		||||
IndexedMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
 | 
			
		||||
{
 | 
			
		||||
    std::vector<EigenMesh3D::hit_result> outs;
 | 
			
		||||
    std::vector<IndexedMesh::hit_result> outs;
 | 
			
		||||
    std::vector<igl::Hit> hits;
 | 
			
		||||
    m_aabb->intersect_ray(*m_tm, s, dir, hits);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // The sort is necessary, the hits are not always sorted.
 | 
			
		||||
    std::sort(hits.begin(), hits.end(),
 | 
			
		||||
              [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
 | 
			
		||||
| 
						 | 
				
			
			@ -334,13 +164,13 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
 | 
			
		|||
    // along an axis of a cube due to floating-point approximations in igl (?)
 | 
			
		||||
    hits.erase(std::unique(hits.begin(), hits.end(),
 | 
			
		||||
                           [](const igl::Hit& a, const igl::Hit& b)
 | 
			
		||||
                              { return a.t == b.t; }),
 | 
			
		||||
                           { return a.t == b.t; }),
 | 
			
		||||
               hits.end());
 | 
			
		||||
 | 
			
		||||
    //  Convert the igl::Hit into hit_result
 | 
			
		||||
    outs.reserve(hits.size());
 | 
			
		||||
    for (const igl::Hit& hit : hits) {
 | 
			
		||||
        outs.emplace_back(EigenMesh3D::hit_result(*this));
 | 
			
		||||
        outs.emplace_back(IndexedMesh::hit_result(*this));
 | 
			
		||||
        outs.back().m_t = double(hit.t);
 | 
			
		||||
        outs.back().m_dir = dir;
 | 
			
		||||
        outs.back().m_source = s;
 | 
			
		||||
| 
						 | 
				
			
			@ -355,8 +185,8 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3R_HOLE_RAYCASTER
 | 
			
		||||
EigenMesh3D::hit_result EigenMesh3D::filter_hits(
 | 
			
		||||
                     const std::vector<EigenMesh3D::hit_result>& object_hits) const
 | 
			
		||||
IndexedMesh::hit_result IndexedMesh::filter_hits(
 | 
			
		||||
    const std::vector<IndexedMesh::hit_result>& object_hits) const
 | 
			
		||||
{
 | 
			
		||||
    assert(! m_holes.empty());
 | 
			
		||||
    hit_result out(*this);
 | 
			
		||||
| 
						 | 
				
			
			@ -377,7 +207,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits(
 | 
			
		|||
    };
 | 
			
		||||
    std::vector<HoleHit> hole_isects;
 | 
			
		||||
    hole_isects.reserve(m_holes.size());
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    auto sf = s.cast<float>();
 | 
			
		||||
    auto dirf = dir.cast<float>();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -452,7 +282,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits(
 | 
			
		|||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
 | 
			
		||||
double IndexedMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
 | 
			
		||||
    double sqdst = 0;
 | 
			
		||||
    Eigen::Matrix<double, 1, 3> pp = p;
 | 
			
		||||
    Eigen::Matrix<double, 1, 3> cc;
 | 
			
		||||
| 
						 | 
				
			
			@ -461,31 +291,19 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
 | 
			
		|||
    return sqdst;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ****************************************************************************
 | 
			
		||||
 * Misc functions
 | 
			
		||||
 * ****************************************************************************/
 | 
			
		||||
 | 
			
		||||
namespace  {
 | 
			
		||||
 | 
			
		||||
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
 | 
			
		||||
                   double eps = 0.05)
 | 
			
		||||
static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
 | 
			
		||||
                          double eps = 0.05)
 | 
			
		||||
{
 | 
			
		||||
    using Line3D = Eigen::ParametrizedLine<double, 3>;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    auto line = Line3D::Through(e1, e2);
 | 
			
		||||
    double d = line.distance(p);
 | 
			
		||||
    return std::abs(d) < eps;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
 | 
			
		||||
    auto p = pp2 - pp1;
 | 
			
		||||
    return std::sqrt(p.transpose() * p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PointSet normals(const PointSet& points,
 | 
			
		||||
                 const EigenMesh3D& mesh,
 | 
			
		||||
                 const IndexedMesh& mesh,
 | 
			
		||||
                 double eps,
 | 
			
		||||
                 std::function<void()> thr, // throw on cancel
 | 
			
		||||
                 const std::vector<unsigned>& pt_indices)
 | 
			
		||||
| 
						 | 
				
			
			@ -531,11 +349,11 @@ PointSet normals(const PointSet& points,
 | 
			
		|||
            // ic will mark a single vertex.
 | 
			
		||||
            int ia = -1, ib = -1, ic = -1;
 | 
			
		||||
 | 
			
		||||
            if (std::abs(distance(p, p1)) < eps) {
 | 
			
		||||
            if (std::abs((p - p1).norm()) < eps) {
 | 
			
		||||
                ic = trindex(0);
 | 
			
		||||
            } else if (std::abs(distance(p, p2)) < eps) {
 | 
			
		||||
            } else if (std::abs((p - p2).norm()) < eps) {
 | 
			
		||||
                ic = trindex(1);
 | 
			
		||||
            } else if (std::abs(distance(p, p3)) < eps) {
 | 
			
		||||
            } else if (std::abs((p - p3).norm()) < eps) {
 | 
			
		||||
                ic = trindex(2);
 | 
			
		||||
            } else if (point_on_edge(p, p1, p2, eps)) {
 | 
			
		||||
                ia = trindex(0);
 | 
			
		||||
| 
						 | 
				
			
			@ -612,148 +430,4 @@ PointSet normals(const PointSet& points,
 | 
			
		|||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace bgi = boost::geometry::index;
 | 
			
		||||
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
 | 
			
		||||
{
 | 
			
		||||
    return e1.second < e2.second;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ClusteredPoints cluster(Index3D &sindex,
 | 
			
		||||
                        unsigned max_points,
 | 
			
		||||
                        std::function<std::vector<PointIndexEl>(
 | 
			
		||||
                            const Index3D &, const PointIndexEl &)> qfn)
 | 
			
		||||
{
 | 
			
		||||
    using Elems = std::vector<PointIndexEl>;
 | 
			
		||||
    
 | 
			
		||||
    // Recursive function for visiting all the points in a given distance to
 | 
			
		||||
    // each other
 | 
			
		||||
    std::function<void(Elems&, Elems&)> group =
 | 
			
		||||
        [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
 | 
			
		||||
    {        
 | 
			
		||||
        for(auto& p : pts) {
 | 
			
		||||
            std::vector<PointIndexEl> tmp = qfn(sindex, p);
 | 
			
		||||
            
 | 
			
		||||
            std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
 | 
			
		||||
            
 | 
			
		||||
            Elems newpts;
 | 
			
		||||
            std::set_difference(tmp.begin(), tmp.end(),
 | 
			
		||||
                                cluster.begin(), cluster.end(),
 | 
			
		||||
                                std::back_inserter(newpts), cmp_ptidx_elements);
 | 
			
		||||
            
 | 
			
		||||
            int c = max_points && newpts.size() + cluster.size() > max_points?
 | 
			
		||||
                        int(max_points - cluster.size()) : int(newpts.size());
 | 
			
		||||
            
 | 
			
		||||
            cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
 | 
			
		||||
            std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
 | 
			
		||||
            
 | 
			
		||||
            if(!newpts.empty() && (!max_points || cluster.size() < max_points))
 | 
			
		||||
                group(newpts, cluster);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    std::vector<Elems> clusters;
 | 
			
		||||
    for(auto it = sindex.begin(); it != sindex.end();) {
 | 
			
		||||
        Elems cluster = {};
 | 
			
		||||
        Elems pts = {*it};
 | 
			
		||||
        group(pts, cluster);
 | 
			
		||||
        
 | 
			
		||||
        for(auto& c : cluster) sindex.remove(c);
 | 
			
		||||
        it = sindex.begin();
 | 
			
		||||
        
 | 
			
		||||
        clusters.emplace_back(cluster);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    ClusteredPoints result;
 | 
			
		||||
    for(auto& cluster : clusters) {
 | 
			
		||||
        result.emplace_back();
 | 
			
		||||
        for(auto c : cluster) result.back().emplace_back(c.second);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
 | 
			
		||||
                                           const PointIndexEl& p,
 | 
			
		||||
                                           double dist,
 | 
			
		||||
                                           unsigned max_points)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
 | 
			
		||||
    sindex.query(
 | 
			
		||||
        bgi::nearest(p.first, max_points),
 | 
			
		||||
        std::back_inserter(tmp)
 | 
			
		||||
        );
 | 
			
		||||
    
 | 
			
		||||
    for(auto it = tmp.begin(); it < tmp.end(); ++it)
 | 
			
		||||
        if(distance(p.first, it->first) > dist) it = tmp.erase(it);
 | 
			
		||||
    
 | 
			
		||||
    return tmp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace
 | 
			
		||||
 | 
			
		||||
// Clustering a set of points by the given criteria
 | 
			
		||||
ClusteredPoints cluster(
 | 
			
		||||
    const std::vector<unsigned>& indices,
 | 
			
		||||
    std::function<Vec3d(unsigned)> pointfn,
 | 
			
		||||
    double dist,
 | 
			
		||||
    unsigned max_points)
 | 
			
		||||
{
 | 
			
		||||
    // A spatial index for querying the nearest points
 | 
			
		||||
    Index3D sindex;
 | 
			
		||||
    
 | 
			
		||||
    // Build the index
 | 
			
		||||
    for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
 | 
			
		||||
    
 | 
			
		||||
    return cluster(sindex, max_points,
 | 
			
		||||
                   [dist, max_points](const Index3D& sidx, const PointIndexEl& p)
 | 
			
		||||
                   {
 | 
			
		||||
                       return distance_queryfn(sidx, p, dist, max_points);
 | 
			
		||||
                   });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Clustering a set of points by the given criteria
 | 
			
		||||
ClusteredPoints cluster(
 | 
			
		||||
    const std::vector<unsigned>& indices,
 | 
			
		||||
    std::function<Vec3d(unsigned)> pointfn,
 | 
			
		||||
    std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
 | 
			
		||||
    unsigned max_points)
 | 
			
		||||
{
 | 
			
		||||
    // A spatial index for querying the nearest points
 | 
			
		||||
    Index3D sindex;
 | 
			
		||||
    
 | 
			
		||||
    // Build the index
 | 
			
		||||
    for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
 | 
			
		||||
    
 | 
			
		||||
    return cluster(sindex, max_points,
 | 
			
		||||
                   [max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
 | 
			
		||||
                   {
 | 
			
		||||
                       std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
 | 
			
		||||
                       sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
 | 
			
		||||
                                      return predicate(p, e);
 | 
			
		||||
                                  }), std::back_inserter(tmp));
 | 
			
		||||
                       return tmp;
 | 
			
		||||
                   });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
 | 
			
		||||
{
 | 
			
		||||
    // A spatial index for querying the nearest points
 | 
			
		||||
    Index3D sindex;
 | 
			
		||||
    
 | 
			
		||||
    // Build the index
 | 
			
		||||
    for(Eigen::Index i = 0; i < pts.rows(); i++)
 | 
			
		||||
        sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
 | 
			
		||||
    
 | 
			
		||||
    return cluster(sindex, max_points,
 | 
			
		||||
                   [dist, max_points](const Index3D& sidx, const PointIndexEl& p)
 | 
			
		||||
                   {
 | 
			
		||||
                       return distance_queryfn(sidx, p, dist, max_points);
 | 
			
		||||
                   });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace sla
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
}} // namespace Slic3r::sla
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +1,10 @@
 | 
			
		|||
#ifndef SLA_EIGENMESH3D_H
 | 
			
		||||
#define SLA_EIGENMESH3D_H
 | 
			
		||||
#ifndef SLA_INDEXEDMESH_H
 | 
			
		||||
#define SLA_INDEXEDMESH_H
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/Point.hpp>
 | 
			
		||||
 | 
			
		||||
// There is an implementation of a hole-aware raycaster that was eventually
 | 
			
		||||
// not used in production version. It is now hidden under following define
 | 
			
		||||
| 
						 | 
				
			
			@ -19,10 +21,12 @@ class TriangleMesh;
 | 
			
		|||
 | 
			
		||||
namespace sla {
 | 
			
		||||
 | 
			
		||||
using PointSet = Eigen::MatrixXd;
 | 
			
		||||
 | 
			
		||||
/// An index-triangle structure for libIGL functions. Also serves as an
 | 
			
		||||
/// alternative (raw) input format for the SLASupportTree.
 | 
			
		||||
//  Implemented in libslic3r/SLA/Common.cpp
 | 
			
		||||
class EigenMesh3D {
 | 
			
		||||
class IndexedMesh {
 | 
			
		||||
    class AABBImpl;
 | 
			
		||||
    
 | 
			
		||||
    const TriangleMesh* m_tm;
 | 
			
		||||
| 
						 | 
				
			
			@ -38,15 +42,15 @@ class EigenMesh3D {
 | 
			
		|||
 | 
			
		||||
public:
 | 
			
		||||
    
 | 
			
		||||
    explicit EigenMesh3D(const TriangleMesh&);
 | 
			
		||||
    explicit IndexedMesh(const TriangleMesh&);
 | 
			
		||||
    
 | 
			
		||||
    EigenMesh3D(const EigenMesh3D& other);
 | 
			
		||||
    EigenMesh3D& operator=(const EigenMesh3D&);
 | 
			
		||||
    IndexedMesh(const IndexedMesh& other);
 | 
			
		||||
    IndexedMesh& operator=(const IndexedMesh&);
 | 
			
		||||
    
 | 
			
		||||
    EigenMesh3D(EigenMesh3D &&other);
 | 
			
		||||
    EigenMesh3D& operator=(EigenMesh3D &&other);
 | 
			
		||||
    IndexedMesh(IndexedMesh &&other);
 | 
			
		||||
    IndexedMesh& operator=(IndexedMesh &&other);
 | 
			
		||||
    
 | 
			
		||||
    ~EigenMesh3D();
 | 
			
		||||
    ~IndexedMesh();
 | 
			
		||||
    
 | 
			
		||||
    inline double ground_level() const { return m_ground_level + m_gnd_offset; }
 | 
			
		||||
    inline void ground_level_offset(double o) { m_gnd_offset = o; }
 | 
			
		||||
| 
						 | 
				
			
			@ -62,15 +66,15 @@ public:
 | 
			
		|||
        // m_t holds a distance from m_source to the intersection.
 | 
			
		||||
        double m_t = infty();
 | 
			
		||||
        int m_face_id = -1;
 | 
			
		||||
        const EigenMesh3D *m_mesh = nullptr;
 | 
			
		||||
        const IndexedMesh *m_mesh = nullptr;
 | 
			
		||||
        Vec3d m_dir;
 | 
			
		||||
        Vec3d m_source;
 | 
			
		||||
        Vec3d m_normal;
 | 
			
		||||
        friend class EigenMesh3D;
 | 
			
		||||
        friend class IndexedMesh;
 | 
			
		||||
        
 | 
			
		||||
        // A valid object of this class can only be obtained from
 | 
			
		||||
        // EigenMesh3D::query_ray_hit method.
 | 
			
		||||
        explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {}
 | 
			
		||||
        // IndexedMesh::query_ray_hit method.
 | 
			
		||||
        explicit inline hit_result(const IndexedMesh& em): m_mesh(&em) {}
 | 
			
		||||
    public:
 | 
			
		||||
        // This denotes no hit on the mesh.
 | 
			
		||||
        static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +87,7 @@ public:
 | 
			
		|||
        inline Vec3d position() const { return m_source + m_dir * m_t; }
 | 
			
		||||
        inline int face() const { return m_face_id; }
 | 
			
		||||
        inline bool is_valid() const { return m_mesh != nullptr; }
 | 
			
		||||
        inline bool is_hit() const { return !std::isinf(m_t); }
 | 
			
		||||
        inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); }
 | 
			
		||||
 | 
			
		||||
        inline const Vec3d& normal() const {
 | 
			
		||||
            assert(is_valid());
 | 
			
		||||
| 
						 | 
				
			
			@ -107,7 +111,7 @@ public:
 | 
			
		|||
    // This function is currently not used anywhere, it was written when the
 | 
			
		||||
    // holes were subtracted on slices, that is, before we started using CGAL
 | 
			
		||||
    // to actually cut the holes into the mesh.
 | 
			
		||||
    hit_result filter_hits(const std::vector<EigenMesh3D::hit_result>& obj_hits) const;
 | 
			
		||||
    hit_result filter_hits(const std::vector<IndexedMesh::hit_result>& obj_hits) const;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    // Casting a ray on the mesh, returns the distance where the hit occures.
 | 
			
		||||
| 
						 | 
				
			
			@ -125,16 +129,18 @@ public:
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    Vec3d normal_by_face_id(int face_id) const;
 | 
			
		||||
 | 
			
		||||
    const TriangleMesh * get_triangle_mesh() const { return m_tm; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Calculate the normals for the selected points (from 'points' set) on the
 | 
			
		||||
// mesh. This will call squared distance for each point.
 | 
			
		||||
PointSet normals(const PointSet& points,
 | 
			
		||||
    const EigenMesh3D& convert_mesh,
 | 
			
		||||
    const IndexedMesh& convert_mesh,
 | 
			
		||||
    double eps = 0.05,  // min distance from edges
 | 
			
		||||
    std::function<void()> throw_on_cancel = [](){},
 | 
			
		||||
    const std::vector<unsigned>& selected_points = {});
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::sla
 | 
			
		||||
 | 
			
		||||
#endif // EIGENMESH3D_H
 | 
			
		||||
#endif // INDEXEDMESH_H
 | 
			
		||||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
#define SLA_JOBCONTROLLER_HPP
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r { namespace sla {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
#include <libslic3r/SLA/Pad.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SpatIndex.hpp>
 | 
			
		||||
#include <libslic3r/SLA/BoostAdapter.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Contour3D.hpp>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
#include "libslic3r/Point.hpp"
 | 
			
		||||
#include "SupportPoint.hpp"
 | 
			
		||||
#include "Hollowing.hpp"
 | 
			
		||||
#include "EigenMesh3D.hpp"
 | 
			
		||||
#include "IndexedMesh.hpp"
 | 
			
		||||
#include "libslic3r/Model.hpp"
 | 
			
		||||
 | 
			
		||||
#include <tbb/parallel_for.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ template<class Pt> Vec3d pos(const Pt &p) { return p.pos.template cast<double>()
 | 
			
		|||
template<class Pt> void pos(Pt &p, const Vec3d &pp) { p.pos = pp.cast<float>(); }
 | 
			
		||||
 | 
			
		||||
template<class PointType>
 | 
			
		||||
void reproject_support_points(const EigenMesh3D &mesh, std::vector<PointType> &pts)
 | 
			
		||||
void reproject_support_points(const IndexedMesh &mesh, std::vector<PointType> &pts)
 | 
			
		||||
{
 | 
			
		||||
    tbb::parallel_for(size_t(0), pts.size(), [&mesh, &pts](size_t idx) {
 | 
			
		||||
        int junk;
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +40,7 @@ inline void reproject_points_and_holes(ModelObject *object)
 | 
			
		|||
 | 
			
		||||
    TriangleMesh rmsh = object->raw_mesh();
 | 
			
		||||
    rmsh.require_shared_vertices();
 | 
			
		||||
    EigenMesh3D emesh{rmsh};
 | 
			
		||||
    IndexedMesh emesh{rmsh};
 | 
			
		||||
 | 
			
		||||
    if (has_sppoints)
 | 
			
		||||
        reproject_support_points(emesh, object->sla_support_points);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@
 | 
			
		|||
#include <exception>
 | 
			
		||||
 | 
			
		||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Rotfinder.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SupportTree.hpp>
 | 
			
		||||
#include "Model.hpp"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										161
									
								
								src/libslic3r/SLA/SpatIndex.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								src/libslic3r/SLA/SpatIndex.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,161 @@
 | 
			
		|||
#include "SpatIndex.hpp"
 | 
			
		||||
 | 
			
		||||
// for concave hull merging decisions
 | 
			
		||||
#include <libslic3r/SLA/BoostAdapter.hpp>
 | 
			
		||||
 | 
			
		||||
#ifdef _MSC_VER
 | 
			
		||||
#pragma warning(push)
 | 
			
		||||
#pragma warning(disable: 4244)
 | 
			
		||||
#pragma warning(disable: 4267)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "boost/geometry/index/rtree.hpp"
 | 
			
		||||
 | 
			
		||||
#ifdef _MSC_VER
 | 
			
		||||
#pragma warning(pop)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace Slic3r { namespace sla {
 | 
			
		||||
 | 
			
		||||
/* **************************************************************************
 | 
			
		||||
 * PointIndex implementation
 | 
			
		||||
 * ************************************************************************** */
 | 
			
		||||
 | 
			
		||||
class PointIndex::Impl {
 | 
			
		||||
public:
 | 
			
		||||
    using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
 | 
			
		||||
                                                     boost::geometry::index::rstar<16, 4> /* ? */ >;
 | 
			
		||||
 | 
			
		||||
    BoostIndex m_store;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
PointIndex::PointIndex(): m_impl(new Impl()) {}
 | 
			
		||||
PointIndex::~PointIndex() {}
 | 
			
		||||
 | 
			
		||||
PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
 | 
			
		||||
PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
 | 
			
		||||
 | 
			
		||||
PointIndex& PointIndex::operator=(const PointIndex &cpy)
 | 
			
		||||
{
 | 
			
		||||
    m_impl.reset(new Impl(*cpy.m_impl));
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PointIndex& PointIndex::operator=(PointIndex &&cpy)
 | 
			
		||||
{
 | 
			
		||||
    m_impl.swap(cpy.m_impl);
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PointIndex::insert(const PointIndexEl &el)
 | 
			
		||||
{
 | 
			
		||||
    m_impl->m_store.insert(el);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PointIndex::remove(const PointIndexEl& el)
 | 
			
		||||
{
 | 
			
		||||
    return m_impl->m_store.remove(el) == 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<PointIndexEl>
 | 
			
		||||
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
 | 
			
		||||
{
 | 
			
		||||
    namespace bgi = boost::geometry::index;
 | 
			
		||||
 | 
			
		||||
    std::vector<PointIndexEl> ret;
 | 
			
		||||
    m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
 | 
			
		||||
{
 | 
			
		||||
    namespace bgi = boost::geometry::index;
 | 
			
		||||
    std::vector<PointIndexEl> ret; ret.reserve(k);
 | 
			
		||||
    m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t PointIndex::size() const
 | 
			
		||||
{
 | 
			
		||||
    return m_impl->m_store.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
 | 
			
		||||
{
 | 
			
		||||
    for(auto& el : m_impl->m_store) fn(el);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
 | 
			
		||||
{
 | 
			
		||||
    for(const auto &el : m_impl->m_store) fn(el);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* **************************************************************************
 | 
			
		||||
 * BoxIndex implementation
 | 
			
		||||
 * ************************************************************************** */
 | 
			
		||||
 | 
			
		||||
class BoxIndex::Impl {
 | 
			
		||||
public:
 | 
			
		||||
    using BoostIndex = boost::geometry::index::
 | 
			
		||||
        rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
 | 
			
		||||
 | 
			
		||||
    BoostIndex m_store;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
BoxIndex::BoxIndex(): m_impl(new Impl()) {}
 | 
			
		||||
BoxIndex::~BoxIndex() {}
 | 
			
		||||
 | 
			
		||||
BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
 | 
			
		||||
BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
 | 
			
		||||
 | 
			
		||||
BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
 | 
			
		||||
{
 | 
			
		||||
    m_impl.reset(new Impl(*cpy.m_impl));
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
 | 
			
		||||
{
 | 
			
		||||
    m_impl.swap(cpy.m_impl);
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BoxIndex::insert(const BoxIndexEl &el)
 | 
			
		||||
{
 | 
			
		||||
    m_impl->m_store.insert(el);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BoxIndex::remove(const BoxIndexEl& el)
 | 
			
		||||
{
 | 
			
		||||
    return m_impl->m_store.remove(el) == 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
 | 
			
		||||
                                        BoxIndex::QueryType qt)
 | 
			
		||||
{
 | 
			
		||||
    namespace bgi = boost::geometry::index;
 | 
			
		||||
 | 
			
		||||
    std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
 | 
			
		||||
 | 
			
		||||
    switch (qt) {
 | 
			
		||||
    case qtIntersects:
 | 
			
		||||
        m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
 | 
			
		||||
        break;
 | 
			
		||||
    case qtWithin:
 | 
			
		||||
        m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t BoxIndex::size() const
 | 
			
		||||
{
 | 
			
		||||
    return m_impl->m_store.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
 | 
			
		||||
{
 | 
			
		||||
    for(auto& el : m_impl->m_store) fn(el);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::sla
 | 
			
		||||
| 
						 | 
				
			
			@ -73,7 +73,7 @@ public:
 | 
			
		|||
    BoxIndex& operator=(BoxIndex&&);
 | 
			
		||||
    
 | 
			
		||||
    void insert(const BoxIndexEl&);
 | 
			
		||||
    inline void insert(const BoundingBox& bb, unsigned idx)
 | 
			
		||||
    void insert(const BoundingBox& bb, unsigned idx)
 | 
			
		||||
    {
 | 
			
		||||
        insert(std::make_pair(bb, unsigned(idx)));
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@
 | 
			
		|||
#define SLA_SUPPORTPOINT_HPP
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/ExPolygon.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r { namespace sla {
 | 
			
		||||
| 
						 | 
				
			
			@ -29,13 +28,13 @@ struct SupportPoint
 | 
			
		|||
                 float pos_y,
 | 
			
		||||
                 float pos_z,
 | 
			
		||||
                 float head_radius,
 | 
			
		||||
                 bool  new_island)
 | 
			
		||||
                 bool  new_island = false)
 | 
			
		||||
        : pos(pos_x, pos_y, pos_z)
 | 
			
		||||
        , head_front_radius(head_radius)
 | 
			
		||||
        , is_new_island(new_island)
 | 
			
		||||
    {}
 | 
			
		||||
    
 | 
			
		||||
    SupportPoint(Vec3f position, float head_radius, bool new_island)
 | 
			
		||||
    SupportPoint(Vec3f position, float head_radius, bool new_island = false)
 | 
			
		||||
        : pos(position)
 | 
			
		||||
        , head_front_radius(head_radius)
 | 
			
		||||
        , is_new_island(new_island)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,7 +50,7 @@ float SupportPointGenerator::distance_limit(float angle) const
 | 
			
		|||
}*/
 | 
			
		||||
 | 
			
		||||
SupportPointGenerator::SupportPointGenerator(
 | 
			
		||||
        const sla::EigenMesh3D &emesh,
 | 
			
		||||
        const sla::IndexedMesh &emesh,
 | 
			
		||||
        const std::vector<ExPolygons> &slices,
 | 
			
		||||
        const std::vector<float> &     heights,
 | 
			
		||||
        const Config &                 config,
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +64,7 @@ SupportPointGenerator::SupportPointGenerator(
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
SupportPointGenerator::SupportPointGenerator(
 | 
			
		||||
        const EigenMesh3D &emesh,
 | 
			
		||||
        const IndexedMesh &emesh,
 | 
			
		||||
        const SupportPointGenerator::Config &config,
 | 
			
		||||
        std::function<void ()> throw_on_cancel, 
 | 
			
		||||
        std::function<void (int)> statusfn)
 | 
			
		||||
| 
						 | 
				
			
			@ -95,8 +95,8 @@ void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& po
 | 
			
		|||
                    m_throw_on_cancel();
 | 
			
		||||
                Vec3f& p = points[point_id].pos;
 | 
			
		||||
                // Project the point upward and downward and choose the closer intersection with the mesh.
 | 
			
		||||
                sla::EigenMesh3D::hit_result hit_up   = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
 | 
			
		||||
                sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
 | 
			
		||||
                sla::IndexedMesh::hit_result hit_up   = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
 | 
			
		||||
                sla::IndexedMesh::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
 | 
			
		||||
 | 
			
		||||
                bool up   = hit_up.is_hit();
 | 
			
		||||
                bool down = hit_down.is_hit();
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +104,7 @@ void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& po
 | 
			
		|||
                if (!up && !down)
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
 | 
			
		||||
                sla::IndexedMesh::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
 | 
			
		||||
                p = p + (hit.distance() * hit.direction()).cast<float>();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -523,15 +523,12 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance)
 | 
			
		||||
void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl)
 | 
			
		||||
{
 | 
			
		||||
    // get iterator to the reorganized vector end
 | 
			
		||||
    auto endit =
 | 
			
		||||
        std::remove_if(pts.begin(), pts.end(),
 | 
			
		||||
                       [tolerance, gnd_lvl](const sla::SupportPoint &sp) {
 | 
			
		||||
        double diff = std::abs(gnd_lvl -
 | 
			
		||||
                               double(sp.pos(Z)));
 | 
			
		||||
        return diff <= tolerance;
 | 
			
		||||
    auto endit = std::remove_if(pts.begin(), pts.end(), [lvl]
 | 
			
		||||
                                (const sla::SupportPoint &sp) {
 | 
			
		||||
        return sp.pos.z() <= lvl;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // erase all elements after the new end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,9 +3,8 @@
 | 
			
		|||
 | 
			
		||||
#include <random>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SupportPoint.hpp>
 | 
			
		||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/IndexedMesh.hpp>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/BoundingBox.hpp>
 | 
			
		||||
#include <libslic3r/ClipperUtils.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -28,10 +27,10 @@ public:
 | 
			
		|||
        inline float tear_pressure() const { return 1.f; }  // pressure that the display exerts    (the force unit per mm2)
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    SupportPointGenerator(const EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
 | 
			
		||||
    SupportPointGenerator(const IndexedMesh& emesh, const std::vector<ExPolygons>& slices,
 | 
			
		||||
                    const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
 | 
			
		||||
    
 | 
			
		||||
    SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
 | 
			
		||||
    SupportPointGenerator(const IndexedMesh& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
 | 
			
		||||
    
 | 
			
		||||
    const std::vector<SupportPoint>& output() const { return m_output; }
 | 
			
		||||
    std::vector<SupportPoint>& output() { return m_output; }
 | 
			
		||||
| 
						 | 
				
			
			@ -207,14 +206,14 @@ private:
 | 
			
		|||
    static void output_structures(const std::vector<Structure> &structures);
 | 
			
		||||
#endif // SLA_SUPPORTPOINTGEN_DEBUG
 | 
			
		||||
    
 | 
			
		||||
    const EigenMesh3D& m_emesh;
 | 
			
		||||
    const IndexedMesh& m_emesh;
 | 
			
		||||
    std::function<void(void)> m_throw_on_cancel;
 | 
			
		||||
    std::function<void(int)>  m_statusfn;
 | 
			
		||||
    
 | 
			
		||||
    std::mt19937 m_rng;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
 | 
			
		||||
void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl);
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::sla
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,9 +5,9 @@
 | 
			
		|||
 | 
			
		||||
#include <numeric>
 | 
			
		||||
#include <libslic3r/SLA/SupportTree.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SpatIndex.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/MTUtils.hpp>
 | 
			
		||||
#include <libslic3r/ClipperUtils.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -28,20 +28,6 @@
 | 
			
		|||
namespace Slic3r {
 | 
			
		||||
namespace sla {
 | 
			
		||||
 | 
			
		||||
// Compile time configuration value definitions:
 | 
			
		||||
 | 
			
		||||
// The max Z angle for a normal at which it will get completely ignored.
 | 
			
		||||
const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0;
 | 
			
		||||
 | 
			
		||||
// The shortest distance of any support structure from the model surface
 | 
			
		||||
const double SupportConfig::safety_distance_mm = 0.5;
 | 
			
		||||
 | 
			
		||||
const double SupportConfig::max_solo_pillar_height_mm = 15.0;
 | 
			
		||||
const double SupportConfig::max_dual_pillar_height_mm = 35.0;
 | 
			
		||||
const double   SupportConfig::optimizer_rel_score_diff = 1e-6;
 | 
			
		||||
const unsigned SupportConfig::optimizer_max_iterations = 1000;
 | 
			
		||||
const unsigned SupportConfig::pillar_cascade_neighbors = 3;
 | 
			
		||||
 | 
			
		||||
void SupportTree::retrieve_full_mesh(TriangleMesh &outmesh) const {
 | 
			
		||||
    outmesh.merge(retrieve_mesh(MeshType::Support));
 | 
			
		||||
    outmesh.merge(retrieve_mesh(MeshType::Pad));
 | 
			
		||||
| 
						 | 
				
			
			@ -103,9 +89,11 @@ SupportTree::UPtr SupportTree::create(const SupportableMesh &sm,
 | 
			
		|||
    builder->m_ctl = ctl;
 | 
			
		||||
    
 | 
			
		||||
    if (sm.cfg.enabled) {
 | 
			
		||||
        builder->build(sm);
 | 
			
		||||
        // Execute takes care about the ground_level
 | 
			
		||||
        SupportTreeBuildsteps::execute(*builder, sm);
 | 
			
		||||
        builder->merge_and_cleanup();   // clean metadata, leave only the meshes.
 | 
			
		||||
    } else {
 | 
			
		||||
        // If a pad gets added later, it will be in the right Z level
 | 
			
		||||
        builder->ground_level = sm.emesh.ground_level();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,9 +5,8 @@
 | 
			
		|||
#include <memory>
 | 
			
		||||
#include <Eigen/Geometry>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Pad.hpp>
 | 
			
		||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/IndexedMesh.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SupportPoint.hpp>
 | 
			
		||||
#include <libslic3r/SLA/JobController.hpp>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +31,7 @@ enum class PillarConnectionMode
 | 
			
		|||
    dynamic
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct SupportConfig
 | 
			
		||||
struct SupportTreeConfig
 | 
			
		||||
{
 | 
			
		||||
    bool   enabled = true;
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +44,8 @@ struct SupportConfig
 | 
			
		|||
    // Radius of the back side of the 3d arrow.
 | 
			
		||||
    double head_back_radius_mm = 0.5;
 | 
			
		||||
 | 
			
		||||
    double head_fallback_radius_mm = 0.25;
 | 
			
		||||
 | 
			
		||||
    // Width in mm from the back sphere center to the front sphere center.
 | 
			
		||||
    double head_width_mm = 1.0;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -95,36 +96,43 @@ struct SupportConfig
 | 
			
		|||
    // /////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
    // The max Z angle for a normal at which it will get completely ignored.
 | 
			
		||||
    static const double normal_cutoff_angle;
 | 
			
		||||
    static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0;
 | 
			
		||||
 | 
			
		||||
    // The shortest distance of any support structure from the model surface
 | 
			
		||||
    static const double safety_distance_mm;
 | 
			
		||||
    static const double constexpr safety_distance_mm = 0.5;
 | 
			
		||||
 | 
			
		||||
    static const double max_solo_pillar_height_mm;
 | 
			
		||||
    static const double max_dual_pillar_height_mm;
 | 
			
		||||
    static const double   optimizer_rel_score_diff;
 | 
			
		||||
    static const unsigned optimizer_max_iterations;
 | 
			
		||||
    static const unsigned pillar_cascade_neighbors;
 | 
			
		||||
    static const double constexpr max_solo_pillar_height_mm = 15.0;
 | 
			
		||||
    static const double constexpr max_dual_pillar_height_mm = 35.0;
 | 
			
		||||
    static const double constexpr optimizer_rel_score_diff = 1e-6;
 | 
			
		||||
    static const unsigned constexpr optimizer_max_iterations = 1000;
 | 
			
		||||
    static const unsigned constexpr pillar_cascade_neighbors = 3;
 | 
			
		||||
    
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: Part of future refactor
 | 
			
		||||
//class SupportConfig {
 | 
			
		||||
//    std::optional<SupportTreeConfig> tree_cfg {std::in_place_t{}}; // fill up
 | 
			
		||||
//    std::optional<PadConfig>         pad_cfg;
 | 
			
		||||
//};
 | 
			
		||||
 | 
			
		||||
enum class MeshType { Support, Pad };
 | 
			
		||||
 | 
			
		||||
struct SupportableMesh
 | 
			
		||||
{
 | 
			
		||||
    EigenMesh3D   emesh;
 | 
			
		||||
    IndexedMesh  emesh;
 | 
			
		||||
    SupportPoints pts;
 | 
			
		||||
    SupportConfig cfg;
 | 
			
		||||
    SupportTreeConfig cfg;
 | 
			
		||||
    PadConfig     pad_cfg;
 | 
			
		||||
 | 
			
		||||
    explicit SupportableMesh(const TriangleMesh & trmsh,
 | 
			
		||||
                             const SupportPoints &sp,
 | 
			
		||||
                             const SupportConfig &c)
 | 
			
		||||
                             const SupportTreeConfig &c)
 | 
			
		||||
        : emesh{trmsh}, pts{sp}, cfg{c}
 | 
			
		||||
    {}
 | 
			
		||||
    
 | 
			
		||||
    explicit SupportableMesh(const EigenMesh3D   &em,
 | 
			
		||||
    explicit SupportableMesh(const IndexedMesh   &em,
 | 
			
		||||
                             const SupportPoints &sp,
 | 
			
		||||
                             const SupportConfig &c)
 | 
			
		||||
                             const SupportTreeConfig &c)
 | 
			
		||||
        : emesh{em}, pts{sp}, cfg{c}
 | 
			
		||||
    {}
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,336 +1,26 @@
 | 
			
		|||
#define NOMINMAX
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SupportTreeMesher.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Contour3D.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
namespace sla {
 | 
			
		||||
 | 
			
		||||
Contour3D sphere(double rho, Portion portion, double fa) {
 | 
			
		||||
    
 | 
			
		||||
    Contour3D ret;
 | 
			
		||||
    
 | 
			
		||||
    // prohibit close to zero radius
 | 
			
		||||
    if(rho <= 1e-6 && rho >= -1e-6) return ret;
 | 
			
		||||
    
 | 
			
		||||
    auto& vertices = ret.points;
 | 
			
		||||
    auto& facets = ret.faces3;
 | 
			
		||||
    
 | 
			
		||||
    // Algorithm:
 | 
			
		||||
    // Add points one-by-one to the sphere grid and form facets using relative
 | 
			
		||||
    // coordinates. Sphere is composed effectively of a mesh of stacked circles.
 | 
			
		||||
    
 | 
			
		||||
    // adjust via rounding to get an even multiple for any provided angle.
 | 
			
		||||
    double angle = (2*PI / floor(2*PI / fa));
 | 
			
		||||
    
 | 
			
		||||
    // Ring to be scaled to generate the steps of the sphere
 | 
			
		||||
    std::vector<double> ring;
 | 
			
		||||
    
 | 
			
		||||
    for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i);
 | 
			
		||||
    
 | 
			
		||||
    const auto sbegin = size_t(2*std::get<0>(portion)/angle);
 | 
			
		||||
    const auto send = size_t(2*std::get<1>(portion)/angle);
 | 
			
		||||
    
 | 
			
		||||
    const size_t steps = ring.size();
 | 
			
		||||
    const double increment = 1.0 / double(steps);
 | 
			
		||||
    
 | 
			
		||||
    // special case: first ring connects to 0,0,0
 | 
			
		||||
    // insert and form facets.
 | 
			
		||||
    if(sbegin == 0)
 | 
			
		||||
        vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho));
 | 
			
		||||
    
 | 
			
		||||
    auto id = coord_t(vertices.size());
 | 
			
		||||
    for (size_t i = 0; i < ring.size(); i++) {
 | 
			
		||||
        // Fixed scaling
 | 
			
		||||
        const double z = -rho + increment*rho*2.0 * (sbegin + 1.0);
 | 
			
		||||
        // radius of the circle for this step.
 | 
			
		||||
        const double r = std::sqrt(std::abs(rho*rho - z*z));
 | 
			
		||||
        Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
 | 
			
		||||
        vertices.emplace_back(Vec3d(b(0), b(1), z));
 | 
			
		||||
        
 | 
			
		||||
        if (sbegin == 0)
 | 
			
		||||
            (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) :
 | 
			
		||||
        			   facets.emplace_back(id - 1, 0, id);
 | 
			
		||||
        ++id;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // General case: insert and form facets for each step,
 | 
			
		||||
    // joining it to the ring below it.
 | 
			
		||||
    for (size_t s = sbegin + 2; s < send - 1; s++) {
 | 
			
		||||
        const double z = -rho + increment*double(s*2.0*rho);
 | 
			
		||||
        const double r = std::sqrt(std::abs(rho*rho - z*z));
 | 
			
		||||
        
 | 
			
		||||
        for (size_t i = 0; i < ring.size(); i++) {
 | 
			
		||||
            Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
 | 
			
		||||
            vertices.emplace_back(Vec3d(b(0), b(1), z));
 | 
			
		||||
            auto id_ringsize = coord_t(id - int(ring.size()));
 | 
			
		||||
            if (i == 0) {
 | 
			
		||||
                // wrap around
 | 
			
		||||
                facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) );
 | 
			
		||||
                facets.emplace_back(id - 1, id_ringsize, id);
 | 
			
		||||
            } else {
 | 
			
		||||
                facets.emplace_back(id_ringsize - 1, id_ringsize, id);
 | 
			
		||||
                facets.emplace_back(id - 1, id_ringsize - 1, id);
 | 
			
		||||
            }
 | 
			
		||||
            id++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // special case: last ring connects to 0,0,rho*2.0
 | 
			
		||||
    // only form facets.
 | 
			
		||||
    if(send >= size_t(2*PI / angle)) {
 | 
			
		||||
        vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho));
 | 
			
		||||
        for (size_t i = 0; i < ring.size(); i++) {
 | 
			
		||||
            auto id_ringsize = coord_t(id - int(ring.size()));
 | 
			
		||||
            if (i == 0) {
 | 
			
		||||
                // third vertex is on the other side of the ring.
 | 
			
		||||
                facets.emplace_back(id - 1, id_ringsize, id);
 | 
			
		||||
            } else {
 | 
			
		||||
                auto ci = coord_t(id_ringsize + coord_t(i));
 | 
			
		||||
                facets.emplace_back(ci - 1, ci, id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    id++;
 | 
			
		||||
    
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
 | 
			
		||||
{
 | 
			
		||||
    Contour3D ret;
 | 
			
		||||
    
 | 
			
		||||
    auto steps = int(ssteps);
 | 
			
		||||
    auto& points = ret.points;
 | 
			
		||||
    auto& indices = ret.faces3;
 | 
			
		||||
    points.reserve(2*ssteps);
 | 
			
		||||
    double a = 2*PI/steps;
 | 
			
		||||
    
 | 
			
		||||
    Vec3d jp = sp;
 | 
			
		||||
    Vec3d endp = {sp(X), sp(Y), sp(Z) + h};
 | 
			
		||||
    
 | 
			
		||||
    // Upper circle points
 | 
			
		||||
    for(int i = 0; i < steps; ++i) {
 | 
			
		||||
        double phi = i*a;
 | 
			
		||||
        double ex = endp(X) + r*std::cos(phi);
 | 
			
		||||
        double ey = endp(Y) + r*std::sin(phi);
 | 
			
		||||
        points.emplace_back(ex, ey, endp(Z));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Lower circle points
 | 
			
		||||
    for(int i = 0; i < steps; ++i) {
 | 
			
		||||
        double phi = i*a;
 | 
			
		||||
        double x = jp(X) + r*std::cos(phi);
 | 
			
		||||
        double y = jp(Y) + r*std::sin(phi);
 | 
			
		||||
        points.emplace_back(x, y, jp(Z));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Now create long triangles connecting upper and lower circles
 | 
			
		||||
    indices.reserve(2*ssteps);
 | 
			
		||||
    auto offs = steps;
 | 
			
		||||
    for(int i = 0; i < steps - 1; ++i) {
 | 
			
		||||
        indices.emplace_back(i, i + offs, offs + i + 1);
 | 
			
		||||
        indices.emplace_back(i, offs + i + 1, i + 1);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Last triangle connecting the first and last vertices
 | 
			
		||||
    auto last = steps - 1;
 | 
			
		||||
    indices.emplace_back(0, last, offs);
 | 
			
		||||
    indices.emplace_back(last, offs + last, offs);
 | 
			
		||||
    
 | 
			
		||||
    // According to the slicing algorithms, we need to aid them with generating
 | 
			
		||||
    // a watertight body. So we create a triangle fan for the upper and lower
 | 
			
		||||
    // ending of the cylinder to close the geometry.
 | 
			
		||||
    points.emplace_back(jp); int ci = int(points.size() - 1);
 | 
			
		||||
    for(int i = 0; i < steps - 1; ++i)
 | 
			
		||||
        indices.emplace_back(i + offs + 1, i + offs, ci);
 | 
			
		||||
    
 | 
			
		||||
    indices.emplace_back(offs, steps + offs - 1, ci);
 | 
			
		||||
    
 | 
			
		||||
    points.emplace_back(endp); ci = int(points.size() - 1);
 | 
			
		||||
    for(int i = 0; i < steps - 1; ++i)
 | 
			
		||||
        indices.emplace_back(ci, i, i + 1);
 | 
			
		||||
    
 | 
			
		||||
    indices.emplace_back(steps - 1, 0, ci);
 | 
			
		||||
    
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Head::Head(double       r_big_mm,
 | 
			
		||||
           double       r_small_mm,
 | 
			
		||||
           double       length_mm,
 | 
			
		||||
           double       penetration,
 | 
			
		||||
           const Vec3d &direction,
 | 
			
		||||
           const Vec3d &offset,
 | 
			
		||||
           const size_t circlesteps)
 | 
			
		||||
    : steps(circlesteps)
 | 
			
		||||
    , dir(direction)
 | 
			
		||||
    , tr(offset)
 | 
			
		||||
           const Vec3d &offset)
 | 
			
		||||
    : dir(direction)
 | 
			
		||||
    , pos(offset)
 | 
			
		||||
    , r_back_mm(r_big_mm)
 | 
			
		||||
    , r_pin_mm(r_small_mm)
 | 
			
		||||
    , width_mm(length_mm)
 | 
			
		||||
    , penetration_mm(penetration)
 | 
			
		||||
{
 | 
			
		||||
    assert(width_mm > 0.);
 | 
			
		||||
    assert(r_back_mm > 0.);
 | 
			
		||||
    assert(r_pin_mm > 0.);
 | 
			
		||||
    
 | 
			
		||||
    // We create two spheres which will be connected with a robe that fits
 | 
			
		||||
    // both circles perfectly.
 | 
			
		||||
    
 | 
			
		||||
    // Set up the model detail level
 | 
			
		||||
    const double detail = 2*PI/steps;
 | 
			
		||||
    
 | 
			
		||||
    // We don't generate whole circles. Instead, we generate only the
 | 
			
		||||
    // portions which are visible (not covered by the robe) To know the
 | 
			
		||||
    // exact portion of the bottom and top circles we need to use some
 | 
			
		||||
    // rules of tangent circles from which we can derive (using simple
 | 
			
		||||
    // triangles the following relations:
 | 
			
		||||
    
 | 
			
		||||
    // The height of the whole mesh
 | 
			
		||||
    const double h = r_big_mm + r_small_mm + width_mm;
 | 
			
		||||
    double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / h );
 | 
			
		||||
    
 | 
			
		||||
    // To generate a whole circle we would pass a portion of (0, Pi)
 | 
			
		||||
    // To generate only a half horizontal circle we can pass (0, Pi/2)
 | 
			
		||||
    // The calculated phi is an offset to the half circles needed to smooth
 | 
			
		||||
    // the transition from the circle to the robe geometry
 | 
			
		||||
    
 | 
			
		||||
    auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail);
 | 
			
		||||
    auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail);
 | 
			
		||||
    
 | 
			
		||||
    for(auto& p : s2.points) p.z() += h;
 | 
			
		||||
    
 | 
			
		||||
    mesh.merge(s1);
 | 
			
		||||
    mesh.merge(s2);
 | 
			
		||||
    
 | 
			
		||||
    for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size();
 | 
			
		||||
        idx1 < s1.points.size() - 1;
 | 
			
		||||
        idx1++, idx2++)
 | 
			
		||||
    {
 | 
			
		||||
        coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
 | 
			
		||||
        coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
 | 
			
		||||
        
 | 
			
		||||
        mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
 | 
			
		||||
        mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
 | 
			
		||||
    auto i2s1 = coord_t(s1.points.size()) - 1;
 | 
			
		||||
    auto i1s2 = coord_t(s1.points.size());
 | 
			
		||||
    auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
 | 
			
		||||
    
 | 
			
		||||
    mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
 | 
			
		||||
    mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
 | 
			
		||||
    
 | 
			
		||||
    // To simplify further processing, we translate the mesh so that the
 | 
			
		||||
    // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
 | 
			
		||||
    for(auto& p : mesh.points) p.z() -= (h + r_small_mm - penetration_mm);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st):
 | 
			
		||||
    r(radius), steps(st), endpt(endp), starts_from_head(false)
 | 
			
		||||
{
 | 
			
		||||
    assert(steps > 0);
 | 
			
		||||
    
 | 
			
		||||
    height = jp(Z) - endp(Z);
 | 
			
		||||
    if(height > EPSILON) { // Endpoint is below the starting point
 | 
			
		||||
        
 | 
			
		||||
        // We just create a bridge geometry with the pillar parameters and
 | 
			
		||||
        // move the data.
 | 
			
		||||
        Contour3D body = cylinder(radius, height, st, endp);
 | 
			
		||||
        mesh.points.swap(body.points);
 | 
			
		||||
        mesh.faces3.swap(body.faces3);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Pillar &Pillar::add_base(double baseheight, double radius)
 | 
			
		||||
{
 | 
			
		||||
    if(baseheight <= 0) return *this;
 | 
			
		||||
    if(baseheight > height) baseheight = height;
 | 
			
		||||
    
 | 
			
		||||
    assert(steps >= 0);
 | 
			
		||||
    auto last = int(steps - 1);
 | 
			
		||||
    
 | 
			
		||||
    if(radius < r ) radius = r;
 | 
			
		||||
    
 | 
			
		||||
    double a = 2*PI/steps;
 | 
			
		||||
    double z = endpt(Z) + baseheight;
 | 
			
		||||
    
 | 
			
		||||
    for(size_t i = 0; i < steps; ++i) {
 | 
			
		||||
        double phi = i*a;
 | 
			
		||||
        double x = endpt(X) + r*std::cos(phi);
 | 
			
		||||
        double y = endpt(Y) + r*std::sin(phi);
 | 
			
		||||
        base.points.emplace_back(x, y, z);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    for(size_t i = 0; i < steps; ++i) {
 | 
			
		||||
        double phi = i*a;
 | 
			
		||||
        double x = endpt(X) + radius*std::cos(phi);
 | 
			
		||||
        double y = endpt(Y) + radius*std::sin(phi);
 | 
			
		||||
        base.points.emplace_back(x, y, z - baseheight);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    auto ep = endpt; ep(Z) += baseheight;
 | 
			
		||||
    base.points.emplace_back(endpt);
 | 
			
		||||
    base.points.emplace_back(ep);
 | 
			
		||||
    
 | 
			
		||||
    auto& indices = base.faces3;
 | 
			
		||||
    auto hcenter = int(base.points.size() - 1);
 | 
			
		||||
    auto lcenter = int(base.points.size() - 2);
 | 
			
		||||
    auto offs = int(steps);
 | 
			
		||||
    for(int i = 0; i < last; ++i) {
 | 
			
		||||
        indices.emplace_back(i, i + offs, offs + i + 1);
 | 
			
		||||
        indices.emplace_back(i, offs + i + 1, i + 1);
 | 
			
		||||
        indices.emplace_back(i, i + 1, hcenter);
 | 
			
		||||
        indices.emplace_back(lcenter, offs + i + 1, offs + i);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    indices.emplace_back(0, last, offs);
 | 
			
		||||
    indices.emplace_back(last, offs + last, offs);
 | 
			
		||||
    indices.emplace_back(hcenter, last, 0);
 | 
			
		||||
    indices.emplace_back(offs, offs + last, lcenter);
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps):
 | 
			
		||||
    r(r_mm), startp(j1), endp(j2)
 | 
			
		||||
{
 | 
			
		||||
    using Quaternion = Eigen::Quaternion<double>;
 | 
			
		||||
    Vec3d dir = (j2 - j1).normalized();
 | 
			
		||||
    double d = distance(j2, j1);
 | 
			
		||||
    
 | 
			
		||||
    mesh = cylinder(r, d, steps);
 | 
			
		||||
    
 | 
			
		||||
    auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
 | 
			
		||||
    for(auto& p : mesh.points) p = quater * p + j1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CompactBridge::CompactBridge(const Vec3d &sp,
 | 
			
		||||
                             const Vec3d &ep,
 | 
			
		||||
                             const Vec3d &n,
 | 
			
		||||
                             double       r,
 | 
			
		||||
                             bool         endball,
 | 
			
		||||
                             size_t       steps)
 | 
			
		||||
{
 | 
			
		||||
    Vec3d startp = sp + r * n;
 | 
			
		||||
    Vec3d dir = (ep - startp).normalized();
 | 
			
		||||
    Vec3d endp = ep - r * dir;
 | 
			
		||||
    
 | 
			
		||||
    Bridge br(startp, endp, r, steps);
 | 
			
		||||
    mesh.merge(br.mesh);
 | 
			
		||||
    
 | 
			
		||||
    // now add the pins
 | 
			
		||||
    double fa = 2*PI/steps;
 | 
			
		||||
    auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa);
 | 
			
		||||
    for(auto& p : upperball.points) p += startp;
 | 
			
		||||
    
 | 
			
		||||
    if(endball) {
 | 
			
		||||
        auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa);
 | 
			
		||||
        for(auto& p : lowerball.points) p += endp;
 | 
			
		||||
        mesh.merge(lowerball);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    mesh.merge(upperball);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Pad::Pad(const TriangleMesh &support_mesh,
 | 
			
		||||
| 
						 | 
				
			
			@ -368,7 +58,6 @@ SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o)
 | 
			
		|||
    , m_pillars{std::move(o.m_pillars)}
 | 
			
		||||
    , m_bridges{std::move(o.m_bridges)}
 | 
			
		||||
    , m_crossbridges{std::move(o.m_crossbridges)}
 | 
			
		||||
    , m_compact_bridges{std::move(o.m_compact_bridges)}
 | 
			
		||||
    , m_pad{std::move(o.m_pad)}
 | 
			
		||||
    , m_meshcache{std::move(o.m_meshcache)}
 | 
			
		||||
    , m_meshcache_valid{o.m_meshcache_valid}
 | 
			
		||||
| 
						 | 
				
			
			@ -382,7 +71,6 @@ SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o)
 | 
			
		|||
    , m_pillars{o.m_pillars}
 | 
			
		||||
    , m_bridges{o.m_bridges}
 | 
			
		||||
    , m_crossbridges{o.m_crossbridges}
 | 
			
		||||
    , m_compact_bridges{o.m_compact_bridges}
 | 
			
		||||
    , m_pad{o.m_pad}
 | 
			
		||||
    , m_meshcache{o.m_meshcache}
 | 
			
		||||
    , m_meshcache_valid{o.m_meshcache_valid}
 | 
			
		||||
| 
						 | 
				
			
			@ -397,7 +85,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o)
 | 
			
		|||
    m_pillars = std::move(o.m_pillars);
 | 
			
		||||
    m_bridges = std::move(o.m_bridges);
 | 
			
		||||
    m_crossbridges = std::move(o.m_crossbridges);
 | 
			
		||||
    m_compact_bridges = std::move(o.m_compact_bridges);
 | 
			
		||||
    m_pad = std::move(o.m_pad);
 | 
			
		||||
    m_meshcache = std::move(o.m_meshcache);
 | 
			
		||||
    m_meshcache_valid = o.m_meshcache_valid;
 | 
			
		||||
| 
						 | 
				
			
			@ -413,7 +100,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o)
 | 
			
		|||
    m_pillars = o.m_pillars;
 | 
			
		||||
    m_bridges = o.m_bridges;
 | 
			
		||||
    m_crossbridges = o.m_crossbridges;
 | 
			
		||||
    m_compact_bridges = o.m_compact_bridges;
 | 
			
		||||
    m_pad = o.m_pad;
 | 
			
		||||
    m_meshcache = o.m_meshcache;
 | 
			
		||||
    m_meshcache_valid = o.m_meshcache_valid;
 | 
			
		||||
| 
						 | 
				
			
			@ -422,7 +108,19 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o)
 | 
			
		|||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TriangleMesh &SupportTreeBuilder::merged_mesh() const
 | 
			
		||||
void SupportTreeBuilder::add_pillar_base(long pid, double baseheight, double radius)
 | 
			
		||||
{
 | 
			
		||||
    std::lock_guard<Mutex> lk(m_mutex);
 | 
			
		||||
    assert(pid >= 0 && size_t(pid) < m_pillars.size());
 | 
			
		||||
    Pillar& pll = m_pillars[size_t(pid)];
 | 
			
		||||
    m_pedestals.emplace_back(pll.endpt, std::min(baseheight, pll.height),
 | 
			
		||||
                             std::max(radius, pll.r), pll.r);
 | 
			
		||||
 | 
			
		||||
    m_pedestals.back().id = m_pedestals.size() - 1;
 | 
			
		||||
    m_meshcache_valid = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const
 | 
			
		||||
{
 | 
			
		||||
    if (m_meshcache_valid) return m_meshcache;
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -430,35 +128,44 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
 | 
			
		|||
    
 | 
			
		||||
    for (auto &head : m_heads) {
 | 
			
		||||
        if (ctl().stopcondition()) break;
 | 
			
		||||
        if (head.is_valid()) merged.merge(head.mesh);
 | 
			
		||||
        if (head.is_valid()) merged.merge(get_mesh(head, steps));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    for (auto &stick : m_pillars) {
 | 
			
		||||
    for (auto &pill : m_pillars) {
 | 
			
		||||
        if (ctl().stopcondition()) break;
 | 
			
		||||
        merged.merge(stick.mesh);
 | 
			
		||||
        merged.merge(stick.base);
 | 
			
		||||
        merged.merge(get_mesh(pill, steps));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto &pedest : m_pedestals) {
 | 
			
		||||
        if (ctl().stopcondition()) break;
 | 
			
		||||
        merged.merge(get_mesh(pedest, steps));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    for (auto &j : m_junctions) {
 | 
			
		||||
        if (ctl().stopcondition()) break;
 | 
			
		||||
        merged.merge(j.mesh);
 | 
			
		||||
        merged.merge(get_mesh(j, steps));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    for (auto &cb : m_compact_bridges) {
 | 
			
		||||
        if (ctl().stopcondition()) break;
 | 
			
		||||
        merged.merge(cb.mesh);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    for (auto &bs : m_bridges) {
 | 
			
		||||
        if (ctl().stopcondition()) break;
 | 
			
		||||
        merged.merge(bs.mesh);
 | 
			
		||||
        merged.merge(get_mesh(bs, steps));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    for (auto &bs : m_crossbridges) {
 | 
			
		||||
        if (ctl().stopcondition()) break;
 | 
			
		||||
        merged.merge(bs.mesh);
 | 
			
		||||
        merged.merge(get_mesh(bs, steps));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    for (auto &bs : m_diffbridges) {
 | 
			
		||||
        if (ctl().stopcondition()) break;
 | 
			
		||||
        merged.merge(get_mesh(bs, steps));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto &anch : m_anchors) {
 | 
			
		||||
        if (ctl().stopcondition()) break;
 | 
			
		||||
        merged.merge(get_mesh(anch, steps));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ctl().stopcondition()) {
 | 
			
		||||
        // In case of failure we have to return an empty mesh
 | 
			
		||||
        m_meshcache = TriangleMesh();
 | 
			
		||||
| 
						 | 
				
			
			@ -499,7 +206,6 @@ const TriangleMesh &SupportTreeBuilder::merge_and_cleanup()
 | 
			
		|||
    m_pillars = {};
 | 
			
		||||
    m_junctions = {};
 | 
			
		||||
    m_bridges = {};
 | 
			
		||||
    m_compact_bridges = {};
 | 
			
		||||
    
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -514,11 +220,4 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const
 | 
			
		|||
    return m_meshcache;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SupportTreeBuilder::build(const SupportableMesh &sm)
 | 
			
		||||
{
 | 
			
		||||
    ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm;
 | 
			
		||||
    return SupportTreeBuildsteps::execute(*this, sm);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
}} // namespace Slic3r::sla
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@
 | 
			
		|||
#define SLA_SUPPORTTREEBUILDER_HPP
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/SLA/Concurrency.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Common.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SupportTree.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Contour3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Pad.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -50,13 +49,6 @@ namespace sla {
 | 
			
		|||
 * nearby pillar.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
using Coordf = double;
 | 
			
		||||
using Portion = std::tuple<double, double>;
 | 
			
		||||
 | 
			
		||||
inline Portion make_portion(double a, double b) {
 | 
			
		||||
    return std::make_tuple(a, b);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<class Vec> double distance(const Vec& p) {
 | 
			
		||||
    return std::sqrt(p.transpose() * p);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -66,33 +58,25 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
 | 
			
		|||
    return distance(p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
 | 
			
		||||
                 double fa=(2*PI/360));
 | 
			
		||||
const Vec3d DOWN = {0.0, 0.0, -1.0};
 | 
			
		||||
 | 
			
		||||
// Down facing cylinder in Z direction with arguments:
 | 
			
		||||
// r: radius
 | 
			
		||||
// h: Height
 | 
			
		||||
// ssteps: how many edges will create the base circle
 | 
			
		||||
// sp: starting point
 | 
			
		||||
Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0});
 | 
			
		||||
struct SupportTreeNode
 | 
			
		||||
{
 | 
			
		||||
    static const constexpr long ID_UNSET = -1;
 | 
			
		||||
 | 
			
		||||
const constexpr long ID_UNSET = -1;
 | 
			
		||||
    long id = ID_UNSET; // For identification withing a tree.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Head {
 | 
			
		||||
    Contour3D mesh;
 | 
			
		||||
    
 | 
			
		||||
    size_t steps = 45;
 | 
			
		||||
    Vec3d dir = {0, 0, -1};
 | 
			
		||||
    Vec3d tr = {0, 0, 0};
 | 
			
		||||
// A pinhead originating from a support point
 | 
			
		||||
struct Head: public SupportTreeNode {
 | 
			
		||||
    Vec3d dir = DOWN;
 | 
			
		||||
    Vec3d pos = {0, 0, 0};
 | 
			
		||||
    
 | 
			
		||||
    double r_back_mm = 1;
 | 
			
		||||
    double r_pin_mm = 0.5;
 | 
			
		||||
    double width_mm = 2;
 | 
			
		||||
    double penetration_mm = 0.5;
 | 
			
		||||
    
 | 
			
		||||
    // For identification purposes. This will be used as the index into the
 | 
			
		||||
    // container holding the head structures. See SLASupportTree::Impl
 | 
			
		||||
    long id = ID_UNSET;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    // If there is a pillar connecting to this head, then the id will be set.
 | 
			
		||||
    long pillar_id = ID_UNSET;
 | 
			
		||||
| 
						 | 
				
			
			@ -106,31 +90,23 @@ struct Head {
 | 
			
		|||
         double r_small_mm,
 | 
			
		||||
         double length_mm,
 | 
			
		||||
         double penetration,
 | 
			
		||||
         const Vec3d &direction = {0, 0, -1},  // direction (normal to the dull end)
 | 
			
		||||
         const Vec3d &offset = {0, 0, 0},      // displacement
 | 
			
		||||
         const size_t circlesteps = 45);
 | 
			
		||||
    
 | 
			
		||||
    void transform()
 | 
			
		||||
         const Vec3d &direction = DOWN,  // direction (normal to the dull end)
 | 
			
		||||
         const Vec3d &offset = {0, 0, 0}      // displacement
 | 
			
		||||
         );
 | 
			
		||||
 | 
			
		||||
    inline double real_width() const
 | 
			
		||||
    {
 | 
			
		||||
        using Quaternion = Eigen::Quaternion<double>;
 | 
			
		||||
        
 | 
			
		||||
        // We rotate the head to the specified direction The head's pointing
 | 
			
		||||
        // side is facing upwards so this means that it would hold a support
 | 
			
		||||
        // point with a normal pointing straight down. This is the reason of
 | 
			
		||||
        // the -1 z coordinate
 | 
			
		||||
        auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir);
 | 
			
		||||
        
 | 
			
		||||
        for(auto& p : mesh.points) p = quatern * p + tr;
 | 
			
		||||
        return 2 * r_pin_mm + width_mm + 2 * r_back_mm ;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    inline double fullwidth() const
 | 
			
		||||
    {
 | 
			
		||||
        return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm;
 | 
			
		||||
        return real_width() - penetration_mm;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    inline Vec3d junction_point() const
 | 
			
		||||
    {
 | 
			
		||||
        return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir;
 | 
			
		||||
        return pos + (fullwidth() - r_back_mm) * dir;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    inline double request_pillar_radius(double radius) const
 | 
			
		||||
| 
						 | 
				
			
			@ -140,31 +116,17 @@ struct Head {
 | 
			
		|||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Junction {
 | 
			
		||||
    Contour3D mesh;
 | 
			
		||||
// A junction connecting bridges and pillars
 | 
			
		||||
struct Junction: public SupportTreeNode {
 | 
			
		||||
    double r = 1;
 | 
			
		||||
    size_t steps = 45;
 | 
			
		||||
    Vec3d pos;
 | 
			
		||||
    
 | 
			
		||||
    long id = ID_UNSET;
 | 
			
		||||
    
 | 
			
		||||
    Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45):
 | 
			
		||||
        r(r_mm), steps(stepnum), pos(tr)
 | 
			
		||||
    {
 | 
			
		||||
        mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps);
 | 
			
		||||
        for(auto& p : mesh.points) p += tr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Pillar {
 | 
			
		||||
    Contour3D mesh;
 | 
			
		||||
    Contour3D base;
 | 
			
		||||
    double r = 1;
 | 
			
		||||
    size_t steps = 0;
 | 
			
		||||
struct Pillar: public SupportTreeNode {
 | 
			
		||||
    double height, r;
 | 
			
		||||
    Vec3d endpt;
 | 
			
		||||
    double height = 0;
 | 
			
		||||
    
 | 
			
		||||
    long id = ID_UNSET;
 | 
			
		||||
    
 | 
			
		||||
    // If the pillar connects to a head, this is the id of that head
 | 
			
		||||
    bool starts_from_head = true; // Could start from a junction as well
 | 
			
		||||
| 
						 | 
				
			
			@ -175,54 +137,52 @@ struct Pillar {
 | 
			
		|||
    
 | 
			
		||||
    // How many pillars are cascaded with this one
 | 
			
		||||
    unsigned links = 0;
 | 
			
		||||
    
 | 
			
		||||
    Pillar(const Vec3d& jp, const Vec3d& endp,
 | 
			
		||||
           double radius = 1, size_t st = 45);
 | 
			
		||||
    
 | 
			
		||||
    Pillar(const Junction &junc, const Vec3d &endp)
 | 
			
		||||
        : Pillar(junc.pos, endp, junc.r, junc.steps)
 | 
			
		||||
    {}
 | 
			
		||||
    
 | 
			
		||||
    Pillar(const Head &head, const Vec3d &endp, double radius = 1)
 | 
			
		||||
        : Pillar(head.junction_point(), endp,
 | 
			
		||||
                 head.request_pillar_radius(radius), head.steps)
 | 
			
		||||
    {}
 | 
			
		||||
    
 | 
			
		||||
    inline Vec3d startpoint() const
 | 
			
		||||
 | 
			
		||||
    Pillar(const Vec3d &endp, double h, double radius = 1.):
 | 
			
		||||
        height{h}, r(radius), endpt(endp), starts_from_head(false) {}
 | 
			
		||||
 | 
			
		||||
    Vec3d startpoint() const
 | 
			
		||||
    {
 | 
			
		||||
        return {endpt(X), endpt(Y), endpt(Z) + height};
 | 
			
		||||
        return {endpt.x(), endpt.y(), endpt.z() + height};
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    inline const Vec3d& endpoint() const { return endpt; }
 | 
			
		||||
    
 | 
			
		||||
    Pillar& add_base(double baseheight = 3, double radius = 2);
 | 
			
		||||
    const Vec3d& endpoint() const { return endpt; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// A base for pillars or bridges that end on the ground
 | 
			
		||||
struct Pedestal: public SupportTreeNode {
 | 
			
		||||
    Vec3d pos;
 | 
			
		||||
    double height, r_bottom, r_top;
 | 
			
		||||
 | 
			
		||||
    Pedestal(const Vec3d &p, double h, double rbottom, double rtop)
 | 
			
		||||
        : pos{p}, height{h}, r_bottom{rbottom}, r_top{rtop}
 | 
			
		||||
    {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// This is the thing that anchors a pillar or bridge to the model body.
 | 
			
		||||
// It is actually a reverse pinhead.
 | 
			
		||||
struct Anchor: public Head { using Head::Head; };
 | 
			
		||||
 | 
			
		||||
// A Bridge between two pillars (with junction endpoints)
 | 
			
		||||
struct Bridge {
 | 
			
		||||
    Contour3D mesh;
 | 
			
		||||
struct Bridge: public SupportTreeNode {
 | 
			
		||||
    double r = 0.8;
 | 
			
		||||
    long id = ID_UNSET;
 | 
			
		||||
    Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero();
 | 
			
		||||
    
 | 
			
		||||
    Bridge(const Vec3d &j1,
 | 
			
		||||
           const Vec3d &j2,
 | 
			
		||||
           double       r_mm  = 0.8,
 | 
			
		||||
           size_t       steps = 45);
 | 
			
		||||
           double       r_mm  = 0.8): r{r_mm}, startp{j1}, endp{j2}
 | 
			
		||||
    {}
 | 
			
		||||
 | 
			
		||||
    double get_length() const { return (endp - startp).norm(); }
 | 
			
		||||
    Vec3d  get_dir() const { return (endp - startp).normalized(); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// A bridge that spans from model surface to model surface with small connecting
 | 
			
		||||
// edges on the endpoints. Used for headless support points.
 | 
			
		||||
struct CompactBridge {
 | 
			
		||||
    Contour3D mesh;
 | 
			
		||||
    long id = ID_UNSET;
 | 
			
		||||
    
 | 
			
		||||
    CompactBridge(const Vec3d& sp,
 | 
			
		||||
                  const Vec3d& ep,
 | 
			
		||||
                  const Vec3d& n,
 | 
			
		||||
                  double r,
 | 
			
		||||
                  bool endball = true,
 | 
			
		||||
                  size_t steps = 45);
 | 
			
		||||
struct DiffBridge: public Bridge {
 | 
			
		||||
    double end_r;
 | 
			
		||||
 | 
			
		||||
    DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e)
 | 
			
		||||
        : Bridge{p_s, p_e, r_s}, end_r{r_e}
 | 
			
		||||
    {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// A wrapper struct around the pad
 | 
			
		||||
| 
						 | 
				
			
			@ -258,13 +218,16 @@ struct Pad {
 | 
			
		|||
// merged mesh. It can be retrieved using a dedicated method (pad())
 | 
			
		||||
class SupportTreeBuilder: public SupportTree {
 | 
			
		||||
    // For heads it is beneficial to use the same IDs as for the support points.
 | 
			
		||||
    std::vector<Head> m_heads;
 | 
			
		||||
    std::vector<size_t> m_head_indices;
 | 
			
		||||
    std::vector<Pillar> m_pillars;
 | 
			
		||||
    std::vector<Junction> m_junctions;
 | 
			
		||||
    std::vector<Bridge> m_bridges;
 | 
			
		||||
    std::vector<Bridge> m_crossbridges;
 | 
			
		||||
    std::vector<CompactBridge> m_compact_bridges;    
 | 
			
		||||
    std::vector<Head>       m_heads;
 | 
			
		||||
    std::vector<size_t>     m_head_indices;
 | 
			
		||||
    std::vector<Pillar>     m_pillars;
 | 
			
		||||
    std::vector<Junction>   m_junctions;
 | 
			
		||||
    std::vector<Bridge>     m_bridges;
 | 
			
		||||
    std::vector<Bridge>     m_crossbridges;
 | 
			
		||||
    std::vector<DiffBridge> m_diffbridges;
 | 
			
		||||
    std::vector<Pedestal>   m_pedestals;
 | 
			
		||||
    std::vector<Anchor>     m_anchors;
 | 
			
		||||
 | 
			
		||||
    Pad m_pad;
 | 
			
		||||
    
 | 
			
		||||
    using Mutex = ccr::SpinningMutex;
 | 
			
		||||
| 
						 | 
				
			
			@ -274,8 +237,8 @@ class SupportTreeBuilder: public SupportTree {
 | 
			
		|||
    mutable bool m_meshcache_valid = false;
 | 
			
		||||
    mutable double m_model_height = 0; // the full height of the model
 | 
			
		||||
    
 | 
			
		||||
    template<class...Args>
 | 
			
		||||
    const Bridge& _add_bridge(std::vector<Bridge> &br, Args&&... args)
 | 
			
		||||
    template<class BridgeT, class...Args>
 | 
			
		||||
    const BridgeT& _add_bridge(std::vector<BridgeT> &br, Args&&... args)
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<Mutex> lk(m_mutex);
 | 
			
		||||
        br.emplace_back(std::forward<Args>(args)...);
 | 
			
		||||
| 
						 | 
				
			
			@ -306,7 +269,7 @@ public:
 | 
			
		|||
        return m_heads.back();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    template<class...Args> long add_pillar(long headid, Args&&... args)
 | 
			
		||||
    template<class...Args> long add_pillar(long headid, double length)
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<Mutex> lk(m_mutex);
 | 
			
		||||
        if (m_pillars.capacity() < m_heads.size())
 | 
			
		||||
| 
						 | 
				
			
			@ -315,7 +278,9 @@ public:
 | 
			
		|||
        assert(headid >= 0 && size_t(headid) < m_head_indices.size());
 | 
			
		||||
        Head &head = m_heads[m_head_indices[size_t(headid)]];
 | 
			
		||||
        
 | 
			
		||||
        m_pillars.emplace_back(head, std::forward<Args>(args)...);
 | 
			
		||||
        Vec3d hjp = head.junction_point() - Vec3d{0, 0, length};
 | 
			
		||||
        m_pillars.emplace_back(hjp, length, head.r_back_mm);
 | 
			
		||||
 | 
			
		||||
        Pillar& pillar = m_pillars.back();
 | 
			
		||||
        pillar.id = long(m_pillars.size() - 1);
 | 
			
		||||
        head.pillar_id = pillar.id;
 | 
			
		||||
| 
						 | 
				
			
			@ -326,11 +291,15 @@ public:
 | 
			
		|||
        return pillar.id;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    void add_pillar_base(long pid, double baseheight = 3, double radius = 2)
 | 
			
		||||
    void add_pillar_base(long pid, double baseheight = 3, double radius = 2);
 | 
			
		||||
 | 
			
		||||
    template<class...Args> const Anchor& add_anchor(Args&&...args)
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<Mutex> lk(m_mutex);
 | 
			
		||||
        assert(pid >= 0 && size_t(pid) < m_pillars.size());
 | 
			
		||||
        m_pillars[size_t(pid)].add_base(baseheight, radius);
 | 
			
		||||
        m_anchors.emplace_back(std::forward<Args>(args)...);
 | 
			
		||||
        m_anchors.back().id = long(m_junctions.size() - 1);
 | 
			
		||||
        m_meshcache_valid = false;
 | 
			
		||||
        return m_anchors.back();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    void increment_bridges(const Pillar& pillar)
 | 
			
		||||
| 
						 | 
				
			
			@ -371,17 +340,6 @@ public:
 | 
			
		|||
        return pillar.id;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const Pillar& head_pillar(unsigned headid) const
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<Mutex> lk(m_mutex);
 | 
			
		||||
        assert(headid < m_head_indices.size());
 | 
			
		||||
        
 | 
			
		||||
        const Head& h = m_heads[m_head_indices[headid]];
 | 
			
		||||
        assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size()));
 | 
			
		||||
        
 | 
			
		||||
        return m_pillars[size_t(h.pillar_id)];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    template<class...Args> const Junction& add_junction(Args&&... args)
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<Mutex> lk(m_mutex);
 | 
			
		||||
| 
						 | 
				
			
			@ -391,18 +349,18 @@ public:
 | 
			
		|||
        return m_junctions.back();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r, size_t n = 45)
 | 
			
		||||
    const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r)
 | 
			
		||||
    {
 | 
			
		||||
        return _add_bridge(m_bridges, s, e, r, n);
 | 
			
		||||
        return _add_bridge(m_bridges, s, e, r);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const Bridge& add_bridge(long headid, const Vec3d &endp, size_t s = 45)
 | 
			
		||||
    const Bridge& add_bridge(long headid, const Vec3d &endp)
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<Mutex> lk(m_mutex);
 | 
			
		||||
        assert(headid >= 0 && size_t(headid) < m_head_indices.size());
 | 
			
		||||
        
 | 
			
		||||
        Head &h = m_heads[m_head_indices[size_t(headid)]];
 | 
			
		||||
        m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm, s);
 | 
			
		||||
        m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm);
 | 
			
		||||
        m_bridges.back().id = long(m_bridges.size() - 1);
 | 
			
		||||
        
 | 
			
		||||
        h.bridge_id = m_bridges.back().id;
 | 
			
		||||
| 
						 | 
				
			
			@ -414,14 +372,10 @@ public:
 | 
			
		|||
    {
 | 
			
		||||
        return _add_bridge(m_crossbridges, std::forward<Args>(args)...);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    template<class...Args> const CompactBridge& add_compact_bridge(Args&&...args)
 | 
			
		||||
 | 
			
		||||
    template<class...Args> const DiffBridge& add_diffbridge(Args&&... args)
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<Mutex> lk(m_mutex);
 | 
			
		||||
        m_compact_bridges.emplace_back(std::forward<Args>(args)...);
 | 
			
		||||
        m_compact_bridges.back().id = long(m_compact_bridges.size() - 1);
 | 
			
		||||
        m_meshcache_valid = false;
 | 
			
		||||
        return m_compact_bridges.back();
 | 
			
		||||
        return _add_bridge(m_diffbridges, std::forward<Args>(args)...);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    Head &head(unsigned id)
 | 
			
		||||
| 
						 | 
				
			
			@ -439,7 +393,7 @@ public:
 | 
			
		|||
    }
 | 
			
		||||
    
 | 
			
		||||
    inline const std::vector<Pillar> &pillars() const { return m_pillars; }
 | 
			
		||||
    inline const std::vector<Head> &heads() const { return m_heads; }
 | 
			
		||||
    inline const std::vector<Head>   &heads() const { return m_heads; }
 | 
			
		||||
    inline const std::vector<Bridge> &bridges() const { return m_bridges; }
 | 
			
		||||
    inline const std::vector<Bridge> &crossbridges() const { return m_crossbridges; }
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -464,7 +418,7 @@ public:
 | 
			
		|||
    const Pad& pad() const { return m_pad; }
 | 
			
		||||
    
 | 
			
		||||
    // WITHOUT THE PAD!!!
 | 
			
		||||
    const TriangleMesh &merged_mesh() const;
 | 
			
		||||
    const TriangleMesh &merged_mesh(size_t steps = 45) const;
 | 
			
		||||
    
 | 
			
		||||
    // WITH THE PAD
 | 
			
		||||
    double full_height() const;
 | 
			
		||||
| 
						 | 
				
			
			@ -488,8 +442,6 @@ public:
 | 
			
		|||
    
 | 
			
		||||
    virtual const TriangleMesh &retrieve_mesh(
 | 
			
		||||
        MeshType meshtype = MeshType::Support) const override;
 | 
			
		||||
 | 
			
		||||
    bool build(const SupportableMesh &supportable_mesh);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::sla
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
 | 
			
		||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Clustering.hpp>
 | 
			
		||||
#include <libslic3r/SLA/SpatIndex.hpp>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
namespace sla {
 | 
			
		||||
| 
						 | 
				
			
			@ -16,9 +17,7 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
 | 
			
		|||
    X, Y, Z
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
inline Vec2d to_vec2(const Vec3d& v3) {
 | 
			
		||||
    return {v3(X), v3(Y)};
 | 
			
		||||
}
 | 
			
		||||
inline Vec2d to_vec2(const Vec3d &v3) { return {v3(X), v3(Y)}; }
 | 
			
		||||
 | 
			
		||||
inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -46,55 +45,71 @@ inline Vec3d spheric_to_dir(const std::pair<double, double> &v)
 | 
			
		|||
    return spheric_to_dir(v.first, v.second);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This function returns the position of the centroid in the input 'clust'
 | 
			
		||||
// vector of point indices.
 | 
			
		||||
template<class DistFn>
 | 
			
		||||
long cluster_centroid(const ClusterEl& clust,
 | 
			
		||||
                      const std::function<Vec3d(size_t)> &pointfn,
 | 
			
		||||
                      DistFn df)
 | 
			
		||||
inline Vec3d spheric_to_dir(const std::array<double, 2> &v)
 | 
			
		||||
{
 | 
			
		||||
    switch(clust.size()) {
 | 
			
		||||
    case 0: /* empty cluster */ return ID_UNSET;
 | 
			
		||||
    case 1: /* only one element */ return 0;
 | 
			
		||||
    case 2: /* if two elements, there is no center */ return 0;
 | 
			
		||||
    default: ;
 | 
			
		||||
    return spheric_to_dir(v[0], v[1]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Give points on a 3D ring with given center, radius and orientation
 | 
			
		||||
// method based on:
 | 
			
		||||
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
 | 
			
		||||
template<size_t N>
 | 
			
		||||
class PointRing {
 | 
			
		||||
    std::array<double, N> m_phis;
 | 
			
		||||
 | 
			
		||||
    // Two vectors that will be perpendicular to each other and to the
 | 
			
		||||
    // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
 | 
			
		||||
    // placeholder.
 | 
			
		||||
    // a and b vectors are perpendicular to the ring direction and to each other.
 | 
			
		||||
    // Together they define the plane where we have to iterate with the
 | 
			
		||||
    // given angles in the 'm_phis' vector
 | 
			
		||||
    Vec3d a = {0, 1, 0}, b;
 | 
			
		||||
    double m_radius = 0.;
 | 
			
		||||
 | 
			
		||||
    static inline bool constexpr is_one(double val)
 | 
			
		||||
    {
 | 
			
		||||
        return std::abs(std::abs(val) - 1) < 1e-20;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The function works by calculating for each point the average distance
 | 
			
		||||
    // from all the other points in the cluster. We create a selector bitmask of
 | 
			
		||||
    // the same size as the cluster. The bitmask will have two true bits and
 | 
			
		||||
    // false bits for the rest of items and we will loop through all the
 | 
			
		||||
    // permutations of the bitmask (combinations of two points). Get the
 | 
			
		||||
    // distance for the two points and add the distance to the averages.
 | 
			
		||||
    // The point with the smallest average than wins.
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
    // The complexity should be O(n^2) but we will mostly apply this function
 | 
			
		||||
    // for small clusters only (cca 3 elements)
 | 
			
		||||
    PointRing(const Vec3d &n)
 | 
			
		||||
    {
 | 
			
		||||
        m_phis = linspace_array<N>(0., 2 * PI);
 | 
			
		||||
 | 
			
		||||
    std::vector<bool> sel(clust.size(), false);   // create full zero bitmask
 | 
			
		||||
    std::fill(sel.end() - 2, sel.end(), true);    // insert the two ones
 | 
			
		||||
    std::vector<double> avgs(clust.size(), 0.0);  // store the average distances
 | 
			
		||||
        // We have to address the case when the direction vector v (same as
 | 
			
		||||
        // dir) is coincident with one of the world axes. In this case two of
 | 
			
		||||
        // its components will be completely zero and one is 1.0. Our method
 | 
			
		||||
        // becomes dangerous here due to division with zero. Instead, vector
 | 
			
		||||
        // 'a' can be an element-wise rotated version of 'v'
 | 
			
		||||
        if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) {
 | 
			
		||||
            a = {n(Z), n(X), n(Y)};
 | 
			
		||||
            b = {n(Y), n(Z), n(X)};
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize();
 | 
			
		||||
            b = a.cross(n);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
        std::array<size_t, 2> idx;
 | 
			
		||||
        for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i;
 | 
			
		||||
    Vec3d get(size_t idx, const Vec3d src, double r) const
 | 
			
		||||
    {
 | 
			
		||||
        double phi = m_phis[idx];
 | 
			
		||||
        double sinphi = std::sin(phi);
 | 
			
		||||
        double cosphi = std::cos(phi);
 | 
			
		||||
 | 
			
		||||
        double d = df(pointfn(clust[idx[0]]),
 | 
			
		||||
                      pointfn(clust[idx[1]]));
 | 
			
		||||
        double rpscos = r * cosphi;
 | 
			
		||||
        double rpssin = r * sinphi;
 | 
			
		||||
 | 
			
		||||
        // add the distance to the sums for both associated points
 | 
			
		||||
        for(auto i : idx) avgs[i] += d;
 | 
			
		||||
        // Point on the sphere
 | 
			
		||||
        return {src(X) + rpscos * a(X) + rpssin * b(X),
 | 
			
		||||
                src(Y) + rpscos * a(Y) + rpssin * b(Y),
 | 
			
		||||
                src(Z) + rpscos * a(Z) + rpssin * b(Z)};
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
        // now continue with the next permutation of the bitmask with two 1s
 | 
			
		||||
    } while(std::next_permutation(sel.begin(), sel.end()));
 | 
			
		||||
 | 
			
		||||
    // Divide by point size in the cluster to get the average (may be redundant)
 | 
			
		||||
    for(auto& a : avgs) a /= clust.size();
 | 
			
		||||
 | 
			
		||||
    // get the lowest average distance and return the index
 | 
			
		||||
    auto minit = std::min_element(avgs.begin(), avgs.end());
 | 
			
		||||
    return long(minit - avgs.begin());
 | 
			
		||||
}
 | 
			
		||||
//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan(""));
 | 
			
		||||
//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan(""));
 | 
			
		||||
 | 
			
		||||
inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) {
 | 
			
		||||
    return (endp - startp).normalized();
 | 
			
		||||
| 
						 | 
				
			
			@ -170,8 +185,8 @@ IntegerOnly<DoubleI> pairhash(I a, I b)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
class SupportTreeBuildsteps {
 | 
			
		||||
    const SupportConfig& m_cfg;
 | 
			
		||||
    const EigenMesh3D& m_mesh;
 | 
			
		||||
    const SupportTreeConfig& m_cfg;
 | 
			
		||||
    const IndexedMesh& m_mesh;
 | 
			
		||||
    const std::vector<SupportPoint>& m_support_pts;
 | 
			
		||||
 | 
			
		||||
    using PtIndices = std::vector<unsigned>;
 | 
			
		||||
| 
						 | 
				
			
			@ -180,7 +195,7 @@ class SupportTreeBuildsteps {
 | 
			
		|||
    PtIndices m_iheads_onmodel;
 | 
			
		||||
    PtIndices m_iheadless;         // headless support points
 | 
			
		||||
    
 | 
			
		||||
    std::map<unsigned, EigenMesh3D::hit_result> m_head_to_ground_scans;
 | 
			
		||||
    std::map<unsigned, IndexedMesh::hit_result> m_head_to_ground_scans;
 | 
			
		||||
 | 
			
		||||
    // normals for support points from model faces.
 | 
			
		||||
    PointSet  m_support_nmls;
 | 
			
		||||
| 
						 | 
				
			
			@ -206,7 +221,7 @@ class SupportTreeBuildsteps {
 | 
			
		|||
    // When bridging heads to pillars... TODO: find a cleaner solution
 | 
			
		||||
    ccr::BlockingMutex m_bridge_mutex;
 | 
			
		||||
 | 
			
		||||
    inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s, 
 | 
			
		||||
    inline IndexedMesh::hit_result ray_mesh_intersect(const Vec3d& s, 
 | 
			
		||||
                                                      const Vec3d& dir)
 | 
			
		||||
    {
 | 
			
		||||
        return m_mesh.query_ray_hit(s, dir);
 | 
			
		||||
| 
						 | 
				
			
			@ -223,16 +238,24 @@ class SupportTreeBuildsteps {
 | 
			
		|||
    // point was inside the model, an "invalid" hit_result will be returned
 | 
			
		||||
    // with a zero distance value instead of a NAN. This way the result can
 | 
			
		||||
    // be used safely for comparison with other distances.
 | 
			
		||||
    EigenMesh3D::hit_result pinhead_mesh_intersect(
 | 
			
		||||
    IndexedMesh::hit_result pinhead_mesh_intersect(
 | 
			
		||||
        const Vec3d& s,
 | 
			
		||||
        const Vec3d& dir,
 | 
			
		||||
        double r_pin,
 | 
			
		||||
        double r_back,
 | 
			
		||||
        double width);
 | 
			
		||||
    
 | 
			
		||||
    template<class...Args>
 | 
			
		||||
    inline double pinhead_mesh_distance(Args&&...args) {
 | 
			
		||||
        return pinhead_mesh_intersect(std::forward<Args>(args)...).distance();
 | 
			
		||||
        double width,
 | 
			
		||||
        double safety_d);
 | 
			
		||||
 | 
			
		||||
    IndexedMesh::hit_result pinhead_mesh_intersect(
 | 
			
		||||
        const Vec3d& s,
 | 
			
		||||
        const Vec3d& dir,
 | 
			
		||||
        double r_pin,
 | 
			
		||||
        double r_back,
 | 
			
		||||
        double width)
 | 
			
		||||
    {
 | 
			
		||||
        return pinhead_mesh_intersect(s, dir, r_pin, r_back, width,
 | 
			
		||||
                                      r_back * m_cfg.safety_distance_mm /
 | 
			
		||||
                                          m_cfg.head_back_radius_mm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Checking bridge (pillar and stick as well) intersection with the model.
 | 
			
		||||
| 
						 | 
				
			
			@ -243,11 +266,21 @@ class SupportTreeBuildsteps {
 | 
			
		|||
    // point was inside the model, an "invalid" hit_result will be returned
 | 
			
		||||
    // with a zero distance value instead of a NAN. This way the result can
 | 
			
		||||
    // be used safely for comparison with other distances.
 | 
			
		||||
    EigenMesh3D::hit_result bridge_mesh_intersect(
 | 
			
		||||
    IndexedMesh::hit_result bridge_mesh_intersect(
 | 
			
		||||
        const Vec3d& s,
 | 
			
		||||
        const Vec3d& dir,
 | 
			
		||||
        double r,
 | 
			
		||||
        bool ins_check = false);
 | 
			
		||||
        double safety_d);
 | 
			
		||||
 | 
			
		||||
    IndexedMesh::hit_result bridge_mesh_intersect(
 | 
			
		||||
        const Vec3d& s,
 | 
			
		||||
        const Vec3d& dir,
 | 
			
		||||
        double r)
 | 
			
		||||
    {
 | 
			
		||||
        return bridge_mesh_intersect(s, dir, r,
 | 
			
		||||
                                     r * m_cfg.safety_distance_mm /
 | 
			
		||||
                                         m_cfg.head_back_radius_mm);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    template<class...Args>
 | 
			
		||||
    inline double bridge_mesh_distance(Args&&...args) {
 | 
			
		||||
| 
						 | 
				
			
			@ -268,20 +301,29 @@ class SupportTreeBuildsteps {
 | 
			
		|||
    inline bool connect_to_ground(Head& head);
 | 
			
		||||
    
 | 
			
		||||
    bool connect_to_model_body(Head &head);
 | 
			
		||||
    
 | 
			
		||||
    bool search_pillar_and_connect(const Head& head);
 | 
			
		||||
 | 
			
		||||
    bool search_pillar_and_connect(const Head& source);
 | 
			
		||||
    
 | 
			
		||||
    // This is a proxy function for pillar creation which will mind the gap
 | 
			
		||||
    // between the pad and the model bottom in zero elevation mode.
 | 
			
		||||
    // jp is the starting junction point which needs to be routed down.
 | 
			
		||||
    // sourcedir is the allowed direction of an optional bridge between the
 | 
			
		||||
    // jp junction and the final pillar.
 | 
			
		||||
    void create_ground_pillar(const Vec3d &jp,
 | 
			
		||||
    bool create_ground_pillar(const Vec3d &jp,
 | 
			
		||||
                              const Vec3d &sourcedir,
 | 
			
		||||
                              double       radius,
 | 
			
		||||
                              long         head_id = ID_UNSET);
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
                              long         head_id = SupportTreeNode::ID_UNSET);
 | 
			
		||||
 | 
			
		||||
    void add_pillar_base(long pid)
 | 
			
		||||
    {
 | 
			
		||||
        m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::optional<DiffBridge> search_widening_path(const Vec3d &jp,
 | 
			
		||||
                                                   const Vec3d &dir,
 | 
			
		||||
                                                   double       radius,
 | 
			
		||||
                                                   double       new_radius);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -324,11 +366,6 @@ public:
 | 
			
		|||
 | 
			
		||||
    void interconnect_pillars();
 | 
			
		||||
 | 
			
		||||
    // Step: process the support points where there is not enough space for a
 | 
			
		||||
    // full pinhead. In this case we will use a rounded sphere as a touching
 | 
			
		||||
    // point and use a thinner bridge (let's call it a stick).
 | 
			
		||||
    void routing_headless ();
 | 
			
		||||
 | 
			
		||||
    inline void merge_result() { m_builder.merged_mesh(); }
 | 
			
		||||
 | 
			
		||||
    static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										266
									
								
								src/libslic3r/SLA/SupportTreeMesher.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								src/libslic3r/SLA/SupportTreeMesher.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,266 @@
 | 
			
		|||
#include "SupportTreeMesher.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Slic3r { namespace sla {
 | 
			
		||||
 | 
			
		||||
Contour3D sphere(double rho, Portion portion, double fa) {
 | 
			
		||||
 | 
			
		||||
    Contour3D ret;
 | 
			
		||||
 | 
			
		||||
    // prohibit close to zero radius
 | 
			
		||||
    if(rho <= 1e-6 && rho >= -1e-6) return ret;
 | 
			
		||||
 | 
			
		||||
    auto& vertices = ret.points;
 | 
			
		||||
    auto& facets = ret.faces3;
 | 
			
		||||
 | 
			
		||||
    // Algorithm:
 | 
			
		||||
    // Add points one-by-one to the sphere grid and form facets using relative
 | 
			
		||||
    // coordinates. Sphere is composed effectively of a mesh of stacked circles.
 | 
			
		||||
 | 
			
		||||
    // adjust via rounding to get an even multiple for any provided angle.
 | 
			
		||||
    double angle = (2*PI / floor(2*PI / fa));
 | 
			
		||||
 | 
			
		||||
    // Ring to be scaled to generate the steps of the sphere
 | 
			
		||||
    std::vector<double> ring;
 | 
			
		||||
 | 
			
		||||
    for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i);
 | 
			
		||||
 | 
			
		||||
    const auto sbegin = size_t(2*std::get<0>(portion)/angle);
 | 
			
		||||
    const auto send = size_t(2*std::get<1>(portion)/angle);
 | 
			
		||||
 | 
			
		||||
    const size_t steps = ring.size();
 | 
			
		||||
    const double increment = 1.0 / double(steps);
 | 
			
		||||
 | 
			
		||||
    // special case: first ring connects to 0,0,0
 | 
			
		||||
    // insert and form facets.
 | 
			
		||||
    if(sbegin == 0)
 | 
			
		||||
        vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho));
 | 
			
		||||
 | 
			
		||||
    auto id = coord_t(vertices.size());
 | 
			
		||||
    for (size_t i = 0; i < ring.size(); i++) {
 | 
			
		||||
        // Fixed scaling
 | 
			
		||||
        const double z = -rho + increment*rho*2.0 * (sbegin + 1.0);
 | 
			
		||||
        // radius of the circle for this step.
 | 
			
		||||
        const double r = std::sqrt(std::abs(rho*rho - z*z));
 | 
			
		||||
        Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
 | 
			
		||||
        vertices.emplace_back(Vec3d(b(0), b(1), z));
 | 
			
		||||
 | 
			
		||||
        if (sbegin == 0)
 | 
			
		||||
            (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) :
 | 
			
		||||
                       facets.emplace_back(id - 1, 0, id);
 | 
			
		||||
        ++id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // General case: insert and form facets for each step,
 | 
			
		||||
    // joining it to the ring below it.
 | 
			
		||||
    for (size_t s = sbegin + 2; s < send - 1; s++) {
 | 
			
		||||
        const double z = -rho + increment*double(s*2.0*rho);
 | 
			
		||||
        const double r = std::sqrt(std::abs(rho*rho - z*z));
 | 
			
		||||
 | 
			
		||||
        for (size_t i = 0; i < ring.size(); i++) {
 | 
			
		||||
            Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
 | 
			
		||||
            vertices.emplace_back(Vec3d(b(0), b(1), z));
 | 
			
		||||
            auto id_ringsize = coord_t(id - int(ring.size()));
 | 
			
		||||
            if (i == 0) {
 | 
			
		||||
                // wrap around
 | 
			
		||||
                facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) );
 | 
			
		||||
                facets.emplace_back(id - 1, id_ringsize, id);
 | 
			
		||||
            } else {
 | 
			
		||||
                facets.emplace_back(id_ringsize - 1, id_ringsize, id);
 | 
			
		||||
                facets.emplace_back(id - 1, id_ringsize - 1, id);
 | 
			
		||||
            }
 | 
			
		||||
            id++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // special case: last ring connects to 0,0,rho*2.0
 | 
			
		||||
    // only form facets.
 | 
			
		||||
    if(send >= size_t(2*PI / angle)) {
 | 
			
		||||
        vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho));
 | 
			
		||||
        for (size_t i = 0; i < ring.size(); i++) {
 | 
			
		||||
            auto id_ringsize = coord_t(id - int(ring.size()));
 | 
			
		||||
            if (i == 0) {
 | 
			
		||||
                // third vertex is on the other side of the ring.
 | 
			
		||||
                facets.emplace_back(id - 1, id_ringsize, id);
 | 
			
		||||
            } else {
 | 
			
		||||
                auto ci = coord_t(id_ringsize + coord_t(i));
 | 
			
		||||
                facets.emplace_back(ci - 1, ci, id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    id++;
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
 | 
			
		||||
{
 | 
			
		||||
    assert(ssteps > 0);
 | 
			
		||||
 | 
			
		||||
    Contour3D ret;
 | 
			
		||||
 | 
			
		||||
    auto steps = int(ssteps);
 | 
			
		||||
    auto& points = ret.points;
 | 
			
		||||
    auto& indices = ret.faces3;
 | 
			
		||||
    points.reserve(2*ssteps);
 | 
			
		||||
    double a = 2*PI/steps;
 | 
			
		||||
 | 
			
		||||
    Vec3d jp = sp;
 | 
			
		||||
    Vec3d endp = {sp(X), sp(Y), sp(Z) + h};
 | 
			
		||||
 | 
			
		||||
    // Upper circle points
 | 
			
		||||
    for(int i = 0; i < steps; ++i) {
 | 
			
		||||
        double phi = i*a;
 | 
			
		||||
        double ex = endp(X) + r*std::cos(phi);
 | 
			
		||||
        double ey = endp(Y) + r*std::sin(phi);
 | 
			
		||||
        points.emplace_back(ex, ey, endp(Z));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Lower circle points
 | 
			
		||||
    for(int i = 0; i < steps; ++i) {
 | 
			
		||||
        double phi = i*a;
 | 
			
		||||
        double x = jp(X) + r*std::cos(phi);
 | 
			
		||||
        double y = jp(Y) + r*std::sin(phi);
 | 
			
		||||
        points.emplace_back(x, y, jp(Z));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Now create long triangles connecting upper and lower circles
 | 
			
		||||
    indices.reserve(2*ssteps);
 | 
			
		||||
    auto offs = steps;
 | 
			
		||||
    for(int i = 0; i < steps - 1; ++i) {
 | 
			
		||||
        indices.emplace_back(i, i + offs, offs + i + 1);
 | 
			
		||||
        indices.emplace_back(i, offs + i + 1, i + 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Last triangle connecting the first and last vertices
 | 
			
		||||
    auto last = steps - 1;
 | 
			
		||||
    indices.emplace_back(0, last, offs);
 | 
			
		||||
    indices.emplace_back(last, offs + last, offs);
 | 
			
		||||
 | 
			
		||||
    // According to the slicing algorithms, we need to aid them with generating
 | 
			
		||||
    // a watertight body. So we create a triangle fan for the upper and lower
 | 
			
		||||
    // ending of the cylinder to close the geometry.
 | 
			
		||||
    points.emplace_back(jp); int ci = int(points.size() - 1);
 | 
			
		||||
    for(int i = 0; i < steps - 1; ++i)
 | 
			
		||||
        indices.emplace_back(i + offs + 1, i + offs, ci);
 | 
			
		||||
 | 
			
		||||
    indices.emplace_back(offs, steps + offs - 1, ci);
 | 
			
		||||
 | 
			
		||||
    points.emplace_back(endp); ci = int(points.size() - 1);
 | 
			
		||||
    for(int i = 0; i < steps - 1; ++i)
 | 
			
		||||
        indices.emplace_back(ci, i, i + 1);
 | 
			
		||||
 | 
			
		||||
    indices.emplace_back(steps - 1, 0, ci);
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps)
 | 
			
		||||
{
 | 
			
		||||
    assert(steps > 0);
 | 
			
		||||
    assert(length >= 0.);
 | 
			
		||||
    assert(r_back > 0.);
 | 
			
		||||
    assert(r_pin > 0.);
 | 
			
		||||
 | 
			
		||||
    Contour3D mesh;
 | 
			
		||||
 | 
			
		||||
    // We create two spheres which will be connected with a robe that fits
 | 
			
		||||
    // both circles perfectly.
 | 
			
		||||
 | 
			
		||||
    // Set up the model detail level
 | 
			
		||||
    const double detail = 2 * PI / steps;
 | 
			
		||||
 | 
			
		||||
    // We don't generate whole circles. Instead, we generate only the
 | 
			
		||||
    // portions which are visible (not covered by the robe) To know the
 | 
			
		||||
    // exact portion of the bottom and top circles we need to use some
 | 
			
		||||
    // rules of tangent circles from which we can derive (using simple
 | 
			
		||||
    // triangles the following relations:
 | 
			
		||||
 | 
			
		||||
    // The height of the whole mesh
 | 
			
		||||
    const double h   = r_back + r_pin + length;
 | 
			
		||||
    double       phi = PI / 2. - std::acos((r_back - r_pin) / h);
 | 
			
		||||
 | 
			
		||||
    // To generate a whole circle we would pass a portion of (0, Pi)
 | 
			
		||||
    // To generate only a half horizontal circle we can pass (0, Pi/2)
 | 
			
		||||
    // The calculated phi is an offset to the half circles needed to smooth
 | 
			
		||||
    // the transition from the circle to the robe geometry
 | 
			
		||||
 | 
			
		||||
    auto &&s1 = sphere(r_back, make_portion(0, PI / 2 + phi), detail);
 | 
			
		||||
    auto &&s2 = sphere(r_pin, make_portion(PI / 2 + phi, PI), detail);
 | 
			
		||||
 | 
			
		||||
    for (auto &p : s2.points) p.z() += h;
 | 
			
		||||
 | 
			
		||||
    mesh.merge(s1);
 | 
			
		||||
    mesh.merge(s2);
 | 
			
		||||
 | 
			
		||||
    for (size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size();
 | 
			
		||||
         idx1 < s1.points.size() - 1; idx1++, idx2++) {
 | 
			
		||||
        coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
 | 
			
		||||
        coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
 | 
			
		||||
 | 
			
		||||
        mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
 | 
			
		||||
        mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
 | 
			
		||||
    auto i2s1 = coord_t(s1.points.size()) - 1;
 | 
			
		||||
    auto i1s2 = coord_t(s1.points.size());
 | 
			
		||||
    auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
 | 
			
		||||
 | 
			
		||||
    mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
 | 
			
		||||
    mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
 | 
			
		||||
 | 
			
		||||
    return mesh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Contour3D halfcone(double       baseheight,
 | 
			
		||||
                   double       r_bottom,
 | 
			
		||||
                   double       r_top,
 | 
			
		||||
                   const Vec3d &pos,
 | 
			
		||||
                   size_t       steps)
 | 
			
		||||
{
 | 
			
		||||
    assert(steps > 0);
 | 
			
		||||
 | 
			
		||||
    if (baseheight <= 0 || steps <= 0) return {};
 | 
			
		||||
 | 
			
		||||
    Contour3D base;
 | 
			
		||||
 | 
			
		||||
    double a    = 2 * PI / steps;
 | 
			
		||||
    auto   last = int(steps - 1);
 | 
			
		||||
    Vec3d  ep{pos.x(), pos.y(), pos.z() + baseheight};
 | 
			
		||||
    for (size_t i = 0; i < steps; ++i) {
 | 
			
		||||
        double phi = i * a;
 | 
			
		||||
        double x   = pos.x() + r_top * std::cos(phi);
 | 
			
		||||
        double y   = pos.y() + r_top * std::sin(phi);
 | 
			
		||||
        base.points.emplace_back(x, y, ep.z());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 0; i < steps; ++i) {
 | 
			
		||||
        double phi = i * a;
 | 
			
		||||
        double x   = pos.x() + r_bottom * std::cos(phi);
 | 
			
		||||
        double y   = pos.y() + r_bottom * std::sin(phi);
 | 
			
		||||
        base.points.emplace_back(x, y, pos.z());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    base.points.emplace_back(pos);
 | 
			
		||||
    base.points.emplace_back(ep);
 | 
			
		||||
 | 
			
		||||
    auto &indices = base.faces3;
 | 
			
		||||
    auto  hcenter = int(base.points.size() - 1);
 | 
			
		||||
    auto  lcenter = int(base.points.size() - 2);
 | 
			
		||||
    auto  offs    = int(steps);
 | 
			
		||||
    for (int i = 0; i < last; ++i) {
 | 
			
		||||
        indices.emplace_back(i, i + offs, offs + i + 1);
 | 
			
		||||
        indices.emplace_back(i, offs + i + 1, i + 1);
 | 
			
		||||
        indices.emplace_back(i, i + 1, hcenter);
 | 
			
		||||
        indices.emplace_back(lcenter, offs + i + 1, offs + i);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    indices.emplace_back(0, last, offs);
 | 
			
		||||
    indices.emplace_back(last, offs + last, offs);
 | 
			
		||||
    indices.emplace_back(hcenter, last, 0);
 | 
			
		||||
    indices.emplace_back(offs, offs + last, lcenter);
 | 
			
		||||
 | 
			
		||||
    return base;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::sla
 | 
			
		||||
							
								
								
									
										117
									
								
								src/libslic3r/SLA/SupportTreeMesher.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/libslic3r/SLA/SupportTreeMesher.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,117 @@
 | 
			
		|||
#ifndef SUPPORTTREEMESHER_HPP
 | 
			
		||||
#define SUPPORTTREEMESHER_HPP
 | 
			
		||||
 | 
			
		||||
#include "libslic3r/Point.hpp"
 | 
			
		||||
 | 
			
		||||
#include "libslic3r/SLA/SupportTreeBuilder.hpp"
 | 
			
		||||
#include "libslic3r/SLA/Contour3D.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Slic3r { namespace sla {
 | 
			
		||||
 | 
			
		||||
using Portion = std::tuple<double, double>;
 | 
			
		||||
 | 
			
		||||
inline Portion make_portion(double a, double b)
 | 
			
		||||
{
 | 
			
		||||
    return std::make_tuple(a, b);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Contour3D sphere(double  rho,
 | 
			
		||||
                 Portion portion = make_portion(0., 2. * PI),
 | 
			
		||||
                 double  fa      = (2. * PI / 360.));
 | 
			
		||||
 | 
			
		||||
// Down facing cylinder in Z direction with arguments:
 | 
			
		||||
// r: radius
 | 
			
		||||
// h: Height
 | 
			
		||||
// ssteps: how many edges will create the base circle
 | 
			
		||||
// sp: starting point
 | 
			
		||||
Contour3D cylinder(double       r,
 | 
			
		||||
                   double       h,
 | 
			
		||||
                   size_t       steps = 45,
 | 
			
		||||
                   const Vec3d &sp    = Vec3d::Zero());
 | 
			
		||||
 | 
			
		||||
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45);
 | 
			
		||||
 | 
			
		||||
Contour3D halfcone(double       baseheight,
 | 
			
		||||
                   double       r_bottom,
 | 
			
		||||
                   double       r_top,
 | 
			
		||||
                   const Vec3d &pt    = Vec3d::Zero(),
 | 
			
		||||
                   size_t       steps = 45);
 | 
			
		||||
 | 
			
		||||
inline Contour3D get_mesh(const Head &h, size_t steps)
 | 
			
		||||
{
 | 
			
		||||
    Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps);
 | 
			
		||||
 | 
			
		||||
    for(auto& p : mesh.points) p.z() -= (h.fullwidth() - h.r_back_mm);
 | 
			
		||||
 | 
			
		||||
    using Quaternion = Eigen::Quaternion<double>;
 | 
			
		||||
 | 
			
		||||
    // We rotate the head to the specified direction. The head's pointing
 | 
			
		||||
    // side is facing upwards so this means that it would hold a support
 | 
			
		||||
    // point with a normal pointing straight down. This is the reason of
 | 
			
		||||
    // the -1 z coordinate
 | 
			
		||||
    auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir);
 | 
			
		||||
 | 
			
		||||
    for(auto& p : mesh.points) p = quatern * p + h.pos;
 | 
			
		||||
 | 
			
		||||
    return mesh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline Contour3D get_mesh(const Pillar &p, size_t steps)
 | 
			
		||||
{
 | 
			
		||||
    if(p.height > EPSILON) { // Endpoint is below the starting point
 | 
			
		||||
        // We just create a bridge geometry with the pillar parameters and
 | 
			
		||||
        // move the data.
 | 
			
		||||
        return cylinder(p.r, p.height, steps, p.endpoint());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline Contour3D get_mesh(const Pedestal &p, size_t steps)
 | 
			
		||||
{
 | 
			
		||||
    return halfcone(p.height, p.r_bottom, p.r_top, p.pos, steps);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline Contour3D get_mesh(const Junction &j, size_t steps)
 | 
			
		||||
{
 | 
			
		||||
    Contour3D mesh = sphere(j.r, make_portion(0, PI), 2 *PI / steps);
 | 
			
		||||
    for(auto& p : mesh.points) p += j.pos;
 | 
			
		||||
    return mesh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline Contour3D get_mesh(const Bridge &br, size_t steps)
 | 
			
		||||
{
 | 
			
		||||
    using Quaternion = Eigen::Quaternion<double>;
 | 
			
		||||
    Vec3d v = (br.endp - br.startp);
 | 
			
		||||
    Vec3d dir = v.normalized();
 | 
			
		||||
    double d = v.norm();
 | 
			
		||||
 | 
			
		||||
    Contour3D mesh = cylinder(br.r, d, steps);
 | 
			
		||||
 | 
			
		||||
    auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
 | 
			
		||||
    for(auto& p : mesh.points) p = quater * p + br.startp;
 | 
			
		||||
 | 
			
		||||
    return mesh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline Contour3D get_mesh(const DiffBridge &br, size_t steps)
 | 
			
		||||
{
 | 
			
		||||
    double h = br.get_length();
 | 
			
		||||
    Contour3D mesh = halfcone(h, br.r, br.end_r, Vec3d::Zero(), steps);
 | 
			
		||||
 | 
			
		||||
    using Quaternion = Eigen::Quaternion<double>;
 | 
			
		||||
 | 
			
		||||
    // We rotate the head to the specified direction. The head's pointing
 | 
			
		||||
    // side is facing upwards so this means that it would hold a support
 | 
			
		||||
    // point with a normal pointing straight down. This is the reason of
 | 
			
		||||
    // the -1 z coordinate
 | 
			
		||||
    auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, 1}, br.get_dir());
 | 
			
		||||
 | 
			
		||||
    for(auto& p : mesh.points) p = quatern * p + br.startp;
 | 
			
		||||
 | 
			
		||||
    return mesh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::sla
 | 
			
		||||
 | 
			
		||||
#endif // SUPPORTTREEMESHER_HPP
 | 
			
		||||
| 
						 | 
				
			
			@ -35,13 +35,16 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Compile the argument for support creation from the static print config.
 | 
			
		||||
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c)
 | 
			
		||||
sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c)
 | 
			
		||||
{
 | 
			
		||||
    sla::SupportConfig scfg;
 | 
			
		||||
    sla::SupportTreeConfig scfg;
 | 
			
		||||
    
 | 
			
		||||
    scfg.enabled = c.supports_enable.getBool();
 | 
			
		||||
    scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
 | 
			
		||||
    scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat();
 | 
			
		||||
    double pillar_r = 0.5 * c.support_pillar_diameter.getFloat();
 | 
			
		||||
    scfg.head_back_radius_mm = pillar_r;
 | 
			
		||||
    scfg.head_fallback_radius_mm =
 | 
			
		||||
        0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r;
 | 
			
		||||
    scfg.head_penetration_mm = c.support_head_penetration.getFloat();
 | 
			
		||||
    scfg.head_width_mm = c.support_head_width.getFloat();
 | 
			
		||||
    scfg.object_elevation_mm = is_zero_elevation(c) ?
 | 
			
		||||
| 
						 | 
				
			
			@ -616,7 +619,7 @@ std::string SLAPrint::validate() const
 | 
			
		|||
            return L("Cannot proceed without support points! "
 | 
			
		||||
                     "Add support points or disable support generation.");
 | 
			
		||||
 | 
			
		||||
        sla::SupportConfig cfg = make_support_cfg(po->config());
 | 
			
		||||
        sla::SupportTreeConfig cfg = make_support_cfg(po->config());
 | 
			
		||||
 | 
			
		||||
        double elv = cfg.object_elevation_mm;
 | 
			
		||||
        
 | 
			
		||||
| 
						 | 
				
			
			@ -925,6 +928,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
 | 
			
		|||
            || opt_key == "support_head_penetration"
 | 
			
		||||
            || opt_key == "support_head_width"
 | 
			
		||||
            || opt_key == "support_pillar_diameter"
 | 
			
		||||
            || opt_key == "support_small_pillar_diameter_percent"
 | 
			
		||||
            || opt_key == "support_max_bridges_on_pillar"
 | 
			
		||||
            || opt_key == "support_pillar_connection_mode"
 | 
			
		||||
            || opt_key == "support_buildplate_only"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -544,7 +544,7 @@ private:
 | 
			
		|||
 | 
			
		||||
bool is_zero_elevation(const SLAPrintObjectConfig &c);
 | 
			
		||||
 | 
			
		||||
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c);
 | 
			
		||||
sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c);
 | 
			
		||||
 | 
			
		||||
sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -360,18 +360,6 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
 | 
			
		|||
        // removed them on purpose. No calculation will be done.
 | 
			
		||||
        po.m_supportdata->pts = po.transformed_support_points();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the zero elevation mode is engaged, we have to filter out all the
 | 
			
		||||
    // points that are on the bottom of the object
 | 
			
		||||
    if (is_zero_elevation(po.config())) {
 | 
			
		||||
        double tolerance = po.config().pad_enable.getBool() ?
 | 
			
		||||
                               po.m_config.pad_wall_thickness.getFloat() :
 | 
			
		||||
                               po.m_config.support_base_height.getFloat();
 | 
			
		||||
 | 
			
		||||
        remove_bottom_points(po.m_supportdata->pts,
 | 
			
		||||
                             po.m_supportdata->emesh.ground_level(),
 | 
			
		||||
                             tolerance);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SLAPrint::Steps::support_tree(SLAPrintObject &po)
 | 
			
		||||
| 
						 | 
				
			
			@ -382,6 +370,13 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
 | 
			
		|||
    
 | 
			
		||||
    if (pcfg.embed_object)
 | 
			
		||||
        po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
 | 
			
		||||
 | 
			
		||||
    // If the zero elevation mode is engaged, we have to filter out all the
 | 
			
		||||
    // points that are on the bottom of the object
 | 
			
		||||
    if (is_zero_elevation(po.config())) {
 | 
			
		||||
        remove_bottom_points(po.m_supportdata->pts,
 | 
			
		||||
                             float(po.m_supportdata->emesh.ground_level() + EPSILON));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    po.m_supportdata->cfg = make_support_cfg(po.m_config);
 | 
			
		||||
//    po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,8 +54,5 @@
 | 
			
		|||
// Enable built-in DPI changed event handler of wxWidgets 3.1.3
 | 
			
		||||
#define ENABLE_WX_3_1_3_DPI_CHANGED_EVENT (1 && ENABLE_2_3_0_ALPHA1)
 | 
			
		||||
 | 
			
		||||
// Enable changing application layout without the need to restart
 | 
			
		||||
#define ENABLE_LAYOUT_NO_RESTART (1 && ENABLE_2_3_0_ALPHA1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif // _prusaslicer_technologies_h_
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -163,6 +163,8 @@ set(SLIC3R_GUI_SOURCES
 | 
			
		|||
    GUI/InstanceCheck.hpp
 | 
			
		||||
    GUI/Search.cpp
 | 
			
		||||
    GUI/Search.hpp
 | 
			
		||||
    GUI/NotificationManager.cpp
 | 
			
		||||
    GUI/NotificationManager.hpp
 | 
			
		||||
    Utils/Http.cpp
 | 
			
		||||
    Utils/Http.hpp
 | 
			
		||||
    Utils/FixModelByWin10.cpp
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,11 +89,13 @@ void BackgroundSlicingProcess::process_fff()
 | 
			
		|||
{
 | 
			
		||||
	assert(m_print == m_fff_print);
 | 
			
		||||
    m_print->process();
 | 
			
		||||
	wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id));
 | 
			
		||||
	wxCommandEvent evt(m_event_slicing_completed_id);
 | 
			
		||||
	evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psBrim).timestamp));
 | 
			
		||||
	wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
 | 
			
		||||
    m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
 | 
			
		||||
 | 
			
		||||
	if (this->set_step_started(bspsGCodeFinalize)) {
 | 
			
		||||
	    if (! m_export_path.empty()) {
 | 
			
		||||
			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
 | 
			
		||||
	    	//FIXME localize the messages
 | 
			
		||||
	    	// Perform the final post-processing of the export path by applying the print statistics over the file name.
 | 
			
		||||
	    	std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
 | 
			
		||||
| 
						 | 
				
			
			@ -124,6 +126,7 @@ void BackgroundSlicingProcess::process_fff()
 | 
			
		|||
	    	run_post_process_scripts(export_path, m_fff_print->config());
 | 
			
		||||
	    	m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
 | 
			
		||||
	    } else if (! m_upload_job.empty()) {
 | 
			
		||||
			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
 | 
			
		||||
			prepare_upload();
 | 
			
		||||
	    } else {
 | 
			
		||||
			m_print->set_status(100, _utf8(L("Slicing complete")));
 | 
			
		||||
| 
						 | 
				
			
			@ -149,6 +152,8 @@ void BackgroundSlicingProcess::process_sla()
 | 
			
		|||
    m_print->process();
 | 
			
		||||
    if (this->set_step_started(bspsGCodeFinalize)) {
 | 
			
		||||
        if (! m_export_path.empty()) {
 | 
			
		||||
			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
 | 
			
		||||
 | 
			
		||||
            const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
 | 
			
		||||
 | 
			
		||||
            Zipper zipper(export_path);
 | 
			
		||||
| 
						 | 
				
			
			@ -170,6 +175,7 @@ void BackgroundSlicingProcess::process_sla()
 | 
			
		|||
 | 
			
		||||
            m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
 | 
			
		||||
        } else if (! m_upload_job.empty()) {
 | 
			
		||||
			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
 | 
			
		||||
            prepare_upload();
 | 
			
		||||
        } else {
 | 
			
		||||
			m_print->set_status(100, _utf8(L("Slicing complete")));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -60,6 +60,10 @@ public:
 | 
			
		|||
	// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished.
 | 
			
		||||
	// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
 | 
			
		||||
	void set_finished_event(int event_id) { m_event_finished_id = event_id; }
 | 
			
		||||
	// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code is being exported to
 | 
			
		||||
	// specified path or uploaded.
 | 
			
		||||
	// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
 | 
			
		||||
	void set_export_began_event(int event_id) { m_event_export_began_id = event_id; }
 | 
			
		||||
 | 
			
		||||
	// Activate either m_fff_print or m_sla_print.
 | 
			
		||||
	// Return true if changed.
 | 
			
		||||
| 
						 | 
				
			
			@ -190,6 +194,9 @@ private:
 | 
			
		|||
	int 						m_event_slicing_completed_id 	= 0;
 | 
			
		||||
	// wxWidgets command ID to be sent to the plater to inform that the task finished.
 | 
			
		||||
	int 						m_event_finished_id  			= 0;
 | 
			
		||||
	// wxWidgets command ID to be sent to the plater to inform that the G-code is being exported.
 | 
			
		||||
	int                         m_event_export_began_id         = 0;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}; // namespace Slic3r
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -353,6 +353,7 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
 | 
			
		|||
    toggle_field("support_head_penetration", supports_en);
 | 
			
		||||
    toggle_field("support_head_width", supports_en);
 | 
			
		||||
    toggle_field("support_pillar_diameter", supports_en);
 | 
			
		||||
    toggle_field("support_small_pillar_diameter_percent", supports_en);
 | 
			
		||||
    toggle_field("support_max_bridges_on_pillar", supports_en);
 | 
			
		||||
    toggle_field("support_pillar_connection_mode", supports_en);
 | 
			
		||||
    toggle_field("support_buildplate_only", supports_en);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,7 @@
 | 
			
		|||
#include "GUI_ObjectManipulation.hpp"
 | 
			
		||||
#include "Mouse3DController.hpp"
 | 
			
		||||
#include "I18N.hpp"
 | 
			
		||||
#include "NotificationManager.hpp"
 | 
			
		||||
 | 
			
		||||
#if ENABLE_RETINA_GL
 | 
			
		||||
#include "slic3r/Utils/RetinaHelper.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -225,56 +226,44 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
 | 
			
		|||
    static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
 | 
			
		||||
 | 
			
		||||
    const Size& cnv_size = canvas.get_canvas_size();
 | 
			
		||||
    float canvas_w = (float)cnv_size.get_width();
 | 
			
		||||
    float canvas_h = (float)cnv_size.get_height();
 | 
			
		||||
 | 
			
		||||
    ImGuiWrapper& imgui = *wxGetApp().imgui();
 | 
			
		||||
    imgui.set_next_window_pos(canvas_w - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, canvas_h, ImGuiCond_Always, 1.0f, 1.0f);
 | 
			
		||||
    imgui.set_next_window_pos(static_cast<float>(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, 
 | 
			
		||||
        static_cast<float>(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f);
 | 
			
		||||
 | 
			
		||||
    imgui.begin(_(L("Variable layer height")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
 | 
			
		||||
    imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
 | 
			
		||||
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
 | 
			
		||||
    imgui.text(_(L("Left mouse button:")));
 | 
			
		||||
    ImGui::PopStyleColor();
 | 
			
		||||
    imgui.text_colored(ORANGE, _L("Left mouse button:"));
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    imgui.text(_(L("Add detail")));
 | 
			
		||||
    imgui.text(_L("Add detail"));
 | 
			
		||||
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
 | 
			
		||||
    imgui.text(_(L("Right mouse button:")));
 | 
			
		||||
    ImGui::PopStyleColor();
 | 
			
		||||
    imgui.text_colored(ORANGE, _L("Right mouse button:"));
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    imgui.text(_(L("Remove detail")));
 | 
			
		||||
    imgui.text(_L("Remove detail"));
 | 
			
		||||
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
 | 
			
		||||
    imgui.text(_(L("Shift + Left mouse button:")));
 | 
			
		||||
    ImGui::PopStyleColor();
 | 
			
		||||
    imgui.text_colored(ORANGE, _L("Shift + Left mouse button:"));
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    imgui.text(_(L("Reset to base")));
 | 
			
		||||
    imgui.text(_L("Reset to base"));
 | 
			
		||||
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
 | 
			
		||||
    imgui.text(_(L("Shift + Right mouse button:")));
 | 
			
		||||
    ImGui::PopStyleColor();
 | 
			
		||||
    imgui.text_colored(ORANGE, _L("Shift + Right mouse button:"));
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    imgui.text(_(L("Smoothing")));
 | 
			
		||||
    imgui.text(_L("Smoothing"));
 | 
			
		||||
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
 | 
			
		||||
    imgui.text(_(L("Mouse wheel:")));
 | 
			
		||||
    ImGui::PopStyleColor();
 | 
			
		||||
    imgui.text_colored(ORANGE, _L("Mouse wheel:"));
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    imgui.text(_(L("Increase/decrease edit area")));
 | 
			
		||||
    imgui.text(_L("Increase/decrease edit area"));
 | 
			
		||||
    
 | 
			
		||||
    ImGui::Separator();
 | 
			
		||||
    if (imgui.button(_(L("Adaptive"))))
 | 
			
		||||
    if (imgui.button(_L("Adaptive")))
 | 
			
		||||
        wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event<float>(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality));
 | 
			
		||||
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    float text_align = ImGui::GetCursorPosX();
 | 
			
		||||
    ImGui::AlignTextToFramePadding();
 | 
			
		||||
    imgui.text(_(L("Quality / Speed")));
 | 
			
		||||
    if (ImGui::IsItemHovered())
 | 
			
		||||
    {
 | 
			
		||||
    imgui.text(_L("Quality / Speed"));
 | 
			
		||||
    if (ImGui::IsItemHovered()) {
 | 
			
		||||
        ImGui::BeginTooltip();
 | 
			
		||||
        ImGui::TextUnformatted(_(L("Higher print quality versus higher print speed.")).ToUTF8());
 | 
			
		||||
        ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8());
 | 
			
		||||
        ImGui::EndTooltip();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -285,13 +274,13 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
 | 
			
		|||
    ImGui::SliderFloat("", &m_adaptive_quality, 0.0f, 1.f, "%.2f");
 | 
			
		||||
 | 
			
		||||
    ImGui::Separator();
 | 
			
		||||
    if (imgui.button(_(L("Smooth"))))
 | 
			
		||||
    if (imgui.button(_L("Smooth")))
 | 
			
		||||
        wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params));
 | 
			
		||||
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    ImGui::SetCursorPosX(text_align);
 | 
			
		||||
    ImGui::AlignTextToFramePadding();
 | 
			
		||||
    imgui.text(_(L("Radius")));
 | 
			
		||||
    imgui.text(_L("Radius"));
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    ImGui::SetCursorPosX(widget_align);
 | 
			
		||||
    ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f);
 | 
			
		||||
| 
						 | 
				
			
			@ -301,7 +290,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
 | 
			
		|||
 | 
			
		||||
    ImGui::SetCursorPosX(text_align);
 | 
			
		||||
    ImGui::AlignTextToFramePadding();
 | 
			
		||||
    imgui.text(_(L("Keep min")));
 | 
			
		||||
    imgui.text(_L("Keep min"));
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    if (ImGui::GetCursorPosX() < widget_align)  // because of line lenght after localization
 | 
			
		||||
        ImGui::SetCursorPosX(widget_align);
 | 
			
		||||
| 
						 | 
				
			
			@ -310,7 +299,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
 | 
			
		|||
    imgui.checkbox("##2", m_smooth_params.keep_min);
 | 
			
		||||
 | 
			
		||||
    ImGui::Separator();
 | 
			
		||||
    if (imgui.button(_(L("Reset"))))
 | 
			
		||||
    if (imgui.button(_L("Reset")))
 | 
			
		||||
        wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
 | 
			
		||||
 | 
			
		||||
    imgui.end();
 | 
			
		||||
| 
						 | 
				
			
			@ -663,19 +652,45 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
 | 
			
		|||
 | 
			
		||||
        m_warnings.emplace_back(warning);
 | 
			
		||||
        std::sort(m_warnings.begin(), m_warnings.end());
 | 
			
		||||
 | 
			
		||||
		std::string text;
 | 
			
		||||
		switch (warning) {
 | 
			
		||||
			case ObjectOutside: text = L("An object outside the print area was detected."); break;
 | 
			
		||||
			case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break;
 | 
			
		||||
			case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break;
 | 
			
		||||
			case SomethingNotShown: text = L("Some objects are not visible."); break;
 | 
			
		||||
			case ObjectClashed: wxGetApp().plater()->get_notification_manager()->push_plater_error_notification(L("An object outside the print area was detected.\n"
 | 
			
		||||
																												 "Resolve the current problem to continue slicing."), 
 | 
			
		||||
				                                                                                                *(wxGetApp().plater()->get_current_canvas3D()));
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
		if (!text.empty())
 | 
			
		||||
			wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D()));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        if (it == m_warnings.end()) // deactivating something that is not active is an easy task
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        m_warnings.erase(it);
 | 
			
		||||
        if (m_warnings.empty()) { // nothing remains to be shown
 | 
			
		||||
 | 
			
		||||
		std::string text;
 | 
			
		||||
		switch (warning) {
 | 
			
		||||
		case ObjectOutside: text = L("An object outside the print area was detected."); break;
 | 
			
		||||
		case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break;
 | 
			
		||||
		case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break;
 | 
			
		||||
		case SomethingNotShown: text = L("Some objects are not visibl.e"); break;
 | 
			
		||||
		case ObjectClashed: wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(); break;
 | 
			
		||||
		}
 | 
			
		||||
		if (!text.empty())
 | 
			
		||||
			wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text);
 | 
			
		||||
 | 
			
		||||
        /*if (m_warnings.empty()) { // nothing remains to be shown
 | 
			
		||||
            reset();
 | 
			
		||||
            m_msg_text = "";// save information for rescaling
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        }*/
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
    // Look at the end of our vector and generate proper texture.
 | 
			
		||||
    std::string text;
 | 
			
		||||
    bool red_colored = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -697,6 +712,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
 | 
			
		|||
    // save information for rescaling
 | 
			
		||||
    m_msg_text = text;
 | 
			
		||||
    m_is_colored_red = red_colored;
 | 
			
		||||
	*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1430,8 +1446,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
 | 
			
		|||
#if ENABLE_SLOPE_RENDERING
 | 
			
		||||
void GLCanvas3D::Slope::render() const
 | 
			
		||||
{
 | 
			
		||||
    if (m_dialog_shown)
 | 
			
		||||
    {
 | 
			
		||||
    if (m_dialog_shown) {
 | 
			
		||||
        const std::array<float, 2>& z_range = m_volumes.get_slope_z_range();
 | 
			
		||||
        std::array<float, 2> angle_range = { Geometry::rad2deg(::acos(z_range[0])) - 90.0f, Geometry::rad2deg(::acos(z_range[1])) - 90.0f };
 | 
			
		||||
        bool modified = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -1439,9 +1454,9 @@ void GLCanvas3D::Slope::render() const
 | 
			
		|||
        ImGuiWrapper& imgui = *wxGetApp().imgui();
 | 
			
		||||
        const Size& cnv_size = m_canvas.get_canvas_size();
 | 
			
		||||
        imgui.set_next_window_pos((float)cnv_size.get_width(), (float)cnv_size.get_height(), ImGuiCond_Always, 1.0f, 1.0f);
 | 
			
		||||
        imgui.begin(_(L("Slope visualization")), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
 | 
			
		||||
        imgui.begin(_L("Slope visualization"), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
 | 
			
		||||
 | 
			
		||||
        imgui.text(_(L("Facets' slope range (degrees)")) + ":");
 | 
			
		||||
        imgui.text(_L("Facets' slope range (degrees)") + ":");
 | 
			
		||||
 | 
			
		||||
        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.0f, 0.0f, 0.5f));
 | 
			
		||||
        ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 0.0f, 0.0f, 0.5f));
 | 
			
		||||
| 
						 | 
				
			
			@ -1453,8 +1468,7 @@ void GLCanvas3D::Slope::render() const
 | 
			
		|||
        float slope_bound = 90.f - angle_range[1];
 | 
			
		||||
        bool mod = ImGui::SliderFloat("##red", &slope_bound, 0.0f, 90.0f, "%.1f");
 | 
			
		||||
        angle_range[1] = 90.f - slope_bound;
 | 
			
		||||
        if (mod)
 | 
			
		||||
        {
 | 
			
		||||
        if (mod) {
 | 
			
		||||
            modified = true;
 | 
			
		||||
            if (angle_range[0] > angle_range[1])
 | 
			
		||||
                angle_range[0] = angle_range[1];
 | 
			
		||||
| 
						 | 
				
			
			@ -1462,15 +1476,14 @@ void GLCanvas3D::Slope::render() const
 | 
			
		|||
 | 
			
		||||
        ImGui::PopStyleColor(4);
 | 
			
		||||
        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.75f, 0.0f, 0.5f));
 | 
			
		||||
                ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f));
 | 
			
		||||
                ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f));
 | 
			
		||||
                ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f));
 | 
			
		||||
        ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f));
 | 
			
		||||
        ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f));
 | 
			
		||||
        ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f));
 | 
			
		||||
 | 
			
		||||
        slope_bound = 90.f - angle_range[0];
 | 
			
		||||
        mod = ImGui::SliderFloat("##yellow", &slope_bound, 0.0f, 90.0f, "%.1f");
 | 
			
		||||
        angle_range[0] = 90.f - slope_bound;
 | 
			
		||||
        if (mod)
 | 
			
		||||
        {
 | 
			
		||||
        if (mod) {
 | 
			
		||||
            modified = true;
 | 
			
		||||
            if (angle_range[1] < angle_range[0])
 | 
			
		||||
                angle_range[1] = angle_range[0];
 | 
			
		||||
| 
						 | 
				
			
			@ -2089,6 +2102,8 @@ void GLCanvas3D::render()
 | 
			
		|||
 | 
			
		||||
    std::string tooltip;
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	// Negative coordinate means out of the window, likely because the window was deactivated.
 | 
			
		||||
	// In that case the tooltip should be hidden.
 | 
			
		||||
    if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) 
 | 
			
		||||
| 
						 | 
				
			
			@ -2118,6 +2133,8 @@ void GLCanvas3D::render()
 | 
			
		|||
        m_tooltip.render(m_mouse.position, *this);
 | 
			
		||||
 | 
			
		||||
    wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
 | 
			
		||||
	
 | 
			
		||||
	wxGetApp().plater()->get_notification_manager()->render_notifications(*this);
 | 
			
		||||
 | 
			
		||||
    wxGetApp().imgui()->render();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3433,6 +3450,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
 | 
			
		||||
        printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
 | 
			
		||||
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
 | 
			
		||||
		 m_dirty = true;
 | 
			
		||||
        // do not return if dragging or tooltip not empty to allow for tooltip update
 | 
			
		||||
        if (!m_mouse.dragging && m_tooltip.is_empty())
 | 
			
		||||
            return;
 | 
			
		||||
| 
						 | 
				
			
			@ -3826,7 +3844,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
 | 
			
		|||
            m_gizmos.reset_all_states();
 | 
			
		||||
 | 
			
		||||
        // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
 | 
			
		||||
        if (m_picking_enabled)
 | 
			
		||||
        //if (m_picking_enabled)
 | 
			
		||||
            m_dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,6 +57,7 @@
 | 
			
		|||
#include "Mouse3DController.hpp"
 | 
			
		||||
#include "RemovableDriveManager.hpp"
 | 
			
		||||
#include "InstanceCheck.hpp"
 | 
			
		||||
#include "NotificationManager.hpp"
 | 
			
		||||
 | 
			
		||||
#ifdef __WXMSW__
 | 
			
		||||
#include <dbt.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -387,7 +388,7 @@ bool GUI_App::on_init_inner()
 | 
			
		|||
    // supplied as argument to --datadir; in that case we should still run the wizard
 | 
			
		||||
    preset_bundle->setup_directories();
 | 
			
		||||
 | 
			
		||||
#ifdef __WXMSW__
 | 
			
		||||
#ifdef __WXMSW__ 
 | 
			
		||||
    associate_3mf_files();
 | 
			
		||||
#endif // __WXMSW__
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -395,6 +396,11 @@ bool GUI_App::on_init_inner()
 | 
			
		|||
    Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) {
 | 
			
		||||
        app_config->set("version_online", into_u8(evt.GetString()));
 | 
			
		||||
        app_config->save();
 | 
			
		||||
		if(this->plater_ != nullptr) {
 | 
			
		||||
			if (*Semver::parse(SLIC3R_VERSION) < * Semver::parse(into_u8(evt.GetString()))) {
 | 
			
		||||
				this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D()));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // initialize label colors and fonts
 | 
			
		||||
| 
						 | 
				
			
			@ -1085,34 +1091,21 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
 | 
			
		|||
            break;
 | 
			
		||||
        case ConfigMenuPreferences:
 | 
			
		||||
        {
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            bool app_layout_changed = false;
 | 
			
		||||
#else
 | 
			
		||||
            bool recreate_app = false;
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            {
 | 
			
		||||
                // the dialog needs to be destroyed before the call to recreate_GUI()
 | 
			
		||||
                // or sometimes the application crashes into wxDialogBase() destructor
 | 
			
		||||
                // so we put it into an inner scope
 | 
			
		||||
                PreferencesDialog dlg(mainframe);
 | 
			
		||||
                dlg.ShowModal();
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
                app_layout_changed = dlg.settings_layout_changed();
 | 
			
		||||
#else
 | 
			
		||||
                recreate_app = dlg.settings_layout_changed();
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            }
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            if (app_layout_changed) {
 | 
			
		||||
                mainframe->GetSizer()->Hide((size_t)0);
 | 
			
		||||
                mainframe->update_layout();
 | 
			
		||||
                mainframe->select_tab(0);
 | 
			
		||||
                mainframe->GetSizer()->Show((size_t)0);
 | 
			
		||||
            }
 | 
			
		||||
#else
 | 
			
		||||
            if (recreate_app)
 | 
			
		||||
                recreate_GUI(_L("Changing of the settings layout") + dots);
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case ConfigMenuLanguage:
 | 
			
		||||
| 
						 | 
				
			
			@ -1480,7 +1473,7 @@ void GUI_App::check_updates(const bool verbose)
 | 
			
		|||
	
 | 
			
		||||
	PresetUpdater::UpdateResult updater_result;
 | 
			
		||||
	try {
 | 
			
		||||
		updater_result = preset_updater->config_update(app_config->orig_version());
 | 
			
		||||
		updater_result = preset_updater->config_update(app_config->orig_version(), verbose);
 | 
			
		||||
		if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
 | 
			
		||||
			mainframe->Close();
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -195,12 +195,15 @@ public:
 | 
			
		|||
    Plater*             plater();
 | 
			
		||||
    Model&      		model();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    AppConfig*      app_config{ nullptr };
 | 
			
		||||
    PresetBundle*   preset_bundle{ nullptr };
 | 
			
		||||
    PresetUpdater*  preset_updater{ nullptr };
 | 
			
		||||
    MainFrame*      mainframe{ nullptr };
 | 
			
		||||
    Plater*         plater_{ nullptr };
 | 
			
		||||
 | 
			
		||||
	PresetUpdater* get_preset_updater() { return preset_updater; }
 | 
			
		||||
 | 
			
		||||
    wxNotebook*     tab_panel() const ;
 | 
			
		||||
    int             extruders_cnt() const;
 | 
			
		||||
    int             extruders_edited_cnt() const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -91,7 +91,7 @@ ObjectList::ObjectList(wxWindow* parent) :
 | 
			
		|||
        // ptFFF
 | 
			
		||||
        CATEGORY_ICON[L("Layers and Perimeters")]    = create_scaled_bitmap("layers");
 | 
			
		||||
        CATEGORY_ICON[L("Infill")]                   = create_scaled_bitmap("infill");
 | 
			
		||||
        CATEGORY_ICON[L("Ironing")]                  = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available
 | 
			
		||||
        CATEGORY_ICON[L("Ironing")]                  = create_scaled_bitmap("ironing");
 | 
			
		||||
        CATEGORY_ICON[L("Support material")]         = create_scaled_bitmap("support");
 | 
			
		||||
        CATEGORY_ICON[L("Speed")]                    = create_scaled_bitmap("time");
 | 
			
		||||
        CATEGORY_ICON[L("Extruders")]                = create_scaled_bitmap("funnel");
 | 
			
		||||
| 
						 | 
				
			
			@ -642,7 +642,7 @@ void ObjectList::msw_rescale_icons()
 | 
			
		|||
        // ptFFF
 | 
			
		||||
        CATEGORY_ICON[L("Layers and Perimeters")]    = create_scaled_bitmap("layers");
 | 
			
		||||
        CATEGORY_ICON[L("Infill")]                   = create_scaled_bitmap("infill");
 | 
			
		||||
        CATEGORY_ICON[L("Ironing")]                  = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available
 | 
			
		||||
        CATEGORY_ICON[L("Ironing")]                  = create_scaled_bitmap("ironing");
 | 
			
		||||
        CATEGORY_ICON[L("Support material")]         = create_scaled_bitmap("support");
 | 
			
		||||
        CATEGORY_ICON[L("Speed")]                    = create_scaled_bitmap("time");
 | 
			
		||||
        CATEGORY_ICON[L("Extruders")]                = create_scaled_bitmap("funnel");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -110,13 +110,8 @@ public:
 | 
			
		|||
            if (!m_can_rescale)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            if (m_force_rescale || is_new_scale_factor())
 | 
			
		||||
                rescale(wxRect());
 | 
			
		||||
#else
 | 
			
		||||
            if (is_new_scale_factor())
 | 
			
		||||
                rescale(wxRect());
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            });
 | 
			
		||||
#else
 | 
			
		||||
        this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) {
 | 
			
		||||
| 
						 | 
				
			
			@ -127,13 +122,8 @@ public:
 | 
			
		|||
            if (!m_can_rescale)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            if (m_force_rescale || is_new_scale_factor())
 | 
			
		||||
                rescale(evt.rect);
 | 
			
		||||
#else
 | 
			
		||||
            if (is_new_scale_factor())
 | 
			
		||||
                rescale(evt.rect);
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            });
 | 
			
		||||
#endif // wxVERSION_EQUAL_OR_GREATER_THAN
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -175,9 +165,7 @@ public:
 | 
			
		|||
    int     em_unit() const             { return m_em_unit; }
 | 
			
		||||
//    int     font_size() const           { return m_font_size; }
 | 
			
		||||
    const wxFont& normal_font() const   { return m_normal_font; }
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    void enable_force_rescale()         { m_force_rescale = true; }
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    virtual void on_dpi_changed(const wxRect &suggested_rect) = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -191,9 +179,7 @@ private:
 | 
			
		|||
    wxFont m_normal_font;
 | 
			
		||||
    float m_prev_scale_factor;
 | 
			
		||||
    bool  m_can_rescale{ true };
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    bool m_force_rescale{ false };
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
    int   m_new_font_point_size;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -233,17 +219,17 @@ private:
 | 
			
		|||
    {
 | 
			
		||||
        this->Freeze();
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
 | 
			
		||||
#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
 | 
			
		||||
        if (m_force_rescale) {
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
#endif // wxVERSION_EQUAL_OR_GREATER_THAN
 | 
			
		||||
            // rescale fonts of all controls
 | 
			
		||||
            scale_controls_fonts(this, m_new_font_point_size);
 | 
			
		||||
            // rescale current window font
 | 
			
		||||
            scale_win_font(this, m_new_font_point_size);
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
 | 
			
		||||
#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
 | 
			
		||||
            m_force_rescale = false;
 | 
			
		||||
        }
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
#endif // wxVERSION_EQUAL_OR_GREATER_THAN
 | 
			
		||||
 | 
			
		||||
        // set normal application font as a current window font
 | 
			
		||||
        m_normal_font = this->GetFont();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -513,9 +513,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
 | 
			
		|||
 | 
			
		||||
        auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
 | 
			
		||||
            static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
 | 
			
		||||
            m_imgui->text(caption);
 | 
			
		||||
            ImGui::PopStyleColor();
 | 
			
		||||
            m_imgui->text_colored(ORANGE, caption);
 | 
			
		||||
            ImGui::SameLine(caption_max);
 | 
			
		||||
            m_imgui->text(text);
 | 
			
		||||
        };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,11 +37,17 @@ namespace GUI {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
static const std::map<const char, std::string> font_icons = {
 | 
			
		||||
    {ImGui::PrintIconMarker     , "cog"        },
 | 
			
		||||
    {ImGui::PrinterIconMarker   , "printer"    },
 | 
			
		||||
    {ImGui::PrinterSlaIconMarker, "sla_printer"},
 | 
			
		||||
    {ImGui::FilamentIconMarker  , "spool"      },
 | 
			
		||||
    {ImGui::MaterialIconMarker  , "resin"      }
 | 
			
		||||
    {ImGui::PrintIconMarker     , "cog"               },
 | 
			
		||||
    {ImGui::PrinterIconMarker   , "printer"           },
 | 
			
		||||
    {ImGui::PrinterSlaIconMarker, "sla_printer"       },
 | 
			
		||||
    {ImGui::FilamentIconMarker  , "spool"             },
 | 
			
		||||
    {ImGui::MaterialIconMarker  , "resin"             },
 | 
			
		||||
	{ImGui::CloseIconMarker     , "cross"             },
 | 
			
		||||
	{ImGui::CloseIconHoverMarker, "cross_focus_large" },
 | 
			
		||||
	{ImGui::TimerDotMarker      , "timer_dot"         },
 | 
			
		||||
    {ImGui::TimerDotEmptyMarker , "timer_dot_empty"   },
 | 
			
		||||
	{ImGui::WarningMarker       , "flag_green"        },
 | 
			
		||||
    {ImGui::ErrorMarker         , "flag_red"          }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ImGuiWrapper::ImGuiWrapper()
 | 
			
		||||
| 
						 | 
				
			
			@ -265,6 +271,11 @@ void ImGuiWrapper::set_next_window_bg_alpha(float alpha)
 | 
			
		|||
    ImGui::SetNextWindowBgAlpha(alpha);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImGuiWrapper::set_next_window_size(float x, float y, ImGuiCond cond)
 | 
			
		||||
{
 | 
			
		||||
	ImGui::SetNextWindowSize(ImVec2(x, y), cond);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImGuiWrapper::begin(const std::string &name, int flags)
 | 
			
		||||
{
 | 
			
		||||
    return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags);
 | 
			
		||||
| 
						 | 
				
			
			@ -296,12 +307,23 @@ bool ImGuiWrapper::button(const wxString &label)
 | 
			
		|||
    return ImGui::Button(label_utf8.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImGuiWrapper::button(const wxString& label, float width, float height)
 | 
			
		||||
{
 | 
			
		||||
	auto label_utf8 = into_u8(label);
 | 
			
		||||
	return ImGui::Button(label_utf8.c_str(), ImVec2(width, height));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImGuiWrapper::radio_button(const wxString &label, bool active)
 | 
			
		||||
{
 | 
			
		||||
    auto label_utf8 = into_u8(label);
 | 
			
		||||
    return ImGui::RadioButton(label_utf8.c_str(), active);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImGuiWrapper::image_button()
 | 
			
		||||
{
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format)
 | 
			
		||||
{
 | 
			
		||||
    return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str());
 | 
			
		||||
| 
						 | 
				
			
			@ -354,6 +376,22 @@ void ImGuiWrapper::text(const wxString &label)
 | 
			
		|||
    this->text(label_utf8.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImGuiWrapper::text_colored(const ImVec4& color, const char* label)
 | 
			
		||||
{
 | 
			
		||||
    ImGui::TextColored(color, label);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImGuiWrapper::text_colored(const ImVec4& color, const std::string& label)
 | 
			
		||||
{
 | 
			
		||||
    this->text_colored(color, label.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label)
 | 
			
		||||
{
 | 
			
		||||
    auto label_utf8 = into_u8(label);
 | 
			
		||||
    this->text_colored(color, label_utf8.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/)
 | 
			
		||||
{
 | 
			
		||||
    return ImGui::SliderFloat(label, v, v_min, v_max, format, power);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,6 +57,7 @@ public:
 | 
			
		|||
 | 
			
		||||
    void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
 | 
			
		||||
    void set_next_window_bg_alpha(float alpha);
 | 
			
		||||
	void set_next_window_size(float x, float y, ImGuiCond cond);
 | 
			
		||||
 | 
			
		||||
    bool begin(const std::string &name, int flags = 0);
 | 
			
		||||
    bool begin(const wxString &name, int flags = 0);
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +66,9 @@ public:
 | 
			
		|||
    void end();
 | 
			
		||||
 | 
			
		||||
    bool button(const wxString &label);
 | 
			
		||||
	bool button(const wxString& label, float width, float height);
 | 
			
		||||
    bool radio_button(const wxString &label, bool active);
 | 
			
		||||
	bool image_button();
 | 
			
		||||
    bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f");
 | 
			
		||||
    bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f");
 | 
			
		||||
    bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f");
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +76,9 @@ public:
 | 
			
		|||
    void text(const char *label);
 | 
			
		||||
    void text(const std::string &label);
 | 
			
		||||
    void text(const wxString &label);
 | 
			
		||||
    void text_colored(const ImVec4& color, const char* label);
 | 
			
		||||
    void text_colored(const ImVec4& color, const std::string& label);
 | 
			
		||||
    void text_colored(const ImVec4& color, const wxString& label);
 | 
			
		||||
    bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
 | 
			
		||||
    bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
 | 
			
		||||
    bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,6 @@
 | 
			
		|||
namespace Slic3r {
 | 
			
		||||
namespace GUI {
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
enum class ERescaleTarget
 | 
			
		||||
{
 | 
			
		||||
    Mainframe,
 | 
			
		||||
| 
						 | 
				
			
			@ -71,15 +70,12 @@ static void rescale_dialog_after_dpi_change(MainFrame& mainframe, SettingsDialog
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
MainFrame::MainFrame() :
 | 
			
		||||
DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"),
 | 
			
		||||
    m_printhost_queue_dlg(new PrintHostQueueDialog(this))
 | 
			
		||||
    , m_recent_projects(9)
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    , m_settings_dialog(this)
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
{
 | 
			
		||||
    // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened.
 | 
			
		||||
    wxGetApp().update_fonts(this);
 | 
			
		||||
| 
						 | 
				
			
			@ -119,43 +115,15 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
 | 
			
		|||
 | 
			
		||||
    m_loaded = true;
 | 
			
		||||
 | 
			
		||||
#if !ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
    // Using SetMinSize() on Mac messes up the window position in some cases
 | 
			
		||||
    // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0
 | 
			
		||||
    // So, if we haven't possibility to set MinSize() for the MainFrame, 
 | 
			
		||||
    // set the MinSize() as a half of regular  for the m_plater and m_tabpanel, when settings layout is in slNew mode
 | 
			
		||||
    // Otherwise, MainFrame will be maximized by height
 | 
			
		||||
    if (slNew) {
 | 
			
		||||
        wxSize size = wxGetApp().get_min_size();
 | 
			
		||||
        size.SetHeight(int(0.5*size.GetHeight()));
 | 
			
		||||
        m_plater->SetMinSize(size);
 | 
			
		||||
        m_tabpanel->SetMinSize(size);
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
#endif // !ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
    // initialize layout
 | 
			
		||||
    m_main_sizer = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
    wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
    sizer->Add(m_main_sizer, 1, wxEXPAND);
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    SetSizer(sizer);
 | 
			
		||||
    // initialize layout from config
 | 
			
		||||
    update_layout();
 | 
			
		||||
    sizer->SetSizeHints(this);
 | 
			
		||||
    Fit();
 | 
			
		||||
#else
 | 
			
		||||
    if (m_plater && m_layout != slOld)
 | 
			
		||||
        sizer->Add(m_plater, 1, wxEXPAND);
 | 
			
		||||
 | 
			
		||||
    if (m_tabpanel && m_layout != slDlg)
 | 
			
		||||
        sizer->Add(m_tabpanel, 1, wxEXPAND);
 | 
			
		||||
 | 
			
		||||
    sizer->SetSizeHints(this);
 | 
			
		||||
    SetSizer(sizer);
 | 
			
		||||
    Fit();
 | 
			
		||||
#endif // !ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
    const wxSize min_size = wxGetApp().get_min_size(); //wxSize(76*wxGetApp().em_unit(), 49*wxGetApp().em_unit());
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
| 
						 | 
				
			
			@ -247,12 +215,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
 | 
			
		|||
    });
 | 
			
		||||
 | 
			
		||||
    wxGetApp().persist_window_geometry(this, true);
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    wxGetApp().persist_window_geometry(&m_settings_dialog, true);
 | 
			
		||||
#else
 | 
			
		||||
    if (m_settings_dialog != nullptr)
 | 
			
		||||
        wxGetApp().persist_window_geometry(m_settings_dialog, true);
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
    update_ui_from_settings();    // FIXME (?)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -260,7 +223,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
 | 
			
		|||
        m_plater->show_action_buttons(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
void MainFrame::update_layout()
 | 
			
		||||
{
 | 
			
		||||
    auto restore_to_creation = [this]() {
 | 
			
		||||
| 
						 | 
				
			
			@ -375,7 +337,6 @@ void MainFrame::update_layout()
 | 
			
		|||
    Layout();
 | 
			
		||||
    Thaw();
 | 
			
		||||
}
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
// Called when closing the application and when switching the application language.
 | 
			
		||||
void MainFrame::shutdown()
 | 
			
		||||
| 
						 | 
				
			
			@ -409,20 +370,9 @@ void MainFrame::shutdown()
 | 
			
		|||
    // In addition, there were some crashes due to the Paint events sent to already destructed windows.
 | 
			
		||||
    this->Show(false);
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    if (m_settings_dialog.IsShown())
 | 
			
		||||
        // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry()
 | 
			
		||||
        m_settings_dialog.Close();
 | 
			
		||||
#else
 | 
			
		||||
    if (m_settings_dialog != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        if (m_settings_dialog->IsShown())
 | 
			
		||||
            // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry()
 | 
			
		||||
            m_settings_dialog->Close();
 | 
			
		||||
 | 
			
		||||
        m_settings_dialog->Destroy();
 | 
			
		||||
    }
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
	// Stop the background thread (Windows and Linux).
 | 
			
		||||
	// Disconnect from a 3DConnextion driver (OSX).
 | 
			
		||||
| 
						 | 
				
			
			@ -481,7 +431,6 @@ void MainFrame::update_title()
 | 
			
		|||
 | 
			
		||||
void MainFrame::init_tabpanel()
 | 
			
		||||
{
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10
 | 
			
		||||
    // with multiple high resolution displays connected.
 | 
			
		||||
    m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
 | 
			
		||||
| 
						 | 
				
			
			@ -490,27 +439,6 @@ void MainFrame::init_tabpanel()
 | 
			
		|||
#endif
 | 
			
		||||
    m_tabpanel->Hide();
 | 
			
		||||
    m_settings_dialog.set_tabpanel(m_tabpanel);
 | 
			
		||||
#else
 | 
			
		||||
    m_layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? slOld :
 | 
			
		||||
        wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? slNew :
 | 
			
		||||
        wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? slDlg : slOld;
 | 
			
		||||
 | 
			
		||||
    // From the very beginning the Print settings should be selected
 | 
			
		||||
    m_last_selected_tab = m_layout == slDlg ? 0 : 1;
 | 
			
		||||
 | 
			
		||||
    if (m_layout == slDlg) {
 | 
			
		||||
        m_settings_dialog = new SettingsDialog(this);
 | 
			
		||||
        m_tabpanel = m_settings_dialog->get_tabpanel();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10
 | 
			
		||||
        // with multiple high resolution displays connected.
 | 
			
		||||
        m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
 | 
			
		||||
#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
 | 
			
		||||
        m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font());
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
    m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) {
 | 
			
		||||
        wxWindow* panel = m_tabpanel->GetCurrentPage();
 | 
			
		||||
| 
						 | 
				
			
			@ -531,20 +459,9 @@ void MainFrame::init_tabpanel()
 | 
			
		|||
            select_tab(0); // select Plater
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    m_plater = new Plater(this, this);
 | 
			
		||||
    m_plater->Hide();
 | 
			
		||||
#else
 | 
			
		||||
    if (m_layout == slOld) {
 | 
			
		||||
        m_plater = new Plater(m_tabpanel, this);
 | 
			
		||||
        m_tabpanel->AddPage(m_plater, _L("Plater"));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        m_plater = new Plater(this, this);
 | 
			
		||||
        if (m_layout == slNew)
 | 
			
		||||
            m_tabpanel->AddPage(new wxPanel(m_tabpanel), _L("Plater")); // empty panel just for Plater tab
 | 
			
		||||
    }
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
    wxGetApp().plater_ = m_plater;
 | 
			
		||||
 | 
			
		||||
    wxGetApp().obj_list()->create_popup_menus();
 | 
			
		||||
| 
						 | 
				
			
			@ -686,7 +603,6 @@ bool MainFrame::can_slice() const
 | 
			
		|||
 | 
			
		||||
bool MainFrame::can_change_view() const
 | 
			
		||||
{
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    switch (m_layout)
 | 
			
		||||
    {
 | 
			
		||||
    default:                   { return false; }
 | 
			
		||||
| 
						 | 
				
			
			@ -697,15 +613,6 @@ bool MainFrame::can_change_view() const
 | 
			
		|||
        return page_id != wxNOT_FOUND && dynamic_cast<const Slic3r::GUI::Plater*>(m_tabpanel->GetPage((size_t)page_id)) != nullptr;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
#else
 | 
			
		||||
    if (m_layout == slNew)
 | 
			
		||||
        return m_plater->IsShown();
 | 
			
		||||
    if (m_layout == slDlg)
 | 
			
		||||
        return true;
 | 
			
		||||
    // slOld layout mode
 | 
			
		||||
    int page_id = m_tabpanel->GetSelection();
 | 
			
		||||
    return page_id != wxNOT_FOUND && dynamic_cast<const Slic3r::GUI::Plater*>(m_tabpanel->GetPage((size_t)page_id)) != nullptr;
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MainFrame::can_select() const
 | 
			
		||||
| 
						 | 
				
			
			@ -746,11 +653,7 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
 | 
			
		|||
    wxGetApp().plater()->msw_rescale();
 | 
			
		||||
 | 
			
		||||
    // update Tabs
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog
 | 
			
		||||
#else
 | 
			
		||||
    if (m_layout != slDlg) // Update tabs later, from the SettingsDialog, when the Settings are in the separated dialog
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
        for (auto tab : wxGetApp().tabs_list)
 | 
			
		||||
            tab->msw_rescale();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -779,10 +682,8 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
 | 
			
		|||
 | 
			
		||||
    this->Maximize(is_maximized);
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    if (m_layout == ESettingsLayout::Dlg)
 | 
			
		||||
        rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog);
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MainFrame::on_sys_color_changed()
 | 
			
		||||
| 
						 | 
				
			
			@ -1516,25 +1417,15 @@ void MainFrame::load_config(const DynamicPrintConfig& config)
 | 
			
		|||
void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
 | 
			
		||||
{
 | 
			
		||||
    bool tabpanel_was_hidden = false;
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    if (m_layout == ESettingsLayout::Dlg) {
 | 
			
		||||
#else
 | 
			
		||||
    if (m_layout == slDlg) {
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
        if (tab==0) {
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            if (m_settings_dialog.IsShown())
 | 
			
		||||
                this->SetFocus();
 | 
			
		||||
#else
 | 
			
		||||
            if (m_settings_dialog->IsShown())
 | 
			
		||||
                this->SetFocus();
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
            // plater should be focused for correct navigation inside search window
 | 
			
		||||
            if (m_plater->canvas3D()->is_search_pressed())
 | 
			
		||||
                m_plater->SetFocus();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
        // Show/Activate Settings Dialog
 | 
			
		||||
#ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
 | 
			
		||||
        if (m_settings_dialog.IsShown())
 | 
			
		||||
| 
						 | 
				
			
			@ -1551,28 +1442,11 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
 | 
			
		|||
            m_settings_dialog.Show();
 | 
			
		||||
        }
 | 
			
		||||
#endif
 | 
			
		||||
#else
 | 
			
		||||
        // Show/Activate Settings Dialog
 | 
			
		||||
        if (m_settings_dialog->IsShown())
 | 
			
		||||
#ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
 | 
			
		||||
            m_settings_dialog->Hide();
 | 
			
		||||
#else
 | 
			
		||||
            m_settings_dialog->SetFocus();
 | 
			
		||||
        else
 | 
			
		||||
#endif
 | 
			
		||||
        m_settings_dialog->Show();
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    }
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    else if (m_layout == ESettingsLayout::New) {
 | 
			
		||||
        m_main_sizer->Show(m_plater, tab == 0);
 | 
			
		||||
        tabpanel_was_hidden = !m_main_sizer->IsShown(m_tabpanel);
 | 
			
		||||
        m_main_sizer->Show(m_tabpanel, tab != 0);
 | 
			
		||||
#else
 | 
			
		||||
    else if (m_layout == slNew) {
 | 
			
		||||
        m_plater->Show(tab == 0);
 | 
			
		||||
        m_tabpanel->Show(tab != 0);
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
        // plater should be focused for correct navigation inside search window
 | 
			
		||||
        if (tab == 0 && m_plater->canvas3D()->is_search_pressed())
 | 
			
		||||
| 
						 | 
				
			
			@ -1589,11 +1463,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
 | 
			
		|||
            tab->update_changed_tree_ui();
 | 
			
		||||
 | 
			
		||||
    // when tab == -1, it means we should show the last selected tab
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab);
 | 
			
		||||
#else
 | 
			
		||||
    m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == slDlg && tab != 0) ? tab-1 : tab);
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set a camera direction, zoom to all objects.
 | 
			
		||||
| 
						 | 
				
			
			@ -1722,34 +1592,6 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe)
 | 
			
		|||
    SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
 | 
			
		||||
#endif // _WIN32
 | 
			
		||||
 | 
			
		||||
#if !ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10
 | 
			
		||||
    // with multiple high resolution displays connected.
 | 
			
		||||
    m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxGetApp().get_min_size(), wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
 | 
			
		||||
#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
 | 
			
		||||
    m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    m_tabpanel->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& evt) {
 | 
			
		||||
        if ((evt.GetModifiers() & wxMOD_CONTROL) != 0) {
 | 
			
		||||
            switch (evt.GetKeyCode()) {
 | 
			
		||||
            case '1': { m_main_frame->select_tab(0); break; }
 | 
			
		||||
            case '2': { m_main_frame->select_tab(1); break; }
 | 
			
		||||
            case '3': { m_main_frame->select_tab(2); break; }
 | 
			
		||||
            case '4': { m_main_frame->select_tab(3); break; }
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
            case 'f':
 | 
			
		||||
#else /* __APPLE__ */
 | 
			
		||||
            case WXK_CONTROL_F:
 | 
			
		||||
#endif /* __APPLE__ */
 | 
			
		||||
            case 'F': { m_main_frame->plater()->search(false); break; }
 | 
			
		||||
            default:break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
#endif // !ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) {
 | 
			
		||||
 | 
			
		||||
        auto key_up_handker = [this](wxKeyEvent& evt) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1779,13 +1621,9 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe)
 | 
			
		|||
                m_tabpanel->Unbind(wxEVT_KEY_UP, key_up_handker);
 | 
			
		||||
        }
 | 
			
		||||
        });
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
    // initialize layout
 | 
			
		||||
    auto sizer = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
#if !ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    sizer->Add(m_tabpanel, 1, wxEXPAND);
 | 
			
		||||
#endif // !ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    sizer->SetSizeHints(this);
 | 
			
		||||
    SetSizer(sizer);
 | 
			
		||||
    Fit();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,11 +55,7 @@ class SettingsDialog : public DPIDialog
 | 
			
		|||
public:
 | 
			
		||||
    SettingsDialog(MainFrame* mainframe);
 | 
			
		||||
    ~SettingsDialog() {}
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    void set_tabpanel(wxNotebook* tabpanel) { m_tabpanel = tabpanel; }
 | 
			
		||||
#else
 | 
			
		||||
    wxNotebook* get_tabpanel() { return m_tabpanel; }
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    void on_dpi_changed(const wxRect& suggested_rect) override;
 | 
			
		||||
| 
						 | 
				
			
			@ -119,7 +115,6 @@ class MainFrame : public DPIFrame
 | 
			
		|||
 | 
			
		||||
    wxFileHistory m_recent_projects;
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    enum class ESettingsLayout
 | 
			
		||||
    {
 | 
			
		||||
        Unknown,
 | 
			
		||||
| 
						 | 
				
			
			@ -129,13 +124,6 @@ class MainFrame : public DPIFrame
 | 
			
		|||
    };
 | 
			
		||||
    
 | 
			
		||||
    ESettingsLayout m_layout{ ESettingsLayout::Unknown };
 | 
			
		||||
#else
 | 
			
		||||
    enum SettingsLayout {
 | 
			
		||||
        slOld = 0,
 | 
			
		||||
        slNew,
 | 
			
		||||
        slDlg,
 | 
			
		||||
    }               m_layout;
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    virtual void on_dpi_changed(const wxRect &suggested_rect);
 | 
			
		||||
| 
						 | 
				
			
			@ -145,9 +133,7 @@ public:
 | 
			
		|||
    MainFrame();
 | 
			
		||||
    ~MainFrame() = default;
 | 
			
		||||
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    void update_layout();
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
	// Called when closing the application and when switching the application language.
 | 
			
		||||
	void 		shutdown();
 | 
			
		||||
| 
						 | 
				
			
			@ -190,12 +176,8 @@ public:
 | 
			
		|||
 | 
			
		||||
    Plater*             m_plater { nullptr };
 | 
			
		||||
    wxNotebook*         m_tabpanel { nullptr };
 | 
			
		||||
#if ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    SettingsDialog      m_settings_dialog;
 | 
			
		||||
    wxWindow*           m_plater_page{ nullptr };
 | 
			
		||||
#else
 | 
			
		||||
    SettingsDialog*     m_settings_dialog { nullptr };
 | 
			
		||||
#endif // ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
    wxProgressDialog*   m_progress_dialog { nullptr };
 | 
			
		||||
    std::shared_ptr<ProgressStatusBar>  m_statusbar;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -134,7 +134,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
 | 
			
		|||
    Vec3d direction;
 | 
			
		||||
    line_from_mouse_pos(mouse_pos, trafo, camera, point, direction);
 | 
			
		||||
 | 
			
		||||
    std::vector<sla::EigenMesh3D::hit_result> hits = m_emesh.query_ray_hits(point, direction);
 | 
			
		||||
    std::vector<sla::IndexedMesh::hit_result> hits = m_emesh.query_ray_hits(point, direction);
 | 
			
		||||
 | 
			
		||||
    if (hits.empty())
 | 
			
		||||
        return false; // no intersection found
 | 
			
		||||
| 
						 | 
				
			
			@ -184,7 +184,7 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
 | 
			
		|||
 | 
			
		||||
        bool is_obscured = false;
 | 
			
		||||
        // Cast a ray in the direction of the camera and look for intersection with the mesh:
 | 
			
		||||
        std::vector<sla::EigenMesh3D::hit_result> hits;
 | 
			
		||||
        std::vector<sla::IndexedMesh::hit_result> hits;
 | 
			
		||||
        // Offset the start of the ray by EPSILON to account for numerical inaccuracies.
 | 
			
		||||
        hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(),
 | 
			
		||||
                                      direction_to_camera.cast<double>());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
 | 
			
		||||
#include "libslic3r/Point.hpp"
 | 
			
		||||
#include "libslic3r/Geometry.hpp"
 | 
			
		||||
#include "libslic3r/SLA/EigenMesh3D.hpp"
 | 
			
		||||
#include "libslic3r/SLA/IndexedMesh.hpp"
 | 
			
		||||
#include "admesh/stl.h"
 | 
			
		||||
 | 
			
		||||
#include "slic3r/GUI/3DScene.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +147,7 @@ public:
 | 
			
		|||
    Vec3f get_triangle_normal(size_t facet_idx) const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    sla::EigenMesh3D m_emesh;
 | 
			
		||||
    sla::IndexedMesh m_emesh;
 | 
			
		||||
    std::vector<stl_normal> m_normals;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@
 | 
			
		|||
#include "AppConfig.hpp"
 | 
			
		||||
#include "GLCanvas3D.hpp"
 | 
			
		||||
#include "Plater.hpp"
 | 
			
		||||
#include "NotificationManager.hpp"
 | 
			
		||||
 | 
			
		||||
#include <wx/glcanvas.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -239,8 +240,7 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
 | 
			
		|||
 | 
			
		||||
    // when the user clicks on [X] or [Close] button we need to trigger
 | 
			
		||||
    // an extra frame to let the dialog disappear
 | 
			
		||||
    if (m_settings_dialog_closed_by_user)
 | 
			
		||||
    {
 | 
			
		||||
    if (m_settings_dialog_closed_by_user) {
 | 
			
		||||
        m_show_settings_dialog = false;
 | 
			
		||||
        m_settings_dialog_closed_by_user = false;
 | 
			
		||||
        canvas.request_extra_frame();
 | 
			
		||||
| 
						 | 
				
			
			@ -261,13 +261,10 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
 | 
			
		|||
 | 
			
		||||
    static ImVec2 last_win_size(0.0f, 0.0f);
 | 
			
		||||
    bool shown = true;
 | 
			
		||||
    if (imgui.begin(_(L("3Dconnexion settings")), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse))
 | 
			
		||||
    {
 | 
			
		||||
        if (shown)
 | 
			
		||||
        {
 | 
			
		||||
    if (imgui.begin(_L("3Dconnexion settings"), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) {
 | 
			
		||||
        if (shown) {
 | 
			
		||||
            ImVec2 win_size = ImGui::GetWindowSize();
 | 
			
		||||
            if ((last_win_size.x != win_size.x) || (last_win_size.y != win_size.y))
 | 
			
		||||
            {
 | 
			
		||||
            if (last_win_size.x != win_size.x || last_win_size.y != win_size.y) {
 | 
			
		||||
                // when the user clicks on [X] button, the next time the dialog is shown 
 | 
			
		||||
                // has a dummy size, so we trigger an extra frame to let it have the correct size
 | 
			
		||||
                last_win_size = win_size;
 | 
			
		||||
| 
						 | 
				
			
			@ -275,59 +272,51 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator);
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Text, color);
 | 
			
		||||
            imgui.text(_(L("Device:")));
 | 
			
		||||
            ImGui::PopStyleColor();
 | 
			
		||||
            imgui.text_colored(color, _L("Device:"));
 | 
			
		||||
            ImGui::SameLine();
 | 
			
		||||
            imgui.text(m_device_str);
 | 
			
		||||
 | 
			
		||||
            ImGui::Separator();
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Text, color);
 | 
			
		||||
            imgui.text(_(L("Speed:")));
 | 
			
		||||
            ImGui::PopStyleColor();
 | 
			
		||||
            imgui.text_colored(color, _L("Speed:"));
 | 
			
		||||
 | 
			
		||||
            float translation_scale = (float)params_copy.translation.scale / Params::DefaultTranslationScale;
 | 
			
		||||
            if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) {
 | 
			
		||||
            if (imgui.slider_float(_L("Translation") + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) {
 | 
			
		||||
            	params_copy.translation.scale = Params::DefaultTranslationScale * (double)translation_scale;
 | 
			
		||||
            	params_changed = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            float rotation_scale = params_copy.rotation.scale / Params::DefaultRotationScale;
 | 
			
		||||
            if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) {
 | 
			
		||||
            if (imgui.slider_float(_L("Rotation") + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) {
 | 
			
		||||
            	params_copy.rotation.scale = Params::DefaultRotationScale * rotation_scale;
 | 
			
		||||
            	params_changed = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            float zoom_scale = params_copy.zoom.scale / Params::DefaultZoomScale;
 | 
			
		||||
            if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) {
 | 
			
		||||
            if (imgui.slider_float(_L("Zoom"), &zoom_scale, 0.1f, 10.0f, "%.1f")) {
 | 
			
		||||
            	params_copy.zoom.scale = Params::DefaultZoomScale * zoom_scale;
 | 
			
		||||
            	params_changed = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ImGui::Separator();
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Text, color);
 | 
			
		||||
            imgui.text(_(L("Deadzone:")));
 | 
			
		||||
            ImGui::PopStyleColor();
 | 
			
		||||
            imgui.text_colored(color, _L("Deadzone:"));
 | 
			
		||||
 | 
			
		||||
            float translation_deadzone = (float)params_copy.translation.deadzone;
 | 
			
		||||
            if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) {
 | 
			
		||||
            if (imgui.slider_float(_L("Translation") + "/" + _L("Zoom"), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) {
 | 
			
		||||
            	params_copy.translation.deadzone = (double)translation_deadzone;
 | 
			
		||||
            	params_changed = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            float rotation_deadzone = params_copy.rotation.deadzone;
 | 
			
		||||
            if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) {
 | 
			
		||||
            if (imgui.slider_float(_L("Rotation") + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) {
 | 
			
		||||
            	params_copy.rotation.deadzone = rotation_deadzone;
 | 
			
		||||
            	params_changed = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ImGui::Separator();
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Text, color);
 | 
			
		||||
            imgui.text(_(L("Options:")));
 | 
			
		||||
            ImGui::PopStyleColor();
 | 
			
		||||
            imgui.text_colored(color, _L("Options:"));
 | 
			
		||||
 | 
			
		||||
            bool swap_yz = params_copy.swap_yz;
 | 
			
		||||
            if (imgui.checkbox("Swap Y/Z axes", swap_yz)) {
 | 
			
		||||
            if (imgui.checkbox(_L("Swap Y/Z axes"), swap_yz)) {
 | 
			
		||||
                params_copy.swap_yz = swap_yz;
 | 
			
		||||
                params_changed = true;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -335,25 +324,20 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
 | 
			
		|||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
 | 
			
		||||
            ImGui::Separator();
 | 
			
		||||
            ImGui::Separator();
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Text, color);
 | 
			
		||||
            imgui.text("DEBUG:");
 | 
			
		||||
            imgui.text("Vectors:");
 | 
			
		||||
            ImGui::PopStyleColor();
 | 
			
		||||
            imgui.text_colored(color, "DEBUG:");
 | 
			
		||||
            imgui.text_colored(color, "Vectors:");
 | 
			
		||||
            Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast<float>();
 | 
			
		||||
            Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast<float>();
 | 
			
		||||
            ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
 | 
			
		||||
            ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
 | 
			
		||||
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Text, color);
 | 
			
		||||
            imgui.text("Queue size:");
 | 
			
		||||
            ImGui::PopStyleColor();
 | 
			
		||||
            imgui.text_colored(color, "Queue size:");
 | 
			
		||||
 | 
			
		||||
            int input_queue_size_current[2] = { int(m_state.input_queue_size_current()), int(m_state.input_queue_max_size_achieved) };
 | 
			
		||||
            ImGui::InputInt2("Current##4", input_queue_size_current, ImGuiInputTextFlags_ReadOnly);
 | 
			
		||||
 | 
			
		||||
            int input_queue_size_param = int(params_copy.input_queue_max_size);
 | 
			
		||||
            if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly))
 | 
			
		||||
            {
 | 
			
		||||
            if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly)) {
 | 
			
		||||
                if (input_queue_size_param > 0) {
 | 
			
		||||
	            	params_copy.input_queue_max_size = input_queue_size_param;
 | 
			
		||||
    	        	params_changed = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -361,23 +345,19 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            ImGui::Separator();
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Text, color);
 | 
			
		||||
            imgui.text("Camera:");
 | 
			
		||||
            ImGui::PopStyleColor();
 | 
			
		||||
            imgui.text_colored(color, "Camera:");
 | 
			
		||||
            Vec3f target = wxGetApp().plater()->get_camera().get_target().cast<float>();
 | 
			
		||||
            ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
 | 
			
		||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
 | 
			
		||||
 | 
			
		||||
            ImGui::Separator();
 | 
			
		||||
            if (imgui.button(_(L("Close"))))
 | 
			
		||||
            {
 | 
			
		||||
            if (imgui.button(_L("Close"))) {
 | 
			
		||||
                // the user clicked on the [Close] button
 | 
			
		||||
                m_settings_dialog_closed_by_user = true;
 | 
			
		||||
                canvas.set_as_dirty();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
        else {
 | 
			
		||||
            // the user clicked on the [X] button
 | 
			
		||||
            m_settings_dialog_closed_by_user = true;
 | 
			
		||||
            canvas.set_as_dirty();
 | 
			
		||||
| 
						 | 
				
			
			@ -424,6 +404,8 @@ void Mouse3DController::disconnected()
 | 
			
		|||
        m_params_by_device[m_device_str] = m_params_ui;
 | 
			
		||||
	    m_device_str.clear();
 | 
			
		||||
	    m_connected = false;
 | 
			
		||||
		wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected, *(wxGetApp().plater()->get_current_canvas3D()));
 | 
			
		||||
 | 
			
		||||
        wxGetApp().plater()->CallAfter([]() {
 | 
			
		||||
        	Plater *plater = wxGetApp().plater();
 | 
			
		||||
        	if (plater != nullptr) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										918
									
								
								src/slic3r/GUI/NotificationManager.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										918
									
								
								src/slic3r/GUI/NotificationManager.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,918 @@
 | 
			
		|||
#include "NotificationManager.hpp"
 | 
			
		||||
 | 
			
		||||
#include "GUI_App.hpp"
 | 
			
		||||
#include "Plater.hpp"
 | 
			
		||||
#include "GLCanvas3D.hpp"
 | 
			
		||||
#include "ImGuiWrapper.hpp"
 | 
			
		||||
 | 
			
		||||
#include "wxExtensions.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/algorithm/string.hpp>
 | 
			
		||||
#include <boost/log/trivial.hpp>
 | 
			
		||||
#include <wx/glcanvas.h>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define NOTIFICATION_MAX_MOVE 3.0f
 | 
			
		||||
 | 
			
		||||
#define GAP_WIDTH 10.0f
 | 
			
		||||
#define SPACE_RIGHT_PANEL 10.0f
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
namespace GUI {
 | 
			
		||||
 | 
			
		||||
wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
 | 
			
		||||
wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
 | 
			
		||||
wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent);
 | 
			
		||||
 | 
			
		||||
namespace Notifications_Internal{
 | 
			
		||||
	void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity)
 | 
			
		||||
	{
 | 
			
		||||
		if (fading_out)
 | 
			
		||||
			ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity));
 | 
			
		||||
		else
 | 
			
		||||
			ImGui::PushStyleColor(idx, col);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
//ScalableBitmap bmp_icon;
 | 
			
		||||
//------PopNotification--------
 | 
			
		||||
NotificationManager::PopNotification::PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler) :
 | 
			
		||||
	  m_data                (n)
 | 
			
		||||
	, m_id                  (id)    
 | 
			
		||||
	, m_remaining_time      (n.duration)
 | 
			
		||||
	, m_last_remaining_time (n.duration)
 | 
			
		||||
	, m_counting_down       (n.duration != 0)
 | 
			
		||||
	, m_text1               (n.text1)
 | 
			
		||||
    , m_hypertext           (n.hypertext)
 | 
			
		||||
    , m_text2               (n.text2)
 | 
			
		||||
	, m_evt_handler         (evt_handler)
 | 
			
		||||
{
 | 
			
		||||
	init();
 | 
			
		||||
}
 | 
			
		||||
NotificationManager::PopNotification::~PopNotification()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y)
 | 
			
		||||
{
 | 
			
		||||
	if (m_finished)
 | 
			
		||||
		return RenderResult::Finished;
 | 
			
		||||
	if (m_close_pending) {
 | 
			
		||||
		// request of extra frame will be done in caller function by ret val ClosePending
 | 
			
		||||
		m_finished = true;
 | 
			
		||||
		return RenderResult::ClosePending;
 | 
			
		||||
	}
 | 
			
		||||
	if (m_hidden) {
 | 
			
		||||
		m_top_y = initial_y - GAP_WIDTH;
 | 
			
		||||
		return RenderResult::Static;
 | 
			
		||||
	}
 | 
			
		||||
	RenderResult    ret_val = m_counting_down ? RenderResult::Countdown : RenderResult::Static;
 | 
			
		||||
	Size            cnv_size = canvas.get_canvas_size();
 | 
			
		||||
	ImGuiWrapper&   imgui = *wxGetApp().imgui();
 | 
			
		||||
	bool            shown = true;
 | 
			
		||||
	std::string     name;
 | 
			
		||||
	ImVec2          mouse_pos = ImGui::GetMousePos();
 | 
			
		||||
 | 
			
		||||
	if (m_line_height != ImGui::CalcTextSize("A").y)
 | 
			
		||||
		init();
 | 
			
		||||
	
 | 
			
		||||
	set_next_window_size(imgui);
 | 
			
		||||
 | 
			
		||||
	//top y of window
 | 
			
		||||
	m_top_y = initial_y + m_window_height;
 | 
			
		||||
	//top right position
 | 
			
		||||
	ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - SPACE_RIGHT_PANEL, 1.0f * (float)cnv_size.get_height() - m_top_y);
 | 
			
		||||
	imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f);
 | 
			
		||||
	imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always);
 | 
			
		||||
 | 
			
		||||
	//find if hovered
 | 
			
		||||
	if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y&& mouse_pos.y < win_pos.y + m_window_height)
 | 
			
		||||
	{
 | 
			
		||||
		ImGui::SetNextWindowFocus();
 | 
			
		||||
		ret_val = RenderResult::Hovered;
 | 
			
		||||
		//reset fading
 | 
			
		||||
		m_fading_out = false;
 | 
			
		||||
		m_current_fade_opacity = 1.f;
 | 
			
		||||
		m_remaining_time = m_data.duration;
 | 
			
		||||
		m_countdown_frame = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (m_counting_down && m_remaining_time < 0)
 | 
			
		||||
		m_close_pending = true;
 | 
			
		||||
 | 
			
		||||
	if (m_close_pending) {
 | 
			
		||||
		// request of extra frame will be done in caller function by ret val ClosePending
 | 
			
		||||
		m_finished = true;
 | 
			
		||||
		return RenderResult::ClosePending;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// color change based on fading out
 | 
			
		||||
	bool fading_pop = false;
 | 
			
		||||
	if (m_fading_out) {
 | 
			
		||||
		if (!m_paused)
 | 
			
		||||
			m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f);
 | 
			
		||||
		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
 | 
			
		||||
		Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
 | 
			
		||||
		fading_pop = true;
 | 
			
		||||
	}
 | 
			
		||||
	// background color
 | 
			
		||||
	if (m_is_gray) {
 | 
			
		||||
		ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
 | 
			
		||||
		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
 | 
			
		||||
	} else if (m_data.level == NotificationLevel::ErrorNotification) {
 | 
			
		||||
		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
 | 
			
		||||
		backcolor.x += 0.3f;
 | 
			
		||||
		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
 | 
			
		||||
	} else if (m_data.level == NotificationLevel::WarningNotification) {
 | 
			
		||||
		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
 | 
			
		||||
		backcolor.x += 0.3f;
 | 
			
		||||
		backcolor.y += 0.15f;
 | 
			
		||||
		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//name of window - probably indentifies window and is shown so last_end add whitespaces according to id
 | 
			
		||||
	for (size_t i = 0; i < m_id; i++)
 | 
			
		||||
		name += " ";
 | 
			
		||||
	if (imgui.begin(name, &shown, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar )) {
 | 
			
		||||
		if (shown) {
 | 
			
		||||
			
 | 
			
		||||
			ImVec2 win_size = ImGui::GetWindowSize();
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
			//FIXME: dont forget to us this for texts
 | 
			
		||||
			//GUI::format(_utf8(L()));
 | 
			
		||||
			
 | 
			
		||||
			/*
 | 
			
		||||
			//countdown numbers
 | 
			
		||||
			ImGui::SetCursorPosX(15);
 | 
			
		||||
			ImGui::SetCursorPosY(15);
 | 
			
		||||
			imgui.text(std::to_string(m_remaining_time).c_str());
 | 
			
		||||
			*/
 | 
			
		||||
			if(m_counting_down)
 | 
			
		||||
				render_countdown(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
 | 
			
		||||
			render_left_sign(imgui);
 | 
			
		||||
			render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
 | 
			
		||||
			render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
 | 
			
		||||
			if (m_multiline && m_lines_count > 3)
 | 
			
		||||
				render_minimize_button(imgui, win_pos.x, win_pos.y);
 | 
			
		||||
		} else {
 | 
			
		||||
			// the user clicked on the [X] button ( ImGuiWindowFlags_NoTitleBar means theres no [X] button)
 | 
			
		||||
			m_close_pending = true;
 | 
			
		||||
			canvas.set_as_dirty();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	imgui.end();
 | 
			
		||||
	
 | 
			
		||||
	if (fading_pop) {
 | 
			
		||||
		ImGui::PopStyleColor();
 | 
			
		||||
		ImGui::PopStyleColor();
 | 
			
		||||
	}
 | 
			
		||||
	if (m_is_gray)
 | 
			
		||||
		ImGui::PopStyleColor();
 | 
			
		||||
	else if (m_data.level == NotificationLevel::ErrorNotification)
 | 
			
		||||
		ImGui::PopStyleColor();
 | 
			
		||||
	else if (m_data.level == NotificationLevel::WarningNotification)
 | 
			
		||||
		ImGui::PopStyleColor();
 | 
			
		||||
	return ret_val;
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::PopNotification::init()
 | 
			
		||||
{
 | 
			
		||||
	std::string text          = m_text1 + " " + m_hypertext;
 | 
			
		||||
	int         last_end      = 0;
 | 
			
		||||
	            m_lines_count = 0;
 | 
			
		||||
 | 
			
		||||
	//determine line width 
 | 
			
		||||
	m_line_height = ImGui::CalcTextSize("A").y;
 | 
			
		||||
 | 
			
		||||
	m_left_indentation = m_line_height;
 | 
			
		||||
	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
 | 
			
		||||
		std::string text;
 | 
			
		||||
		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
 | 
			
		||||
		float picture_width = ImGui::CalcTextSize(text.c_str()).x;
 | 
			
		||||
		m_left_indentation = picture_width + m_line_height / 2;
 | 
			
		||||
	}
 | 
			
		||||
	m_window_width_offset = m_left_indentation + m_line_height * 2;
 | 
			
		||||
	m_window_width = m_line_height * 25;
 | 
			
		||||
	
 | 
			
		||||
	// count lines
 | 
			
		||||
	m_endlines.clear();
 | 
			
		||||
	while (last_end < text.length() - 1)
 | 
			
		||||
	{
 | 
			
		||||
		int next_hard_end = text.find_first_of('\n', last_end);
 | 
			
		||||
		if (next_hard_end > 0 && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) {
 | 
			
		||||
			//next line is ended by '/n'
 | 
			
		||||
			m_endlines.push_back(next_hard_end);
 | 
			
		||||
			last_end = next_hard_end + 1;
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			// find next suitable endline
 | 
			
		||||
			if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - 3.5f * m_line_height) {// m_window_width_offset) {
 | 
			
		||||
				// more than one line till end
 | 
			
		||||
				int next_space = text.find_first_of(' ', last_end);
 | 
			
		||||
				if (next_space > 0) {
 | 
			
		||||
					int next_space_candidate = text.find_first_of(' ', next_space + 1);
 | 
			
		||||
					while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) {
 | 
			
		||||
						next_space = next_space_candidate;
 | 
			
		||||
						next_space_candidate = text.find_first_of(' ', next_space + 1);
 | 
			
		||||
					}
 | 
			
		||||
					m_endlines.push_back(next_space);
 | 
			
		||||
					last_end = next_space + 1;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				m_endlines.push_back(text.length());
 | 
			
		||||
				last_end = text.length();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		m_lines_count++;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui)
 | 
			
		||||
{ 
 | 
			
		||||
	if (m_multiline) {
 | 
			
		||||
		m_window_height = m_lines_count * m_line_height;
 | 
			
		||||
	}else
 | 
			
		||||
	{
 | 
			
		||||
		m_window_height = 2 * m_line_height;
 | 
			
		||||
	}
 | 
			
		||||
	m_window_height += 1 * m_line_height; // top and bottom
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
 | 
			
		||||
{
 | 
			
		||||
	ImVec2      win_size(win_size_x, win_size_y);
 | 
			
		||||
	ImVec2      win_pos(win_pos_x, win_pos_y);
 | 
			
		||||
	float       x_offset = m_left_indentation;
 | 
			
		||||
	std::string fulltext = m_text1 + m_hypertext; //+ m_text2;
 | 
			
		||||
	ImVec2      text_size = ImGui::CalcTextSize(fulltext.c_str());
 | 
			
		||||
	// text posistions are calculated by lines count
 | 
			
		||||
	// large texts has "more" button or are displayed whole
 | 
			
		||||
	// smaller texts are divided as one liners and two liners
 | 
			
		||||
	if (m_lines_count > 2) {
 | 
			
		||||
		if (m_multiline) {
 | 
			
		||||
			
 | 
			
		||||
			int last_end = 0;
 | 
			
		||||
			float starting_y = m_line_height/2;//10;
 | 
			
		||||
			float shift_y = m_line_height;// -m_line_height / 20;
 | 
			
		||||
			for (size_t i = 0; i < m_lines_count; i++) {
 | 
			
		||||
			    std::string line = m_text1.substr(last_end , m_endlines[i] - last_end);
 | 
			
		||||
				last_end = m_endlines[i] + 1;
 | 
			
		||||
				ImGui::SetCursorPosX(x_offset);
 | 
			
		||||
				ImGui::SetCursorPosY(starting_y + i * shift_y);
 | 
			
		||||
				imgui.text(line.c_str());
 | 
			
		||||
			}
 | 
			
		||||
			//hyperlink text
 | 
			
		||||
			if (!m_hypertext.empty())
 | 
			
		||||
			{
 | 
			
		||||
				render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext);
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			
 | 
			
		||||
		} else {
 | 
			
		||||
			// line1
 | 
			
		||||
			ImGui::SetCursorPosX(x_offset);
 | 
			
		||||
			ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
 | 
			
		||||
			imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
 | 
			
		||||
			// line2
 | 
			
		||||
			std::string line = m_text1.substr(m_endlines[0] + 1, m_endlines[1] - m_endlines[0] - 1);
 | 
			
		||||
			if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x)
 | 
			
		||||
			{
 | 
			
		||||
				line = line.substr(0, line.length() - 6);
 | 
			
		||||
				line += "..";
 | 
			
		||||
			}else
 | 
			
		||||
				line += "  ";
 | 
			
		||||
			ImGui::SetCursorPosX(x_offset);
 | 
			
		||||
			ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2);
 | 
			
		||||
			imgui.text(line.c_str());
 | 
			
		||||
			// "More" hypertext
 | 
			
		||||
			render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		//text 1
 | 
			
		||||
		float cursor_y = win_size.y / 2 - text_size.y / 2;
 | 
			
		||||
		float cursor_x = x_offset;
 | 
			
		||||
		if(m_lines_count > 1) {
 | 
			
		||||
			// line1
 | 
			
		||||
			ImGui::SetCursorPosX(x_offset);
 | 
			
		||||
			ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
 | 
			
		||||
			imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
 | 
			
		||||
			// line2
 | 
			
		||||
			std::string line = m_text1.substr(m_endlines[0] + 1);
 | 
			
		||||
			cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2;
 | 
			
		||||
			ImGui::SetCursorPosX(x_offset);
 | 
			
		||||
			ImGui::SetCursorPosY(cursor_y);
 | 
			
		||||
			imgui.text(line.c_str());
 | 
			
		||||
			cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x;
 | 
			
		||||
		} else {
 | 
			
		||||
			ImGui::SetCursorPosX(x_offset);
 | 
			
		||||
			ImGui::SetCursorPosY(cursor_y);
 | 
			
		||||
			imgui.text(m_text1.c_str());
 | 
			
		||||
			cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x;
 | 
			
		||||
		}
 | 
			
		||||
		//hyperlink text
 | 
			
		||||
		if (!m_hypertext.empty())
 | 
			
		||||
		{
 | 
			
		||||
			render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//notification text 2
 | 
			
		||||
		//text 2 is suposed to be after the hyperlink - currently it is not used
 | 
			
		||||
		/*
 | 
			
		||||
		if (!m_text2.empty())
 | 
			
		||||
		{
 | 
			
		||||
			ImVec2 part_size = ImGui::CalcTextSize(m_hypertext.c_str());
 | 
			
		||||
			ImGui::SetCursorPosX(win_size.x / 2 + text_size.x / 2 - part_size.x + 8 - x_offset);
 | 
			
		||||
			ImGui::SetCursorPosY(cursor_y);
 | 
			
		||||
			imgui.text(m_text2.c_str());
 | 
			
		||||
		}
 | 
			
		||||
		*/
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more)
 | 
			
		||||
{
 | 
			
		||||
	//invisible button
 | 
			
		||||
	ImVec2 part_size = ImGui::CalcTextSize(text.c_str());
 | 
			
		||||
	ImGui::SetCursorPosX(text_x -4);
 | 
			
		||||
	ImGui::SetCursorPosY(text_y -5);
 | 
			
		||||
	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
 | 
			
		||||
	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
 | 
			
		||||
	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
 | 
			
		||||
	if (imgui.button("   ", part_size.x + 6, part_size.y + 10))
 | 
			
		||||
	{
 | 
			
		||||
		if (more)
 | 
			
		||||
		{
 | 
			
		||||
			m_multiline = true;
 | 
			
		||||
			set_next_window_size(imgui);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			on_text_click();
 | 
			
		||||
			m_close_pending = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
 | 
			
		||||
	//hover color
 | 
			
		||||
	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
 | 
			
		||||
	if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
 | 
			
		||||
		orange_color.y += 0.2f;
 | 
			
		||||
 | 
			
		||||
	//text
 | 
			
		||||
	Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity);
 | 
			
		||||
	ImGui::SetCursorPosX(text_x);
 | 
			
		||||
	ImGui::SetCursorPosY(text_y);
 | 
			
		||||
	imgui.text(text.c_str());
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
 | 
			
		||||
	//underline
 | 
			
		||||
	ImVec2 lineEnd = ImGui::GetItemRectMax();
 | 
			
		||||
	lineEnd.y -= 2;
 | 
			
		||||
	ImVec2 lineStart = lineEnd;
 | 
			
		||||
	lineStart.x = ImGui::GetItemRectMin().x;
 | 
			
		||||
	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))));
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
 | 
			
		||||
{
 | 
			
		||||
	ImVec2 win_size(win_size_x, win_size_y);
 | 
			
		||||
	ImVec2 win_pos(win_pos_x, win_pos_y);
 | 
			
		||||
	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
 | 
			
		||||
	orange_color.w = 0.8f;
 | 
			
		||||
	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
 | 
			
		||||
	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
 | 
			
		||||
	Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
 | 
			
		||||
	Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
 | 
			
		||||
	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	//button - if part if treggered
 | 
			
		||||
	std::string button_text;
 | 
			
		||||
	button_text = ImGui::CloseIconMarker;
 | 
			
		||||
	
 | 
			
		||||
	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
 | 
			
		||||
		                           ImVec2(win_pos.x, win_pos.y + win_size.y - (m_multiline? 2 * m_line_height : 0)),
 | 
			
		||||
		                           true))
 | 
			
		||||
	{
 | 
			
		||||
		button_text = ImGui::CloseIconHoverMarker;
 | 
			
		||||
	}
 | 
			
		||||
	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
 | 
			
		||||
	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
 | 
			
		||||
	ImGui::SetCursorPosX(win_size.x - m_line_height * 2.25f);
 | 
			
		||||
	ImGui::SetCursorPosY(win_size.y / 2 - button_size.y/2);
 | 
			
		||||
	if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
 | 
			
		||||
	{
 | 
			
		||||
		m_close_pending = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//invisible large button
 | 
			
		||||
	ImGui::SetCursorPosX(win_size.x - win_size.x / 10.f);
 | 
			
		||||
	ImGui::SetCursorPosY(0);
 | 
			
		||||
	if (imgui.button(" ", win_size.x / 10.f, win_size.y - (m_multiline ? 2 * m_line_height : 0)))
 | 
			
		||||
	{
 | 
			
		||||
		m_close_pending = true;
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::PopNotification::render_countdown(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
 | 
			
		||||
{
 | 
			
		||||
	/*
 | 
			
		||||
	ImVec2 win_size(win_size_x, win_size_y);
 | 
			
		||||
	ImVec2 win_pos(win_pos_x, win_pos_y);
 | 
			
		||||
	
 | 
			
		||||
	//countdown dots
 | 
			
		||||
	std::string dot_text;
 | 
			
		||||
	dot_text = m_remaining_time <= (float)m_data.duration / 4 * 3 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
 | 
			
		||||
	ImGui::SetCursorPosX(win_size.x - m_line_height);
 | 
			
		||||
	//ImGui::SetCursorPosY(win_size.y / 2 - 24);
 | 
			
		||||
	ImGui::SetCursorPosY(0);
 | 
			
		||||
	imgui.text(dot_text.c_str());
 | 
			
		||||
 | 
			
		||||
	dot_text = m_remaining_time < m_data.duration / 2 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
 | 
			
		||||
	ImGui::SetCursorPosX(win_size.x - m_line_height);
 | 
			
		||||
	//ImGui::SetCursorPosY(win_size.y / 2 - 9);
 | 
			
		||||
	ImGui::SetCursorPosY(win_size.y / 2 - m_line_height / 2);
 | 
			
		||||
	imgui.text(dot_text.c_str());
 | 
			
		||||
 | 
			
		||||
	dot_text = m_remaining_time <= m_data.duration / 4 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
 | 
			
		||||
	ImGui::SetCursorPosX(win_size.x - m_line_height);
 | 
			
		||||
	//ImGui::SetCursorPosY(win_size.y / 2 + 6);
 | 
			
		||||
	ImGui::SetCursorPosY(win_size.y - m_line_height);
 | 
			
		||||
	imgui.text(dot_text.c_str());
 | 
			
		||||
	*/
 | 
			
		||||
	if (!m_fading_out && m_remaining_time <= m_data.duration / 4) {
 | 
			
		||||
		m_fading_out = true;
 | 
			
		||||
		m_fading_time = m_remaining_time;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if (m_last_remaining_time != m_remaining_time) {
 | 
			
		||||
		m_last_remaining_time = m_remaining_time;
 | 
			
		||||
		m_countdown_frame = 0;
 | 
			
		||||
	}
 | 
			
		||||
	/*
 | 
			
		||||
	//countdown line
 | 
			
		||||
	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
 | 
			
		||||
	float  invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x);
 | 
			
		||||
	invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame);
 | 
			
		||||
	ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5);
 | 
			
		||||
	ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5);
 | 
			
		||||
	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f);
 | 
			
		||||
	if (!m_paused)
 | 
			
		||||
		m_countdown_frame++;
 | 
			
		||||
		*/
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui)
 | 
			
		||||
{
 | 
			
		||||
	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
 | 
			
		||||
		std::string text;
 | 
			
		||||
		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
 | 
			
		||||
		ImGui::SetCursorPosX(m_line_height / 3);
 | 
			
		||||
		ImGui::SetCursorPosY(m_window_height / 2 - m_line_height / 2);
 | 
			
		||||
		imgui.text(text.c_str());
 | 
			
		||||
	} 
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y)
 | 
			
		||||
{
 | 
			
		||||
	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
 | 
			
		||||
	orange_color.w = 0.8f;
 | 
			
		||||
	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
 | 
			
		||||
	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
 | 
			
		||||
	Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
 | 
			
		||||
	Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
 | 
			
		||||
	Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	//button - if part if treggered
 | 
			
		||||
	std::string button_text;
 | 
			
		||||
	button_text = ImGui::CloseIconMarker;
 | 
			
		||||
	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1),
 | 
			
		||||
		ImVec2(win_pos_x, win_pos_y + m_window_height),
 | 
			
		||||
		true)) 
 | 
			
		||||
	{
 | 
			
		||||
		button_text = ImGui::CloseIconHoverMarker;
 | 
			
		||||
	}
 | 
			
		||||
	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
 | 
			
		||||
	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
 | 
			
		||||
	ImGui::SetCursorPosX(m_window_width - m_line_height * 2.25f);
 | 
			
		||||
	ImGui::SetCursorPosY(m_window_height - button_size.y - 5);
 | 
			
		||||
	if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
 | 
			
		||||
	{
 | 
			
		||||
		m_multiline = false;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
	ImGui::PopStyleColor();
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::PopNotification::on_text_click()
 | 
			
		||||
{
 | 
			
		||||
	switch (m_data.type) {
 | 
			
		||||
	case NotificationType::ExportToRemovableFinished :
 | 
			
		||||
		assert(m_evt_handler != nullptr);
 | 
			
		||||
		if (m_evt_handler != nullptr)
 | 
			
		||||
			wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED));
 | 
			
		||||
		break;
 | 
			
		||||
	case NotificationType::SlicingComplete :
 | 
			
		||||
		//wxGetApp().plater()->export_gcode(false);
 | 
			
		||||
		assert(m_evt_handler != nullptr);
 | 
			
		||||
		if (m_evt_handler != nullptr)
 | 
			
		||||
			wxPostEvent(m_evt_handler, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED));
 | 
			
		||||
		break;
 | 
			
		||||
	case NotificationType::PresetUpdateAviable :
 | 
			
		||||
		//wxGetApp().plater()->export_gcode(false);
 | 
			
		||||
		assert(m_evt_handler != nullptr);
 | 
			
		||||
		if (m_evt_handler != nullptr)
 | 
			
		||||
			wxPostEvent(m_evt_handler, PresetUpdateAviableClickedEvent(EVT_PRESET_UPDATE_AVIABLE_CLICKED));
 | 
			
		||||
		break;
 | 
			
		||||
	case NotificationType::NewAppAviable:
 | 
			
		||||
		wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases");
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::PopNotification::update(const NotificationData& n)
 | 
			
		||||
{
 | 
			
		||||
	m_text1          = n.text1;
 | 
			
		||||
	m_hypertext      = n.hypertext;
 | 
			
		||||
    m_text2          = n.text2;
 | 
			
		||||
	init();
 | 
			
		||||
}
 | 
			
		||||
bool NotificationManager::PopNotification::compare_text(const std::string& text)
 | 
			
		||||
{
 | 
			
		||||
	std::string t1(m_text1);
 | 
			
		||||
	std::string t2(text);
 | 
			
		||||
	t1.erase(std::remove_if(t1.begin(), t1.end(), ::isspace), t1.end());
 | 
			
		||||
	t2.erase(std::remove_if(t2.begin(), t2.end(), ::isspace), t2.end());
 | 
			
		||||
	if (t1.compare(t2) == 0)
 | 
			
		||||
		return true;
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool large) :
 | 
			
		||||
	  NotificationManager::PopNotification(n, id, evt_handler)
 | 
			
		||||
{
 | 
			
		||||
	set_large(large);
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
 | 
			
		||||
{
 | 
			
		||||
	if (!m_is_large)
 | 
			
		||||
		PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
 | 
			
		||||
	else {
 | 
			
		||||
		ImVec2 win_size(win_size_x, win_size_y);
 | 
			
		||||
		ImVec2 win_pos(win_pos_x, win_pos_y);
 | 
			
		||||
 | 
			
		||||
		ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str());
 | 
			
		||||
		float x_offset = m_left_indentation;
 | 
			
		||||
		std::string fulltext = m_text1 + m_hypertext + m_text2;
 | 
			
		||||
		ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str());
 | 
			
		||||
		float cursor_y = win_size.y / 2 - text_size.y / 2;
 | 
			
		||||
		if (m_has_print_info) {
 | 
			
		||||
			x_offset = 20;
 | 
			
		||||
			cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2;
 | 
			
		||||
			ImGui::SetCursorPosX(x_offset);
 | 
			
		||||
			ImGui::SetCursorPosY(cursor_y);
 | 
			
		||||
			imgui.text(m_print_info.c_str());
 | 
			
		||||
			cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2;
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::SetCursorPosX(x_offset);
 | 
			
		||||
		ImGui::SetCursorPosY(cursor_y);
 | 
			
		||||
		imgui.text(m_text1.c_str());
 | 
			
		||||
 | 
			
		||||
		render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext);
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::SlicingCompleteLargeNotification::set_print_info(std::string info)
 | 
			
		||||
{
 | 
			
		||||
	m_print_info = info;
 | 
			
		||||
	m_has_print_info = true;
 | 
			
		||||
	if(m_is_large)
 | 
			
		||||
		m_lines_count = 2;
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l)
 | 
			
		||||
{
 | 
			
		||||
	m_is_large = l;
 | 
			
		||||
	m_counting_down = !l;
 | 
			
		||||
	m_hypertext = l ? _u8L("Export G-Code.") : std::string();
 | 
			
		||||
	m_hidden = !l;
 | 
			
		||||
}
 | 
			
		||||
//------NotificationManager--------
 | 
			
		||||
NotificationManager::NotificationManager(wxEvtHandler* evt_handler) :
 | 
			
		||||
	m_evt_handler(evt_handler)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
NotificationManager::~NotificationManager()
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications)
 | 
			
		||||
	{
 | 
			
		||||
		delete notification;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp)
 | 
			
		||||
{
 | 
			
		||||
	auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(),
 | 
			
		||||
		boost::bind(&NotificationData::type, _1) == type);	
 | 
			
		||||
	if (it != basic_notifications.end())
 | 
			
		||||
		push_notification_data( *it, canvas, timestamp);
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp)
 | 
			
		||||
{
 | 
			
		||||
	push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, canvas, timestamp );
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, GLCanvas3D& canvas, int timestamp)
 | 
			
		||||
{
 | 
			
		||||
	switch (level)
 | 
			
		||||
	{
 | 
			
		||||
	case Slic3r::GUI::NotificationManager::NotificationLevel::RegularNotification:
 | 
			
		||||
		push_notification_data({ NotificationType::CustomNotification, level, 10, text }, canvas, timestamp);
 | 
			
		||||
		break;
 | 
			
		||||
	case Slic3r::GUI::NotificationManager::NotificationLevel::ErrorNotification:
 | 
			
		||||
		push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp);
 | 
			
		||||
 | 
			
		||||
		break;
 | 
			
		||||
	case Slic3r::GUI::NotificationManager::NotificationLevel::ImportantNotification:
 | 
			
		||||
		push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas)
 | 
			
		||||
{
 | 
			
		||||
	set_all_slicing_errors_gray(false);
 | 
			
		||||
	push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, canvas, 0);
 | 
			
		||||
	close_notification_of_type(NotificationType::SlicingComplete);
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step)
 | 
			
		||||
{
 | 
			
		||||
	NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text };
 | 
			
		||||
 | 
			
		||||
	NotificationManager::SlicingWarningNotification* notification = new NotificationManager::SlicingWarningNotification(data, m_next_id++, m_evt_handler);
 | 
			
		||||
	notification->set_object_id(oid);
 | 
			
		||||
	notification->set_warning_step(warning_step);
 | 
			
		||||
	if 
 | 
			
		||||
		(push_notification_data(notification, canvas, 0)) {
 | 
			
		||||
		notification->set_gray(gray);		
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		delete notification;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::push_plater_error_notification(const std::string& text, GLCanvas3D& canvas)
 | 
			
		||||
{
 | 
			
		||||
	push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, canvas, 0);
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas)
 | 
			
		||||
{
 | 
			
		||||
	push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text }, canvas, 0);
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::close_plater_error_notification()
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications) {
 | 
			
		||||
		if (notification->get_type() == NotificationType::PlaterError) {
 | 
			
		||||
			notification->close();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::close_plater_warning_notification(const std::string& text)
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications) {
 | 
			
		||||
		if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) {
 | 
			
		||||
			notification->close();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::set_all_slicing_errors_gray(bool g)
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications) {
 | 
			
		||||
		if (notification->get_type() == NotificationType::SlicingError) {
 | 
			
		||||
			notification->set_gray(g);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::set_all_slicing_warnings_gray(bool g)
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications) {
 | 
			
		||||
		if (notification->get_type() == NotificationType::SlicingWarning) {
 | 
			
		||||
			notification->set_gray(g);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::set_slicing_warning_gray(const std::string& text, bool g)
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications) {
 | 
			
		||||
		if (notification->get_type() == NotificationType::SlicingWarning && notification->compare_text(text)) {
 | 
			
		||||
			notification->set_gray(g);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::close_slicing_errors_and_warnings()
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications) {
 | 
			
		||||
		if (notification->get_type() == NotificationType::SlicingError || notification->get_type() == NotificationType::SlicingWarning) {
 | 
			
		||||
			notification->close();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large)
 | 
			
		||||
{
 | 
			
		||||
	std::string hypertext;
 | 
			
		||||
	int         time = 10;
 | 
			
		||||
	if(large)
 | 
			
		||||
	{
 | 
			
		||||
		hypertext = _u8L("Export G-Code.");
 | 
			
		||||
		time = 0;
 | 
			
		||||
	}
 | 
			
		||||
	NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time,  _u8L("Slicing finished."), hypertext };
 | 
			
		||||
 | 
			
		||||
	NotificationManager::SlicingCompleteLargeNotification* notification = new NotificationManager::SlicingCompleteLargeNotification(data, m_next_id++, m_evt_handler, large);
 | 
			
		||||
	if (push_notification_data(notification, canvas, timestamp)) {
 | 
			
		||||
	} else {
 | 
			
		||||
		delete notification;
 | 
			
		||||
	}	
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::set_slicing_complete_print_time(std::string info)
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications) {
 | 
			
		||||
		if (notification->get_type() == NotificationType::SlicingComplete) {
 | 
			
		||||
			dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_print_info(info);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::set_slicing_complete_large(bool large)
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications) {
 | 
			
		||||
		if (notification->get_type() == NotificationType::SlicingComplete) {
 | 
			
		||||
			dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_large(large);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::close_notification_of_type(const NotificationType type)
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications) {
 | 
			
		||||
		if (notification->get_type() == type) {
 | 
			
		||||
			notification->close();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::compare_warning_oids(const std::vector<size_t>& living_oids)
 | 
			
		||||
{
 | 
			
		||||
	for (PopNotification* notification : m_pop_notifications) {
 | 
			
		||||
		if (notification->get_type() == NotificationType::SlicingWarning) {
 | 
			
		||||
			auto w = dynamic_cast<SlicingWarningNotification*>(notification);
 | 
			
		||||
			bool found = false;
 | 
			
		||||
			for (size_t oid : living_oids) {
 | 
			
		||||
				if (w->get_object_id() == oid) {
 | 
			
		||||
					found = true;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if (!found)
 | 
			
		||||
				notification->close();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
bool NotificationManager::push_notification_data(const NotificationData ¬ification_data,  GLCanvas3D& canvas, int timestamp)
 | 
			
		||||
{
 | 
			
		||||
	PopNotification* n = new PopNotification(notification_data, m_next_id++, m_evt_handler);
 | 
			
		||||
	bool r = push_notification_data(n, canvas, timestamp);
 | 
			
		||||
	if (!r)
 | 
			
		||||
		delete n;
 | 
			
		||||
	return r;
 | 
			
		||||
}
 | 
			
		||||
bool NotificationManager::push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp)
 | 
			
		||||
{
 | 
			
		||||
	// if timestamped notif, push only new one
 | 
			
		||||
	if (timestamp != 0) {
 | 
			
		||||
		if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) {
 | 
			
		||||
			m_used_timestamps.insert(timestamp);
 | 
			
		||||
		} else {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (!this->find_older(notification)) {
 | 
			
		||||
			m_pop_notifications.emplace_back(notification);
 | 
			
		||||
		canvas.request_extra_frame();
 | 
			
		||||
		return true;
 | 
			
		||||
	} else {
 | 
			
		||||
		m_pop_notifications.back()->update(notification->get_data());
 | 
			
		||||
		canvas.request_extra_frame();
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
void NotificationManager::render_notifications(GLCanvas3D& canvas)
 | 
			
		||||
{
 | 
			
		||||
	float    last_x = 0.0f;
 | 
			
		||||
	float    current_height = 0.0f;
 | 
			
		||||
	bool     request_next_frame = false;
 | 
			
		||||
	bool     render_main = false;
 | 
			
		||||
	bool     hovered = false;	
 | 
			
		||||
	sort_notifications();
 | 
			
		||||
	// iterate thru notifications and render them / erease them
 | 
			
		||||
	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) {
 | 
			
		||||
		if ((*it)->get_finished()) {
 | 
			
		||||
			delete (*it);
 | 
			
		||||
			it = m_pop_notifications.erase(it);
 | 
			
		||||
		} else {
 | 
			
		||||
			(*it)->set_paused(m_hovered);
 | 
			
		||||
			PopNotification::RenderResult res = (*it)->render(canvas, last_x);
 | 
			
		||||
			if (res != PopNotification::RenderResult::Finished) {
 | 
			
		||||
				last_x = (*it)->get_top() + GAP_WIDTH;
 | 
			
		||||
				current_height = std::max(current_height, (*it)->get_current_top());
 | 
			
		||||
				render_main = true;
 | 
			
		||||
			}
 | 
			
		||||
			if (res == PopNotification::RenderResult::Countdown || res == PopNotification::RenderResult::ClosePending || res == PopNotification::RenderResult::Finished)
 | 
			
		||||
				request_next_frame = true;
 | 
			
		||||
			if (res == PopNotification::RenderResult::Hovered)
 | 
			
		||||
				hovered = true;
 | 
			
		||||
			++it;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	m_hovered = hovered;
 | 
			
		||||
 | 
			
		||||
	//actualizate timers and request frame if needed
 | 
			
		||||
	wxWindow* p = dynamic_cast<wxWindow*> (wxGetApp().plater());
 | 
			
		||||
	while (p->GetParent())
 | 
			
		||||
		p = p->GetParent();
 | 
			
		||||
	wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
 | 
			
		||||
	if (!top_level_wnd->IsActive())
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (!m_hovered && m_last_time < wxGetLocalTime())
 | 
			
		||||
	{
 | 
			
		||||
		if (wxGetLocalTime() - m_last_time == 1)
 | 
			
		||||
		{
 | 
			
		||||
			for(auto notification : m_pop_notifications)
 | 
			
		||||
			{
 | 
			
		||||
				notification->substract_remaining_time();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		m_last_time = wxGetLocalTime();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (request_next_frame)
 | 
			
		||||
		canvas.request_extra_frame();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void NotificationManager::sort_notifications()
 | 
			
		||||
{
 | 
			
		||||
	std::sort(m_pop_notifications.begin(), m_pop_notifications.end(), [](PopNotification* n1, PopNotification* n2) {
 | 
			
		||||
		int n1l = (int)n1->get_data().level;
 | 
			
		||||
		int n2l = (int)n2->get_data().level;
 | 
			
		||||
		if (n1l == n2l && n1->get_is_gray() && !n2->get_is_gray())
 | 
			
		||||
			return true;
 | 
			
		||||
		return (n1l < n2l);
 | 
			
		||||
		});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NotificationManager::find_older(NotificationManager::PopNotification* notification)
 | 
			
		||||
{
 | 
			
		||||
	NotificationType type = notification->get_type();
 | 
			
		||||
	std::string text = notification->get_data().text1;
 | 
			
		||||
	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) {
 | 
			
		||||
		if((*it)->get_type() == type && !(*it)->get_finished()) {
 | 
			
		||||
			if (type == NotificationType::CustomNotification || type == NotificationType::PlaterWarning) {
 | 
			
		||||
				if (!(*it)->compare_text(text))
 | 
			
		||||
					continue;
 | 
			
		||||
			}else if (type == NotificationType::SlicingWarning) {
 | 
			
		||||
				auto w1 = dynamic_cast<SlicingWarningNotification*>(notification);
 | 
			
		||||
				auto w2 = dynamic_cast<SlicingWarningNotification*>(*it);
 | 
			
		||||
				if (w1 != nullptr && w2 != nullptr) {
 | 
			
		||||
					if (!(*it)->compare_text(text) || w1->get_object_id() != w2->get_object_id()) {
 | 
			
		||||
						continue;
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (it != m_pop_notifications.end() - 1)
 | 
			
		||||
				std::rotate(it, it + 1, m_pop_notifications.end());
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NotificationManager::dpi_changed()
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}//namespace GUI
 | 
			
		||||
}//namespace Slic3r
 | 
			
		||||
							
								
								
									
										268
									
								
								src/slic3r/GUI/NotificationManager.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/slic3r/GUI/NotificationManager.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,268 @@
 | 
			
		|||
#ifndef slic3r_GUI_NotificationManager_hpp_
 | 
			
		||||
#define slic3r_GUI_NotificationManager_hpp_
 | 
			
		||||
 | 
			
		||||
#include "Event.hpp"
 | 
			
		||||
#include "I18N.hpp"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <deque>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
namespace GUI {
 | 
			
		||||
 | 
			
		||||
using EjectDriveNotificationClickedEvent = SimpleEvent;
 | 
			
		||||
wxDECLARE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
 | 
			
		||||
using ExportGcodeNotificationClickedEvent = SimpleEvent;
 | 
			
		||||
wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
 | 
			
		||||
using PresetUpdateAviableClickedEvent = SimpleEvent;
 | 
			
		||||
wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent);
 | 
			
		||||
 | 
			
		||||
class GLCanvas3D;
 | 
			
		||||
class ImGuiWrapper;
 | 
			
		||||
 | 
			
		||||
enum class NotificationType
 | 
			
		||||
{
 | 
			
		||||
	CustomNotification,
 | 
			
		||||
	SlicingComplete,
 | 
			
		||||
	SlicingNotPossible,
 | 
			
		||||
	ExportToRemovableFinished,
 | 
			
		||||
	Mouse3dDisconnected,
 | 
			
		||||
	Mouse3dConnected,
 | 
			
		||||
	NewPresetsAviable,
 | 
			
		||||
	NewAppAviable,
 | 
			
		||||
	PresetUpdateAviable,
 | 
			
		||||
	LoadingFailed,
 | 
			
		||||
	ValidateError, // currently not used - instead Slicing error is used for both slicing and validate errors
 | 
			
		||||
	SlicingError,
 | 
			
		||||
	SlicingWarning,
 | 
			
		||||
	PlaterError,
 | 
			
		||||
	PlaterWarning,
 | 
			
		||||
	ApplyError
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
class NotificationManager
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	enum class NotificationLevel : int
 | 
			
		||||
	{
 | 
			
		||||
		ErrorNotification =     4,
 | 
			
		||||
		WarningNotification =   3,
 | 
			
		||||
		ImportantNotification = 2,
 | 
			
		||||
		RegularNotification   = 1,
 | 
			
		||||
	};
 | 
			
		||||
	// duration 0 means not disapearing
 | 
			
		||||
	struct NotificationData {
 | 
			
		||||
		NotificationType    type;
 | 
			
		||||
		NotificationLevel   level;
 | 
			
		||||
		const int           duration;
 | 
			
		||||
		const std::string   text1;
 | 
			
		||||
		const std::string   hypertext = std::string();
 | 
			
		||||
		const std::string   text2     = std::string();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	//Pop notification - shows only once to user.
 | 
			
		||||
	class PopNotification
 | 
			
		||||
	{
 | 
			
		||||
	public:
 | 
			
		||||
		enum class RenderResult
 | 
			
		||||
		{
 | 
			
		||||
			Finished,
 | 
			
		||||
			ClosePending,
 | 
			
		||||
			Static,
 | 
			
		||||
			Countdown,
 | 
			
		||||
			Hovered
 | 
			
		||||
		};
 | 
			
		||||
		 PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler);
 | 
			
		||||
		virtual ~PopNotification();
 | 
			
		||||
		RenderResult           render(GLCanvas3D& canvas, const float& initial_y);
 | 
			
		||||
		// close will dissapear notification on next render
 | 
			
		||||
		void                   close() { m_close_pending = true; }
 | 
			
		||||
		// data from newer notification of same type
 | 
			
		||||
		void                   update(const NotificationData& n);
 | 
			
		||||
		bool                   get_finished() const { return m_finished; }
 | 
			
		||||
		// returns top after movement
 | 
			
		||||
		float                  get_top() const { return m_top_y; }
 | 
			
		||||
		//returns top in actual frame
 | 
			
		||||
		float                  get_current_top() const { return m_top_y; }
 | 
			
		||||
		const NotificationType get_type() const { return m_data.type; }
 | 
			
		||||
		const NotificationData get_data() const { return m_data;  }
 | 
			
		||||
		const bool             get_is_gray() const { return m_is_gray; }
 | 
			
		||||
		// Call equals one second down
 | 
			
		||||
		void                   substract_remaining_time() { m_remaining_time--; }
 | 
			
		||||
		void                   set_gray(bool g) { m_is_gray = g; }
 | 
			
		||||
		void                   set_paused(bool p) { m_paused = p; }
 | 
			
		||||
		bool                   compare_text(const std::string& text);
 | 
			
		||||
	protected:
 | 
			
		||||
		// Call after every size change
 | 
			
		||||
		void         init();
 | 
			
		||||
		// Calculetes correct size but not se it in imgui!
 | 
			
		||||
		virtual void set_next_window_size(ImGuiWrapper& imgui);
 | 
			
		||||
		virtual void render_text(ImGuiWrapper& imgui,
 | 
			
		||||
			                     const float win_size_x, const float win_size_y,
 | 
			
		||||
			                     const float win_pos_x , const float win_pos_y);
 | 
			
		||||
		void         render_close_button(ImGuiWrapper& imgui,
 | 
			
		||||
			                             const float win_size_x, const float win_size_y,
 | 
			
		||||
			                             const float win_pos_x , const float win_pos_y);
 | 
			
		||||
		void         render_countdown(ImGuiWrapper& imgui,
 | 
			
		||||
			                          const float win_size_x, const float win_size_y,
 | 
			
		||||
			                          const float win_pos_x , const float win_pos_y);
 | 
			
		||||
		void         render_hypertext(ImGuiWrapper& imgui,
 | 
			
		||||
			                          const float text_x, const float text_y,
 | 
			
		||||
		                              const std::string text,
 | 
			
		||||
		                              bool more = false);
 | 
			
		||||
		void         render_left_sign(ImGuiWrapper& imgui);
 | 
			
		||||
		void         render_minimize_button(ImGuiWrapper& imgui,
 | 
			
		||||
			                                const float win_pos_x, const float win_pos_y);
 | 
			
		||||
		void         on_text_click();
 | 
			
		||||
 | 
			
		||||
		const NotificationData m_data;
 | 
			
		||||
 | 
			
		||||
		int              m_id;
 | 
			
		||||
		// Main text
 | 
			
		||||
		std::string      m_text1;
 | 
			
		||||
		// Clickable text
 | 
			
		||||
		std::string      m_hypertext;
 | 
			
		||||
		// Aditional text after hypertext - currently not used
 | 
			
		||||
		std::string      m_text2;
 | 
			
		||||
		// Countdown variables
 | 
			
		||||
		long             m_remaining_time;
 | 
			
		||||
		bool             m_counting_down;
 | 
			
		||||
		long             m_last_remaining_time;
 | 
			
		||||
		bool             m_paused{ false };
 | 
			
		||||
		int              m_countdown_frame{ 0 };
 | 
			
		||||
		bool             m_fading_out{ false };
 | 
			
		||||
		// total time left when fading beggins
 | 
			
		||||
		float            m_fading_time{ 0.0f }; 
 | 
			
		||||
		float            m_current_fade_opacity{ 1.f };
 | 
			
		||||
		// If hidden the notif is alive but not visible to user
 | 
			
		||||
		bool             m_hidden               { false };
 | 
			
		||||
		//  m_finished = true - does not render, marked to delete
 | 
			
		||||
		bool             m_finished             { false }; 
 | 
			
		||||
		// Will go to m_finished next render
 | 
			
		||||
		bool             m_close_pending        { false }; 
 | 
			
		||||
		// variables to count positions correctly
 | 
			
		||||
		float            m_window_width_offset;
 | 
			
		||||
		float            m_left_indentation;
 | 
			
		||||
		// Total size of notification window - varies based on monitor
 | 
			
		||||
		float            m_window_height        { 56.0f };  
 | 
			
		||||
		float            m_window_width         { 450.0f };
 | 
			
		||||
		//Distance from bottom of notifications to top of this notification
 | 
			
		||||
		float            m_top_y                { 0.0f };  
 | 
			
		||||
		
 | 
			
		||||
		// Height of text
 | 
			
		||||
		// Used as basic scaling unit!
 | 
			
		||||
		float            m_line_height;
 | 
			
		||||
		std::vector<int> m_endlines;
 | 
			
		||||
		// Gray are f.e. eorrors when its uknown if they are still valid
 | 
			
		||||
		bool             m_is_gray              { false };
 | 
			
		||||
		//if multiline = true, notification is showing all lines(>2)
 | 
			
		||||
		bool             m_multiline            { false };
 | 
			
		||||
		int              m_lines_count{ 1 };
 | 
			
		||||
		wxEvtHandler*    m_evt_handler;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	class SlicingCompleteLargeNotification : public PopNotification
 | 
			
		||||
	{
 | 
			
		||||
	public:
 | 
			
		||||
		SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool largeds);
 | 
			
		||||
		void set_large(bool l);
 | 
			
		||||
		bool get_large() { return m_is_large; }
 | 
			
		||||
 | 
			
		||||
		void set_print_info(std::string info);
 | 
			
		||||
	protected:
 | 
			
		||||
		virtual void render_text(ImGuiWrapper& imgui,
 | 
			
		||||
			                     const float win_size_x, const float win_size_y,
 | 
			
		||||
			                     const float win_pos_x, const float win_pos_y) 
 | 
			
		||||
			                     override;
 | 
			
		||||
 | 
			
		||||
		bool        m_is_large;
 | 
			
		||||
		bool        m_has_print_info { false };
 | 
			
		||||
		std::string m_print_info { std::string() };
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	class SlicingWarningNotification : public PopNotification
 | 
			
		||||
	{
 | 
			
		||||
	public:
 | 
			
		||||
		SlicingWarningNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler) : PopNotification(n, id, evt_handler) {}
 | 
			
		||||
		void         set_object_id(size_t id) { object_id = id; }
 | 
			
		||||
		const size_t get_object_id() { return object_id; }
 | 
			
		||||
		void         set_warning_step(int ws) { warning_step = ws; }
 | 
			
		||||
		const int    get_warning_step() { return warning_step; }
 | 
			
		||||
	protected:
 | 
			
		||||
		size_t object_id;
 | 
			
		||||
		int    warning_step;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	NotificationManager(wxEvtHandler* evt_handler);
 | 
			
		||||
	~NotificationManager();
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	// only type means one of basic_notification (see below)
 | 
			
		||||
	void push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp = 0);
 | 
			
		||||
	// only text means Undefined type
 | 
			
		||||
	void push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp = 0);
 | 
			
		||||
	void push_notification(const std::string& text, NotificationLevel level, GLCanvas3D& canvas, int timestamp = 0);
 | 
			
		||||
	// creates Slicing Error notification with custom text
 | 
			
		||||
	void push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas);
 | 
			
		||||
	// creates Slicing Warning notification with custom text
 | 
			
		||||
	void push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step);
 | 
			
		||||
	// marks slicing errors as gray
 | 
			
		||||
	void set_all_slicing_errors_gray(bool g);
 | 
			
		||||
	// marks slicing warings as gray
 | 
			
		||||
	void set_all_slicing_warnings_gray(bool g);
 | 
			
		||||
	void set_slicing_warning_gray(const std::string& text, bool g);
 | 
			
		||||
	// imidietly stops showing slicing errors
 | 
			
		||||
	void close_slicing_errors_and_warnings();
 | 
			
		||||
	void compare_warning_oids(const std::vector<size_t>& living_oids);
 | 
			
		||||
	void push_plater_error_notification(const std::string& text, GLCanvas3D& canvas);
 | 
			
		||||
	void push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas);
 | 
			
		||||
	void close_plater_error_notification();
 | 
			
		||||
	void close_plater_warning_notification(const std::string& text);
 | 
			
		||||
	// creates special notification slicing complete
 | 
			
		||||
	// if large = true prints printing time and export button 
 | 
			
		||||
	void push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large);
 | 
			
		||||
	void set_slicing_complete_print_time(std::string info);
 | 
			
		||||
	void set_slicing_complete_large(bool large);
 | 
			
		||||
	// renders notifications in queue and deletes expired ones
 | 
			
		||||
	void render_notifications(GLCanvas3D& canvas);
 | 
			
		||||
	// finds and closes all notifications of given type
 | 
			
		||||
	void close_notification_of_type(const NotificationType type);
 | 
			
		||||
	void dpi_changed();
 | 
			
		||||
private:
 | 
			
		||||
	//pushes notification into the queue of notifications that are rendered
 | 
			
		||||
	//can be used to create custom notification
 | 
			
		||||
	bool push_notification_data(const NotificationData& notification_data, GLCanvas3D& canvas, int timestamp);
 | 
			
		||||
	bool push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp);
 | 
			
		||||
	//finds older notification of same type and moves it to the end of queue. returns true if found
 | 
			
		||||
	bool find_older(NotificationManager::PopNotification* notification);
 | 
			
		||||
	void sort_notifications();
 | 
			
		||||
 | 
			
		||||
	wxEvtHandler*                m_evt_handler;
 | 
			
		||||
	std::deque<PopNotification*> m_pop_notifications;
 | 
			
		||||
	int                          m_next_id { 1 };
 | 
			
		||||
	long                         m_last_time { 0 };
 | 
			
		||||
	bool                         m_hovered { false };
 | 
			
		||||
	//timestamps used for slining finished - notification could be gone so it needs to be stored here
 | 
			
		||||
	std::unordered_set<int>      m_used_timestamps;
 | 
			
		||||
 | 
			
		||||
	//prepared (basic) notifications
 | 
			
		||||
	const std::vector<NotificationData> basic_notifications = {
 | 
			
		||||
		{NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10,  _u8L("Slicing is not possible.")},
 | 
			
		||||
		{NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0,  _u8L("Exporting finished."),  _u8L("Eject drive.") },
 | 
			
		||||
		{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10,  _u8L("3D Mouse disconnected.") },
 | 
			
		||||
		{NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5,  _u8L("3D Mouse connected.") },
 | 
			
		||||
		{NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20,  _u8L("New Presets are available."),  _u8L("See here.") },
 | 
			
		||||
		{NotificationType::PresetUpdateAviable, NotificationLevel::ImportantNotification, 20,  _u8L("Configuration update is available."),  _u8L("See more.")},
 | 
			
		||||
		{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20,  _u8L("New version is available."),  _u8L("See Releases page.")},
 | 
			
		||||
		//{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20,  _u8L("New vesion of PrusaSlicer is available.",  _u8L("Download page.") },
 | 
			
		||||
		//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20,  _u8L("Loading of model has Failed") },
 | 
			
		||||
		//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10,  _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}//namespace GUI
 | 
			
		||||
}//namespace Slic3r
 | 
			
		||||
 | 
			
		||||
#endif //slic3r_GUI_NotificationManager_hpp_
 | 
			
		||||
| 
						 | 
				
			
			@ -74,8 +74,10 @@
 | 
			
		|||
#include "../Utils/PrintHost.hpp"
 | 
			
		||||
#include "../Utils/FixModelByWin10.hpp"
 | 
			
		||||
#include "../Utils/UndoRedo.hpp"
 | 
			
		||||
#include "../Utils/PresetUpdater.hpp"
 | 
			
		||||
#include "RemovableDriveManager.hpp"
 | 
			
		||||
#include "InstanceCheck.hpp"
 | 
			
		||||
#include "NotificationManager.hpp"
 | 
			
		||||
#include "PresetComboBoxes.hpp"
 | 
			
		||||
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
| 
						 | 
				
			
			@ -102,6 +104,7 @@ wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS,     SimpleEvent);
 | 
			
		|||
wxDEFINE_EVENT(EVT_SLICING_UPDATE,                  SlicingStatusEvent);
 | 
			
		||||
wxDEFINE_EVENT(EVT_SLICING_COMPLETED,               wxCommandEvent);
 | 
			
		||||
wxDEFINE_EVENT(EVT_PROCESS_COMPLETED,               wxCommandEvent);
 | 
			
		||||
wxDEFINE_EVENT(EVT_EXPORT_BEGAN,                    wxCommandEvent);
 | 
			
		||||
 | 
			
		||||
// Sidebar widgets
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -569,7 +572,7 @@ struct Sidebar::priv
 | 
			
		|||
    wxButton *btn_export_gcode;
 | 
			
		||||
    wxButton *btn_reslice;
 | 
			
		||||
    ScalableButton *btn_send_gcode;
 | 
			
		||||
    ScalableButton *btn_remove_device;
 | 
			
		||||
    ScalableButton *btn_eject_device;
 | 
			
		||||
	ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected)
 | 
			
		||||
 | 
			
		||||
    bool                is_collapsed {false};
 | 
			
		||||
| 
						 | 
				
			
			@ -742,12 +745,12 @@ Sidebar::Sidebar(Plater *parent)
 | 
			
		|||
    };
 | 
			
		||||
 | 
			
		||||
    init_scalable_btn(&p->btn_send_gcode   , "export_gcode", _L("Send to printer") + "\tCtrl+Shift+G");
 | 
			
		||||
    init_scalable_btn(&p->btn_remove_device, "eject_sd"       , _L("Remove device") + "\tCtrl+T");
 | 
			
		||||
    init_scalable_btn(&p->btn_eject_device, "eject_sd"       , _L("Remove device") + "\tCtrl+T");
 | 
			
		||||
	init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + "\tCtrl+U");
 | 
			
		||||
 | 
			
		||||
    // regular buttons "Slice now" and "Export G-code" 
 | 
			
		||||
 | 
			
		||||
    const int scaled_height = p->btn_remove_device->GetBitmapHeight() + 4;
 | 
			
		||||
    const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4;
 | 
			
		||||
    auto init_btn = [this](wxButton **btn, wxString label, const int button_height) {
 | 
			
		||||
        *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition,
 | 
			
		||||
                            wxSize(-1, button_height), wxBU_EXACTFIT);
 | 
			
		||||
| 
						 | 
				
			
			@ -765,7 +768,7 @@ Sidebar::Sidebar(Plater *parent)
 | 
			
		|||
    complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND);
 | 
			
		||||
    complect_btns_sizer->Add(p->btn_send_gcode);
 | 
			
		||||
	complect_btns_sizer->Add(p->btn_export_gcode_removable);
 | 
			
		||||
    complect_btns_sizer->Add(p->btn_remove_device);
 | 
			
		||||
    complect_btns_sizer->Add(p->btn_eject_device);
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
    btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5);
 | 
			
		||||
| 
						 | 
				
			
			@ -788,7 +791,7 @@ Sidebar::Sidebar(Plater *parent)
 | 
			
		|||
        p->plater->select_view_3D("Preview");
 | 
			
		||||
    });
 | 
			
		||||
    p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); });
 | 
			
		||||
    p->btn_remove_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); });
 | 
			
		||||
    p->btn_eject_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); });
 | 
			
		||||
	p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -931,9 +934,9 @@ void Sidebar::msw_rescale()
 | 
			
		|||
    p->object_info->msw_rescale();
 | 
			
		||||
 | 
			
		||||
    p->btn_send_gcode->msw_rescale();
 | 
			
		||||
    p->btn_remove_device->msw_rescale();
 | 
			
		||||
    p->btn_eject_device->msw_rescale();
 | 
			
		||||
	p->btn_export_gcode_removable->msw_rescale();
 | 
			
		||||
    const int scaled_height = p->btn_remove_device->GetBitmap().GetHeight() + 4;
 | 
			
		||||
    const int scaled_height = p->btn_eject_device->GetBitmap().GetHeight() + 4;
 | 
			
		||||
    p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height));
 | 
			
		||||
    p->btn_reslice     ->SetMinSize(wxSize(-1, scaled_height));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -956,7 +959,7 @@ void Sidebar::sys_color_changed()
 | 
			
		|||
 | 
			
		||||
    // btn...->msw_rescale() updates icon on button, so use it
 | 
			
		||||
    p->btn_send_gcode->msw_rescale();
 | 
			
		||||
    p->btn_remove_device->msw_rescale();
 | 
			
		||||
    p->btn_eject_device->msw_rescale();
 | 
			
		||||
    p->btn_export_gcode_removable->msw_rescale();
 | 
			
		||||
 | 
			
		||||
    p->scrolled->Layout();
 | 
			
		||||
| 
						 | 
				
			
			@ -1192,6 +1195,12 @@ void Sidebar::update_sliced_info_sizer()
 | 
			
		|||
                    new_label += format_wxstr("\n   - %1%", _L("normal mode"));
 | 
			
		||||
                    info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time);
 | 
			
		||||
                    fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text);
 | 
			
		||||
 | 
			
		||||
					// uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate
 | 
			
		||||
					//if (p->plater->is_sidebar_collapsed())
 | 
			
		||||
					p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed());
 | 
			
		||||
					p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time);
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                if (ps.estimated_silent_print_time != "N/A") {
 | 
			
		||||
                    new_label += format_wxstr("\n   - %1%", _L("stealth mode"));
 | 
			
		||||
| 
						 | 
				
			
			@ -1227,15 +1236,16 @@ void Sidebar::enable_buttons(bool enable)
 | 
			
		|||
    p->btn_reslice->Enable(enable);
 | 
			
		||||
    p->btn_export_gcode->Enable(enable);
 | 
			
		||||
    p->btn_send_gcode->Enable(enable);
 | 
			
		||||
    p->btn_remove_device->Enable(enable);
 | 
			
		||||
    p->btn_eject_device->Enable(enable);
 | 
			
		||||
	p->btn_export_gcode_removable->Enable(enable);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Sidebar::show_reslice(bool show)         const { return p->btn_reslice->Show(show); }
 | 
			
		||||
bool Sidebar::show_export(bool show)          const { return p->btn_export_gcode->Show(show); }
 | 
			
		||||
bool Sidebar::show_send(bool show)            const { return p->btn_send_gcode->Show(show); }
 | 
			
		||||
bool Sidebar::show_disconnect(bool show)      const { return p->btn_remove_device->Show(show); }
 | 
			
		||||
bool Sidebar::show_export_removable(bool show)const { return p->btn_export_gcode_removable->Show(show); }
 | 
			
		||||
bool Sidebar::show_reslice(bool show)          const { return p->btn_reslice->Show(show); }
 | 
			
		||||
bool Sidebar::show_export(bool show)           const { return p->btn_export_gcode->Show(show); }
 | 
			
		||||
bool Sidebar::show_send(bool show)             const { return p->btn_send_gcode->Show(show); }
 | 
			
		||||
bool Sidebar::show_export_removable(bool show) const { return p->btn_export_gcode_removable->Show(show); }
 | 
			
		||||
bool Sidebar::show_eject(bool show)            const { return p->btn_eject_device->Show(show); }
 | 
			
		||||
bool Sidebar::get_eject_shown()                const { return p->btn_eject_device->IsShown(); }
 | 
			
		||||
 | 
			
		||||
bool Sidebar::is_multifilament()
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -1433,6 +1443,7 @@ struct Plater::priv
 | 
			
		|||
    GLToolbar view_toolbar;
 | 
			
		||||
    GLToolbar collapse_toolbar;
 | 
			
		||||
    Preview *preview;
 | 
			
		||||
	NotificationManager* notification_manager;
 | 
			
		||||
 | 
			
		||||
    BackgroundSlicingProcess    background_process;
 | 
			
		||||
    bool suppressed_backround_processing_update { false };
 | 
			
		||||
| 
						 | 
				
			
			@ -1617,7 +1628,17 @@ struct Plater::priv
 | 
			
		|||
    void on_slicing_update(SlicingStatusEvent&);
 | 
			
		||||
    void on_slicing_completed(wxCommandEvent&);
 | 
			
		||||
    void on_process_completed(wxCommandEvent&);
 | 
			
		||||
	void on_export_began(wxCommandEvent&);
 | 
			
		||||
    void on_layer_editing_toggled(bool enable);
 | 
			
		||||
	void on_slicing_began();
 | 
			
		||||
 | 
			
		||||
	void clear_warnings();
 | 
			
		||||
	void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid);
 | 
			
		||||
	void actualizate_warnings(const Model& model, size_t print_oid);
 | 
			
		||||
	// Displays dialog window with list of warnings. 
 | 
			
		||||
	// Returns true if user clicks OK.
 | 
			
		||||
	// Returns true if current_warnings vector is empty without showning the dialog
 | 
			
		||||
	bool warnings_dialog();
 | 
			
		||||
 | 
			
		||||
    void on_action_add(SimpleEvent&);
 | 
			
		||||
    void on_action_split_objects(SimpleEvent&);
 | 
			
		||||
| 
						 | 
				
			
			@ -1668,7 +1689,7 @@ struct Plater::priv
 | 
			
		|||
    // Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes.
 | 
			
		||||
    bool 						writing_to_removable_device = { false };
 | 
			
		||||
    bool                        inside_snapshot_capture() { return m_prevent_snapshots != 0; }
 | 
			
		||||
 | 
			
		||||
	bool                        process_completed_with_error { false };
 | 
			
		||||
private:
 | 
			
		||||
    bool init_object_menu();
 | 
			
		||||
    bool init_common_menu(wxMenu* menu, const bool is_part = false);
 | 
			
		||||
| 
						 | 
				
			
			@ -1696,6 +1717,11 @@ private:
 | 
			
		|||
                                                              * */
 | 
			
		||||
    std::string 				m_last_fff_printer_profile_name;
 | 
			
		||||
    std::string 				m_last_sla_printer_profile_name;
 | 
			
		||||
 | 
			
		||||
	// vector of all warnings generated by last slicing
 | 
			
		||||
	std::vector<std::pair<Slic3r::PrintStateBase::Warning, size_t>> current_warnings;
 | 
			
		||||
	bool show_warning_dialog { false };
 | 
			
		||||
	
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
 | 
			
		||||
| 
						 | 
				
			
			@ -1741,6 +1767,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
 | 
			
		|||
        });
 | 
			
		||||
    background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
 | 
			
		||||
    background_process.set_finished_event(EVT_PROCESS_COMPLETED);
 | 
			
		||||
	background_process.set_export_began_event(EVT_EXPORT_BEGAN);
 | 
			
		||||
    // Default printer technology for default config.
 | 
			
		||||
    background_process.select_technology(this->printer_technology);
 | 
			
		||||
    // Register progress callback from the Print class to the Plater.
 | 
			
		||||
| 
						 | 
				
			
			@ -1852,8 +1879,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
 | 
			
		|||
    preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); });
 | 
			
		||||
    preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); });
 | 
			
		||||
 | 
			
		||||
    q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
 | 
			
		||||
	q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
 | 
			
		||||
    q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
 | 
			
		||||
	q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this);
 | 
			
		||||
    q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); });
 | 
			
		||||
    q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1880,16 +1908,27 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
 | 
			
		|||
    });
 | 
			
		||||
#endif /* _WIN32 */
 | 
			
		||||
 | 
			
		||||
    this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) {
 | 
			
		||||
	notification_manager = new NotificationManager(this->q);
 | 
			
		||||
	this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); });
 | 
			
		||||
	this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); });
 | 
			
		||||
	this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) {  wxGetApp().get_preset_updater()->on_update_notification_confirm(); });
 | 
			
		||||
 | 
			
		||||
	this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) {
 | 
			
		||||
		if (evt.data.second) {
 | 
			
		||||
			this->show_action_buttons(this->ready_to_slice);
 | 
			
		||||
			Slic3r::GUI::show_info(this->q, format_wxstr(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),
 | 
			
		||||
				evt.data.first.name, evt.data.first.path));
 | 
			
		||||
		} else
 | 
			
		||||
			Slic3r::GUI::show_info(this->q, format_wxstr(_L("Ejecting of device %s(%s) has failed."),
 | 
			
		||||
				evt.data.first.name, evt.data.first.path));
 | 
			
		||||
			notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path),
 | 
			
		||||
				                                    NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D());
 | 
			
		||||
		} else {
 | 
			
		||||
			notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path),
 | 
			
		||||
				                                    NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D());
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
    this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) {
 | 
			
		||||
		this->show_action_buttons(this->ready_to_slice); 
 | 
			
		||||
		if (!this->sidebar->get_eject_shown()) {
 | 
			
		||||
			notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
    this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); });
 | 
			
		||||
    // Start the background thread and register this window as a target for update events.
 | 
			
		||||
    wxGetApp().removable_drive_manager()->init(this->q);
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
| 
						 | 
				
			
			@ -2519,6 +2558,8 @@ void Plater::priv::reset()
 | 
			
		|||
{
 | 
			
		||||
    Plater::TakeSnapshot snapshot(q, _L("Reset Project"));
 | 
			
		||||
 | 
			
		||||
	clear_warnings();
 | 
			
		||||
 | 
			
		||||
    set_project_filename(wxEmptyString);
 | 
			
		||||
 | 
			
		||||
    // Prevent toolpaths preview from rendering while we modify the Print object
 | 
			
		||||
| 
						 | 
				
			
			@ -2688,22 +2729,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
 | 
			
		|||
		// The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors.
 | 
			
		||||
        std::string err = this->background_process.validate();
 | 
			
		||||
        if (err.empty()) {
 | 
			
		||||
			notification_manager->set_all_slicing_errors_gray(true);
 | 
			
		||||
            if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled())
 | 
			
		||||
                return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
 | 
			
		||||
        } else {
 | 
			
		||||
            // The print is not valid.
 | 
			
		||||
            // Only show the error message immediately, if the top level parent of this window is active.
 | 
			
		||||
            auto p = dynamic_cast<wxWindow*>(this->q);
 | 
			
		||||
            while (p->GetParent())
 | 
			
		||||
                p = p->GetParent();
 | 
			
		||||
            auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
 | 
			
		||||
            if (! postpone_error_messages && top_level_wnd && top_level_wnd->IsActive()) {
 | 
			
		||||
                // The error returned from the Print needs to be translated into the local language.
 | 
			
		||||
                GUI::show_error(this->q, err);
 | 
			
		||||
            } else {
 | 
			
		||||
                // Show the error message once the main window gets activated.
 | 
			
		||||
                this->delayed_error_message = err;
 | 
			
		||||
            }
 | 
			
		||||
			// The print is not valid.
 | 
			
		||||
			// Show error as notification.
 | 
			
		||||
			notification_manager->push_slicing_error_notification(err, *q->get_current_canvas3D());
 | 
			
		||||
            return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
 | 
			
		||||
        }
 | 
			
		||||
    } else if (! this->delayed_error_message.empty()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2711,6 +2743,14 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
 | 
			
		|||
        return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	//actualizate warnings
 | 
			
		||||
	if (invalidated != Print::APPLY_STATUS_UNCHANGED) {
 | 
			
		||||
		actualizate_warnings(this->q->model(), this->background_process.current_print()->id().id);
 | 
			
		||||
		notification_manager->set_all_slicing_warnings_gray(true);
 | 
			
		||||
		show_warning_dialog = false;
 | 
			
		||||
		process_completed_with_error = false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() &&
 | 
			
		||||
        (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) {
 | 
			
		||||
        // The background processing was killed and it will not be restarted.
 | 
			
		||||
| 
						 | 
				
			
			@ -2773,6 +2813,8 @@ bool Plater::priv::restart_background_process(unsigned int state)
 | 
			
		|||
                this->statusbar()->set_status_text(_L("Cancelling"));
 | 
			
		||||
                this->background_process.stop();
 | 
			
		||||
            });
 | 
			
		||||
			if (!show_warning_dialog)
 | 
			
		||||
				on_slicing_began();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -2799,6 +2841,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova
 | 
			
		|||
    if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
	show_warning_dialog = true;
 | 
			
		||||
    if (! output_path.empty()) {
 | 
			
		||||
        background_process.schedule_export(output_path.string(), output_path_on_removable_media);
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -3285,11 +3328,20 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
 | 
			
		|||
                state = print_object->step_state_with_warnings(static_cast<SLAPrintObjectStep>(warning_step));
 | 
			
		||||
        }
 | 
			
		||||
        // Now process state.warnings.
 | 
			
		||||
		for (auto const& warning : state.warnings) {
 | 
			
		||||
			if (warning.current) {
 | 
			
		||||
				notification_manager->push_slicing_warning_notification(warning.message, false, *q->get_current_canvas3D(), object_id.id, warning_step);
 | 
			
		||||
				add_warning(warning, object_id.id);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::on_slicing_completed(wxCommandEvent &)
 | 
			
		||||
void Plater::priv::on_slicing_completed(wxCommandEvent & evt)
 | 
			
		||||
{
 | 
			
		||||
	//notification_manager->push_notification(NotificationType::SlicingComplete, *q->get_current_canvas3D(), evt.GetInt());
 | 
			
		||||
	notification_manager->push_slicing_complete_notification(*q->get_current_canvas3D(), evt.GetInt(), is_sidebar_collapsed());
 | 
			
		||||
 | 
			
		||||
    switch (this->printer_technology) {
 | 
			
		||||
    case ptFFF:
 | 
			
		||||
        this->update_fff_scene();
 | 
			
		||||
| 
						 | 
				
			
			@ -3302,8 +3354,63 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &)
 | 
			
		|||
        break;
 | 
			
		||||
    default: break;
 | 
			
		||||
    }
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
void Plater::priv::on_export_began(wxCommandEvent& evt)
 | 
			
		||||
{
 | 
			
		||||
	if (show_warning_dialog)
 | 
			
		||||
		warnings_dialog();
 | 
			
		||||
}
 | 
			
		||||
void Plater::priv::on_slicing_began()
 | 
			
		||||
{
 | 
			
		||||
	clear_warnings();
 | 
			
		||||
	notification_manager->close_notification_of_type(NotificationType::SlicingComplete);
 | 
			
		||||
}
 | 
			
		||||
void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid)
 | 
			
		||||
{
 | 
			
		||||
	for (auto const& it : current_warnings) {
 | 
			
		||||
		if (warning.message_id == it.first.message_id) {
 | 
			
		||||
			if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message))
 | 
			
		||||
				return;
 | 
			
		||||
		} 
 | 
			
		||||
	}
 | 
			
		||||
	current_warnings.emplace_back(std::pair<Slic3r::PrintStateBase::Warning, size_t>(warning, oid));
 | 
			
		||||
}
 | 
			
		||||
void Plater::priv::actualizate_warnings(const Model& model, size_t print_oid)
 | 
			
		||||
{
 | 
			
		||||
	std::vector<size_t> living_oids;
 | 
			
		||||
	living_oids.push_back(model.id().id);
 | 
			
		||||
	living_oids.push_back(print_oid);
 | 
			
		||||
	for (auto it = model.objects.begin(); it != model.objects.end(); ++it) {
 | 
			
		||||
		living_oids.push_back((*it)->id().id);
 | 
			
		||||
	}
 | 
			
		||||
	notification_manager->compare_warning_oids(living_oids);
 | 
			
		||||
}
 | 
			
		||||
void Plater::priv::clear_warnings()
 | 
			
		||||
{
 | 
			
		||||
	notification_manager->close_slicing_errors_and_warnings();
 | 
			
		||||
	this->current_warnings.clear();
 | 
			
		||||
}
 | 
			
		||||
bool Plater::priv::warnings_dialog()
 | 
			
		||||
{
 | 
			
		||||
	if (current_warnings.empty())
 | 
			
		||||
		return true;
 | 
			
		||||
	std::string text = _u8L("There are active warnings concerning sliced models:\n");
 | 
			
		||||
	bool empt = true;
 | 
			
		||||
	for (auto const& it : current_warnings) {
 | 
			
		||||
		int next_n = it.first.message.find_first_of('\n', 0);
 | 
			
		||||
		text += "\n";
 | 
			
		||||
		if (next_n != std::string::npos)
 | 
			
		||||
			text += it.first.message.substr(0, next_n);
 | 
			
		||||
		else
 | 
			
		||||
			text += it.first.message;
 | 
			
		||||
	}
 | 
			
		||||
	//text += "\n\nDo you still wish to export?";
 | 
			
		||||
	wxMessageDialog msg_wingow(this->q, text, wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK);
 | 
			
		||||
	const auto res = msg_wingow.ShowModal();
 | 
			
		||||
	return res == wxID_OK;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
void Plater::priv::on_process_completed(wxCommandEvent &evt)
 | 
			
		||||
{
 | 
			
		||||
    // Stop the background task, wait until the thread goes into the "Idle" state.
 | 
			
		||||
| 
						 | 
				
			
			@ -3322,14 +3429,13 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
 | 
			
		|||
    if (error) {
 | 
			
		||||
        wxString message = evt.GetString();
 | 
			
		||||
        if (message.IsEmpty())
 | 
			
		||||
            message = _L("Export failed");
 | 
			
		||||
        if (q->m_tracking_popup_menu)
 | 
			
		||||
        	// We don't want to pop-up a message box when tracking a pop-up menu.
 | 
			
		||||
        	// We postpone the error message instead.
 | 
			
		||||
            q->m_tracking_popup_menu_error_message = message;
 | 
			
		||||
        else
 | 
			
		||||
	        show_error(q, message);
 | 
			
		||||
            message = _L("Export failed.");
 | 
			
		||||
		notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D());
 | 
			
		||||
        this->statusbar()->set_status_text(message);
 | 
			
		||||
		const wxString invalid_str = _L("Invalid data");
 | 
			
		||||
		for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport })
 | 
			
		||||
			sidebar->set_btn_label(btn, invalid_str);
 | 
			
		||||
		process_completed_with_error = true;
 | 
			
		||||
    }
 | 
			
		||||
    if (canceled)
 | 
			
		||||
        this->statusbar()->set_status_text(_L("Cancelled"));
 | 
			
		||||
| 
						 | 
				
			
			@ -3355,18 +3461,21 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
 | 
			
		|||
    default: break;
 | 
			
		||||
    }
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
    if (canceled) {
 | 
			
		||||
        if (wxGetApp().get_mode() == comSimple)
 | 
			
		||||
            sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now");
 | 
			
		||||
        show_action_buttons(true);
 | 
			
		||||
    }
 | 
			
		||||
    else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple)
 | 
			
		||||
    else if (wxGetApp().get_mode() == comSimple)
 | 
			
		||||
	{
 | 
			
		||||
		wxGetApp().removable_drive_manager()->set_exporting_finished(true);
 | 
			
		||||
		show_action_buttons(false);
 | 
			
		||||
	}
 | 
			
		||||
    this->writing_to_removable_device = false;
 | 
			
		||||
	else if (this->writing_to_removable_device)
 | 
			
		||||
	{
 | 
			
		||||
		show_action_buttons(false);
 | 
			
		||||
		notification_manager->push_notification(NotificationType::ExportToRemovableFinished, *q->get_current_canvas3D());
 | 
			
		||||
	}
 | 
			
		||||
	this->writing_to_removable_device = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::priv::on_layer_editing_toggled(bool enable)
 | 
			
		||||
| 
						 | 
				
			
			@ -4013,7 +4122,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const
 | 
			
		|||
			sidebar->show_export(true) |
 | 
			
		||||
			sidebar->show_send(send_gcode_shown) |
 | 
			
		||||
			sidebar->show_export_removable(removable_media_status.has_removable_drives) |
 | 
			
		||||
			sidebar->show_disconnect(removable_media_status.has_eject))
 | 
			
		||||
			sidebar->show_eject(removable_media_status.has_eject))
 | 
			
		||||
            sidebar->Layout();
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
| 
						 | 
				
			
			@ -4025,7 +4134,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const
 | 
			
		|||
            sidebar->show_export(!ready_to_slice) |
 | 
			
		||||
            sidebar->show_send(send_gcode_shown && !ready_to_slice) |
 | 
			
		||||
			sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) |
 | 
			
		||||
            sidebar->show_disconnect(!ready_to_slice && removable_media_status.has_eject))
 | 
			
		||||
            sidebar->show_eject(!ready_to_slice && removable_media_status.has_eject))
 | 
			
		||||
            sidebar->Layout();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4588,6 +4697,9 @@ void Plater::export_gcode(bool prefer_removable)
 | 
			
		|||
    if (p->model.objects.empty())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
	if (p->process_completed_with_error)//here
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
    // If possible, remove accents from accented latin characters.
 | 
			
		||||
    // This function is useful for generating file names to be processed by legacy firmwares.
 | 
			
		||||
    fs::path default_output_file;
 | 
			
		||||
| 
						 | 
				
			
			@ -4847,7 +4959,6 @@ void Plater::export_toolpaths_to_obj() const
 | 
			
		|||
    p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void Plater::reslice()
 | 
			
		||||
{
 | 
			
		||||
    // Stop arrange and (or) optimize rotation tasks.
 | 
			
		||||
| 
						 | 
				
			
			@ -5535,6 +5646,16 @@ Mouse3DController& Plater::get_mouse3d_controller()
 | 
			
		|||
    return p->mouse3d_controller;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const NotificationManager* Plater::get_notification_manager() const
 | 
			
		||||
{
 | 
			
		||||
	return p->notification_manager;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NotificationManager* Plater::get_notification_manager()
 | 
			
		||||
{
 | 
			
		||||
	return p->notification_manager;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Plater::can_delete() const { return p->can_delete(); }
 | 
			
		||||
bool Plater::can_delete_all() const { return p->can_delete_all(); }
 | 
			
		||||
bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,6 +45,7 @@ class ObjectLayers;
 | 
			
		|||
class ObjectList;
 | 
			
		||||
class GLCanvas3D;
 | 
			
		||||
class Mouse3DController;
 | 
			
		||||
class NotificationManager;
 | 
			
		||||
struct Camera;
 | 
			
		||||
class Bed3D;
 | 
			
		||||
class GLToolbar;
 | 
			
		||||
| 
						 | 
				
			
			@ -95,8 +96,9 @@ public:
 | 
			
		|||
    bool                    show_reslice(bool show) const;
 | 
			
		||||
	bool                    show_export(bool show) const;
 | 
			
		||||
	bool                    show_send(bool show) const;
 | 
			
		||||
    bool                    show_disconnect(bool show)const;
 | 
			
		||||
    bool                    show_eject(bool show)const;
 | 
			
		||||
	bool                    show_export_removable(bool show) const;
 | 
			
		||||
	bool                    get_eject_shown() const;
 | 
			
		||||
    bool                    is_multifilament();
 | 
			
		||||
    void                    update_mode();
 | 
			
		||||
    bool                    is_collapsed();
 | 
			
		||||
| 
						 | 
				
			
			@ -303,6 +305,9 @@ public:
 | 
			
		|||
    Mouse3DController& get_mouse3d_controller();
 | 
			
		||||
 | 
			
		||||
	void set_bed_shape() const;
 | 
			
		||||
    
 | 
			
		||||
	const NotificationManager* get_notification_manager() const;
 | 
			
		||||
	NotificationManager* get_notification_manager();
 | 
			
		||||
 | 
			
		||||
    // ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
 | 
			
		||||
	class SuppressSnapshots
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -234,30 +234,6 @@ void PreferencesDialog::accept()
 | 
			
		|||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if !ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
	if (m_settings_layout_changed) {
 | 
			
		||||
		// the dialog needs to be destroyed before the call to recreate_gui()
 | 
			
		||||
		// or sometimes the application crashes into wxDialogBase() destructor
 | 
			
		||||
		// so we put it into an inner scope
 | 
			
		||||
		wxMessageDialog dialog(nullptr,
 | 
			
		||||
			            _L("Switching the settings layout mode will trigger application restart.\n"
 | 
			
		||||
				                  "You will lose content of the plater.") + "\n\n" +
 | 
			
		||||
			                   _L("Do you want to proceed?"),
 | 
			
		||||
			wxString(SLIC3R_APP_NAME) + " - " + _L("Switching the settings layout mode"),
 | 
			
		||||
			wxICON_QUESTION | wxOK | wxCANCEL);
 | 
			
		||||
 | 
			
		||||
		if (dialog.ShowModal() == wxID_CANCEL)
 | 
			
		||||
		{
 | 
			
		||||
			int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 :
 | 
			
		||||
				            app_config->get("new_settings_layout_mode") == "1" ? 1 :
 | 
			
		||||
				            app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0;
 | 
			
		||||
 | 
			
		||||
			m_layout_mode_box->SetSelection(selection);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
#endif // !ENABLE_LAYOUT_NO_RESTART
 | 
			
		||||
 | 
			
		||||
	for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it)
 | 
			
		||||
		app_config->set(it->first, it->second);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3959,6 +3959,7 @@ void TabSLAPrint::build()
 | 
			
		|||
 | 
			
		||||
    optgroup = page->new_optgroup(L("Support pillar"));
 | 
			
		||||
    optgroup->append_single_option_line("support_pillar_diameter");
 | 
			
		||||
    optgroup->append_single_option_line("support_small_pillar_diameter_percent");
 | 
			
		||||
    optgroup->append_single_option_line("support_max_bridges_on_pillar");
 | 
			
		||||
    
 | 
			
		||||
    optgroup->append_single_option_line("support_pillar_connection_mode");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,7 @@
 | 
			
		|||
#include "slic3r/GUI/GUI_App.hpp"
 | 
			
		||||
#include "slic3r/GUI/Plater.hpp"
 | 
			
		||||
#include "slic3r/GUI/format.hpp"
 | 
			
		||||
#include "slic3r/GUI/NotificationManager.hpp"
 | 
			
		||||
#include "slic3r/Utils/Http.hpp"
 | 
			
		||||
#include "slic3r/Config/Version.hpp"
 | 
			
		||||
#include "slic3r/Config/Snapshot.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -154,6 +155,9 @@ struct PresetUpdater::priv
 | 
			
		|||
	bool cancel;
 | 
			
		||||
	std::thread thread;
 | 
			
		||||
 | 
			
		||||
	bool has_waiting_updates { false };
 | 
			
		||||
	Updates waiting_updates;
 | 
			
		||||
 | 
			
		||||
	priv();
 | 
			
		||||
 | 
			
		||||
	void set_download_prefs(AppConfig *app_config);
 | 
			
		||||
| 
						 | 
				
			
			@ -165,6 +169,7 @@ struct PresetUpdater::priv
 | 
			
		|||
	void check_install_indices() const;
 | 
			
		||||
	Updates get_config_updates(const Semver& old_slic3r_version) const;
 | 
			
		||||
	void perform_updates(Updates &&updates, bool snapshot = true) const;
 | 
			
		||||
	void set_waiting_updates(Updates u);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
PresetUpdater::priv::priv()
 | 
			
		||||
| 
						 | 
				
			
			@ -326,7 +331,15 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
 | 
			
		|||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			Slic3r::rename_file(idx_path_temp, idx_path);
 | 
			
		||||
			index = std::move(new_index);
 | 
			
		||||
			//if we rename path we need to change it in Index object too or create the object again
 | 
			
		||||
			//index = std::move(new_index);
 | 
			
		||||
			try {
 | 
			
		||||
				index.load(idx_path);
 | 
			
		||||
			}
 | 
			
		||||
			catch (const std::exception& /* err */) {
 | 
			
		||||
				BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path, vendor.name);
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			if (cancel)
 | 
			
		||||
				return;
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -632,6 +645,12 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PresetUpdater::priv::set_waiting_updates(Updates u)
 | 
			
		||||
{
 | 
			
		||||
	waiting_updates = u;
 | 
			
		||||
	has_waiting_updates = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PresetUpdater::PresetUpdater() :
 | 
			
		||||
	p(new priv())
 | 
			
		||||
{}
 | 
			
		||||
| 
						 | 
				
			
			@ -690,9 +709,9 @@ void PresetUpdater::slic3r_update_notify()
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3r_version) const
 | 
			
		||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const
 | 
			
		||||
{
 | 
			
		||||
	if (! p->enabled_config_update) { return R_NOOP; }
 | 
			
		||||
 	if (! p->enabled_config_update) { return R_NOOP; }
 | 
			
		||||
 | 
			
		||||
	auto updates = p->get_config_updates(old_slic3r_version);
 | 
			
		||||
	if (updates.incompats.size() > 0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -779,30 +798,38 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		// regular update
 | 
			
		||||
		BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", updates.updates.size());
 | 
			
		||||
		if (no_notification) {
 | 
			
		||||
			BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
 | 
			
		||||
 | 
			
		||||
		std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
 | 
			
		||||
		for (const auto &update : updates.updates) {
 | 
			
		||||
			std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
 | 
			
		||||
			updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
 | 
			
		||||
		}
 | 
			
		||||
			std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
 | 
			
		||||
			for (const auto& update : updates.updates) {
 | 
			
		||||
				std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
 | 
			
		||||
				updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		GUI::MsgUpdateConfig dlg(updates_msg);
 | 
			
		||||
			GUI::MsgUpdateConfig dlg(updates_msg);
 | 
			
		||||
 | 
			
		||||
		const auto res = dlg.ShowModal();
 | 
			
		||||
		if (res == wxID_OK) {
 | 
			
		||||
			BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
 | 
			
		||||
			p->perform_updates(std::move(updates));
 | 
			
		||||
			const auto res = dlg.ShowModal();
 | 
			
		||||
			if (res == wxID_OK) {
 | 
			
		||||
				BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
 | 
			
		||||
				p->perform_updates(std::move(updates));
 | 
			
		||||
 | 
			
		||||
			// Reload global configuration
 | 
			
		||||
			auto *app_config = GUI::wxGetApp().app_config;
 | 
			
		||||
			GUI::wxGetApp().preset_bundle->load_presets(*app_config);
 | 
			
		||||
			GUI::wxGetApp().load_current_presets();
 | 
			
		||||
			return R_UPDATE_INSTALLED;
 | 
			
		||||
				// Reload global configuration
 | 
			
		||||
				auto* app_config = GUI::wxGetApp().app_config;
 | 
			
		||||
				GUI::wxGetApp().preset_bundle->load_presets(*app_config);
 | 
			
		||||
				GUI::wxGetApp().load_current_presets();
 | 
			
		||||
				return R_UPDATE_INSTALLED;
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				BOOST_LOG_TRIVIAL(info) << "User refused the update";
 | 
			
		||||
				return R_UPDATE_REJECT;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			BOOST_LOG_TRIVIAL(info) << "User refused the update";
 | 
			
		||||
			return R_UPDATE_REJECT;
 | 
			
		||||
			p->set_waiting_updates(updates);
 | 
			
		||||
			GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAviable, *(GUI::wxGetApp().plater()->get_current_canvas3D()));
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// MsgUpdateConfig will show after the notificaation is clicked
 | 
			
		||||
	} else {
 | 
			
		||||
		BOOST_LOG_TRIVIAL(info) << "No configuration updates available.";
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -825,5 +852,37 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool
 | 
			
		|||
	p->perform_updates(std::move(updates), snapshot);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PresetUpdater::on_update_notification_confirm()
 | 
			
		||||
{
 | 
			
		||||
	if (!p->has_waiting_updates)
 | 
			
		||||
		return;
 | 
			
		||||
	BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
 | 
			
		||||
 | 
			
		||||
	std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
 | 
			
		||||
	for (const auto& update : p->waiting_updates.updates) {
 | 
			
		||||
		std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
 | 
			
		||||
		updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	GUI::MsgUpdateConfig dlg(updates_msg);
 | 
			
		||||
 | 
			
		||||
	const auto res = dlg.ShowModal();
 | 
			
		||||
	if (res == wxID_OK) {
 | 
			
		||||
		BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
 | 
			
		||||
		p->perform_updates(std::move(p->waiting_updates));
 | 
			
		||||
 | 
			
		||||
		// Reload global configuration
 | 
			
		||||
		auto* app_config = GUI::wxGetApp().app_config;
 | 
			
		||||
		GUI::wxGetApp().preset_bundle->load_presets(*app_config);
 | 
			
		||||
		GUI::wxGetApp().load_current_presets();
 | 
			
		||||
		p->has_waiting_updates = false;
 | 
			
		||||
		//return R_UPDATE_INSTALLED;
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		BOOST_LOG_TRIVIAL(info) << "User refused the update";
 | 
			
		||||
		//return R_UPDATE_REJECT;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,16 +35,20 @@ public:
 | 
			
		|||
		R_INCOMPAT_CONFIGURED,
 | 
			
		||||
		R_UPDATE_INSTALLED,
 | 
			
		||||
		R_UPDATE_REJECT,
 | 
			
		||||
		R_UPDATE_NOTIFICATION
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// If updating is enabled, check if updates are available in cache, if so, ask about installation.
 | 
			
		||||
	// A false return value implies Slic3r should exit due to incompatibility of configuration.
 | 
			
		||||
	// Providing old slic3r version upgrade profiles on upgrade of an application even in case
 | 
			
		||||
	// that the config index installed from the Internet is equal to the index contained in the installation package.
 | 
			
		||||
	UpdateResult config_update(const Semver &old_slic3r_version) const;
 | 
			
		||||
	// no_notification = force modal textbox, otherwise some cases only shows notification
 | 
			
		||||
	UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const;
 | 
			
		||||
 | 
			
		||||
	// "Update" a list of bundles from resources (behaves like an online update).
 | 
			
		||||
	void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
 | 
			
		||||
 | 
			
		||||
	void on_update_notification_confirm();
 | 
			
		||||
private:
 | 
			
		||||
	struct priv;
 | 
			
		||||
	std::unique_ptr<priv> p;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
 | 
			
		||||
add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp 
 | 
			
		||||
    sla_print_tests.cpp 
 | 
			
		||||
    sla_test_utils.hpp sla_test_utils.cpp
 | 
			
		||||
    sla_test_utils.hpp sla_test_utils.cpp sla_treebuilder_tests.cpp
 | 
			
		||||
    sla_raycast_tests.cpp)
 | 
			
		||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
 | 
			
		||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,8 @@
 | 
			
		|||
 | 
			
		||||
#include "sla_test_utils.hpp"
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/SLA/SupportTreeMesher.hpp>
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
const char *const BELOW_PAD_TEST_OBJECTS[] = {
 | 
			
		||||
| 
						 | 
				
			
			@ -37,9 +39,9 @@ TEST_CASE("Support point generator should be deterministic if seeded",
 | 
			
		|||
          "[SLASupportGeneration], [SLAPointGen]") {
 | 
			
		||||
    TriangleMesh mesh = load_model("A_upsidedown.obj");
 | 
			
		||||
    
 | 
			
		||||
    sla::EigenMesh3D emesh{mesh};
 | 
			
		||||
    sla::IndexedMesh emesh{mesh};
 | 
			
		||||
    
 | 
			
		||||
    sla::SupportConfig supportcfg;
 | 
			
		||||
    sla::SupportTreeConfig supportcfg;
 | 
			
		||||
    sla::SupportPointGenerator::Config autogencfg;
 | 
			
		||||
    autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
 | 
			
		||||
    sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}};
 | 
			
		||||
| 
						 | 
				
			
			@ -124,14 +126,14 @@ TEST_CASE("WingedPadAroundObjectIsValid", "[SLASupportGeneration]") {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("ElevatedSupportGeometryIsValid", "[SLASupportGeneration]") {
 | 
			
		||||
    sla::SupportConfig supportcfg;
 | 
			
		||||
    sla::SupportTreeConfig supportcfg;
 | 
			
		||||
    supportcfg.object_elevation_mm = 5.;
 | 
			
		||||
    
 | 
			
		||||
    for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") {
 | 
			
		||||
    sla::SupportConfig supportcfg;
 | 
			
		||||
    sla::SupportTreeConfig supportcfg;
 | 
			
		||||
    supportcfg.object_elevation_mm = 0;
 | 
			
		||||
    
 | 
			
		||||
    for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg);
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +141,7 @@ TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") {
 | 
			
		|||
 | 
			
		||||
TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") {
 | 
			
		||||
    
 | 
			
		||||
    sla::SupportConfig supportcfg;
 | 
			
		||||
    sla::SupportTreeConfig supportcfg;
 | 
			
		||||
    
 | 
			
		||||
    for (auto fname : SUPPORT_TEST_MODELS)
 | 
			
		||||
        test_support_model_collision(fname, supportcfg);
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +149,7 @@ TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") {
 | 
			
		|||
 | 
			
		||||
TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") {
 | 
			
		||||
    
 | 
			
		||||
    sla::SupportConfig supportcfg;
 | 
			
		||||
    sla::SupportTreeConfig supportcfg;
 | 
			
		||||
    supportcfg.object_elevation_mm = 0;
 | 
			
		||||
    
 | 
			
		||||
    for (auto fname : SUPPORT_TEST_MODELS)
 | 
			
		||||
| 
						 | 
				
			
			@ -228,3 +230,12 @@ TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]")
 | 
			
		|||
        cntr.from_obj(infile);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("halfcone test", "[halfcone]") {
 | 
			
		||||
    sla::DiffBridge br{Vec3d{1., 1., 1.}, Vec3d{10., 10., 10.}, 0.25, 0.5};
 | 
			
		||||
 | 
			
		||||
    TriangleMesh m = sla::to_triangle_mesh(sla::get_mesh(br, 45));
 | 
			
		||||
 | 
			
		||||
    m.require_shared_vertices();
 | 
			
		||||
    m.WriteOBJFile("Halfcone.obj");
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
#include <catch2/catch.hpp>
 | 
			
		||||
#include <test_utils.hpp>
 | 
			
		||||
 | 
			
		||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
 | 
			
		||||
#include <libslic3r/SLA/IndexedMesh.hpp>
 | 
			
		||||
#include <libslic3r/SLA/Hollowing.hpp>
 | 
			
		||||
 | 
			
		||||
#include "sla_test_utils.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +65,7 @@ TEST_CASE("Raycaster with loaded drillholes", "[sla_raycast]")
 | 
			
		|||
    cube.merge(*cube_inside);
 | 
			
		||||
    cube.require_shared_vertices();
 | 
			
		||||
    
 | 
			
		||||
    sla::EigenMesh3D emesh{cube};
 | 
			
		||||
    sla::IndexedMesh emesh{cube};
 | 
			
		||||
    emesh.load_holes(holes);
 | 
			
		||||
    
 | 
			
		||||
    Vec3d s = center.cast<double>();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,13 +2,13 @@
 | 
			
		|||
#include "libslic3r/SLA/AGGRaster.hpp"
 | 
			
		||||
 | 
			
		||||
void test_support_model_collision(const std::string          &obj_filename,
 | 
			
		||||
                                  const sla::SupportConfig   &input_supportcfg,
 | 
			
		||||
                                  const sla::SupportTreeConfig   &input_supportcfg,
 | 
			
		||||
                                  const sla::HollowingConfig &hollowingcfg,
 | 
			
		||||
                                  const sla::DrainHoles      &drainholes)
 | 
			
		||||
{
 | 
			
		||||
    SupportByproducts byproducts;
 | 
			
		||||
    
 | 
			
		||||
    sla::SupportConfig supportcfg = input_supportcfg;
 | 
			
		||||
    sla::SupportTreeConfig supportcfg = input_supportcfg;
 | 
			
		||||
    
 | 
			
		||||
    // Set head penetration to a small negative value which should ensure that
 | 
			
		||||
    // the supports will not touch the model body.
 | 
			
		||||
| 
						 | 
				
			
			@ -69,11 +69,12 @@ void export_failed_case(const std::vector<ExPolygons> &support_slices, const Sup
 | 
			
		|||
    m.merge(byproducts.input_mesh);
 | 
			
		||||
    m.repair();
 | 
			
		||||
    m.require_shared_vertices();
 | 
			
		||||
    m.WriteOBJFile(byproducts.obj_fname.c_str());
 | 
			
		||||
    m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" +
 | 
			
		||||
                    byproducts.obj_fname).c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void test_supports(const std::string          &obj_filename,
 | 
			
		||||
                   const sla::SupportConfig   &supportcfg,
 | 
			
		||||
                   const sla::SupportTreeConfig   &supportcfg,
 | 
			
		||||
                   const sla::HollowingConfig &hollowingcfg,
 | 
			
		||||
                   const sla::DrainHoles      &drainholes,
 | 
			
		||||
                   SupportByproducts          &out)
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +105,7 @@ void test_supports(const std::string          &obj_filename,
 | 
			
		|||
    
 | 
			
		||||
    // Create the special index-triangle mesh with spatial indexing which
 | 
			
		||||
    // is the input of the support point and support mesh generators
 | 
			
		||||
    sla::EigenMesh3D emesh{mesh};
 | 
			
		||||
    sla::IndexedMesh emesh{mesh};
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3R_HOLE_RAYCASTER
 | 
			
		||||
    if (hollowingcfg.enabled) 
 | 
			
		||||
| 
						 | 
				
			
			@ -129,8 +130,7 @@ void test_supports(const std::string          &obj_filename,
 | 
			
		|||
    // If there is no elevation, support points shall be removed from the
 | 
			
		||||
    // bottom of the object.
 | 
			
		||||
    if (std::abs(supportcfg.object_elevation_mm) < EPSILON) {
 | 
			
		||||
        sla::remove_bottom_points(support_points, zmin,
 | 
			
		||||
                                  supportcfg.base_height_mm);
 | 
			
		||||
        sla::remove_bottom_points(support_points, zmin + supportcfg.base_height_mm);
 | 
			
		||||
    } else {
 | 
			
		||||
        // Should be support points at least on the bottom of the model
 | 
			
		||||
        REQUIRE_FALSE(support_points.empty());
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +141,8 @@ void test_supports(const std::string          &obj_filename,
 | 
			
		|||
    
 | 
			
		||||
    // Generate the actual support tree
 | 
			
		||||
    sla::SupportTreeBuilder treebuilder;
 | 
			
		||||
    treebuilder.build(sla::SupportableMesh{emesh, support_points, supportcfg});
 | 
			
		||||
    sla::SupportableMesh    sm{emesh, support_points, supportcfg};
 | 
			
		||||
    sla::SupportTreeBuildsteps::execute(treebuilder, sm);
 | 
			
		||||
    
 | 
			
		||||
    check_support_tree_integrity(treebuilder, supportcfg);
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -157,8 +158,8 @@ void test_supports(const std::string          &obj_filename,
 | 
			
		|||
    if (std::abs(supportcfg.object_elevation_mm) < EPSILON)
 | 
			
		||||
        allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm;
 | 
			
		||||
    
 | 
			
		||||
    REQUIRE(obb.min.z() >= allowed_zmin);
 | 
			
		||||
    REQUIRE(obb.max.z() <= zmax);
 | 
			
		||||
    REQUIRE(obb.min.z() >= Approx(allowed_zmin));
 | 
			
		||||
    REQUIRE(obb.max.z() <= Approx(zmax));
 | 
			
		||||
    
 | 
			
		||||
    // Move out the support tree into the byproducts, we can examine it further
 | 
			
		||||
    // in various tests.
 | 
			
		||||
| 
						 | 
				
			
			@ -168,15 +169,15 @@ void test_supports(const std::string          &obj_filename,
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, 
 | 
			
		||||
                                  const sla::SupportConfig &cfg)
 | 
			
		||||
                                  const sla::SupportTreeConfig &cfg)
 | 
			
		||||
{
 | 
			
		||||
    double gnd  = stree.ground_level;
 | 
			
		||||
    double H1   = cfg.max_solo_pillar_height_mm;
 | 
			
		||||
    double H2   = cfg.max_dual_pillar_height_mm;
 | 
			
		||||
    
 | 
			
		||||
    for (const sla::Head &head : stree.heads()) {
 | 
			
		||||
        REQUIRE((!head.is_valid() || head.pillar_id != sla::ID_UNSET ||
 | 
			
		||||
                head.bridge_id != sla::ID_UNSET));
 | 
			
		||||
        REQUIRE((!head.is_valid() || head.pillar_id != sla::SupportTreeNode::ID_UNSET ||
 | 
			
		||||
                head.bridge_id != sla::SupportTreeNode::ID_UNSET));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    for (const sla::Pillar &pillar : stree.pillars()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,16 +67,16 @@ struct SupportByproducts
 | 
			
		|||
const constexpr float CLOSING_RADIUS = 0.005f;
 | 
			
		||||
 | 
			
		||||
void check_support_tree_integrity(const sla::SupportTreeBuilder &stree,
 | 
			
		||||
                                  const sla::SupportConfig &cfg);
 | 
			
		||||
                                  const sla::SupportTreeConfig &cfg);
 | 
			
		||||
 | 
			
		||||
void test_supports(const std::string          &obj_filename,
 | 
			
		||||
                   const sla::SupportConfig   &supportcfg,
 | 
			
		||||
                   const sla::SupportTreeConfig   &supportcfg,
 | 
			
		||||
                   const sla::HollowingConfig &hollowingcfg,
 | 
			
		||||
                   const sla::DrainHoles      &drainholes,
 | 
			
		||||
                   SupportByproducts          &out);
 | 
			
		||||
 | 
			
		||||
inline void test_supports(const std::string &obj_filename,
 | 
			
		||||
                   const sla::SupportConfig &supportcfg,
 | 
			
		||||
                   const sla::SupportTreeConfig &supportcfg,
 | 
			
		||||
                   SupportByproducts        &out) 
 | 
			
		||||
{
 | 
			
		||||
    sla::HollowingConfig hcfg;
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +85,7 @@ inline void test_supports(const std::string &obj_filename,
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
inline void test_supports(const std::string &obj_filename,
 | 
			
		||||
                   const sla::SupportConfig &supportcfg = {})
 | 
			
		||||
                   const sla::SupportTreeConfig &supportcfg = {})
 | 
			
		||||
{
 | 
			
		||||
    SupportByproducts byproducts;
 | 
			
		||||
    test_supports(obj_filename, supportcfg, byproducts);
 | 
			
		||||
| 
						 | 
				
			
			@ -97,13 +97,13 @@ void export_failed_case(const std::vector<ExPolygons> &support_slices,
 | 
			
		|||
 | 
			
		||||
void test_support_model_collision(
 | 
			
		||||
    const std::string          &obj_filename,
 | 
			
		||||
    const sla::SupportConfig   &input_supportcfg,
 | 
			
		||||
    const sla::SupportTreeConfig   &input_supportcfg,
 | 
			
		||||
    const sla::HollowingConfig &hollowingcfg,
 | 
			
		||||
    const sla::DrainHoles      &drainholes);
 | 
			
		||||
 | 
			
		||||
inline void test_support_model_collision(
 | 
			
		||||
    const std::string        &obj_filename,
 | 
			
		||||
    const sla::SupportConfig &input_supportcfg = {}) 
 | 
			
		||||
    const sla::SupportTreeConfig &input_supportcfg = {}) 
 | 
			
		||||
{
 | 
			
		||||
    sla::HollowingConfig hcfg;
 | 
			
		||||
    hcfg.enabled = false;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										99
									
								
								tests/sla_print/sla_treebuilder_tests.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								tests/sla_print/sla_treebuilder_tests.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,99 @@
 | 
			
		|||
//#include <catch2/catch.hpp>
 | 
			
		||||
//#include <test_utils.hpp>
 | 
			
		||||
 | 
			
		||||
//#include "libslic3r/TriangleMesh.hpp"
 | 
			
		||||
//#include "libslic3r/SLA/SupportTreeBuildsteps.hpp"
 | 
			
		||||
//#include "libslic3r/SLA/SupportTreeMesher.hpp"
 | 
			
		||||
 | 
			
		||||
//TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") {
 | 
			
		||||
//    using namespace Slic3r;
 | 
			
		||||
 | 
			
		||||
//    TriangleMesh cube = make_cube(10., 10., 10.);
 | 
			
		||||
 | 
			
		||||
//    sla::SupportConfig cfg = {}; // use default config
 | 
			
		||||
//    sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}};
 | 
			
		||||
//    sla::SupportableMesh sm{cube, pts, cfg};
 | 
			
		||||
 | 
			
		||||
//    size_t steps = 45;
 | 
			
		||||
//    SECTION("Bridge is straight horizontal and pointing away from the cube") {
 | 
			
		||||
 | 
			
		||||
//        sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{15., 5., 5.},
 | 
			
		||||
//                           pts[0].head_front_radius);
 | 
			
		||||
 | 
			
		||||
//        auto hit = sla::query_hit(sm, bridge);
 | 
			
		||||
 | 
			
		||||
//        REQUIRE(std::isinf(hit.distance()));
 | 
			
		||||
 | 
			
		||||
//        cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
 | 
			
		||||
//        cube.require_shared_vertices();
 | 
			
		||||
//        cube.WriteOBJFile("cube1.obj");
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
//    SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") {
 | 
			
		||||
//        sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{15., 5., 0.},
 | 
			
		||||
//                           pts[0].head_front_radius);
 | 
			
		||||
 | 
			
		||||
//        auto hit = sla::query_hit(sm, bridge);
 | 
			
		||||
 | 
			
		||||
//        REQUIRE(std::isinf(hit.distance()));
 | 
			
		||||
 | 
			
		||||
//        cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
 | 
			
		||||
//        cube.require_shared_vertices();
 | 
			
		||||
//        cube.WriteOBJFile("cube2.obj");
 | 
			
		||||
//    }
 | 
			
		||||
//}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") {
 | 
			
		||||
//    using namespace Slic3r;
 | 
			
		||||
 | 
			
		||||
//    TriangleMesh sphere = make_sphere(1.);
 | 
			
		||||
 | 
			
		||||
//    sla::SupportConfig cfg = {}; // use default config
 | 
			
		||||
//    cfg.head_back_radius_mm = cfg.head_front_radius_mm;
 | 
			
		||||
//    sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}};
 | 
			
		||||
//    sla::SupportableMesh sm{sphere, pts, cfg};
 | 
			
		||||
 | 
			
		||||
//    size_t steps = 45;
 | 
			
		||||
//    SECTION("Bridge is straight horizontal and pointing away from the sphere") {
 | 
			
		||||
 | 
			
		||||
//        sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{2., 0., 0.},
 | 
			
		||||
//                           pts[0].head_front_radius);
 | 
			
		||||
 | 
			
		||||
//        auto hit = sla::query_hit(sm, bridge);
 | 
			
		||||
 | 
			
		||||
//        sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
 | 
			
		||||
//        sphere.require_shared_vertices();
 | 
			
		||||
//        sphere.WriteOBJFile("sphere1.obj");
 | 
			
		||||
 | 
			
		||||
//        REQUIRE(std::isinf(hit.distance()));
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
//    SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") {
 | 
			
		||||
 | 
			
		||||
//        sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{2., 0., -2.},
 | 
			
		||||
//                           pts[0].head_front_radius);
 | 
			
		||||
 | 
			
		||||
//        auto hit = sla::query_hit(sm, bridge);
 | 
			
		||||
 | 
			
		||||
//        sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
 | 
			
		||||
//        sphere.require_shared_vertices();
 | 
			
		||||
//        sphere.WriteOBJFile("sphere2.obj");
 | 
			
		||||
 | 
			
		||||
//        REQUIRE(std::isinf(hit.distance()));
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
//    SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") {
 | 
			
		||||
 | 
			
		||||
//        sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{1., 0., -2.},
 | 
			
		||||
//                           pts[0].head_front_radius);
 | 
			
		||||
 | 
			
		||||
//        auto hit = sla::query_hit(sm, bridge);
 | 
			
		||||
 | 
			
		||||
//        sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
 | 
			
		||||
//        sphere.require_shared_vertices();
 | 
			
		||||
//        sphere.WriteOBJFile("sphere3.obj");
 | 
			
		||||
 | 
			
		||||
//        REQUIRE(std::isinf(hit.distance()));
 | 
			
		||||
//    }
 | 
			
		||||
//}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue