mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_gcode_window
This commit is contained in:
		
						commit
						db71a6308d
					
				
					 97 changed files with 4090 additions and 2862 deletions
				
			
		|  | @ -146,21 +146,33 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) | |||
| # WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. | ||||
| # We pick it from environment if it is not defined in another way | ||||
| if(WIN32) | ||||
|         if(NOT DEFINED WIN10SDK_PATH) | ||||
|                 if(DEFINED ENV{WIN10SDK_PATH}) | ||||
|                         set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") | ||||
|                 endif() | ||||
|     if(NOT DEFINED WIN10SDK_PATH) | ||||
|         if(DEFINED ENV{WIN10SDK_PATH}) | ||||
|                 set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") | ||||
|         endif() | ||||
|         if(DEFINED WIN10SDK_PATH AND NOT EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h") | ||||
|                 message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") | ||||
|                 message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found") | ||||
|                 message("STL fixing by the Netfabb service will not be compiled") | ||||
|                 unset(WIN10SDK_PATH) | ||||
|     endif() | ||||
|     if(DEFINED WIN10SDK_PATH) | ||||
|         if (EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h") | ||||
|             set(WIN10SDK_INCLUDE_PATH "${WIN10SDK_PATH}/Include") | ||||
|         else() | ||||
|             message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") | ||||
|             message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found") | ||||
|             message("STL fixing by the Netfabb service will not be compiled") | ||||
|             unset(WIN10SDK_PATH) | ||||
|         endif() | ||||
|     if(WIN10SDK_PATH) | ||||
|     else() | ||||
|         # Try to use the default Windows 10 SDK path. | ||||
|         set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}") | ||||
|         if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h") | ||||
|             message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found") | ||||
|             message("STL fixing by the Netfabb service will not be compiled") | ||||
|             unset(WIN10SDK_INCLUDE_PATH) | ||||
|         endif() | ||||
|     endif() | ||||
|     if(WIN10SDK_INCLUDE_PATH) | ||||
|         message("Building with Win10 Netfabb STL fixing service support") | ||||
|         add_definitions(-DHAS_WIN10SDK) | ||||
|         include_directories("${WIN10SDK_PATH}/Include") | ||||
|         include_directories("${WIN10SDK_INCLUDE_PATH}") | ||||
|     else() | ||||
|         message("Building without Win10 Netfabb STL fixing service support") | ||||
|     endif() | ||||
|  |  | |||
							
								
								
									
										1
									
								
								deps/wxWidgets/wxWidgets.cmake
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								deps/wxWidgets/wxWidgets.cmake
									
										
									
									
										vendored
									
									
								
							|  | @ -20,6 +20,7 @@ prusaslicer_add_cmake_project(wxWidgets | |||
|         ${_wx_toolkit} | ||||
|         "-DCMAKE_DEBUG_POSTFIX:STRING=" | ||||
|         -DwxBUILD_DEBUG_LEVEL=0 | ||||
|         -DwxUSE_MEDIACTRL=OFF | ||||
|         -DwxUSE_DETECT_SM=OFF | ||||
|         -DwxUSE_UNICODE=ON | ||||
|         -DwxUSE_OPENGL=ON | ||||
|  |  | |||
							
								
								
									
										67
									
								
								resources/icons/notification_cancel.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								resources/icons/notification_cancel.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | |||
| <?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" | ||||
|    version="1.1" | ||||
|    id="Layer_1" | ||||
|    x="0px" | ||||
|    y="0px" | ||||
|    viewBox="0 0 800 800" | ||||
|    style="enable-background:new 0 0 800 800;" | ||||
|    xml:space="preserve" | ||||
|    sodipodi:docname="notification_cancel.svg" | ||||
|    inkscape:version="1.0 (4035a4fb49, 2020-05-01)"><metadata | ||||
|    id="metadata15"><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="defs13" /><sodipodi:namedview | ||||
|    inkscape:document-rotation="0" | ||||
|    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="namedview11" | ||||
|    showgrid="false" | ||||
|    inkscape:zoom="1.26" | ||||
|    inkscape:cx="400" | ||||
|    inkscape:cy="389.28571" | ||||
|    inkscape:window-x="-11" | ||||
|    inkscape:window-y="-11" | ||||
|    inkscape:window-maximized="1" | ||||
|    inkscape:current-layer="Layer_1" /> | ||||
| <style | ||||
|    type="text/css" | ||||
|    id="style2"> | ||||
| 	.st0{fill:#ED6B21;} | ||||
| </style> | ||||
| 
 | ||||
| 
 | ||||
| <g | ||||
|    id="g4" | ||||
|    transform="matrix(0.9775,0,0,0.9775,53.547,53.54775)"> | ||||
| 	<path | ||||
|    id="path2" | ||||
|    class="st0" | ||||
|    d="M 597.2,701.3 H 110.6 C 53.2,701.3 6.5,654.6 6.5,597.2 V 110.6 C 6.5,53.2 53.2,6.5 110.6,6.5 h 486.6 c 57.4,0 104.1,46.7 104.1,104.1 v 486.6 c 0,57.4 -46.7,104.1 -104.1,104.1 z M 110.6,52.4 c -32,0 -58.2,26 -58.2,58.2 v 486.6 c 0,32 26,58.2 58.2,58.2 h 486.6 c 32,0 58.2,-26 58.2,-58.2 V 110.6 c 0,-32 -26,-58.2 -58.2,-58.2 z" /> | ||||
| </g> | ||||
| <path | ||||
|    style="fill:#ed6b21;fill-opacity:1;stroke-width:0.674603" | ||||
|    d="m 150.65676,738.12999 c -12.4717,-1.39663 -26.66772,-5.94192 -37.84321,-12.11671 -17.754551,-9.80992 -33.768844,-26.68981 -42.418124,-44.71089 -5.985061,-12.4701 -8.760227,-23.35456 -9.821918,-38.52249 -0.48061,-6.8663 -0.640464,-87.42616 -0.497289,-250.61508 0.195544,-222.88027 0.294923,-240.94223 1.356742,-246.58759 4.2349,-22.51562 13.68014,-40.62012 29.200931,-55.97194 14.237938,-14.082924 31.958648,-23.427941 52.602238,-27.739791 5.87892,-1.227937 14.00696,-1.268146 256.3492,-1.268146 h 250.27778 l 7.08334,1.561512 c 21.30688,4.697075 36.90336,13.216072 51.96052,28.381502 14.67865,14.784203 23.1932,30.350373 27.76125,50.752683 l 1.56791,7.00271 v 250.95239 c 0,242.72256 -0.0418,251.15149 -1.26428,257.0238 -9.30592,44.69034 -45.18963,77.43352 -89.75566,81.90028 -9.17898,0.92002 -488.33076,0.87927 -496.55943,-0.0425 z M 652.87275,692.49 c 19.93824,-6.17834 34.6922,-21.42493 40.00111,-41.33675 l 1.51306,-5.67494 V 399.58544 153.69259 l -1.52571,-5.73412 c -5.66288,-21.28292 -21.4158,-36.89778 -42.2051,-41.83523 -5.63965,-1.33941 -7.66026,-1.3488 -253.17948,-1.17613 l -247.49447,0.17405 -4.72222,1.5953 c -18.05932,6.10093 -31.7315,19.23923 -37.4918,36.0278 -1.04762,3.05333 -2.22128,7.52472 -2.60813,9.93642 -0.47859,2.9836 -0.705,81.91876 -0.70847,246.99889 -0.005,218.14117 0.10226,243.1829 1.05916,248.25397 4.27172,22.63802 22.24346,40.86392 44.80877,45.4425 3.58848,0.72811 49.16893,0.87009 250.95237,0.78171 l 246.56747,-0.10801 z" | ||||
|    id="path17" /><rect | ||||
|    y="239.60318" | ||||
|    x="240.87302" | ||||
|    height="321.58731" | ||||
|    width="322.22223" | ||||
|    id="rect30" | ||||
|    style="fill:#ed6b21;stroke-width:0.4" /></svg> | ||||
| After Width: | Height: | Size: 3.6 KiB | 
							
								
								
									
										67
									
								
								resources/icons/notification_cancel_hover.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								resources/icons/notification_cancel_hover.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | |||
| <?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" | ||||
|    version="1.1" | ||||
|    id="Layer_1" | ||||
|    x="0px" | ||||
|    y="0px" | ||||
|    viewBox="0 0 800 800" | ||||
|    style="enable-background:new 0 0 800 800;" | ||||
|    xml:space="preserve" | ||||
|    sodipodi:docname="notification_cancel_hover.svg" | ||||
|    inkscape:version="1.0 (4035a4fb49, 2020-05-01)"><metadata | ||||
|    id="metadata15"><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="defs13" /><sodipodi:namedview | ||||
|    inkscape:document-rotation="0" | ||||
|    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="namedview11" | ||||
|    showgrid="false" | ||||
|    inkscape:zoom="1.26" | ||||
|    inkscape:cx="400" | ||||
|    inkscape:cy="389.28571" | ||||
|    inkscape:window-x="-11" | ||||
|    inkscape:window-y="-11" | ||||
|    inkscape:window-maximized="1" | ||||
|    inkscape:current-layer="Layer_1" /> | ||||
| <style | ||||
|    type="text/css" | ||||
|    id="style2"> | ||||
| 	.st0{fill:#ED6B21;} | ||||
| </style> | ||||
| 
 | ||||
| 
 | ||||
| <g | ||||
|    id="g4" | ||||
|    transform="matrix(1.173,0,0,1.173,-15.64045,-15.6397)"> | ||||
| 	<path | ||||
|    id="path2" | ||||
|    class="st0" | ||||
|    d="M 597.2,701.3 H 110.6 C 53.2,701.3 6.5,654.6 6.5,597.2 V 110.6 C 6.5,53.2 53.2,6.5 110.6,6.5 h 486.6 c 57.4,0 104.1,46.7 104.1,104.1 v 486.6 c 0,57.4 -46.7,104.1 -104.1,104.1 z M 110.6,52.4 c -32,0 -58.2,26 -58.2,58.2 v 486.6 c 0,32 26,58.2 58.2,58.2 h 486.6 c 32,0 58.2,-26 58.2,-58.2 V 110.6 c 0,-32 -26,-58.2 -58.2,-58.2 z" /> | ||||
| </g> | ||||
| <path | ||||
|    style="fill:#ed6b21;fill-opacity:1;stroke-width:0.809524" | ||||
|    d="M 100.89126,805.85899 C 85.925222,804.18303 68.889998,798.72868 55.47941,791.31894 34.173949,779.54703 14.956797,759.29116 4.5776612,737.66587 -2.604412,722.70175 -5.9346112,709.6404 -7.2086404,691.43888 -7.7853724,683.19932 -7.9771972,586.52749 -7.8053872,390.70078 -7.5707344,123.24446 -7.4514796,101.57011 -6.1772968,94.795676 -1.0954168,67.776932 10.238871,46.051532 28.86382,27.629348 45.949346,10.729839 67.214198,-0.4841812 91.986506,-5.6584012 99.04121,-7.1319256 108.79486,-7.1801764 399.60555,-7.1801764 h 300.33333 l 8.50001,1.8738144 c 25.56826,5.63649 44.28403,15.859286 62.35262,34.057802 17.61438,17.741044 27.83184,36.420448 33.3135,60.90322 l 1.8815,8.403252 V 399.20078 c 0,291.26707 -0.0502,301.38179 -1.51714,308.42856 -11.1671,53.62841 -54.22756,92.92022 -107.70679,98.28034 -11.01478,1.10402 -585.99691,1.05512 -595.87132,-0.051 z M 703.55045,751.091 c 23.92589,-7.41401 41.63064,-25.70992 48.00133,-49.6041 l 1.81567,-6.80993 V 399.60553 104.53411 L 751.5366,97.653164 C 744.74115,72.11366 725.83764,53.375828 700.89048,47.450888 694.1229,45.843596 691.69817,45.832328 397.07511,46.039532 l -296.99337,0.20886 -5.666662,1.91436 c -21.671184,7.321116 -38.0778,23.087076 -44.99016,43.23336 -1.257144,3.663996 -2.665536,9.029668 -3.129756,11.923708 -0.574308,3.58032 -0.846,98.30251 -0.850164,296.39866 -0.006,261.76941 0.122712,291.81948 1.270992,297.90477 5.126064,27.16562 26.692152,49.0367 53.77052,54.531 4.30618,0.87373 59.00272,1.04411 301.14285,0.93805 l 295.88096,-0.12961 z" | ||||
|    id="path17" /><rect | ||||
|    y="207.62682" | ||||
|    x="209.15077" | ||||
|    height="385.90479" | ||||
|    width="386.66669" | ||||
|    id="rect30" | ||||
|    style="fill:#ed6b21;stroke-width:0.48" /></svg> | ||||
| After Width: | Height: | Size: 3.7 KiB | 
|  | @ -28,6 +28,7 @@ src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | |||
| src/slic3r/GUI/GUI.cpp | ||||
| src/slic3r/GUI/GUI_App.cpp | ||||
| src/slic3r/GUI/GUI_Init.cpp | ||||
| src/slic3r/GUI/GUI_Factories.cpp | ||||
| src/slic3r/GUI/GUI_ObjectLayers.cpp | ||||
| src/slic3r/GUI/GUI_ObjectList.cpp | ||||
| src/slic3r/GUI/GUI_ObjectManipulation.cpp | ||||
|  |  | |||
|  | @ -123,6 +123,8 @@ namespace ImGui | |||
| 	const char ErrorMarker             = 0x11; | ||||
|     const char EjectButton             = 0x12; | ||||
|     const char EjectHoverButton        = 0x13; | ||||
|     const char CancelButton            = 0x14; | ||||
|     const char CancelHoverButton       = 0x15; | ||||
| //    void MyFunction(const char* name, const MyMatrix44& v);
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -21,22 +21,30 @@ public: | |||
|         min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} | ||||
|     BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : | ||||
|         min(p1), max(p1), defined(false) { merge(p2); merge(p3); } | ||||
|     BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero()) | ||||
| 
 | ||||
|     template<class It, class = IteratorOnly<It> > | ||||
|     BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero()) | ||||
|     { | ||||
|         if (points.empty()) { | ||||
|         if (from == to) { | ||||
|             this->defined = false; | ||||
|             // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
 | ||||
|         } else { | ||||
|             typename std::vector<PointClass>::const_iterator it = points.begin(); | ||||
|             this->min = *it; | ||||
|             this->max = *it; | ||||
|             for (++ it; it != points.end(); ++ it) { | ||||
|                 this->min = this->min.cwiseMin(*it); | ||||
|                 this->max = this->max.cwiseMax(*it); | ||||
|             auto it = from; | ||||
|             this->min = it->template cast<typename PointClass::Scalar>(); | ||||
|             this->max = this->min; | ||||
|             for (++ it; it != to; ++ it) { | ||||
|                 auto vec = it->template cast<typename PointClass::Scalar>(); | ||||
|                 this->min = this->min.cwiseMin(vec); | ||||
|                 this->max = this->max.cwiseMax(vec); | ||||
|             } | ||||
|             this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     BoundingBoxBase(const std::vector<PointClass> &points) | ||||
|         : BoundingBoxBase(points.begin(), points.end()) | ||||
|     {} | ||||
| 
 | ||||
|     void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); } | ||||
|     void merge(const PointClass &point); | ||||
|     void merge(const std::vector<PointClass> &points); | ||||
|  | @ -74,19 +82,27 @@ public: | |||
|         { if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; } | ||||
|     BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : | ||||
|         BoundingBoxBase<PointClass>(p1, p1) { merge(p2); merge(p3); } | ||||
|     BoundingBox3Base(const std::vector<PointClass>& points) | ||||
| 
 | ||||
|     template<class It, class = IteratorOnly<It> > BoundingBox3Base(It from, It to) | ||||
|     { | ||||
|         if (points.empty()) | ||||
|         if (from == to) | ||||
|             throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor"); | ||||
|         typename std::vector<PointClass>::const_iterator it = points.begin(); | ||||
|         this->min = *it; | ||||
|         this->max = *it; | ||||
|         for (++ it; it != points.end(); ++ it) { | ||||
|             this->min = this->min.cwiseMin(*it); | ||||
|             this->max = this->max.cwiseMax(*it); | ||||
| 
 | ||||
|         auto it = from; | ||||
|         this->min = it->template cast<typename PointClass::Scalar>(); | ||||
|         this->max = this->min; | ||||
|         for (++ it; it != to; ++ it) { | ||||
|             auto vec = it->template cast<typename PointClass::Scalar>(); | ||||
|             this->min = this->min.cwiseMin(vec); | ||||
|             this->max = this->max.cwiseMax(vec); | ||||
|         } | ||||
|         this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2)); | ||||
|     } | ||||
| 
 | ||||
|     BoundingBox3Base(const std::vector<PointClass> &points) | ||||
|         : BoundingBox3Base(points.begin(), points.end()) | ||||
|     {} | ||||
| 
 | ||||
|     void merge(const PointClass &point); | ||||
|     void merge(const std::vector<PointClass> &points); | ||||
|     void merge(const BoundingBox3Base<PointClass> &bb); | ||||
|  | @ -188,9 +204,7 @@ public: | |||
| class BoundingBoxf3 : public BoundingBox3Base<Vec3d>  | ||||
| { | ||||
| public: | ||||
|     BoundingBoxf3() : BoundingBox3Base<Vec3d>() {} | ||||
|     BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {} | ||||
|     BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {} | ||||
|     using BoundingBox3Base::BoundingBox3Base; | ||||
| 
 | ||||
|     BoundingBoxf3 transformed(const Transform3d& matrix) const; | ||||
| }; | ||||
|  |  | |||
|  | @ -320,7 +320,7 @@ static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_ | |||
|     loops = union_pt_chained_outside_in(loops, false); | ||||
|     std::reverse(loops.begin(), loops.end()); | ||||
|     extrusion_entities_append_loops(brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), | ||||
|                                     float(flow.width), float(print.skirt_first_layer_height())); | ||||
|                                     float(flow.width()), float(print.skirt_first_layer_height())); | ||||
| } | ||||
| 
 | ||||
| // Produce brim lines around those objects, that have the brim enabled.
 | ||||
|  | @ -495,7 +495,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance | |||
| 				if (i + 1 == j && first_path.size() > 3 && first_path.front().X == first_path.back().X && first_path.front().Y == first_path.back().Y) { | ||||
| 					auto *loop = new ExtrusionLoop(); | ||||
|                     brim.entities.emplace_back(loop); | ||||
| 					loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height())); | ||||
| 					loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); | ||||
| 		            Points &points = loop->paths.front().polyline.points; | ||||
| 		            points.reserve(first_path.size()); | ||||
| 		            for (const ClipperLib_Z::IntPoint &pt : first_path) | ||||
|  | @ -506,7 +506,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance | |||
| 			    	ExtrusionEntityCollection this_loop_trimmed; | ||||
| 					this_loop_trimmed.entities.reserve(j - i); | ||||
| 			    	for (; i < j; ++ i) { | ||||
| 			            this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height()))); | ||||
| 			            this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()))); | ||||
| 						const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first; | ||||
| 			            Points &points = static_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points; | ||||
| 			            points.reserve(path.size()); | ||||
|  | @ -522,7 +522,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance | |||
| 			} | ||||
| 		} | ||||
|     } else { | ||||
|         extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height())); | ||||
|         extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); | ||||
|     } | ||||
| 
 | ||||
|     make_inner_brim(print, top_level_objects_with_brim, brim); | ||||
|  |  | |||
|  | @ -1441,6 +1441,24 @@ private: | |||
| class ConfigOptionDef | ||||
| { | ||||
| public: | ||||
|     enum class GUIType { | ||||
|         undefined, | ||||
|         // Open enums, integer value could be one of the enumerated values or something else.
 | ||||
|         i_enum_open, | ||||
|         // Open enums, float value could be one of the enumerated values or something else.
 | ||||
|         f_enum_open, | ||||
|         // Color picker, string value.
 | ||||
|         color, | ||||
|         // ???
 | ||||
|         select_open, | ||||
|         // Currently unused.
 | ||||
|         slider, | ||||
|         // Static text
 | ||||
|         legend, | ||||
|         // Vector value, but edited as a single string.
 | ||||
|         one_string, | ||||
|     }; | ||||
| 
 | ||||
| 	// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.
 | ||||
| 	t_config_option_key 				opt_key; | ||||
|     // What type? bool, int, string etc.
 | ||||
|  | @ -1524,7 +1542,7 @@ public: | |||
|     // Usually empty. 
 | ||||
|     // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection,
 | ||||
|     // "select_open" - to open a selection dialog (currently only a serial port selection).
 | ||||
|     std::string                         gui_type; | ||||
|     GUIType                             gui_type { GUIType::undefined }; | ||||
|     // Usually empty. Otherwise "serialized" or "show_value"
 | ||||
|     // The flags may be combined.
 | ||||
|     // "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon.
 | ||||
|  |  | |||
|  | @ -621,7 +621,7 @@ ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, double min_c | |||
| ExPolygon  elephant_foot_compensation(const ExPolygon  &input, const Flow &external_perimeter_flow, const double compensation) | ||||
| { | ||||
|     // The contour shall be wide enough to apply the external perimeter plus compensation on both sides.
 | ||||
|     double min_contour_width = double(external_perimeter_flow.width + external_perimeter_flow.spacing()); | ||||
|     double min_contour_width = double(external_perimeter_flow.width() + external_perimeter_flow.spacing()); | ||||
|     return elephant_foot_compensation(input, min_contour_width, compensation); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -52,7 +52,9 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale | |||
| { | ||||
|     // Instantiating the Flow class to get the line spacing.
 | ||||
|     // Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler.
 | ||||
|     Flow flow(this->width, this->height, 0.f, is_bridge(this->role())); | ||||
|     bool bridge = is_bridge(this->role()); | ||||
|     assert(! bridge || this->width == this->height); | ||||
|     auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f); | ||||
|     polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon)); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -28,6 +28,8 @@ struct SurfaceFillParams | |||
| //    coordf_t    	overlap = 0.;
 | ||||
|     // Angle as provided by the region config, in radians.
 | ||||
|     float       	angle = 0.f; | ||||
|     // Is bridging used for this fill? Bridging parameters may be used even if this->flow.bridge() is not set.
 | ||||
|     bool 			bridge; | ||||
|     // Non-negative for a bridge.
 | ||||
|     float 			bridge_angle = 0.f; | ||||
| 
 | ||||
|  | @ -42,7 +44,7 @@ struct SurfaceFillParams | |||
| 
 | ||||
|     // width, height of extrusion, nozzle diameter, is bridge
 | ||||
|     // For the output, for fill generator.
 | ||||
|     Flow 			flow = Flow(0.f, 0.f, 0.f, false); | ||||
|     Flow 			flow; | ||||
| 
 | ||||
| 	// For the output
 | ||||
|     ExtrusionRole	extrusion_role = ExtrusionRole(0); | ||||
|  | @ -70,21 +72,22 @@ struct SurfaceFillParams | |||
| //		RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust);
 | ||||
| 		RETURN_COMPARE_NON_EQUAL(anchor_length); | ||||
| 		RETURN_COMPARE_NON_EQUAL(anchor_length_max); | ||||
| 		RETURN_COMPARE_NON_EQUAL(flow.width); | ||||
| 		RETURN_COMPARE_NON_EQUAL(flow.height); | ||||
| 		RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter); | ||||
| 		RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, flow.bridge); | ||||
| 		RETURN_COMPARE_NON_EQUAL(flow.width()); | ||||
| 		RETURN_COMPARE_NON_EQUAL(flow.height()); | ||||
| 		RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter()); | ||||
| 		RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, bridge); | ||||
| 		RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	bool operator==(const SurfaceFillParams &rhs) const { | ||||
| 		return  this->extruder 			== rhs.extruder 		&& | ||||
| 				this->pattern 			== rhs.pattern 			&& | ||||
| 				this->pattern 			== rhs.pattern 			&& | ||||
| 				this->spacing 			== rhs.spacing 			&& | ||||
| //				this->overlap 			== rhs.overlap 			&&
 | ||||
| 				this->angle   			== rhs.angle   			&& | ||||
| 				this->bridge   			== rhs.bridge   		&& | ||||
| //				this->bridge_angle 		== rhs.bridge_angle		&&
 | ||||
| 				this->density   		== rhs.density   		&& | ||||
| //				this->dont_adjust   	== rhs.dont_adjust 		&&
 | ||||
| 				this->anchor_length  	== rhs.anchor_length    && | ||||
|  | @ -128,6 +131,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer) | |||
| 
 | ||||
| 		        if (surface.is_solid()) { | ||||
| 		            params.density = 100.f; | ||||
| 					//FIXME for non-thick bridges, shall we allow a bottom surface pattern?
 | ||||
| 		            params.pattern = (surface.is_external() && ! is_bridge) ?  | ||||
| 						(surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) : | ||||
| 		                region_config.top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear; | ||||
|  | @ -143,17 +147,13 @@ std::vector<SurfaceFill> group_fills(const Layer &layer) | |||
| 		        params.bridge_angle = float(surface.bridge_angle); | ||||
| 		        params.angle 		= float(Geometry::deg2rad(region_config.fill_angle.value)); | ||||
| 		         | ||||
| 		        // calculate the actual flow we'll be using for this infill
 | ||||
| 		        params.flow = layerm.region()->flow( | ||||
| 		            extrusion_role, | ||||
| 		            (surface.thickness == -1) ? layer.height : surface.thickness, 	// extrusion height
 | ||||
| 		            is_bridge || Fill::use_bridge_flow(params.pattern), 			// bridge flow?
 | ||||
| 		            layer.id() == 0,          										// first layer?
 | ||||
| 		            -1,                                 							// auto width
 | ||||
| 		            *layer.object() | ||||
| 		        ); | ||||
| 		         | ||||
| 		        // Calculate flow spacing for infill pattern generation.
 | ||||
| 		        // Calculate the actual flow we'll be using for this infill.
 | ||||
| 		        params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); | ||||
| 				params.flow   = params.bridge ? | ||||
| 					layerm.bridging_flow(extrusion_role) : | ||||
| 					layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness); | ||||
| 
 | ||||
| 				// Calculate flow spacing for infill pattern generation.
 | ||||
| 		        if (surface.is_solid() || is_bridge) { | ||||
| 		            params.spacing = params.flow.spacing(); | ||||
| 		            // Don't limit anchor length for solid or bridging infill.
 | ||||
|  | @ -164,14 +164,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer) | |||
| 		            // for all layers, for avoiding the ugly effect of
 | ||||
| 		            // misaligned infill on first layer because of different extrusion width and
 | ||||
| 		            // layer height
 | ||||
| 		            params.spacing = layerm.region()->flow( | ||||
| 			                frInfill, | ||||
| 			                layer.object()->config().layer_height.value,  // TODO: handle infill_every_layers?
 | ||||
| 			                false,  // no bridge
 | ||||
| 			                false,  // no first layer
 | ||||
| 			                -1,     // auto width
 | ||||
| 			                *layer.object() | ||||
| 			            ).spacing(); | ||||
| 		            params.spacing = layerm.flow(frInfill, layer.object()->config().layer_height).spacing(); | ||||
| 		            // Anchor a sparse infill to inner perimeters with the following anchor length:
 | ||||
| 			        params.anchor_length = float(region_config.infill_anchor); | ||||
| 					if (region_config.infill_anchor.percent) | ||||
|  | @ -278,7 +271,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer) | |||
| 				region_id = region_some_infill; | ||||
| 			const LayerRegion& layerm = *layer.regions()[region_id]; | ||||
| 	        for (SurfaceFill &surface_fill : surface_fills) | ||||
| 	        	if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height) < EPSILON) { | ||||
| 	        	if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) { | ||||
| 	        		internal_solid_fill = &surface_fill; | ||||
| 	        		break; | ||||
| 	        	} | ||||
|  | @ -290,14 +283,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer) | |||
| 		        params.extrusion_role = erInternalInfill; | ||||
| 		        params.angle 		= float(Geometry::deg2rad(layerm.region()->config().fill_angle.value)); | ||||
| 		        // calculate the actual flow we'll be using for this infill
 | ||||
| 		        params.flow = layerm.region()->flow( | ||||
| 		            frSolidInfill, | ||||
| 		            layer.height, 		// extrusion height
 | ||||
| 		            false, 				// bridge flow?
 | ||||
| 		            layer.id() == 0,    // first layer?
 | ||||
| 		            -1,                 // auto width
 | ||||
| 		            *layer.object() | ||||
| 		        ); | ||||
| 				params.flow = layerm.flow(frSolidInfill); | ||||
| 		        params.spacing = params.flow.spacing();	         | ||||
| 				surface_fills.emplace_back(params); | ||||
| 				surface_fills.back().surface.surface_type = stInternalSolid; | ||||
|  | @ -365,9 +351,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: | |||
|         f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; | ||||
| 
 | ||||
|         // calculate flow spacing for infill pattern generation
 | ||||
|         bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; | ||||
|         bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge; | ||||
|         double link_max_length = 0.; | ||||
|         if (! surface_fill.params.flow.bridge) { | ||||
|         if (! surface_fill.params.bridge) { | ||||
| #if 0 | ||||
|             link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing()); | ||||
| //            printf("flow spacing: %f,  is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
 | ||||
|  | @ -380,7 +366,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: | |||
|         // Maximum length of the perimeter segment linking two infill lines.
 | ||||
|         f->link_max_length = (coord_t)scale_(link_max_length); | ||||
|         // Used by the concentric infill pattern to clip the loops to create extrusion paths.
 | ||||
|         f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); | ||||
|         f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); | ||||
| 
 | ||||
|         // apply half spacing using this flow's own spacing and generate infill
 | ||||
|         FillParams params; | ||||
|  | @ -402,15 +388,15 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: | |||
| 		        // calculate actual flow from spacing (which might have been adjusted by the infill
 | ||||
| 		        // pattern generator)
 | ||||
| 		        double flow_mm3_per_mm = surface_fill.params.flow.mm3_per_mm(); | ||||
| 		        double flow_width      = surface_fill.params.flow.width; | ||||
| 		        double flow_width      = surface_fill.params.flow.width(); | ||||
| 		        if (using_internal_flow) { | ||||
| 		            // if we used the internal flow we're not doing a solid infill
 | ||||
| 		            // so we can safely ignore the slight variation that might have
 | ||||
| 		            // been applied to f->spacing
 | ||||
| 		        } else { | ||||
| 		            Flow new_flow = Flow::new_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter, surface_fill.params.flow.height, surface_fill.params.flow.bridge); | ||||
| 		            Flow new_flow   = surface_fill.params.flow.with_spacing(float(f->spacing)); | ||||
| 		        	flow_mm3_per_mm = new_flow.mm3_per_mm(); | ||||
| 		        	flow_width      = new_flow.width; | ||||
| 		        	flow_width      = new_flow.width(); | ||||
| 		        } | ||||
| 		        // Save into layer.
 | ||||
| 				ExtrusionEntityCollection* eec = nullptr; | ||||
|  | @ -420,7 +406,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: | |||
| 		        extrusion_entities_append_paths( | ||||
| 		            eec->entities, std::move(polylines), | ||||
| 		            surface_fill.params.extrusion_role, | ||||
| 		            flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height); | ||||
| 		            flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); | ||||
| 		    } | ||||
| 		} | ||||
|     } | ||||
|  | @ -618,9 +604,9 @@ void Layer::make_ironing() | |||
|         fill.spacing = ironing_params.line_spacing; | ||||
|         fill.angle = float(ironing_params.angle + 0.25 * M_PI); | ||||
|         fill.link_max_length = (coord_t)scale_(3. * fill.spacing); | ||||
| 		double height = ironing_params.height * fill.spacing / nozzle_dmr; | ||||
|         Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height), false); | ||||
|         double flow_mm3_per_mm = flow.mm3_per_mm(); | ||||
| 		double extrusion_height = ironing_params.height * fill.spacing / nozzle_dmr; | ||||
| 		float  extrusion_width  = Flow::rounded_rectangle_extrusion_width_from_spacing(float(nozzle_dmr), float(extrusion_height)); | ||||
| 		double flow_mm3_per_mm = nozzle_dmr * extrusion_height; | ||||
|         Surface surface_fill(stTop, ExPolygon()); | ||||
|         for (ExPolygon &expoly : ironing_areas) { | ||||
| 			surface_fill.expolygon = std::move(expoly); | ||||
|  | @ -638,7 +624,7 @@ void Layer::make_ironing() | |||
| 		        extrusion_entities_append_paths( | ||||
| 		            eec->entities, std::move(polylines), | ||||
| 		            erIroning, | ||||
| 		            flow_mm3_per_mm, float(flow.width), float(height)); | ||||
| 		            flow_mm3_per_mm, extrusion_width, float(extrusion_height)); | ||||
| 		    } | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -122,20 +122,13 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResol | |||
| 
 | ||||
| // This constructor builds a Flow object from an extrusion width config setting
 | ||||
| // and other context properties.
 | ||||
| Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio) | ||||
| Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height) | ||||
| { | ||||
|     // we need layer height unless it's a bridge
 | ||||
|     if (height <= 0 && bridge_flow_ratio == 0)  | ||||
|     if (height <= 0) | ||||
|         throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()"); | ||||
| 
 | ||||
|     float w; | ||||
|     if (bridge_flow_ratio > 0) { | ||||
|         // If bridge flow was requested, calculate the bridge width.
 | ||||
|         height = w = (bridge_flow_ratio == 1.) ? | ||||
|             // optimization to avoid sqrt()
 | ||||
|             nozzle_diameter : | ||||
|             sqrt(bridge_flow_ratio) * nozzle_diameter; | ||||
|     } else if (! width.percent && width.value == 0.) { | ||||
|     if (! width.percent && width.value == 0.) { | ||||
|         // If user left option to 0, calculate a sane default width.
 | ||||
|         w = auto_extrusion_width(role, nozzle_diameter); | ||||
|     } else { | ||||
|  | @ -143,71 +136,89 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent | |||
|         w = float(width.get_abs_value(height)); | ||||
|     } | ||||
|      | ||||
|     return Flow(w, height, nozzle_diameter, bridge_flow_ratio > 0); | ||||
|     return Flow(w, height, rounded_rectangle_extrusion_spacing(w, height), nozzle_diameter, false); | ||||
| } | ||||
| 
 | ||||
| // This constructor builds a Flow object from a given centerline spacing.
 | ||||
| Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge)  | ||||
| // Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions.
 | ||||
| Flow Flow::with_spacing(float new_spacing) const | ||||
| { | ||||
|     // we need layer height unless it's a bridge
 | ||||
|     if (height <= 0 && !bridge)  | ||||
|         throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()"); | ||||
|     // Calculate width from spacing.
 | ||||
|     // For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions.
 | ||||
|     // For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads.
 | ||||
|     float width = float(bridge ? | ||||
|         (spacing - BRIDGE_EXTRA_SPACING) :  | ||||
| #ifdef HAS_PERIMETER_LINE_OVERLAP | ||||
|         (spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI)); | ||||
| #else | ||||
|         (spacing + height * (1. - 0.25 * PI))); | ||||
| #endif | ||||
|     return Flow(width, bridge ? width : height, nozzle_diameter, bridge); | ||||
|     Flow out = *this; | ||||
|     if (m_bridge) { | ||||
|         // Diameter of the rounded extrusion.
 | ||||
|         assert(m_width == m_height); | ||||
|         float gap          = m_spacing - m_width; | ||||
|         auto  new_diameter = new_spacing - gap; | ||||
|         out.m_width        = out.m_height = new_diameter; | ||||
|     } else { | ||||
|         assert(m_width >= m_height); | ||||
|         out.m_width += new_spacing - m_spacing; | ||||
|         if (out.m_width < out.m_height) | ||||
|             throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing()"); | ||||
|     } | ||||
|     out.m_spacing = new_spacing; | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| // This method returns the centerline spacing between two adjacent extrusions 
 | ||||
| // having the same extrusion width (and other properties).
 | ||||
| float Flow::spacing() const  | ||||
| // Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing.
 | ||||
| Flow Flow::with_cross_section(float area_new) const | ||||
| { | ||||
| #ifdef HAS_PERIMETER_LINE_OVERLAP | ||||
|     if (this->bridge) | ||||
|         return this->width + BRIDGE_EXTRA_SPACING; | ||||
|     // rectangle with semicircles at the ends
 | ||||
|     float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI); | ||||
|     float res = this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing); | ||||
| #else | ||||
|     float res = float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI))); | ||||
| #endif | ||||
| //    assert(res > 0.f);
 | ||||
| 	if (res <= 0.f) | ||||
| 		throw FlowErrorNegativeSpacing(); | ||||
| 	return res; | ||||
|     assert(! m_bridge); | ||||
|     assert(m_width >= m_height); | ||||
| 
 | ||||
|     // Adjust for bridge_flow_ratio, maintain the extrusion spacing.
 | ||||
|     float area = this->mm3_per_mm(); | ||||
|     if (area_new > area + EPSILON) { | ||||
|         // Increasing the flow rate.
 | ||||
|         float new_full_spacing = area_new / m_height; | ||||
|         if (new_full_spacing > m_spacing) { | ||||
|             // Filling up the spacing without an air gap. Grow the extrusion in height.
 | ||||
|             float height = area_new / m_spacing; | ||||
|             return Flow(rounded_rectangle_extrusion_width_from_spacing(m_spacing, height), height, m_spacing, m_nozzle_diameter, false); | ||||
|         } else { | ||||
|             return this->with_width(rounded_rectangle_extrusion_width_from_spacing(area / m_height, m_height)); | ||||
|         } | ||||
|     } else if (area_new < area - EPSILON) { | ||||
|         // Decreasing the flow rate.
 | ||||
|         float width_new = m_width - (area - area_new) / m_height; | ||||
|         assert(width_new > 0); | ||||
|         if (width_new > m_height) { | ||||
|             // Shrink the extrusion width.
 | ||||
|             return this->with_width(width_new); | ||||
|         } else { | ||||
|             // Create a rounded extrusion.
 | ||||
|             auto dmr = float(sqrt(area_new / M_PI)); | ||||
|             return Flow(dmr, dmr, m_spacing, m_nozzle_diameter, false); | ||||
|         } | ||||
|     } else | ||||
|         return *this; | ||||
| } | ||||
| 
 | ||||
| // This method returns the centerline spacing between an extrusion using this
 | ||||
| // flow and another one using another flow.
 | ||||
| // this->spacing(other) shall return the same value as other.spacing(*this)
 | ||||
| float Flow::spacing(const Flow &other) const | ||||
| float Flow::rounded_rectangle_extrusion_spacing(float width, float height) | ||||
| { | ||||
|     assert(this->height == other.height); | ||||
|     assert(this->bridge == other.bridge); | ||||
|     float res = float(this->bridge ?  | ||||
|         0.5 * this->width + 0.5 * other.width + BRIDGE_EXTRA_SPACING : | ||||
|         0.5 * this->spacing() + 0.5 * other.spacing()); | ||||
| //    assert(res > 0.f);
 | ||||
| 	if (res <= 0.f) | ||||
| 		throw FlowErrorNegativeSpacing(); | ||||
| 	return res; | ||||
|     auto out = width - height * float(1. - 0.25 * PI); | ||||
|     if (out <= 0.f) | ||||
|         throw FlowErrorNegativeSpacing(); | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| float Flow::rounded_rectangle_extrusion_width_from_spacing(float spacing, float height) | ||||
| { | ||||
|     return float(spacing + height * (1. - 0.25 * PI)); | ||||
| } | ||||
| 
 | ||||
| float Flow::bridge_extrusion_spacing(float dmr) | ||||
| { | ||||
|     return dmr + BRIDGE_EXTRA_SPACING; | ||||
| } | ||||
| 
 | ||||
| // This method returns extrusion volume per head move unit.
 | ||||
| double Flow::mm3_per_mm() const  | ||||
| double Flow::mm3_per_mm() const | ||||
| { | ||||
|     float res = this->bridge ? | ||||
|     float res = m_bridge ? | ||||
|         // Area of a circle with dmr of this->width.
 | ||||
|         float((this->width * this->width) * 0.25 * PI) : | ||||
|         float((m_width * m_width) * 0.25 * PI) : | ||||
|         // Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
 | ||||
|         float(this->height * (this->width - this->height * (1. - 0.25 * PI))); | ||||
|         float(m_height * (m_width - m_height * (1. - 0.25 * PI))); | ||||
|     //assert(res > 0.);
 | ||||
| 	if (res <= 0.) | ||||
| 		throw FlowErrorNegativeFlow(); | ||||
|  | @ -222,9 +233,7 @@ Flow support_material_flow(const PrintObject *object, float layer_height) | |||
|         (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width, | ||||
|         // if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
 | ||||
|         float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)), | ||||
|         (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value), | ||||
|         // bridge_flow_ratio
 | ||||
|         0.f); | ||||
|         (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); | ||||
| } | ||||
| 
 | ||||
| Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) | ||||
|  | @ -235,9 +244,7 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig | |||
|         // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
 | ||||
|         (width.value > 0) ? width : object->config().extrusion_width, | ||||
|         float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)), | ||||
|         (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)), | ||||
|         // bridge_flow_ratio
 | ||||
|         0.f); | ||||
|         (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value))); | ||||
| } | ||||
| 
 | ||||
| Flow support_material_interface_flow(const PrintObject *object, float layer_height) | ||||
|  | @ -248,9 +255,7 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig | |||
|         (object->config().support_material_extrusion_width > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width, | ||||
|         // if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
 | ||||
|         float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_interface_extruder-1)), | ||||
|         (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value), | ||||
|         // bridge_flow_ratio
 | ||||
|         0.f); | ||||
|         (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -13,11 +13,6 @@ class PrintObject; | |||
| // Extra spacing of bridge threads, in mm.
 | ||||
| #define BRIDGE_EXTRA_SPACING 0.05 | ||||
| 
 | ||||
| // Overlap factor of perimeter lines. Currently no overlap.
 | ||||
| #ifdef HAS_PERIMETER_LINE_OVERLAP | ||||
|     #define PERIMETER_LINE_OVERLAP_FACTOR 1.0 | ||||
| #endif | ||||
| 
 | ||||
| enum FlowRole { | ||||
|     frExternalPerimeter, | ||||
|     frPerimeter, | ||||
|  | @ -56,26 +51,26 @@ public: | |||
| class Flow | ||||
| { | ||||
| public: | ||||
|     Flow() = default; | ||||
|     Flow(float width, float height, float nozzle_diameter) : | ||||
|         Flow(width, height, rounded_rectangle_extrusion_spacing(width, height), nozzle_diameter, false) {} | ||||
| 
 | ||||
|     // Non bridging flow: Maximum width of an extrusion with semicircles at the ends.
 | ||||
|     // Bridging flow: Bridge thread diameter.
 | ||||
|     float width; | ||||
|     float   width()           const { return m_width; } | ||||
|     coord_t scaled_width()    const { return coord_t(scale_(m_width)); } | ||||
|     // Non bridging flow: Layer height.
 | ||||
|     // Bridging flow: Bridge thread diameter = layer height.
 | ||||
|     float height; | ||||
|     float   height()          const { return m_height; } | ||||
|     // Spacing between the extrusion centerlines.
 | ||||
|     float   spacing()         const { return m_spacing; } | ||||
|     coord_t scaled_spacing()  const { return coord_t(scale_(m_spacing)); } | ||||
|     // Nozzle diameter. 
 | ||||
|     float nozzle_diameter; | ||||
|     float   nozzle_diameter() const { return m_nozzle_diameter; } | ||||
|     // Is it a bridge?
 | ||||
|     bool  bridge; | ||||
|      | ||||
|     Flow(float _w, float _h, float _nd, bool _bridge = false) : | ||||
|         width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {} | ||||
| 
 | ||||
|     float   spacing() const; | ||||
|     float   spacing(const Flow &other) const; | ||||
|     double  mm3_per_mm() const; | ||||
|     coord_t scaled_width() const { return coord_t(scale_(this->width)); } | ||||
|     coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); } | ||||
|     coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); } | ||||
|     bool    bridge()          const { return m_bridge; } | ||||
|     // Cross section area of the extrusion.
 | ||||
|     double  mm3_per_mm()      const; | ||||
| 
 | ||||
|     // Elephant foot compensation spacing to be used to detect narrow parts, where the elephant foot compensation cannot be applied.
 | ||||
|     // To be used on frExternalPerimeter only.
 | ||||
|  | @ -83,13 +78,32 @@ public: | |||
|     // Here an overlap of 0.2x external perimeter spacing is allowed for by the elephant foot compensation.
 | ||||
|     coord_t scaled_elephant_foot_spacing() const { return coord_t(0.5f * float(this->scaled_width() + 0.6f * this->scaled_spacing())); } | ||||
| 
 | ||||
|     bool operator==(const Flow &rhs) const { return this->width == rhs.width && this->height == rhs.height && this->nozzle_diameter == rhs.nozzle_diameter && this->bridge == rhs.bridge; } | ||||
|      | ||||
|     static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); | ||||
|     // Create a flow from the spacing of extrusion lines.
 | ||||
|     // This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale
 | ||||
|     // to fit a region with integer number of lines.
 | ||||
|     static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); | ||||
|     bool operator==(const Flow &rhs) const { return m_width == rhs.m_width && m_height == rhs.m_height && m_nozzle_diameter == rhs.m_nozzle_diameter && m_bridge == rhs.m_bridge; } | ||||
| 
 | ||||
|     Flow        with_width (float width)  const {  | ||||
|         assert(! m_bridge);  | ||||
|         return Flow(width, m_height, rounded_rectangle_extrusion_spacing(width, m_height), m_nozzle_diameter, m_bridge); | ||||
|     } | ||||
|     Flow        with_height(float height) const {  | ||||
|         assert(! m_bridge);  | ||||
|         return Flow(m_width, height, rounded_rectangle_extrusion_spacing(m_width, height), m_nozzle_diameter, m_bridge); | ||||
|     } | ||||
|     // Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions.
 | ||||
|     Flow        with_spacing(float spacing) const; | ||||
|     // Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing.
 | ||||
|     Flow        with_cross_section(float area) const; | ||||
|     Flow        with_flow_ratio(double ratio) const { return this->with_cross_section(this->mm3_per_mm() * ratio); } | ||||
| 
 | ||||
|     static Flow bridging_flow(float dmr, float nozzle_diameter) { return Flow { dmr, dmr, bridge_extrusion_spacing(dmr), nozzle_diameter, true }; } | ||||
| 
 | ||||
|     static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height); | ||||
| 
 | ||||
|     // Spacing of extrusions with rounded extrusion model.
 | ||||
|     static float rounded_rectangle_extrusion_spacing(float width, float height); | ||||
|     // Width of extrusions with rounded extrusion model.
 | ||||
|     static float rounded_rectangle_extrusion_width_from_spacing(float spacing, float height); | ||||
|     // Spacing of round thread extrusions.
 | ||||
|     static float bridge_extrusion_spacing(float dmr); | ||||
| 
 | ||||
|     // Sane extrusion width defautl based on nozzle diameter.
 | ||||
|     // The defaults were derived from manual Prusa MK3 profiles.
 | ||||
|  | @ -100,6 +114,20 @@ public: | |||
|     // on active extruder etc. Therefore the value calculated by this function shall be used as a hint only.
 | ||||
| 	static double extrusion_width(const std::string &opt_key, const ConfigOptionFloatOrPercent *opt, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0); | ||||
| 	static double extrusion_width(const std::string &opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0); | ||||
| 
 | ||||
| private: | ||||
|     Flow(float width, float height, float spacing, float nozzle_diameter, bool bridge) :  | ||||
|         m_width(width), m_height(height), m_spacing(spacing), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge)  | ||||
|         {  | ||||
|             // Gap fill violates this condition.
 | ||||
|             //assert(width >= height); 
 | ||||
|         } | ||||
| 
 | ||||
|     float       m_width { 0 }; | ||||
|     float       m_height { 0 }; | ||||
|     float       m_spacing { 0 }; | ||||
|     float       m_nozzle_diameter { 0 }; | ||||
|     bool        m_bridge { false }; | ||||
| }; | ||||
| 
 | ||||
| extern Flow support_material_flow(const PrintObject *object, float layer_height = 0.f); | ||||
|  |  | |||
|  | @ -2014,9 +2014,10 @@ namespace Slic3r { | |||
|         typedef std::map<int, ObjectData> IdToObjectDataMap; | ||||
| 
 | ||||
|         bool m_fullpath_sources{ true }; | ||||
|         bool m_zip64 { true }; | ||||
| 
 | ||||
|     public: | ||||
|         bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr); | ||||
|         bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64); | ||||
| 
 | ||||
|     private: | ||||
|         bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data); | ||||
|  | @ -2036,10 +2037,11 @@ namespace Slic3r { | |||
|         bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); | ||||
|     }; | ||||
| 
 | ||||
|     bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data) | ||||
|     bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) | ||||
|     { | ||||
|         clear_errors(); | ||||
|         m_fullpath_sources = fullpath_sources; | ||||
|         m_zip64 = zip64; | ||||
|         return _save_model_to_file(filename, model, config, thumbnail_data); | ||||
|     } | ||||
| 
 | ||||
|  | @ -2233,9 +2235,13 @@ namespace Slic3r { | |||
|     { | ||||
|         mz_zip_writer_staged_context context; | ||||
|         if (!mz_zip_writer_add_staged_open(&archive, &context, MODEL_FILE.c_str(),  | ||||
|             // Maximum expected and allowed 3MF file size is 16GiB.
 | ||||
|             // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records.
 | ||||
|             (uint64_t(1) << 30) * 16, | ||||
|             m_zip64 ?  | ||||
|                 // Maximum expected and allowed 3MF file size is 16GiB.
 | ||||
|                 // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records.
 | ||||
|                 (uint64_t(1) << 30) * 16 :  | ||||
|                 // Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see
 | ||||
|                 // GH issue #6193.
 | ||||
|                 (uint64_t(1) << 32) - 1, | ||||
|             nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0)) { | ||||
|             add_error("Unable to add model file to archive"); | ||||
|             return false; | ||||
|  | @ -2926,13 +2932,13 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c | |||
|     return res; | ||||
| } | ||||
| 
 | ||||
| bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data) | ||||
| bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) | ||||
| { | ||||
|     if (path == nullptr || model == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|     _3MF_Exporter exporter; | ||||
|     bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data); | ||||
|     bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data, zip64); | ||||
|     if (!res) | ||||
|         exporter.log_errors(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ namespace Slic3r { | |||
| 
 | ||||
|     // Save the given model and the config data contained in the given Print into a 3mf file.
 | ||||
|     // The model could be modified during the export process if meshes are not repaired or have no shared vertices
 | ||||
|     extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr); | ||||
|     extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr, bool zip64 = true); | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ | |||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cstdlib> | ||||
| #include <chrono> | ||||
| #include <math.h> | ||||
| #include <string_view> | ||||
| 
 | ||||
|  | @ -1113,15 +1114,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu | |||
|     const double       layer_height         = first_object->config().layer_height.value; | ||||
|     const double       first_layer_height   = first_object->config().first_layer_height.get_abs_value(layer_height); | ||||
|     for (const PrintRegion* region : print.regions()) { | ||||
|         _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width); | ||||
|         _write_format(file, "; perimeters extrusion width = %.2fmm\n",          region->flow(frPerimeter,         layer_height, false, false, -1., *first_object).width); | ||||
|         _write_format(file, "; infill extrusion width = %.2fmm\n",              region->flow(frInfill,            layer_height, false, false, -1., *first_object).width); | ||||
|         _write_format(file, "; solid infill extrusion width = %.2fmm\n",        region->flow(frSolidInfill,       layer_height, false, false, -1., *first_object).width); | ||||
|         _write_format(file, "; top infill extrusion width = %.2fmm\n",          region->flow(frTopSolidInfill,    layer_height, false, false, -1., *first_object).width); | ||||
|         _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frExternalPerimeter, layer_height).width()); | ||||
|         _write_format(file, "; perimeters extrusion width = %.2fmm\n",          region->flow(*first_object, frPerimeter,         layer_height).width()); | ||||
|         _write_format(file, "; infill extrusion width = %.2fmm\n",              region->flow(*first_object, frInfill,            layer_height).width()); | ||||
|         _write_format(file, "; solid infill extrusion width = %.2fmm\n",        region->flow(*first_object, frSolidInfill,       layer_height).width()); | ||||
|         _write_format(file, "; top infill extrusion width = %.2fmm\n",          region->flow(*first_object, frTopSolidInfill,    layer_height).width()); | ||||
|         if (print.has_support_material()) | ||||
|             _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width); | ||||
|             _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); | ||||
|         if (print.config().first_layer_extrusion_width.value > 0) | ||||
|             _write_format(file, "; first layer extrusion width = %.2fmm\n",   region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width); | ||||
|             _write_format(file, "; first layer extrusion width = %.2fmm\n",   region->flow(*first_object, frPerimeter, first_layer_height, true).width()); | ||||
|         _write_format(file, "\n"); | ||||
|     } | ||||
|     print.throw_if_canceled(); | ||||
|  | @ -1137,6 +1138,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu | |||
|     // Prepare the helper object for replacing placeholders in custom G-code and output filename.
 | ||||
|     m_placeholder_parser = print.placeholder_parser(); | ||||
|     m_placeholder_parser.update_timestamp(); | ||||
|     m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count()); | ||||
|     print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode"); | ||||
| 
 | ||||
|     // Get optimal tool ordering to minimize tool switches of a multi-exruder print.
 | ||||
|  | @ -1823,7 +1825,8 @@ namespace Skirt { | |||
|         // Extrude skirt at the print_z of the raft layers and normal object layers
 | ||||
|         // not at the print_z of the interlaced support material layers.
 | ||||
|         std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder_out; | ||||
|         assert(skirt_done.empty()); | ||||
|         //For sequential print, the following test may fail when extruding the 2nd and other objects.
 | ||||
|         // assert(skirt_done.empty());
 | ||||
|         if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt) { | ||||
|             skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); | ||||
|             skirt_done.emplace_back(layer_tools.print_z); | ||||
|  | @ -2178,14 +2181,13 @@ void GCode::process_layer( | |||
|             const std::pair<size_t, size_t> loops = loops_it->second; | ||||
|             this->set_origin(0., 0.); | ||||
|             m_avoid_crossing_perimeters.use_external_mp(); | ||||
|             Flow layer_skirt_flow(print.skirt_flow()); | ||||
|             layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])); | ||||
|             Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); | ||||
|             double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); | ||||
|             for (size_t i = loops.first; i < loops.second; ++i) { | ||||
|                 // Adjust flow according to this layer's layer height.
 | ||||
|                 ExtrusionLoop loop = *dynamic_cast<const ExtrusionLoop*>(print.skirt().entities[i]); | ||||
|                 for (ExtrusionPath &path : loop.paths) { | ||||
|                     path.height = layer_skirt_flow.height; | ||||
|                     path.height = layer_skirt_flow.height(); | ||||
|                     path.mm3_per_mm = mm3_per_mm; | ||||
|                 } | ||||
|                 //FIXME using the support_material_speed of the 1st object printed.
 | ||||
|  |  | |||
|  | @ -59,7 +59,10 @@ public: | |||
|     // (this collection contains only ExtrusionEntityCollection objects)
 | ||||
|     ExtrusionEntityCollection   fills; | ||||
|      | ||||
|     Flow    flow(FlowRole role, bool bridge = false, double width = -1) const; | ||||
|     Flow    flow(FlowRole role) const; | ||||
|     Flow    flow(FlowRole role, double layer_height) const; | ||||
|     Flow    bridging_flow(FlowRole role) const; | ||||
| 
 | ||||
|     void    slices_to_fill_surfaces_clipped(); | ||||
|     void    prepare_fill_surfaces(); | ||||
|     void    make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces); | ||||
|  |  | |||
|  | @ -15,16 +15,31 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const | ||||
| Flow LayerRegion::flow(FlowRole role) const | ||||
| { | ||||
|     return m_region->flow( | ||||
|         role, | ||||
|         m_layer->height, | ||||
|         bridge, | ||||
|         m_layer->id() == 0, | ||||
|         width, | ||||
|         *m_layer->object() | ||||
|     ); | ||||
|     return this->flow(role, m_layer->height); | ||||
| } | ||||
| 
 | ||||
| Flow LayerRegion::flow(FlowRole role, double layer_height) const | ||||
| { | ||||
|     return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0); | ||||
| } | ||||
| 
 | ||||
| Flow LayerRegion::bridging_flow(FlowRole role) const | ||||
| { | ||||
|     const PrintRegion       ®ion         = *this->region(); | ||||
|     const PrintRegionConfig ®ion_config  = region.config(); | ||||
|     if (this->layer()->object()->config().thick_bridges) { | ||||
|         // The old Slic3r way (different from all other slicers): Use rounded extrusions.
 | ||||
|         // Get the configured nozzle_diameter for the extruder associated to the flow role requested.
 | ||||
|         // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
 | ||||
|         auto nozzle_diameter = float(region.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1)); | ||||
|         // Applies default bridge spacing.
 | ||||
|         return Flow::bridging_flow(float(sqrt(region_config.bridge_flow_ratio)) * nozzle_diameter, nozzle_diameter); | ||||
|     } else { | ||||
|         // The same way as other slicers: Use normal extrusions. Apply bridge_flow_ratio while maintaining the original spacing.
 | ||||
|         return this->flow(role).with_flow_ratio(region_config.bridge_flow_ratio); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
 | ||||
|  | @ -84,7 +99,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec | |||
|      | ||||
|     g.layer_id              = (int)this->layer()->id(); | ||||
|     g.ext_perimeter_flow    = this->flow(frExternalPerimeter); | ||||
|     g.overhang_flow         = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object()); | ||||
|     g.overhang_flow         = this->bridging_flow(frPerimeter); | ||||
|     g.solid_infill_flow     = this->flow(frSolidInfill); | ||||
|      | ||||
|     g.process(); | ||||
|  | @ -266,11 +281,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly | |||
|                 // would get merged into a single one while they need different directions
 | ||||
|                 // also, supply the original expolygon instead of the grown one, because in case
 | ||||
|                 // of very thin (but still working) anchors, the grown expolygon would go beyond them
 | ||||
|                 BridgeDetector bd( | ||||
|                     initial, | ||||
|                     lower_layer->lslices, | ||||
|                     this->flow(frInfill, true).scaled_width() | ||||
|                 ); | ||||
|                 BridgeDetector bd(initial, lower_layer->lslices, this->bridging_flow(frInfill).scaled_width()); | ||||
|                 #ifdef SLIC3R_DEBUG | ||||
|                 printf("Processing bridge at layer %zu:\n", this->layer()->id()); | ||||
|                 #endif | ||||
|  |  | |||
|  | @ -1302,52 +1302,54 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b | |||
| 
 | ||||
| void ModelObject::split(ModelObjectPtrs* new_objects) | ||||
| { | ||||
|     if (this->volumes.size() > 1) { | ||||
|         // We can't split meshes if there's more than one volume, because
 | ||||
|         // we can't group the resulting meshes by object afterwards
 | ||||
|         new_objects->emplace_back(this); | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     ModelVolume* volume = this->volumes.front(); | ||||
|     TriangleMeshPtrs meshptrs = volume->mesh().split(); | ||||
|     size_t counter = 1; | ||||
|     for (TriangleMesh *mesh : meshptrs) { | ||||
|     for (ModelVolume* volume : this->volumes) { | ||||
|         if (volume->type() != ModelVolumeType::MODEL_PART) | ||||
|             continue; | ||||
| 
 | ||||
|         // FIXME: crashes if not satisfied
 | ||||
|         if (mesh->facets_count() < 3) continue; | ||||
|         TriangleMeshPtrs meshptrs = volume->mesh().split(); | ||||
|         size_t counter = 1; | ||||
|         for (TriangleMesh* mesh : meshptrs) { | ||||
| 
 | ||||
|         mesh->repair(); | ||||
|          | ||||
|         // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
 | ||||
|         ModelObject* new_object = m_model->add_object();     | ||||
|         new_object->name   = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : ""); | ||||
|             // FIXME: crashes if not satisfied
 | ||||
|             if (mesh->facets_count() < 3) continue; | ||||
| 
 | ||||
|         // Don't copy the config's ID.
 | ||||
| 		new_object->config.assign_config(this->config); | ||||
| 		assert(new_object->config.id().valid()); | ||||
| 		assert(new_object->config.id() != this->config.id()); | ||||
|         new_object->instances.reserve(this->instances.size()); | ||||
|         for (const ModelInstance *model_instance : this->instances) | ||||
|             new_object->add_instance(*model_instance); | ||||
|         ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh)); | ||||
|             mesh->repair(); | ||||
| 
 | ||||
|         for (ModelInstance* model_instance : new_object->instances) | ||||
|         { | ||||
|             Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); | ||||
|             model_instance->set_offset(model_instance->get_offset() + shift); | ||||
|             // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
 | ||||
|             ModelObject* new_object = m_model->add_object(); | ||||
|             if (meshptrs.size() == 1) { | ||||
|                 new_object->name = volume->name; | ||||
|                 // Don't copy the config's ID.
 | ||||
|                 new_object->config.assign_config(this->config.size() > 0 ? this->config : volume->config); | ||||
|             } | ||||
|             else { | ||||
|                 new_object->name = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : ""); | ||||
|                 // Don't copy the config's ID.
 | ||||
|                 new_object->config.assign_config(this->config); | ||||
|             } | ||||
|             assert(new_object->config.id().valid()); | ||||
|             assert(new_object->config.id() != this->config.id()); | ||||
|             new_object->instances.reserve(this->instances.size()); | ||||
|             for (const ModelInstance* model_instance : this->instances) | ||||
|                 new_object->add_instance(*model_instance); | ||||
|             ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh)); | ||||
| 
 | ||||
|             for (ModelInstance* model_instance : new_object->instances) | ||||
|             { | ||||
|                 Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); | ||||
|                 model_instance->set_offset(model_instance->get_offset() + shift); | ||||
|             } | ||||
| 
 | ||||
|             new_vol->set_offset(Vec3d::Zero()); | ||||
|             // reset the source to disable reload from disk
 | ||||
|             new_vol->source = ModelVolume::Source(); | ||||
|             new_objects->emplace_back(new_object); | ||||
|             delete mesh; | ||||
|         } | ||||
| 
 | ||||
|         new_vol->set_offset(Vec3d::Zero()); | ||||
|         // reset the source to disable reload from disk
 | ||||
|         new_vol->source = ModelVolume::Source(); | ||||
|         new_objects->emplace_back(new_object); | ||||
|         delete mesh; | ||||
|     } | ||||
|      | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void ModelObject::merge() | ||||
| { | ||||
|     if (this->volumes.size() == 1) { | ||||
|  | @ -1738,6 +1740,7 @@ size_t ModelVolume::split(unsigned int max_extruders) | |||
|         this->object->volumes[ivolume]->translate(offset); | ||||
|         this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); | ||||
|         this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); | ||||
|         this->object->volumes[ivolume]->m_is_splittable = 0; | ||||
|         delete mesh; | ||||
|         ++ idx; | ||||
|     } | ||||
|  |  | |||
|  | @ -69,7 +69,7 @@ static bool clip_narrow_corner( | |||
|     Vec2i64 p2 = it2->cast<int64_t>(); | ||||
|     Vec2i64 p02; | ||||
|     Vec2i64 p22; | ||||
|     int64_t dist2_next; | ||||
|     int64_t dist2_next = 0; | ||||
| 
 | ||||
|     // As long as there is at least a single triangle left in the polygon.
 | ||||
|     while (polygon.size() >= 3) { | ||||
|  |  | |||
|  | @ -22,74 +22,54 @@ namespace Slic3r { | |||
| class TriangleMeshDataAdapter { | ||||
| public: | ||||
|     const TriangleMesh &mesh; | ||||
|      | ||||
|     float voxel_scale; | ||||
| 
 | ||||
|     size_t polygonCount() const { return mesh.its.indices.size(); } | ||||
|     size_t pointCount() const   { return mesh.its.vertices.size(); } | ||||
|     size_t vertexCount(size_t) const { return 3; } | ||||
|      | ||||
| 
 | ||||
|     // Return position pos in local grid index space for polygon n and vertex v
 | ||||
|     void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const; | ||||
|     // The actual mesh will appear to openvdb as scaled uniformly by voxel_size
 | ||||
|     // And the voxel count per unit volume can be affected this way.
 | ||||
|     void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const | ||||
|     { | ||||
|         auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v))); | ||||
|         Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>() * voxel_scale; | ||||
|         pos = {p.x(), p.y(), p.z()}; | ||||
|     } | ||||
| 
 | ||||
|     TriangleMeshDataAdapter(const TriangleMesh &m, float voxel_sc = 1.f) | ||||
|         : mesh{m}, voxel_scale{voxel_sc} {}; | ||||
| }; | ||||
| 
 | ||||
| class Contour3DDataAdapter { | ||||
| public: | ||||
|     const sla::Contour3D &mesh; | ||||
|      | ||||
|     size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); } | ||||
|     size_t pointCount() const   { return mesh.points.size(); } | ||||
|     size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; } | ||||
|      | ||||
|     // Return position pos in local grid index space for polygon n and vertex v
 | ||||
|     void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const; | ||||
| }; | ||||
| 
 | ||||
| void TriangleMeshDataAdapter::getIndexSpacePoint(size_t          n, | ||||
|                                                  size_t          v, | ||||
|                                                  openvdb::Vec3d &pos) const | ||||
| { | ||||
|     auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v))); | ||||
|     Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>(); | ||||
|     pos = {p.x(), p.y(), p.z()}; | ||||
| } | ||||
| 
 | ||||
| void Contour3DDataAdapter::getIndexSpacePoint(size_t          n, | ||||
|                                               size_t          v, | ||||
|                                               openvdb::Vec3d &pos) const | ||||
| { | ||||
|     size_t vidx = 0; | ||||
|     if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v))); | ||||
|     else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v))); | ||||
|      | ||||
|     Slic3r::Vec3d p = mesh.points[vidx]; | ||||
|     pos = {p.x(), p.y(), p.z()}; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // TODO: Do I need to call initialize? Seems to work without it as well but the
 | ||||
| // docs say it should be called ones. It does a mutex lock-unlock sequence all
 | ||||
| // even if was called previously.
 | ||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, | ||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &            mesh, | ||||
|                                      const openvdb::math::Transform &tr, | ||||
|                                      float               exteriorBandWidth, | ||||
|                                      float               interiorBandWidth, | ||||
|                                      int                 flags) | ||||
|                                      float voxel_scale, | ||||
|                                      float exteriorBandWidth, | ||||
|                                      float interiorBandWidth, | ||||
|                                      int   flags) | ||||
| { | ||||
|     openvdb::initialize(); | ||||
| 
 | ||||
|     TriangleMeshPtrs meshparts = mesh.split(); | ||||
|     TriangleMeshPtrs meshparts_raw = mesh.split(); | ||||
|     auto meshparts = reserve_vector<std::unique_ptr<TriangleMesh>>(meshparts_raw.size()); | ||||
|     for (auto *p : meshparts_raw) | ||||
|         meshparts.emplace_back(p); | ||||
| 
 | ||||
|     auto it = std::remove_if(meshparts.begin(), meshparts.end(), | ||||
|     [](TriangleMesh *m){ | ||||
|         m->require_shared_vertices(); | ||||
|         return !m->is_manifold() || m->volume() < EPSILON; | ||||
|     }); | ||||
|     auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) { | ||||
|          m->require_shared_vertices(); | ||||
|          return m->volume() < EPSILON; | ||||
|      }); | ||||
| 
 | ||||
|     meshparts.erase(it, meshparts.end()); | ||||
| 
 | ||||
|     openvdb::FloatGrid::Ptr grid; | ||||
|     for (TriangleMesh *m : meshparts) { | ||||
|     for (auto &m : meshparts) { | ||||
|         auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>( | ||||
|             TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth, | ||||
|             TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth, | ||||
|             interiorBandWidth, flags); | ||||
| 
 | ||||
|         if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); | ||||
|  | @ -106,19 +86,9 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, | |||
|             interiorBandWidth, flags); | ||||
|     } | ||||
| 
 | ||||
|     return grid; | ||||
| } | ||||
|     grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale)); | ||||
| 
 | ||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh, | ||||
|                                      const openvdb::math::Transform &tr, | ||||
|                                      float exteriorBandWidth, | ||||
|                                      float interiorBandWidth, | ||||
|                                      int flags) | ||||
| { | ||||
|     openvdb::initialize(); | ||||
|     return openvdb::tools::meshToVolume<openvdb::FloatGrid>( | ||||
|         Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth, | ||||
|         flags); | ||||
|     return grid; | ||||
| } | ||||
| 
 | ||||
| template<class Grid> | ||||
|  | @ -128,20 +98,25 @@ sla::Contour3D _volumeToMesh(const Grid &grid, | |||
|                              bool        relaxDisorientedTriangles) | ||||
| { | ||||
|     openvdb::initialize(); | ||||
|      | ||||
| 
 | ||||
|     std::vector<openvdb::Vec3s> points; | ||||
|     std::vector<openvdb::Vec3I> triangles; | ||||
|     std::vector<openvdb::Vec4I> quads; | ||||
|      | ||||
| 
 | ||||
|     openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue, | ||||
|                                  adaptivity, relaxDisorientedTriangles); | ||||
|      | ||||
| 
 | ||||
|     float scale = 1.; | ||||
|     try { | ||||
|         scale = grid.template metaValue<float>("voxel_scale"); | ||||
|     }  catch (...) { } | ||||
| 
 | ||||
|     sla::Contour3D ret; | ||||
|     ret.points.reserve(points.size()); | ||||
|     ret.faces3.reserve(triangles.size()); | ||||
|     ret.faces4.reserve(quads.size()); | ||||
| 
 | ||||
|     for (auto &v : points) ret.points.emplace_back(to_vec3d(v)); | ||||
|     for (auto &v : points) ret.points.emplace_back(to_vec3d(v) / scale); | ||||
|     for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v)); | ||||
|     for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v)); | ||||
| 
 | ||||
|  | @ -166,9 +141,18 @@ sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid, | |||
|                          relaxDisorientedTriangles); | ||||
| } | ||||
| 
 | ||||
| openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir) | ||||
| openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, | ||||
|                                         double                    iso, | ||||
|                                         double                    er, | ||||
|                                         double                    ir) | ||||
| { | ||||
|     return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir)); | ||||
|     auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso), | ||||
|                                                     float(er), float(ir)); | ||||
| 
 | ||||
|     // Copies voxel_scale metadata, if it exists.
 | ||||
|     new_grid->insertMeta(*grid.deepCopyMeta()); | ||||
| 
 | ||||
|     return new_grid; | ||||
| } | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -21,14 +21,16 @@ inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast<double> | |||
| inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; } | ||||
| inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; } | ||||
| 
 | ||||
| // Here voxel_scale defines the scaling of voxels which affects the voxel count.
 | ||||
| // 1.0 value means a voxel for every unit cube. 2 means the model is scaled to
 | ||||
| // be 2x larger and the voxel count is increased by the increment in the scaled
 | ||||
| // volume, thus 4 times. This kind a sampling accuracy selection is not
 | ||||
| // achievable through the Transform parameter. (TODO: or is it?)
 | ||||
| // The resulting grid will contain the voxel_scale in its metadata under the
 | ||||
| // "voxel_scale" key to be used in grid_to_mesh function.
 | ||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &            mesh, | ||||
|                                      const openvdb::math::Transform &tr = {}, | ||||
|                                      float exteriorBandWidth = 3.0f, | ||||
|                                      float interiorBandWidth = 3.0f, | ||||
|                                      int   flags             = 0); | ||||
| 
 | ||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &          mesh, | ||||
|                                      const openvdb::math::Transform &tr = {}, | ||||
|                                      float voxel_scale = 1.f, | ||||
|                                      float exteriorBandWidth = 3.0f, | ||||
|                                      float interiorBandWidth = 3.0f, | ||||
|                                      int   flags             = 0); | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance) | ||||
| static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance) | ||||
| { | ||||
|     ExtrusionPaths paths; | ||||
|     ExtrusionPath path(role); | ||||
|  | @ -62,15 +62,15 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi | |||
|             path.polyline.append(line.b); | ||||
|             // Convert from spacing to extrusion width based on the extrusion model
 | ||||
|             // of a square extrusion ended with semi circles.
 | ||||
|             flow.width = unscale<float>(w) + flow.height * float(1. - 0.25 * PI); | ||||
|             Flow new_flow = flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI)); | ||||
|             #ifdef SLIC3R_DEBUG | ||||
|             printf("  filling %f gap\n", flow.width); | ||||
|             #endif | ||||
|             path.mm3_per_mm  = flow.mm3_per_mm(); | ||||
|             path.width       = flow.width; | ||||
|             path.height      = flow.height; | ||||
|             path.mm3_per_mm  = new_flow.mm3_per_mm(); | ||||
|             path.width       = new_flow.width(); | ||||
|             path.height      = new_flow.height(); | ||||
|         } else { | ||||
|             thickness_delta = fabs(scale_(flow.width) - w); | ||||
|             thickness_delta = fabs(scale_(flow.width()) - w); | ||||
|             if (thickness_delta <= tolerance) { | ||||
|                 // the width difference between this line and the current flow width is 
 | ||||
|                 // within the accepted tolerance
 | ||||
|  | @ -88,7 +88,7 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi | |||
|     return paths; | ||||
| } | ||||
| 
 | ||||
| static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector<ExtrusionEntity*> &out) | ||||
| static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, const Flow &flow, std::vector<ExtrusionEntity*> &out) | ||||
| { | ||||
| 	// This value determines granularity of adaptive width, as G-code does not allow
 | ||||
| 	// variable extrusion within a single move; this value shall only affect the amount
 | ||||
|  | @ -205,8 +205,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime | |||
|                 paths, | ||||
|                 intersection_pl({ polygon }, perimeter_generator.lower_slices_polygons()), | ||||
|                 role, | ||||
|                 is_external ? perimeter_generator.ext_mm3_per_mm()          : perimeter_generator.mm3_per_mm(), | ||||
|                 is_external ? perimeter_generator.ext_perimeter_flow.width  : perimeter_generator.perimeter_flow.width, | ||||
|                 is_external ? perimeter_generator.ext_mm3_per_mm()           : perimeter_generator.mm3_per_mm(), | ||||
|                 is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(), | ||||
|                 (float)perimeter_generator.layer_height); | ||||
|              | ||||
|             // get overhang paths by checking what parts of this loop fall 
 | ||||
|  | @ -217,8 +217,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime | |||
|                 diff_pl({ polygon }, perimeter_generator.lower_slices_polygons()), | ||||
|                 erOverhangPerimeter, | ||||
|                 perimeter_generator.mm3_per_mm_overhang(), | ||||
|                 perimeter_generator.overhang_flow.width, | ||||
|                 perimeter_generator.overhang_flow.height); | ||||
|                 perimeter_generator.overhang_flow.width(), | ||||
|                 perimeter_generator.overhang_flow.height()); | ||||
|              | ||||
|             // Reapply the nearest point search for starting point.
 | ||||
|             // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
 | ||||
|  | @ -226,8 +226,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime | |||
|         } else { | ||||
|             ExtrusionPath path(role); | ||||
|             path.polyline   = polygon.split_at_first_point(); | ||||
|             path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm()          : perimeter_generator.mm3_per_mm(); | ||||
|             path.width      = is_external ? perimeter_generator.ext_perimeter_flow.width  : perimeter_generator.perimeter_flow.width; | ||||
|             path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm()           : perimeter_generator.mm3_per_mm(); | ||||
|             path.width      = is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(); | ||||
|             path.height     = (float)perimeter_generator.layer_height; | ||||
|             paths.push_back(path); | ||||
|         } | ||||
|  | @ -286,7 +286,7 @@ void PerimeterGenerator::process() | |||
|     m_ext_mm3_per_mm           		= this->ext_perimeter_flow.mm3_per_mm(); | ||||
|     coord_t ext_perimeter_width     = this->ext_perimeter_flow.scaled_width(); | ||||
|     coord_t ext_perimeter_spacing   = this->ext_perimeter_flow.scaled_spacing(); | ||||
|     coord_t ext_perimeter_spacing2  = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow); | ||||
|     coord_t ext_perimeter_spacing2  = scaled<coord_t>(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); | ||||
|      | ||||
|     // overhang perimeters
 | ||||
|     m_mm3_per_mm_overhang      		= this->overhang_flow.mm3_per_mm(); | ||||
|  | @ -346,7 +346,7 @@ void PerimeterGenerator::process() | |||
|                     if (this->config->thin_walls) { | ||||
|                         // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
 | ||||
|                         // (actually, something larger than that still may exist due to mitering or other causes)
 | ||||
|                         coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter / 3)); | ||||
|                         coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3)); | ||||
|                         ExPolygons expp = offset2_ex( | ||||
|                             // medial axis requires non-overlapping geometry
 | ||||
|                             diff_ex(to_polygons(last), | ||||
|  |  | |||
|  | @ -428,9 +428,10 @@ const std::vector<std::string>& Preset::print_options() | |||
|         "min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", | ||||
|         "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", | ||||
|         "support_material_pattern", "support_material_with_sheath", "support_material_spacing", | ||||
|         "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", | ||||
|         "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", | ||||
|         "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius", | ||||
|         "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers", | ||||
|         "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",  | ||||
|         "support_material_contact_distance", "support_material_bottom_contact_distance", | ||||
|         "support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", | ||||
|         "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder", | ||||
|         "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", | ||||
|         "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", | ||||
|  |  | |||
|  | @ -1326,7 +1326,8 @@ std::string Print::validate(std::string* warning) const | |||
|                     return L("The Wipe Tower is only supported for multiple objects if they have equal layer heights"); | ||||
|                 if (slicing_params.raft_layers() != slicing_params0.raft_layers()) | ||||
|                     return L("The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers"); | ||||
|                 if (object->config().support_material_contact_distance != m_objects.front()->config().support_material_contact_distance) | ||||
|                 if (slicing_params0.gap_object_support != slicing_params.gap_object_support || | ||||
|                     slicing_params0.gap_support_object != slicing_params.gap_support_object) | ||||
|                     return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance"); | ||||
|                 if (! equal_layering(slicing_params, slicing_params0)) | ||||
|                     return L("The Wipe Tower is only supported for multiple objects if they are sliced equally."); | ||||
|  | @ -1577,9 +1578,7 @@ Flow Print::brim_flow() const | |||
|         frPerimeter, | ||||
| 		width, | ||||
|         (float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), | ||||
| 		(float)this->skirt_first_layer_height(), | ||||
|         0 | ||||
|     ); | ||||
| 		(float)this->skirt_first_layer_height()); | ||||
| } | ||||
| 
 | ||||
| Flow Print::skirt_flow() const | ||||
|  | @ -1599,9 +1598,7 @@ Flow Print::skirt_flow() const | |||
|         frPerimeter, | ||||
| 		width, | ||||
| 		(float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), | ||||
| 		(float)this->skirt_first_layer_height(), | ||||
|         0 | ||||
|     ); | ||||
| 		(float)this->skirt_first_layer_height()); | ||||
| } | ||||
| 
 | ||||
| bool Print::has_support_material() const | ||||
|  | @ -1818,7 +1815,7 @@ void Print::_make_skirt() | |||
|             ExtrusionPath( | ||||
|                 erSkirt, | ||||
|                 (float)mm3_per_mm,         // this will be overridden at G-code export time
 | ||||
|                 flow.width, | ||||
|                 flow.width(), | ||||
| 				(float)first_layer_height  // this will be overridden at G-code export time
 | ||||
|             ))); | ||||
|         eloop.paths.back().polyline = loop.split_at_first_point(); | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ public: | |||
|     const PrintRegionConfig&    config() const { return m_config; } | ||||
| 	// 1-based extruder identifier for this region and role.
 | ||||
| 	unsigned int 				extruder(FlowRole role) const; | ||||
|     Flow                        flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; | ||||
|     Flow                        flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const; | ||||
|     // Average diameter of nozzles participating on extruding this region.
 | ||||
|     coordf_t                    nozzle_dmr_avg(const PrintConfig &print_config) const; | ||||
|     // Average diameter of nozzles participating on extruding this region.
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| #include "PrintConfig.hpp" | ||||
| #include "Config.hpp" | ||||
| #include "I18N.hpp" | ||||
| 
 | ||||
| #include <set> | ||||
|  | @ -66,7 +67,7 @@ void PrintConfigDef::init_common_params() | |||
|     def->label = L("G-code thumbnails"); | ||||
|     def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\""); | ||||
|     def->mode = comExpert; | ||||
|     def->gui_type = "one_string"; | ||||
|     def->gui_type = ConfigOptionDef::GUIType::one_string; | ||||
|     def->set_default_value(new ConfigOptionPoints()); | ||||
| 
 | ||||
|     def = this->add("layer_height", coFloat); | ||||
|  | @ -116,7 +117,7 @@ void PrintConfigDef::init_common_params() | |||
|     def = this->add("printhost_port", coString); | ||||
|     def->label = L("Printer"); | ||||
|     def->tooltip = L("Name of the printer"); | ||||
|     def->gui_type = "select_open"; | ||||
|     def->gui_type = ConfigOptionDef::GUIType::select_open; | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionString("")); | ||||
|      | ||||
|  | @ -568,7 +569,7 @@ void PrintConfigDef::init_fff_params() | |||
|     def->set_default_value(new ConfigOptionBool(true)); | ||||
| 
 | ||||
|     def = this->add("extruder", coInt); | ||||
|     def->gui_type = "i_enum_open"; | ||||
|     def->gui_type = ConfigOptionDef::GUIType::i_enum_open; | ||||
|     def->label = L("Extruder"); | ||||
|     def->category = L("Extruders"); | ||||
|     def->tooltip = L("The extruder to use (unless more specific extruder settings are specified). " | ||||
|  | @ -606,7 +607,7 @@ void PrintConfigDef::init_fff_params() | |||
|     def = this->add("extruder_colour", coStrings); | ||||
|     def->label = L("Extruder Color"); | ||||
|     def->tooltip = L("This is only used in the Slic3r interface as a visual help."); | ||||
|     def->gui_type = "color"; | ||||
|     def->gui_type = ConfigOptionDef::GUIType::color; | ||||
|     // Empty string means no color assigned yet.
 | ||||
|     def->set_default_value(new ConfigOptionStrings { "" }); | ||||
| 
 | ||||
|  | @ -668,7 +669,7 @@ void PrintConfigDef::init_fff_params() | |||
|     def = this->add("filament_colour", coStrings); | ||||
|     def->label = L("Color"); | ||||
|     def->tooltip = L("This is only used in the Slic3r interface as a visual help."); | ||||
|     def->gui_type = "color"; | ||||
|     def->gui_type = ConfigOptionDef::GUIType::color; | ||||
|     def->set_default_value(new ConfigOptionStrings { "#29B2B2" }); | ||||
| 
 | ||||
|     def = this->add("filament_notes", coStrings); | ||||
|  | @ -812,7 +813,7 @@ void PrintConfigDef::init_fff_params() | |||
|     def = this->add("filament_type", coStrings); | ||||
|     def->label = L("Filament type"); | ||||
|     def->tooltip = L("The filament material type for use in custom G-codes."); | ||||
|     def->gui_type = "f_enum_open"; | ||||
|     def->gui_type = ConfigOptionDef::GUIType::f_enum_open; | ||||
|     def->gui_flags = "show_value"; | ||||
|     def->enum_values.push_back("PLA"); | ||||
|     def->enum_values.push_back("PET"); | ||||
|  | @ -881,7 +882,7 @@ void PrintConfigDef::init_fff_params() | |||
|     def->set_default_value(new ConfigOptionFloat(45)); | ||||
| 
 | ||||
|     def = this->add("fill_density", coPercent); | ||||
|     def->gui_type = "f_enum_open"; | ||||
|     def->gui_type = ConfigOptionDef::GUIType::f_enum_open; | ||||
|     def->gui_flags = "show_value"; | ||||
|     def->label = L("Fill density"); | ||||
|     def->category = L("Infill"); | ||||
|  | @ -1168,7 +1169,7 @@ void PrintConfigDef::init_fff_params() | |||
|                      "Set this parameter to zero to disable anchoring perimeters connected to a single infill line."); | ||||
|     def->sidetext = L("mm or %"); | ||||
|     def->ratio_over = "infill_extrusion_width"; | ||||
|     def->gui_type = "f_enum_open"; | ||||
|     def->gui_type = ConfigOptionDef::GUIType::f_enum_open; | ||||
|     def->enum_values.push_back("0"); | ||||
|     def->enum_values.push_back("1"); | ||||
|     def->enum_values.push_back("2"); | ||||
|  | @ -1950,7 +1951,7 @@ void PrintConfigDef::init_fff_params() | |||
| 
 | ||||
| #if 0 | ||||
|     def = this->add("seam_preferred_direction", coFloat); | ||||
| //    def->gui_type = "slider";
 | ||||
| //    def->gui_type = ConfigOptionDef::GUIType::slider;
 | ||||
|     def->label = L("Direction"); | ||||
|     def->sidetext = L("°"); | ||||
|     def->full_label = L("Preferred direction of the seam"); | ||||
|  | @ -1960,7 +1961,7 @@ void PrintConfigDef::init_fff_params() | |||
|     def->set_default_value(new ConfigOptionFloat(0)); | ||||
| 
 | ||||
|     def = this->add("seam_preferred_direction_jitter", coFloat); | ||||
| //    def->gui_type = "slider";
 | ||||
| //    def->gui_type = ConfigOptionDef::GUIType::slider;
 | ||||
|     def->label = L("Jitter"); | ||||
|     def->sidetext = L("°"); | ||||
|     def->full_label = L("Seam preferred direction jitter"); | ||||
|  | @ -2234,8 +2235,8 @@ void PrintConfigDef::init_fff_params() | |||
|     def->set_default_value(new ConfigOptionBool(false)); | ||||
| 
 | ||||
|     def = this->add("support_material_contact_distance", coFloat); | ||||
|     def->gui_type = "f_enum_open"; | ||||
|     def->label = L("Contact Z distance"); | ||||
|     def->gui_type = ConfigOptionDef::GUIType::f_enum_open; | ||||
|     def->label = L("Top contact Z distance"); | ||||
|     def->category = L("Support material"); | ||||
|     def->tooltip = L("The vertical distance between object and support material interface. " | ||||
|                    "Setting this to 0 will also prevent Slic3r from using bridge flow and speed " | ||||
|  | @ -2243,12 +2244,31 @@ void PrintConfigDef::init_fff_params() | |||
|     def->sidetext = L("mm"); | ||||
| //    def->min = 0;
 | ||||
|     def->enum_values.push_back("0"); | ||||
|     def->enum_values.push_back("0.1"); | ||||
|     def->enum_values.push_back("0.2"); | ||||
|     def->enum_labels.push_back(L("0 (soluble)")); | ||||
|     def->enum_labels.push_back(L("0.1 (detachable)")); | ||||
|     def->enum_labels.push_back(L("0.2 (detachable)")); | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionFloat(0.2)); | ||||
| 
 | ||||
|     def = this->add("support_material_bottom_contact_distance", coFloat); | ||||
|     def->gui_type = ConfigOptionDef::GUIType::f_enum_open; | ||||
|     def->label = L("Bottom contact Z distance"); | ||||
|     def->category = L("Support material"); | ||||
|     def->tooltip = L("The vertical distance between the object top surface and the support material interface. " | ||||
|                    "If set to zero, support_material_contact_distance will be used for both top and bottom contact Z distances."); | ||||
|     def->sidetext = L("mm"); | ||||
| //    def->min = 0;
 | ||||
|     def->enum_values.push_back("0"); | ||||
|     def->enum_values.push_back("0.1"); | ||||
|     def->enum_values.push_back("0.2"); | ||||
|     def->enum_labels.push_back(L("same as top")); | ||||
|     def->enum_labels.push_back(L("0.1")); | ||||
|     def->enum_labels.push_back(L("0.2")); | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionFloat(0)); | ||||
| 
 | ||||
|     def = this->add("support_material_enforce_layers", coInt); | ||||
|     def->label = L("Enforce support for the first"); | ||||
|     def->category = L("Support material"); | ||||
|  | @ -2298,15 +2318,39 @@ void PrintConfigDef::init_fff_params() | |||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionInt(1)); | ||||
| 
 | ||||
|     def = this->add("support_material_interface_layers", coInt); | ||||
|     def->label = L("Interface layers"); | ||||
|     auto support_material_interface_layers = def = this->add("support_material_interface_layers", coInt); | ||||
|     def->gui_type = ConfigOptionDef::GUIType::i_enum_open; | ||||
|     def->label = L("Top interface layers"); | ||||
|     def->category = L("Support material"); | ||||
|     def->tooltip = L("Number of interface layers to insert between the object(s) and support material."); | ||||
|     def->sidetext = L("layers"); | ||||
|     def->min = 0; | ||||
|     def->enum_values.push_back("0"); | ||||
|     def->enum_values.push_back("1"); | ||||
|     def->enum_values.push_back("2"); | ||||
|     def->enum_values.push_back("3"); | ||||
|     def->enum_labels.push_back(L("0 (off)")); | ||||
|     def->enum_labels.push_back(L("1 (light)")); | ||||
|     def->enum_labels.push_back(L("2 (default)")); | ||||
|     def->enum_labels.push_back(L("3 (heavy)")); | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionInt(3)); | ||||
| 
 | ||||
|     def = this->add("support_material_bottom_interface_layers", coInt); | ||||
|     def->gui_type = ConfigOptionDef::GUIType::i_enum_open; | ||||
|     def->label = L("Bottom interface layers"); | ||||
|     def->category = L("Support material"); | ||||
|     def->tooltip = L("Number of interface layers to insert between the object(s) and support material. " | ||||
|                      "Set to -1 to use support_material_interface_layers"); | ||||
|     def->sidetext = L("layers"); | ||||
|     def->min = -1; | ||||
|     def->enum_values.push_back("-1"); | ||||
|     append(def->enum_values, support_material_interface_layers->enum_values); | ||||
|     def->enum_labels.push_back(L("same as top")); | ||||
|     append(def->enum_labels, support_material_interface_layers->enum_labels); | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionInt(-1)); | ||||
| 
 | ||||
|     def = this->add("support_material_interface_spacing", coFloat); | ||||
|     def->label = L("Interface pattern spacing"); | ||||
|     def->category = L("Support material"); | ||||
|  | @ -2415,6 +2459,13 @@ void PrintConfigDef::init_fff_params() | |||
|     def->max = max_temp; | ||||
|     def->set_default_value(new ConfigOptionInts { 200 }); | ||||
| 
 | ||||
|     def = this->add("thick_bridges", coBool); | ||||
|     def->label = L("Thick bridges"); | ||||
|     def->category = L("Layers and Perimeters"); | ||||
|     def->tooltip = L("Print bridges with round extrusions."); | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionBool(true)); | ||||
| 
 | ||||
|     def = this->add("thin_walls", coBool); | ||||
|     def->label = L("Detect thin walls"); | ||||
|     def->category = L("Layers and Perimeters"); | ||||
|  | @ -2823,7 +2874,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def = this->add("material_type", coString); | ||||
|     def->label = L("SLA material type"); | ||||
|     def->tooltip = L("SLA material type"); | ||||
|     def->gui_type = "f_enum_open";   // TODO: ???
 | ||||
|     def->gui_type = ConfigOptionDef::GUIType::f_enum_open;   // TODO: ???
 | ||||
|     def->gui_flags = "show_value"; | ||||
|     def->enum_values.push_back("Tough"); | ||||
|     def->enum_values.push_back("Flexible"); | ||||
|  |  | |||
|  | @ -503,12 +503,14 @@ public: | |||
|     ConfigOptionFloat               support_material_angle; | ||||
|     ConfigOptionBool                support_material_buildplate_only; | ||||
|     ConfigOptionFloat               support_material_contact_distance; | ||||
|     ConfigOptionFloat               support_material_bottom_contact_distance; | ||||
|     ConfigOptionInt                 support_material_enforce_layers; | ||||
|     ConfigOptionInt                 support_material_extruder; | ||||
|     ConfigOptionFloatOrPercent      support_material_extrusion_width; | ||||
|     ConfigOptionBool                support_material_interface_contact_loops; | ||||
|     ConfigOptionInt                 support_material_interface_extruder; | ||||
|     ConfigOptionInt                 support_material_interface_layers; | ||||
|     ConfigOptionInt                 support_material_bottom_interface_layers; | ||||
|     // Spacing between interface lines (the hatching distance). Set zero to get a solid interface.
 | ||||
|     ConfigOptionFloat               support_material_interface_spacing; | ||||
|     ConfigOptionFloatOrPercent      support_material_interface_speed; | ||||
|  | @ -522,6 +524,7 @@ public: | |||
|     ConfigOptionInt                 support_material_threshold; | ||||
|     ConfigOptionBool                support_material_with_sheath; | ||||
|     ConfigOptionFloatOrPercent      support_material_xy_spacing; | ||||
|     ConfigOptionBool                thick_bridges; | ||||
|     ConfigOptionFloat               xy_size_compensation; | ||||
|     ConfigOptionBool                wipe_into_objects; | ||||
| 
 | ||||
|  | @ -553,12 +556,14 @@ protected: | |||
|         OPT_PTR(support_material_angle); | ||||
|         OPT_PTR(support_material_buildplate_only); | ||||
|         OPT_PTR(support_material_contact_distance); | ||||
|         OPT_PTR(support_material_bottom_contact_distance); | ||||
|         OPT_PTR(support_material_enforce_layers); | ||||
|         OPT_PTR(support_material_interface_contact_loops); | ||||
|         OPT_PTR(support_material_extruder); | ||||
|         OPT_PTR(support_material_extrusion_width); | ||||
|         OPT_PTR(support_material_interface_extruder); | ||||
|         OPT_PTR(support_material_interface_layers); | ||||
|         OPT_PTR(support_material_bottom_interface_layers); | ||||
|         OPT_PTR(support_material_interface_spacing); | ||||
|         OPT_PTR(support_material_interface_speed); | ||||
|         OPT_PTR(support_material_pattern); | ||||
|  | @ -569,6 +574,7 @@ protected: | |||
|         OPT_PTR(support_material_xy_spacing); | ||||
|         OPT_PTR(support_material_threshold); | ||||
|         OPT_PTR(support_material_with_sheath); | ||||
|         OPT_PTR(thick_bridges); | ||||
|         OPT_PTR(xy_size_compensation); | ||||
|         OPT_PTR(wipe_into_objects); | ||||
|     } | ||||
|  |  | |||
|  | @ -546,14 +546,9 @@ bool PrintObject::invalidate_state_by_config_options( | |||
|             || opt_key == "extra_perimeters" | ||||
|             || opt_key == "gap_fill_enabled" | ||||
|             || opt_key == "gap_fill_speed" | ||||
|             || opt_key == "overhangs" | ||||
|             || opt_key == "first_layer_extrusion_width" | ||||
|             || opt_key == "fuzzy_skin" | ||||
|             || opt_key == "fuzzy_skin_thickness" | ||||
|             || opt_key == "fuzzy_skin_point_dist" | ||||
|             || opt_key == "perimeter_extrusion_width" | ||||
|             || opt_key == "infill_overlap" | ||||
|             || opt_key == "thin_walls" | ||||
|             || opt_key == "external_perimeters_first") { | ||||
|             steps.emplace_back(posPerimeters); | ||||
|         } else if ( | ||||
|  | @ -585,7 +580,9 @@ bool PrintObject::invalidate_state_by_config_options( | |||
|             || opt_key == "support_material_enforce_layers" | ||||
|             || opt_key == "support_material_extruder" | ||||
|             || opt_key == "support_material_extrusion_width" | ||||
|             || opt_key == "support_material_bottom_contact_distance" | ||||
|             || opt_key == "support_material_interface_layers" | ||||
|             || opt_key == "support_material_bottom_interface_layers" | ||||
|             || opt_key == "support_material_interface_pattern" | ||||
|             || opt_key == "support_material_interface_contact_loops" | ||||
|             || opt_key == "support_material_interface_extruder" | ||||
|  | @ -652,7 +649,13 @@ bool PrintObject::invalidate_state_by_config_options( | |||
|             steps.emplace_back(posPrepareInfill); | ||||
|         } else if ( | ||||
|                opt_key == "external_perimeter_extrusion_width" | ||||
|             || opt_key == "perimeter_extruder") { | ||||
|             || opt_key == "perimeter_extruder" | ||||
|             || opt_key == "fuzzy_skin" | ||||
|             || opt_key == "fuzzy_skin_thickness" | ||||
|             || opt_key == "fuzzy_skin_point_dist" | ||||
|             || opt_key == "overhangs" | ||||
|             || opt_key == "thin_walls" | ||||
|             || opt_key == "thick_bridges") { | ||||
|             steps.emplace_back(posPerimeters); | ||||
|             steps.emplace_back(posSupportMaterial); | ||||
|         } else if (opt_key == "bridge_flow_ratio") { | ||||
|  | @ -1456,26 +1459,18 @@ void PrintObject::bridge_over_infill() | |||
|         const PrintRegion ®ion = *m_print->regions()[region_id]; | ||||
|          | ||||
|         // skip bridging in case there are no voids
 | ||||
|         if (region.config().fill_density.value == 100) continue; | ||||
|          | ||||
|         // get bridge flow
 | ||||
|         Flow bridge_flow = region.flow( | ||||
|             frSolidInfill, | ||||
|             -1,     // layer height, not relevant for bridge flow
 | ||||
|             true,   // bridge
 | ||||
|             false,  // first layer
 | ||||
|             -1,     // custom width, not relevant for bridge flow
 | ||||
|             *this | ||||
|         ); | ||||
|          | ||||
|         if (region.config().fill_density.value == 100) | ||||
|             continue; | ||||
| 
 | ||||
| 		for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) { | ||||
|             // skip first layer
 | ||||
| 			if (layer_it == m_layers.begin()) | ||||
|                 continue; | ||||
|              | ||||
|             Layer* layer        = *layer_it; | ||||
|             LayerRegion* layerm = layer->m_regions[region_id]; | ||||
|              | ||||
|             Layer       *layer       = *layer_it; | ||||
|             LayerRegion *layerm      = layer->m_regions[region_id]; | ||||
|             Flow         bridge_flow = layerm->bridging_flow(frSolidInfill); | ||||
| 
 | ||||
|             // extract the stInternalSolid surfaces that might be transformed into bridges
 | ||||
|             Polygons internal_solid; | ||||
|             layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); | ||||
|  | @ -1488,7 +1483,7 @@ void PrintObject::bridge_over_infill() | |||
|                 Polygons to_bridge_pp = internal_solid; | ||||
|                  | ||||
|                 // iterate through lower layers spanned by bridge_flow
 | ||||
|                 double bottom_z = layer->print_z - bridge_flow.height; | ||||
|                 double bottom_z = layer->print_z - bridge_flow.height(); | ||||
|                 for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) { | ||||
|                     const Layer* lower_layer = m_layers[i]; | ||||
|                      | ||||
|  |  | |||
|  | @ -18,31 +18,25 @@ unsigned int PrintRegion::extruder(FlowRole role) const | |||
|     return extruder; | ||||
| } | ||||
| 
 | ||||
| Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const | ||||
| Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer) const | ||||
| { | ||||
|     ConfigOptionFloatOrPercent config_width; | ||||
|     if (width != -1) { | ||||
|         // use the supplied custom width, if any
 | ||||
|         config_width.value = width; | ||||
|         config_width.percent = false; | ||||
|     // Get extrusion width from configuration.
 | ||||
|     // (might be an absolute value, or a percent value, or zero for auto)
 | ||||
|     if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) { | ||||
|         config_width = m_print->config().first_layer_extrusion_width; | ||||
|     } else if (role == frExternalPerimeter) { | ||||
|         config_width = m_config.external_perimeter_extrusion_width; | ||||
|     } else if (role == frPerimeter) { | ||||
|         config_width = m_config.perimeter_extrusion_width; | ||||
|     } else if (role == frInfill) { | ||||
|         config_width = m_config.infill_extrusion_width; | ||||
|     } else if (role == frSolidInfill) { | ||||
|         config_width = m_config.solid_infill_extrusion_width; | ||||
|     } else if (role == frTopSolidInfill) { | ||||
|         config_width = m_config.top_infill_extrusion_width; | ||||
|     } else { | ||||
|         // otherwise, get extrusion width from configuration
 | ||||
|         // (might be an absolute value, or a percent value, or zero for auto)
 | ||||
|         if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) { | ||||
|             config_width = m_print->config().first_layer_extrusion_width; | ||||
|         } else if (role == frExternalPerimeter) { | ||||
|             config_width = m_config.external_perimeter_extrusion_width; | ||||
|         } else if (role == frPerimeter) { | ||||
|             config_width = m_config.perimeter_extrusion_width; | ||||
|         } else if (role == frInfill) { | ||||
|             config_width = m_config.infill_extrusion_width; | ||||
|         } else if (role == frSolidInfill) { | ||||
|             config_width = m_config.solid_infill_extrusion_width; | ||||
|         } else if (role == frTopSolidInfill) { | ||||
|             config_width = m_config.top_infill_extrusion_width; | ||||
|         } else { | ||||
|             throw Slic3r::InvalidArgument("Unknown role"); | ||||
|         } | ||||
|         throw Slic3r::InvalidArgument("Unknown role"); | ||||
|     } | ||||
| 
 | ||||
|     if (config_width.value == 0) | ||||
|  | @ -50,8 +44,8 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir | |||
|      | ||||
|     // Get the configured nozzle_diameter for the extruder associated to the flow role requested.
 | ||||
|     // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
 | ||||
|     double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1); | ||||
|     return Flow::new_from_config_width(role, config_width, (float)nozzle_diameter, (float)layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0f); | ||||
|     auto nozzle_diameter = float(m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1)); | ||||
|     return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height)); | ||||
| } | ||||
| 
 | ||||
| coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #include <tbb/mutex.h> | ||||
| #include <tbb/parallel_for.h> | ||||
| #include <tbb/parallel_reduce.h> | ||||
| #include <tbb/task_arena.h> | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <numeric> | ||||
|  | @ -76,13 +77,18 @@ template<> struct _ccr<true> | |||
|             from, to, init, std::forward<MergeFn>(mergefn), | ||||
|             [](typename I::value_type &i) { return i; }, granularity); | ||||
|     } | ||||
| 
 | ||||
|     static size_t max_concurreny() | ||||
|     { | ||||
|         return tbb::this_task_arena::max_concurrency(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template<> struct _ccr<false> | ||||
| { | ||||
| private: | ||||
|     struct _Mtx { inline void lock() {} inline void unlock() {} }; | ||||
|      | ||||
| 
 | ||||
| public: | ||||
|     using SpinningMutex = _Mtx; | ||||
|     using BlockingMutex = _Mtx; | ||||
|  | @ -133,6 +139,8 @@ public: | |||
|         return reduce(from, to, init, std::forward<MergeFn>(mergefn), | ||||
|                       [](typename I::value_type &i) { return i; }); | ||||
|     } | ||||
| 
 | ||||
|     static size_t max_concurreny() { return 1; } | ||||
| }; | ||||
| 
 | ||||
| using ccr = _ccr<USE_FULL_CONCURRENCY>; | ||||
|  |  | |||
|  | @ -26,64 +26,99 @@ inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); } | |||
| template<class S, class = FloatingOnly<S>> | ||||
| inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; } | ||||
| 
 | ||||
| static TriangleMesh _generate_interior(const TriangleMesh  &mesh, | ||||
|                                        const JobController &ctl, | ||||
|                                        double               min_thickness, | ||||
|                                        double               voxel_scale, | ||||
|                                        double               closing_dist) | ||||
| struct Interior { | ||||
|     TriangleMesh mesh; | ||||
|     openvdb::FloatGrid::Ptr gridptr; | ||||
|     mutable std::optional<openvdb::FloatGrid::ConstAccessor> accessor; | ||||
| 
 | ||||
|     double closing_distance = 0.; | ||||
|     double thickness = 0.; | ||||
|     double voxel_scale = 1.; | ||||
|     double nb_in = 3.;  // narrow band width inwards
 | ||||
|     double nb_out = 3.; // narrow band width outwards
 | ||||
|     // Full narrow band is the sum of the two above values.
 | ||||
| 
 | ||||
|     void reset_accessor() const  // This resets the accessor and its cache
 | ||||
|     // Not a thread safe call!
 | ||||
|     { | ||||
|         if (gridptr) | ||||
|             accessor = gridptr->getConstAccessor(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| void InteriorDeleter::operator()(Interior *p) | ||||
| { | ||||
|     delete p; | ||||
| } | ||||
| 
 | ||||
| TriangleMesh &get_mesh(Interior &interior) | ||||
| { | ||||
|     return interior.mesh; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &get_mesh(const Interior &interior) | ||||
| { | ||||
|     return interior.mesh; | ||||
| } | ||||
| 
 | ||||
| static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh, | ||||
|                                              const JobController &ctl, | ||||
|                                              double min_thickness, | ||||
|                                              double voxel_scale, | ||||
|                                              double closing_dist) | ||||
| { | ||||
|     TriangleMesh imesh{mesh}; | ||||
|      | ||||
|     _scale(voxel_scale, imesh); | ||||
|      | ||||
|     double offset = voxel_scale * min_thickness; | ||||
|     double D = voxel_scale * closing_dist; | ||||
|     float  out_range = 0.1f * float(offset); | ||||
|     float  in_range = 1.1f * float(offset + D); | ||||
|      | ||||
| 
 | ||||
|     if (ctl.stopcondition()) return {}; | ||||
|     else ctl.statuscb(0, L("Hollowing")); | ||||
|      | ||||
|     auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range); | ||||
|      | ||||
| 
 | ||||
|     auto gridptr = mesh_to_grid(mesh, {}, voxel_scale, out_range, in_range); | ||||
| 
 | ||||
|     assert(gridptr); | ||||
|      | ||||
| 
 | ||||
|     if (!gridptr) { | ||||
|         BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL"; | ||||
|         return {}; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     if (ctl.stopcondition()) return {}; | ||||
|     else ctl.statuscb(30, L("Hollowing")); | ||||
|      | ||||
|     if (closing_dist > .0) { | ||||
|         gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range)); | ||||
|     } else { | ||||
|         D = -offset; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     double iso_surface = D; | ||||
|     auto   narrowb = double(in_range); | ||||
|     gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); | ||||
| 
 | ||||
|     if (ctl.stopcondition()) return {}; | ||||
|     else ctl.statuscb(70, L("Hollowing")); | ||||
|      | ||||
|     double iso_surface = D; | ||||
| 
 | ||||
|     double adaptivity = 0.; | ||||
|     auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); | ||||
|      | ||||
|     _scale(1. / voxel_scale, omesh); | ||||
|      | ||||
|     InteriorPtr interior = InteriorPtr{new Interior{}}; | ||||
| 
 | ||||
|     interior->mesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); | ||||
|     interior->gridptr = gridptr; | ||||
| 
 | ||||
|     if (ctl.stopcondition()) return {}; | ||||
|     else ctl.statuscb(100, L("Hollowing")); | ||||
|      | ||||
|     return omesh; | ||||
| 
 | ||||
|     interior->closing_distance = D; | ||||
|     interior->thickness = offset; | ||||
|     interior->voxel_scale = voxel_scale; | ||||
|     interior->nb_in = narrowb; | ||||
|     interior->nb_out = narrowb; | ||||
| 
 | ||||
|     return interior; | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &   mesh, | ||||
|                                                 const HollowingConfig &hc, | ||||
|                                                 const JobController &  ctl) | ||||
| InteriorPtr generate_interior(const TriangleMesh &   mesh, | ||||
|                               const HollowingConfig &hc, | ||||
|                               const JobController &  ctl) | ||||
| { | ||||
|     static const double MIN_OVERSAMPL = 3.; | ||||
|     static const double MAX_OVERSAMPL = 8.; | ||||
|          | ||||
| 
 | ||||
|     // I can't figure out how to increase the grid resolution through openvdb
 | ||||
|     // API so the model will be scaled up before conversion and the result
 | ||||
|     // scaled down. Voxels have a unit size. If I set voxelSize smaller, it
 | ||||
|  | @ -92,26 +127,29 @@ std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &   mesh, | |||
|     //
 | ||||
|     // max 8x upscale, min is native voxel size
 | ||||
|     auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality; | ||||
|     auto meshptr = std::make_unique<TriangleMesh>( | ||||
|         _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, | ||||
|                            hc.closing_distance)); | ||||
|      | ||||
|     if (meshptr && !meshptr->empty()) { | ||||
|          | ||||
| 
 | ||||
|     InteriorPtr interior = | ||||
|         generate_interior_verbose(mesh, ctl, hc.min_thickness, voxel_scale, | ||||
|                                   hc.closing_distance); | ||||
| 
 | ||||
|     if (interior && !interior->mesh.empty()) { | ||||
| 
 | ||||
|         // This flips the normals to be outward facing...
 | ||||
|         meshptr->require_shared_vertices(); | ||||
|         indexed_triangle_set its = std::move(meshptr->its); | ||||
|          | ||||
|         interior->mesh.require_shared_vertices(); | ||||
|         indexed_triangle_set its = std::move(interior->mesh.its); | ||||
| 
 | ||||
|         Slic3r::simplify_mesh(its); | ||||
|          | ||||
| 
 | ||||
|         // flip normals back...
 | ||||
|         for (stl_triangle_vertex_indices &ind : its.indices) | ||||
|             std::swap(ind(0), ind(2)); | ||||
|          | ||||
|         *meshptr = Slic3r::TriangleMesh{its}; | ||||
| 
 | ||||
|         interior->mesh = Slic3r::TriangleMesh{its}; | ||||
|         interior->mesh.repaired = true; | ||||
|         interior->mesh.require_shared_vertices(); | ||||
|     } | ||||
|      | ||||
|     return meshptr; | ||||
| 
 | ||||
|     return interior; | ||||
| } | ||||
| 
 | ||||
| Contour3D DrainHole::to_mesh() const | ||||
|  | @ -273,12 +311,264 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices, | |||
|         obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]); | ||||
| } | ||||
| 
 | ||||
| void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg) | ||||
| void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags) | ||||
| { | ||||
|     std::unique_ptr<Slic3r::TriangleMesh> inter_ptr = | ||||
|             Slic3r::sla::generate_interior(mesh); | ||||
|     InteriorPtr interior = generate_interior(mesh, cfg, JobController{}); | ||||
|     if (!interior) return; | ||||
| 
 | ||||
|     if (inter_ptr) mesh.merge(*inter_ptr); | ||||
|     hollow_mesh(mesh, *interior, flags); | ||||
| } | ||||
| 
 | ||||
| void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) | ||||
| { | ||||
|     if (mesh.empty() || interior.mesh.empty()) return; | ||||
| 
 | ||||
|     if (flags & hfRemoveInsideTriangles && interior.gridptr) | ||||
|         remove_inside_triangles(mesh, interior); | ||||
| 
 | ||||
|     mesh.merge(interior.mesh); | ||||
|     mesh.require_shared_vertices(); | ||||
| } | ||||
| 
 | ||||
| // Get the distance of p to the interior's zero iso_surface. Interior should
 | ||||
| // have its zero isosurface positioned at offset + closing_distance inwards form
 | ||||
| // the model surface.
 | ||||
| static double get_distance_raw(const Vec3f &p, const Interior &interior) | ||||
| { | ||||
|     assert(interior.gridptr); | ||||
| 
 | ||||
|     if (!interior.accessor) interior.reset_accessor(); | ||||
| 
 | ||||
|     auto v       = (p * interior.voxel_scale).cast<double>(); | ||||
|     auto grididx = interior.gridptr->transform().worldToIndexCellCentered( | ||||
|         {v.x(), v.y(), v.z()}); | ||||
| 
 | ||||
|     return interior.accessor->getValue(grididx) ; | ||||
| } | ||||
| 
 | ||||
| struct TriangleBubble { Vec3f center; double R; }; | ||||
| 
 | ||||
| // Return the distance of bubble center to the interior boundary or NaN if the
 | ||||
| // triangle is too big to be measured.
 | ||||
| static double get_distance(const TriangleBubble &b, const Interior &interior) | ||||
| { | ||||
|     double R = b.R * interior.voxel_scale; | ||||
|     double D = get_distance_raw(b.center, interior); | ||||
| 
 | ||||
|     return (D > 0. && R >= interior.nb_out) || | ||||
|            (D < 0. && R >= interior.nb_in)  || | ||||
|            ((D - R) < 0. && 2 * R > interior.thickness) ? | ||||
|                 std::nan("") : | ||||
|                 // FIXME: Adding interior.voxel_scale is a compromise supposed
 | ||||
|                 // to prevent the deletion of the triangles forming the interior
 | ||||
|                 // itself. This has a side effect that a small portion of the
 | ||||
|                 // bad triangles will still be visible.
 | ||||
|                 D - interior.closing_distance /*+ 2 * interior.voxel_scale*/; | ||||
| } | ||||
| 
 | ||||
| double get_distance(const Vec3f &p, const Interior &interior) | ||||
| { | ||||
|     double d = get_distance_raw(p, interior) - interior.closing_distance; | ||||
|     return d / interior.voxel_scale; | ||||
| } | ||||
| 
 | ||||
| // A face that can be divided. Stores the indices into the original mesh if its
 | ||||
| // part of that mesh and the vertices it consists of.
 | ||||
| enum { NEW_FACE = -1}; | ||||
| struct DivFace { | ||||
|     Vec3i indx; | ||||
|     std::array<Vec3f, 3> verts; | ||||
|     long faceid = NEW_FACE; | ||||
|     long parent = NEW_FACE; | ||||
| }; | ||||
| 
 | ||||
| // Divide a face recursively and call visitor on all the sub-faces.
 | ||||
| template<class Fn> | ||||
| void divide_triangle(const DivFace &face, Fn &&visitor) | ||||
| { | ||||
|     std::array<Vec3f, 3> edges = {(face.verts[0] - face.verts[1]), | ||||
|                                   (face.verts[1] - face.verts[2]), | ||||
|                                   (face.verts[2] - face.verts[0])}; | ||||
| 
 | ||||
|     std::array<size_t, 3> edgeidx = {0, 1, 2}; | ||||
| 
 | ||||
|     std::sort(edgeidx.begin(), edgeidx.end(), [&edges](size_t e1, size_t e2) { | ||||
|         return edges[e1].squaredNorm() > edges[e2].squaredNorm(); | ||||
|     }); | ||||
| 
 | ||||
|     DivFace child1, child2; | ||||
| 
 | ||||
|     child1.parent   = face.faceid == NEW_FACE ? face.parent : face.faceid; | ||||
|     child1.indx(0)  = -1; | ||||
|     child1.indx(1)  = face.indx(edgeidx[1]); | ||||
|     child1.indx(2)  = face.indx((edgeidx[1] + 1) % 3); | ||||
|     child1.verts[0] = (face.verts[edgeidx[0]] + face.verts[(edgeidx[0] + 1) % 3]) / 2.; | ||||
|     child1.verts[1] = face.verts[edgeidx[1]]; | ||||
|     child1.verts[2] = face.verts[(edgeidx[1] + 1) % 3]; | ||||
| 
 | ||||
|     if (visitor(child1)) | ||||
|         divide_triangle(child1, std::forward<Fn>(visitor)); | ||||
| 
 | ||||
|     child2.parent   = face.faceid == NEW_FACE ? face.parent : face.faceid; | ||||
|     child2.indx(0)  = -1; | ||||
|     child2.indx(1)  = face.indx(edgeidx[2]); | ||||
|     child2.indx(2)  = face.indx((edgeidx[2] + 1) % 3); | ||||
|     child2.verts[0] = child1.verts[0]; | ||||
|     child2.verts[1] = face.verts[edgeidx[2]]; | ||||
|     child2.verts[2] = face.verts[(edgeidx[2] + 1) % 3]; | ||||
| 
 | ||||
|     if (visitor(child2)) | ||||
|         divide_triangle(child2, std::forward<Fn>(visitor)); | ||||
| } | ||||
| 
 | ||||
| void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, | ||||
|                              const std::vector<bool> &exclude_mask) | ||||
| { | ||||
|     enum TrPos { posInside, posTouch, posOutside }; | ||||
| 
 | ||||
|     auto &faces       = mesh.its.indices; | ||||
|     auto &vertices    = mesh.its.vertices; | ||||
|     auto bb           = mesh.bounding_box(); | ||||
| 
 | ||||
|     bool use_exclude_mask = faces.size() == exclude_mask.size(); | ||||
|     auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) { | ||||
|         return use_exclude_mask && exclude_mask[face_id]; | ||||
|     }; | ||||
| 
 | ||||
|     // TODO: Parallel mode not working yet
 | ||||
|     using exec_policy = ccr_seq; | ||||
| 
 | ||||
|     // Info about the needed modifications on the input mesh.
 | ||||
|     struct MeshMods { | ||||
| 
 | ||||
|         // Just a thread safe wrapper for a vector of triangles.
 | ||||
|         struct { | ||||
|             std::vector<std::array<Vec3f, 3>> data; | ||||
|             exec_policy::SpinningMutex        mutex; | ||||
| 
 | ||||
|             void emplace_back(const std::array<Vec3f, 3> &pts) | ||||
|             { | ||||
|                 std::lock_guard lk{mutex}; | ||||
|                 data.emplace_back(pts); | ||||
|             } | ||||
| 
 | ||||
|             size_t size() const { return data.size(); } | ||||
|             const std::array<Vec3f, 3>& operator[](size_t idx) const | ||||
|             { | ||||
|                 return data[idx]; | ||||
|             } | ||||
| 
 | ||||
|         } new_triangles; | ||||
| 
 | ||||
|         // A vector of bool for all faces signaling if it needs to be removed
 | ||||
|         // or not.
 | ||||
|         std::vector<bool> to_remove; | ||||
| 
 | ||||
|         MeshMods(const TriangleMesh &mesh): | ||||
|             to_remove(mesh.its.indices.size(), false) {} | ||||
| 
 | ||||
|         // Number of triangles that need to be removed.
 | ||||
|         size_t to_remove_cnt() const | ||||
|         { | ||||
|             return std::accumulate(to_remove.begin(), to_remove.end(), size_t(0)); | ||||
|         } | ||||
| 
 | ||||
|     } mesh_mods{mesh}; | ||||
| 
 | ||||
|     // Must return true if further division of the face is needed.
 | ||||
|     auto divfn = [&interior, bb, &mesh_mods](const DivFace &f) { | ||||
|         BoundingBoxf3 facebb { f.verts.begin(), f.verts.end() }; | ||||
| 
 | ||||
|         // Face is certainly outside the cavity
 | ||||
|         if (! facebb.intersects(bb) && f.faceid != NEW_FACE) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         TriangleBubble bubble{facebb.center().cast<float>(), facebb.radius()}; | ||||
| 
 | ||||
|         double D = get_distance(bubble, interior); | ||||
|         double R = bubble.R * interior.voxel_scale; | ||||
| 
 | ||||
|         if (std::isnan(D)) // The distance cannot be measured, triangle too big
 | ||||
|             return true; | ||||
| 
 | ||||
|         // Distance of the bubble wall to the interior wall. Negative if the
 | ||||
|         // bubble is overlapping with the interior
 | ||||
|         double bubble_distance = D - R; | ||||
| 
 | ||||
|         // The face is crossing the interior or inside, it must be removed and
 | ||||
|         // parts of it re-added, that are outside the interior
 | ||||
|         if (bubble_distance < 0.) { | ||||
|             if (f.faceid != NEW_FACE) | ||||
|                 mesh_mods.to_remove[f.faceid] = true; | ||||
| 
 | ||||
|             if (f.parent != NEW_FACE) // Top parent needs to be removed as well
 | ||||
|                 mesh_mods.to_remove[f.parent] = true; | ||||
| 
 | ||||
|             // If the outside part is between the interior end the exterior
 | ||||
|             // (inside the wall being invisible), no further division is needed.
 | ||||
|             if ((R + D) < interior.thickness) | ||||
|                 return false; | ||||
| 
 | ||||
|             return true; | ||||
|         } else if (f.faceid == NEW_FACE) { | ||||
|             // New face completely outside needs to be re-added.
 | ||||
|             mesh_mods.new_triangles.emplace_back(f.verts); | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     }; | ||||
| 
 | ||||
|     interior.reset_accessor(); | ||||
| 
 | ||||
|     exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) { | ||||
|         const Vec3i &face = faces[face_idx]; | ||||
| 
 | ||||
|         // If the triangle is excluded, we need to keep it.
 | ||||
|         if (is_excluded(face_idx)) | ||||
|             return; | ||||
| 
 | ||||
|         std::array<Vec3f, 3> pts = | ||||
|             { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; | ||||
| 
 | ||||
|         BoundingBoxf3 facebb { pts.begin(), pts.end() }; | ||||
| 
 | ||||
|         // Face is certainly outside the cavity
 | ||||
|         if (! facebb.intersects(bb)) return; | ||||
| 
 | ||||
|         DivFace df{face, pts, long(face_idx)}; | ||||
| 
 | ||||
|         if (divfn(df)) | ||||
|             divide_triangle(df, divfn); | ||||
| 
 | ||||
|     }, exec_policy::max_concurreny()); | ||||
| 
 | ||||
|     auto new_faces = reserve_vector<Vec3i>(faces.size() + | ||||
|                                            mesh_mods.new_triangles.size()); | ||||
| 
 | ||||
|     for (size_t face_idx = 0; face_idx < faces.size(); ++face_idx) { | ||||
|         if (!mesh_mods.to_remove[face_idx]) | ||||
|             new_faces.emplace_back(faces[face_idx]); | ||||
|     } | ||||
| 
 | ||||
|     for(size_t i = 0; i < mesh_mods.new_triangles.size(); ++i) { | ||||
|         size_t o = vertices.size(); | ||||
|         vertices.emplace_back(mesh_mods.new_triangles[i][0]); | ||||
|         vertices.emplace_back(mesh_mods.new_triangles[i][1]); | ||||
|         vertices.emplace_back(mesh_mods.new_triangles[i][2]); | ||||
|         new_faces.emplace_back(int(o), int(o + 1), int(o + 2)); | ||||
|     } | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) | ||||
|             << "Trimming: " << mesh_mods.to_remove_cnt() << " triangles removed"; | ||||
|     BOOST_LOG_TRIVIAL(info) | ||||
|             << "Trimming: " << mesh_mods.new_triangles.size() << " triangles added"; | ||||
| 
 | ||||
|     faces.swap(new_faces); | ||||
|     new_faces = {}; | ||||
| 
 | ||||
|     mesh = TriangleMesh{mesh.its}; | ||||
|     mesh.repaired = true; | ||||
|     mesh.require_shared_vertices(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,6 +19,17 @@ struct HollowingConfig | |||
|     bool enabled = true; | ||||
| }; | ||||
| 
 | ||||
| enum HollowingFlags { hfRemoveInsideTriangles = 0x1 }; | ||||
| 
 | ||||
| // All data related to a generated mesh interior. Includes the 3D grid and mesh
 | ||||
| // and various metadata. No need to manipulate from outside.
 | ||||
| struct Interior; | ||||
| struct InteriorDeleter { void operator()(Interior *p); }; | ||||
| using  InteriorPtr = std::unique_ptr<Interior, InteriorDeleter>; | ||||
| 
 | ||||
| TriangleMesh &      get_mesh(Interior &interior); | ||||
| const TriangleMesh &get_mesh(const Interior &interior); | ||||
| 
 | ||||
| struct DrainHole | ||||
| { | ||||
|     Vec3f pos; | ||||
|  | @ -60,11 +71,26 @@ using DrainHoles = std::vector<DrainHole>; | |||
| 
 | ||||
| constexpr float HoleStickOutLength = 1.f; | ||||
| 
 | ||||
| std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh, | ||||
|                                                 const HollowingConfig &  = {}, | ||||
|                                                 const JobController &ctl = {}); | ||||
| InteriorPtr generate_interior(const TriangleMesh &mesh, | ||||
|                               const HollowingConfig &  = {}, | ||||
|                               const JobController &ctl = {}); | ||||
| 
 | ||||
| void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg); | ||||
| // Will do the hollowing
 | ||||
| void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0); | ||||
| 
 | ||||
| // Hollowing prepared in "interior", merge with original mesh
 | ||||
| void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0); | ||||
| 
 | ||||
| void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, | ||||
|                              const std::vector<bool> &exclude_mask = {}); | ||||
| 
 | ||||
| double get_distance(const Vec3f &p, const Interior &interior); | ||||
| 
 | ||||
| template<class T> | ||||
| FloatingOnly<T> get_distance(const Vec<3, T> &p, const Interior &interior) | ||||
| { | ||||
|     return get_distance(Vec3f(p.template cast<float>()), interior); | ||||
| } | ||||
| 
 | ||||
| void cut_drainholes(std::vector<ExPolygons> & obj_slices, | ||||
|                     const std::vector<float> &slicegrid, | ||||
|  |  | |||
|  | @ -1120,7 +1120,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const | |||
|         return this->pad_mesh(); | ||||
|     case slaposDrillHoles: | ||||
|         if (m_hollowing_data) | ||||
|             return m_hollowing_data->hollow_mesh_with_holes; | ||||
|             return get_mesh_to_print(); | ||||
|         [[fallthrough]]; | ||||
|     default: | ||||
|         return TriangleMesh(); | ||||
|  | @ -1149,8 +1149,9 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const | |||
| 
 | ||||
| const TriangleMesh &SLAPrintObject::hollowed_interior_mesh() const | ||||
| { | ||||
|     if (m_hollowing_data && m_config.hollowing_enable.getBool()) | ||||
|         return m_hollowing_data->interior; | ||||
|     if (m_hollowing_data && m_hollowing_data->interior && | ||||
|         m_config.hollowing_enable.getBool()) | ||||
|         return sla::get_mesh(*m_hollowing_data->interior); | ||||
|      | ||||
|     return EMPTY_MESH; | ||||
| } | ||||
|  |  | |||
|  | @ -85,6 +85,10 @@ public: | |||
|     // Get the mesh that is going to be printed with all the modifications
 | ||||
|     // like hollowing and drilled holes.
 | ||||
|     const TriangleMesh & get_mesh_to_print() const { | ||||
|         return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh(); | ||||
|     } | ||||
| 
 | ||||
|     const TriangleMesh & get_mesh_to_slice() const { | ||||
|         return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -327,9 +331,10 @@ private: | |||
|     class HollowingData | ||||
|     { | ||||
|     public: | ||||
|          | ||||
|         TriangleMesh interior; | ||||
| 
 | ||||
|         sla::InteriorPtr interior; | ||||
|         mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh
 | ||||
|         mutable TriangleMesh hollow_mesh_with_holes_trimmed; | ||||
|     }; | ||||
|      | ||||
|     std::unique_ptr<HollowingData> m_hollowing_data; | ||||
|  |  | |||
|  | @ -1,3 +1,5 @@ | |||
| #include <unordered_set> | ||||
| 
 | ||||
| #include <libslic3r/Exception.hpp> | ||||
| #include <libslic3r/SLAPrintSteps.hpp> | ||||
| #include <libslic3r/MeshBoolean.hpp> | ||||
|  | @ -84,17 +86,17 @@ SLAPrint::Steps::Steps(SLAPrint *print) | |||
| void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin o) | ||||
| { | ||||
|     if (o == soSupport && !po.m_supportdata) return; | ||||
|      | ||||
| 
 | ||||
|     auto faded_lyrs = size_t(po.m_config.faded_layers.getInt()); | ||||
|     double min_w = m_print->m_printer_config.elefant_foot_min_width.getFloat() / 2.; | ||||
|     double start_efc = m_print->m_printer_config.elefant_foot_compensation.getFloat(); | ||||
|      | ||||
| 
 | ||||
|     double doffs = m_print->m_printer_config.absolute_correction.getFloat(); | ||||
|     coord_t clpr_offs = scaled(doffs); | ||||
|      | ||||
| 
 | ||||
|     faded_lyrs = std::min(po.m_slice_index.size(), faded_lyrs); | ||||
|     size_t faded_lyrs_efc = std::max(size_t(1), faded_lyrs - 1); | ||||
|      | ||||
| 
 | ||||
|     auto efc = [start_efc, faded_lyrs_efc](size_t pos) { | ||||
|         return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc; | ||||
|     }; | ||||
|  | @ -102,13 +104,13 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin | |||
|     std::vector<ExPolygons> &slices = o == soModel ? | ||||
|                                           po.m_model_slices : | ||||
|                                           po.m_supportdata->support_slices; | ||||
|      | ||||
| 
 | ||||
|     if (clpr_offs != 0) for (size_t i = 0; i < po.m_slice_index.size(); ++i) { | ||||
|         size_t idx = po.m_slice_index[i].get_slice_idx(o); | ||||
|         if (idx < slices.size()) | ||||
|             slices[idx] = offset_ex(slices[idx], float(clpr_offs)); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     if (start_efc > 0.) for (size_t i = 0; i < faded_lyrs; ++i) { | ||||
|         size_t idx = po.m_slice_index[i].get_slice_idx(o); | ||||
|         if (idx < slices.size()) | ||||
|  | @ -124,28 +126,157 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) | |||
|         BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; | ||||
|         return; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; | ||||
| 
 | ||||
|     double thickness = po.m_config.hollowing_min_thickness.getFloat(); | ||||
|     double quality  = po.m_config.hollowing_quality.getFloat(); | ||||
|     double closing_d = po.m_config.hollowing_closing_distance.getFloat(); | ||||
|     sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; | ||||
|     auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg); | ||||
| 
 | ||||
|     if (meshptr->empty()) | ||||
|     sla::InteriorPtr interior = generate_interior(po.transformed_mesh(), hlwcfg); | ||||
| 
 | ||||
|     if (!interior || sla::get_mesh(*interior).empty()) | ||||
|         BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; | ||||
|     else { | ||||
|         po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); | ||||
|         po.m_hollowing_data->interior = *meshptr; | ||||
|         po.m_hollowing_data->interior = std::move(interior); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct FaceHash { | ||||
| 
 | ||||
|     // A hash is created for each triangle to be identifiable. The hash uses
 | ||||
|     // only the triangle's geometric traits, not the index in a particular mesh.
 | ||||
|     std::unordered_set<std::string> facehash; | ||||
| 
 | ||||
|     static std::string facekey(const Vec3i &face, | ||||
|                                const std::vector<Vec3f> &vertices) | ||||
|     { | ||||
|         // Scale to integer to avoid floating points
 | ||||
|         std::array<Vec<3, int64_t>, 3> pts = { | ||||
|             scaled<int64_t>(vertices[face(0)]), | ||||
|             scaled<int64_t>(vertices[face(1)]), | ||||
|             scaled<int64_t>(vertices[face(2)]) | ||||
|         }; | ||||
| 
 | ||||
|         // Get the first two sides of the triangle, do a cross product and move
 | ||||
|         // that vector to the center of the triangle. This encodes all
 | ||||
|         // information to identify an identical triangle at the same position.
 | ||||
|         Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2]; | ||||
|         Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3; | ||||
| 
 | ||||
|         // Return a concatenated string representation of the coordinates
 | ||||
|         return std::to_string(c(0)) + std::to_string(c(1)) + std::to_string(c(2)); | ||||
|     }; | ||||
| 
 | ||||
|     FaceHash(const indexed_triangle_set &its) | ||||
|     { | ||||
|         for (const Vec3i &face : its.indices) { | ||||
|             std::string keystr = facekey(face, its.vertices); | ||||
|             facehash.insert(keystr); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     bool find(const std::string &key) | ||||
|     { | ||||
|         auto it = facehash.find(key); | ||||
|         return it != facehash.end(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // Create exclude mask for triangle removal inside hollowed interiors.
 | ||||
| // This is necessary when the interior is already part of the mesh which was
 | ||||
| // drilled using CGAL mesh boolean operation. Excluded will be the triangles
 | ||||
| // originally part of the interior mesh and triangles that make up the drilled
 | ||||
| // hole walls.
 | ||||
| static std::vector<bool> create_exclude_mask( | ||||
|         const indexed_triangle_set &its, | ||||
|         const sla::Interior &interior, | ||||
|         const std::vector<sla::DrainHole> &holes) | ||||
| { | ||||
|     FaceHash interior_hash{sla::get_mesh(interior).its}; | ||||
| 
 | ||||
|     std::vector<bool> exclude_mask(its.indices.size(), false); | ||||
| 
 | ||||
|     std::vector< std::vector<size_t> > neighbor_index = | ||||
|             create_neighbor_index(its); | ||||
| 
 | ||||
|     auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face) | ||||
|     { | ||||
|         for (int i = 0; i < 3; ++i) { | ||||
|             const std::vector<size_t> &neighbors = neighbor_index[face(i)]; | ||||
|             for (size_t fi_n : neighbors) exclude_mask[fi_n] = true; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     for (size_t fi = 0; fi < its.indices.size(); ++fi) { | ||||
|         auto &face = its.indices[fi]; | ||||
| 
 | ||||
|         if (interior_hash.find(FaceHash::facekey(face, its.vertices))) { | ||||
|             exclude_mask[fi] = true; | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         if (exclude_mask[fi]) { | ||||
|             exclude_neighbors(face); | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         // Lets deal with the holes. All the triangles of a hole and all the
 | ||||
|         // neighbors of these triangles need to be kept. The neigbors were
 | ||||
|         // created by CGAL mesh boolean operation that modified the original
 | ||||
|         // interior inside the input mesh to contain the holes.
 | ||||
|         Vec3d tr_center = ( | ||||
|             its.vertices[face(0)] + | ||||
|             its.vertices[face(1)] + | ||||
|             its.vertices[face(2)] | ||||
|         ).cast<double>() / 3.; | ||||
| 
 | ||||
|         // If the center is more than half a mm inside the interior,
 | ||||
|         // it cannot possibly be part of a hole wall.
 | ||||
|         if (sla::get_distance(tr_center, interior) < -0.5) | ||||
|             continue; | ||||
| 
 | ||||
|         Vec3f U = its.vertices[face(1)] - its.vertices[face(0)]; | ||||
|         Vec3f V = its.vertices[face(2)] - its.vertices[face(0)]; | ||||
|         Vec3f C = U.cross(V); | ||||
|         Vec3f face_normal = C.normalized(); | ||||
| 
 | ||||
|         for (const sla::DrainHole &dh : holes) { | ||||
|             Vec3d dhpos = dh.pos.cast<double>(); | ||||
|             Vec3d dhend = dhpos + dh.normal.cast<double>() * dh.height; | ||||
| 
 | ||||
|             Linef3 holeaxis{dhpos, dhend}; | ||||
| 
 | ||||
|             double D_hole_center = line_alg::distance_to(holeaxis, tr_center); | ||||
|             double D_hole        = std::abs(D_hole_center - dh.radius); | ||||
|             float dot            = dh.normal.dot(face_normal); | ||||
| 
 | ||||
|             // Empiric tolerances for center distance and normals angle.
 | ||||
|             // For triangles that are part of a hole wall the angle of
 | ||||
|             // triangle normal and the hole axis is around 90 degrees,
 | ||||
|             // so the dot product is around zero.
 | ||||
|             double D_tol = dh.radius / sla::DrainHole::steps; | ||||
|             float normal_angle_tol = 1.f / sla::DrainHole::steps; | ||||
| 
 | ||||
|             if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) { | ||||
|                 exclude_mask[fi] = true; | ||||
|                 exclude_neighbors(face); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return exclude_mask; | ||||
| } | ||||
| 
 | ||||
| // Drill holes into the hollowed/original mesh.
 | ||||
| void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | ||||
| { | ||||
|     bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty(); | ||||
|     bool is_hollowed = (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()); | ||||
|     bool is_hollowed = | ||||
|         (po.m_hollowing_data && po.m_hollowing_data->interior && | ||||
|          !sla::get_mesh(*po.m_hollowing_data->interior).empty()); | ||||
| 
 | ||||
|     if (! is_hollowed && ! needs_drilling) { | ||||
|         // In this case we can dump any data that might have been
 | ||||
|  | @ -163,19 +294,25 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | |||
|     // holes that are no longer on the frontend.
 | ||||
|     TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; | ||||
|     hollowed_mesh = po.transformed_mesh(); | ||||
|     if (! po.m_hollowing_data->interior.empty()) { | ||||
|         hollowed_mesh.merge(po.m_hollowing_data->interior); | ||||
|         hollowed_mesh.require_shared_vertices(); | ||||
|     } | ||||
|     if (is_hollowed) | ||||
|         sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); | ||||
| 
 | ||||
|     TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; | ||||
| 
 | ||||
|     if (! needs_drilling) { | ||||
|         mesh_view = po.transformed_mesh(); | ||||
| 
 | ||||
|         if (is_hollowed) | ||||
|             sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, | ||||
|                              sla::hfRemoveInsideTriangles); | ||||
| 
 | ||||
|         BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; | ||||
|         return; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; | ||||
|     sla::DrainHoles drainholes = po.transformed_drainhole_points(); | ||||
|      | ||||
| 
 | ||||
|     std::uniform_real_distribution<float> dist(0., float(EPSILON)); | ||||
|     auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}); | ||||
|     for (sla::DrainHole holept : drainholes) { | ||||
|  | @ -187,15 +324,25 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | |||
|         auto cgal_m = MeshBoolean::cgal::triangle_mesh_to_cgal(m); | ||||
|         MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_m); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) | ||||
|         throw Slic3r::SlicingError(L("Too many overlapping holes.")); | ||||
|      | ||||
| 
 | ||||
|     auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); | ||||
|      | ||||
| 
 | ||||
|     try { | ||||
|         MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); | ||||
|         hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); | ||||
|         mesh_view = hollowed_mesh; | ||||
| 
 | ||||
|         if (is_hollowed) { | ||||
|             auto &interior = *po.m_hollowing_data->interior; | ||||
|             std::vector<bool> exclude_mask = | ||||
|                     create_exclude_mask(mesh_view.its, interior, drainholes); | ||||
| 
 | ||||
|             sla::remove_inside_triangles(mesh_view, interior, exclude_mask); | ||||
|         } | ||||
| 
 | ||||
|     } catch (const std::runtime_error &) { | ||||
|         throw Slic3r::SlicingError(L( | ||||
|             "Drilling holes into the mesh failed. " | ||||
|  | @ -212,11 +359,11 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | |||
| // of it. In any case, the model and the supports have to be sliced in the
 | ||||
| // same imaginary grid (the height vector argument to TriangleMeshSlicer).
 | ||||
| void SLAPrint::Steps::slice_model(SLAPrintObject &po) | ||||
| {    | ||||
|     const TriangleMesh &mesh = po.get_mesh_to_print(); | ||||
| { | ||||
|     const TriangleMesh &mesh = po.get_mesh_to_slice(); | ||||
| 
 | ||||
|     // We need to prepare the slice index...
 | ||||
|      | ||||
| 
 | ||||
|     double  lhd  = m_print->m_objects.front()->m_config.layer_height.getFloat(); | ||||
|     float   lh   = float(lhd); | ||||
|     coord_t lhs  = scaled(lhd); | ||||
|  | @ -226,43 +373,49 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) | |||
|     auto    minZf = float(minZ); | ||||
|     coord_t minZs = scaled(minZ); | ||||
|     coord_t maxZs = scaled(maxZ); | ||||
|      | ||||
| 
 | ||||
|     po.m_slice_index.clear(); | ||||
|      | ||||
| 
 | ||||
|     size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs); | ||||
|     po.m_slice_index.reserve(cap); | ||||
|      | ||||
| 
 | ||||
|     po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh); | ||||
|      | ||||
| 
 | ||||
|     for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) | ||||
|         po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh); | ||||
|      | ||||
| 
 | ||||
|     // Just get the first record that is from the model:
 | ||||
|     auto slindex_it = | ||||
|         po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z))); | ||||
|      | ||||
| 
 | ||||
|     if(slindex_it == po.m_slice_index.end()) | ||||
|         //TRN To be shown at the status bar on SLA slicing error.
 | ||||
|         throw Slic3r::RuntimeError( | ||||
|             L("Slicing had to be stopped due to an internal error: " | ||||
|               "Inconsistent slice index.")); | ||||
|      | ||||
| 
 | ||||
|     po.m_model_height_levels.clear(); | ||||
|     po.m_model_height_levels.reserve(po.m_slice_index.size()); | ||||
|     for(auto it = slindex_it; it != po.m_slice_index.end(); ++it) | ||||
|         po.m_model_height_levels.emplace_back(it->slice_level()); | ||||
|      | ||||
| 
 | ||||
|     TriangleMeshSlicer slicer(&mesh); | ||||
|      | ||||
| 
 | ||||
|     po.m_model_slices.clear(); | ||||
|     float closing_r  = float(po.config().slice_closing_radius.value); | ||||
|     auto  thr        = [this]() { m_print->throw_if_canceled(); }; | ||||
|     auto &slice_grid = po.m_model_height_levels; | ||||
|     slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr); | ||||
|      | ||||
|     if (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()) { | ||||
|         po.m_hollowing_data->interior.repair(true); | ||||
|         TriangleMeshSlicer interior_slicer(&po.m_hollowing_data->interior); | ||||
| 
 | ||||
|     sla::Interior *interior = po.m_hollowing_data ? | ||||
|                                   po.m_hollowing_data->interior.get() : | ||||
|                                   nullptr; | ||||
| 
 | ||||
|     if (interior && ! sla::get_mesh(*interior).empty()) { | ||||
|         TriangleMesh interiormesh = sla::get_mesh(*interior); | ||||
|         interiormesh.repaired = false; | ||||
|         interiormesh.repair(true); | ||||
|         TriangleMeshSlicer interior_slicer(&interiormesh); | ||||
|         std::vector<ExPolygons> interior_slices; | ||||
|         interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); | ||||
| 
 | ||||
|  | @ -273,17 +426,17 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) | |||
|                                   diff_ex(po.m_model_slices[i], slice); | ||||
|                            }); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     auto mit = slindex_it; | ||||
|     for (size_t id = 0; | ||||
|          id < po.m_model_slices.size() && mit != po.m_slice_index.end(); | ||||
|          id++) { | ||||
|         mit->set_model_slice_idx(po, id); ++mit; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     // We apply the printer correction offset here.
 | ||||
|     apply_printer_corrections(po, soModel); | ||||
|          | ||||
| 
 | ||||
|     if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool()) | ||||
|     { | ||||
|         po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); | ||||
|  | @ -296,22 +449,22 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) | |||
| { | ||||
|     // If supports are disabled, we can skip the model scan.
 | ||||
|     if(!po.m_config.supports_enable.getBool()) return; | ||||
|      | ||||
|     const TriangleMesh &mesh = po.get_mesh_to_print(); | ||||
|      | ||||
| 
 | ||||
|     const TriangleMesh &mesh = po.get_mesh_to_slice(); | ||||
| 
 | ||||
|     if (!po.m_supportdata) | ||||
|         po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); | ||||
|      | ||||
| 
 | ||||
|     const ModelObject& mo = *po.m_model_object; | ||||
|      | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << "Support point count " | ||||
|                              << mo.sla_support_points.size(); | ||||
|      | ||||
| 
 | ||||
|     // Unless the user modified the points or we already did the calculation,
 | ||||
|     // we will do the autoplacement. Otherwise we will just blindly copy the
 | ||||
|     // frontend data into the backend cache.
 | ||||
|     if (mo.sla_points_status != sla::PointsStatus::UserModified) { | ||||
|          | ||||
| 
 | ||||
|         // calculate heights of slices (slices are calculated already)
 | ||||
|         const std::vector<float>& heights = po.m_model_height_levels; | ||||
| 
 | ||||
|  | @ -319,27 +472,27 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) | |||
|         // calculated on slices, the algorithm then raycasts the points
 | ||||
|         // so they actually lie on the mesh.
 | ||||
| //        po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
 | ||||
|          | ||||
| 
 | ||||
|         throw_if_canceled(); | ||||
|         sla::SupportPointGenerator::Config config; | ||||
|         const SLAPrintObjectConfig& cfg = po.config(); | ||||
|          | ||||
| 
 | ||||
|         // the density config value is in percents:
 | ||||
|         config.density_relative = float(cfg.support_points_density_relative / 100.f); | ||||
|         config.minimal_distance = float(cfg.support_points_minimal_distance); | ||||
|         config.head_diameter    = float(cfg.support_head_front_diameter); | ||||
|          | ||||
| 
 | ||||
|         // scaling for the sub operations
 | ||||
|         double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0; | ||||
|         double init = current_status(); | ||||
|          | ||||
| 
 | ||||
|         auto statuscb = [this, d, init](unsigned st) | ||||
|         { | ||||
|             double current = init + st * d; | ||||
|             if(std::round(current_status()) < std::round(current)) | ||||
|                 report_status(current, OBJ_STEP_LABELS(slaposSupportPoints)); | ||||
|         }; | ||||
|          | ||||
| 
 | ||||
|         // Construction of this object does the calculation.
 | ||||
|         throw_if_canceled(); | ||||
|         sla::SupportPointGenerator auto_supports( | ||||
|  | @ -350,10 +503,10 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) | |||
|         const std::vector<sla::SupportPoint>& points = auto_supports.output(); | ||||
|         throw_if_canceled(); | ||||
|         po.m_supportdata->pts = points; | ||||
|          | ||||
| 
 | ||||
|         BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " | ||||
|                                  << po.m_supportdata->pts.size(); | ||||
|          | ||||
| 
 | ||||
|         // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
 | ||||
|         // the update status to GLGizmoSlaSupports
 | ||||
|         report_status(-1, L("Generating support points"), | ||||
|  | @ -368,9 +521,9 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) | |||
| void SLAPrint::Steps::support_tree(SLAPrintObject &po) | ||||
| { | ||||
|     if(!po.m_supportdata) return; | ||||
|      | ||||
| 
 | ||||
|     sla::PadConfig pcfg = make_pad_cfg(po.m_config); | ||||
|      | ||||
| 
 | ||||
|     if (pcfg.embed_object) | ||||
|         po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); | ||||
| 
 | ||||
|  | @ -380,15 +533,15 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) | |||
|         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());
 | ||||
|      | ||||
| 
 | ||||
|     // scaling for the sub operations
 | ||||
|     double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; | ||||
|     double init = current_status(); | ||||
|     sla::JobController ctl; | ||||
|      | ||||
| 
 | ||||
|     ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { | ||||
|         double current = init + st * d; | ||||
|         if (std::round(current_status()) < std::round(current)) | ||||
|  | @ -397,26 +550,26 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) | |||
|     }; | ||||
|     ctl.stopcondition = [this]() { return canceled(); }; | ||||
|     ctl.cancelfn = [this]() { throw_if_canceled(); }; | ||||
|      | ||||
| 
 | ||||
|     po.m_supportdata->create_support_tree(ctl); | ||||
|      | ||||
| 
 | ||||
|     if (!po.m_config.supports_enable.getBool()) return; | ||||
|      | ||||
| 
 | ||||
|     throw_if_canceled(); | ||||
|      | ||||
| 
 | ||||
|     // Create the unified mesh
 | ||||
|     auto rc = SlicingStatus::RELOAD_SCENE; | ||||
|      | ||||
| 
 | ||||
|     // This is to prevent "Done." being displayed during merged_mesh()
 | ||||
|     report_status(-1, L("Visualizing supports")); | ||||
|      | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << "Processed support point count " | ||||
|                              << po.m_supportdata->pts.size(); | ||||
|      | ||||
| 
 | ||||
|     // Check the mesh for later troubleshooting.
 | ||||
|     if(po.support_mesh().empty()) | ||||
|         BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; | ||||
|      | ||||
| 
 | ||||
|     report_status(-1, L("Visualizing supports"), rc); | ||||
| } | ||||
| 
 | ||||
|  | @ -424,15 +577,15 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { | |||
|     // this step can only go after the support tree has been created
 | ||||
|     // and before the supports had been sliced. (or the slicing has to be
 | ||||
|     // repeated)
 | ||||
|      | ||||
| 
 | ||||
|     if(po.m_config.pad_enable.getBool()) { | ||||
|         // Get the distilled pad configuration from the config
 | ||||
|         sla::PadConfig pcfg = make_pad_cfg(po.m_config); | ||||
|          | ||||
| 
 | ||||
|         ExPolygons bp; // This will store the base plate of the pad.
 | ||||
|         double   pad_h             = pcfg.full_height(); | ||||
|         const TriangleMesh &trmesh = po.transformed_mesh(); | ||||
|          | ||||
| 
 | ||||
|         if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { | ||||
|             // No support (thus no elevation) or zero elevation mode
 | ||||
|             // we sometimes call it "builtin pad" is enabled so we will
 | ||||
|  | @ -442,19 +595,19 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { | |||
|                                float(po.m_config.layer_height.getFloat()), | ||||
|                                [this](){ throw_if_canceled(); }); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); | ||||
|         auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad); | ||||
|          | ||||
| 
 | ||||
|         if (!validate_pad(pad_mesh, pcfg)) | ||||
|             throw Slic3r::SlicingError( | ||||
|                     L("No pad can be generated for this model with the " | ||||
|                       "current configuration")); | ||||
|          | ||||
| 
 | ||||
|     } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { | ||||
|         po.m_supportdata->support_tree_ptr->remove_pad(); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     throw_if_canceled(); | ||||
|     report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE); | ||||
| } | ||||
|  | @ -464,25 +617,25 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { | |||
| // be part of the slices)
 | ||||
| void SLAPrint::Steps::slice_supports(SLAPrintObject &po) { | ||||
|     auto& sd = po.m_supportdata; | ||||
|      | ||||
| 
 | ||||
|     if(sd) sd->support_slices.clear(); | ||||
|      | ||||
| 
 | ||||
|     // Don't bother if no supports and no pad is present.
 | ||||
|     if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool()) | ||||
|         return; | ||||
|      | ||||
| 
 | ||||
|     if(sd && sd->support_tree_ptr) { | ||||
|         auto heights = reserve_vector<float>(po.m_slice_index.size()); | ||||
|          | ||||
| 
 | ||||
|         for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level()); | ||||
| 
 | ||||
|         sd->support_slices = sd->support_tree_ptr->slice( | ||||
|             heights, float(po.config().slice_closing_radius.value)); | ||||
|     } | ||||
|      | ||||
|     for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i)  | ||||
| 
 | ||||
|     for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) | ||||
|         po.m_slice_index[i].set_support_slice_idx(po, i); | ||||
|      | ||||
| 
 | ||||
|     apply_printer_corrections(po, soSupport); | ||||
| 
 | ||||
|     // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
 | ||||
|  | @ -497,37 +650,37 @@ using ClipperPolygons = std::vector<ClipperPolygon>; | |||
| static ClipperPolygons polyunion(const ClipperPolygons &subjects) | ||||
| { | ||||
|     ClipperLib::Clipper clipper; | ||||
|      | ||||
| 
 | ||||
|     bool closed = true; | ||||
|      | ||||
| 
 | ||||
|     for(auto& path : subjects) { | ||||
|         clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); | ||||
|         clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     auto mode = ClipperLib::pftPositive; | ||||
|      | ||||
| 
 | ||||
|     return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode); | ||||
| } | ||||
| 
 | ||||
| static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips) | ||||
| { | ||||
|     ClipperLib::Clipper clipper; | ||||
|      | ||||
| 
 | ||||
|     bool closed = true; | ||||
|      | ||||
| 
 | ||||
|     for(auto& path : subjects) { | ||||
|         clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); | ||||
|         clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     for(auto& path : clips) { | ||||
|         clipper.AddPath(path.Contour, ClipperLib::ptClip, closed); | ||||
|         clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     auto mode = ClipperLib::pftPositive; | ||||
|      | ||||
| 
 | ||||
|     return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode); | ||||
| } | ||||
| 
 | ||||
|  | @ -535,28 +688,28 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo | |||
| static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o) | ||||
| { | ||||
|     namespace sl = libnest2d::sl; | ||||
|      | ||||
| 
 | ||||
|     if (!record.print_obj()) return {}; | ||||
|      | ||||
| 
 | ||||
|     ClipperPolygons polygons; | ||||
|     auto &input_polygons = record.get_slice(o); | ||||
|     auto &instances = record.print_obj()->instances(); | ||||
|     bool is_lefthanded = record.print_obj()->is_left_handed(); | ||||
|     polygons.reserve(input_polygons.size() * instances.size()); | ||||
|      | ||||
| 
 | ||||
|     for (const ExPolygon& polygon : input_polygons) { | ||||
|         if(polygon.contour.empty()) continue; | ||||
|          | ||||
| 
 | ||||
|         for (size_t i = 0; i < instances.size(); ++i) | ||||
|         { | ||||
|             ClipperPolygon poly; | ||||
|              | ||||
| 
 | ||||
|             // We need to reverse if is_lefthanded is true but
 | ||||
|             bool needreverse = is_lefthanded; | ||||
|              | ||||
| 
 | ||||
|             // should be a move
 | ||||
|             poly.Contour.reserve(polygon.contour.size() + 1); | ||||
|              | ||||
| 
 | ||||
|             auto& cntr = polygon.contour.points; | ||||
|             if(needreverse) | ||||
|                 for(auto it = cntr.rbegin(); it != cntr.rend(); ++it) | ||||
|  | @ -564,12 +717,12 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o | |||
|             else | ||||
|                 for(auto& p : cntr) | ||||
|                     poly.Contour.emplace_back(p.x(), p.y()); | ||||
|              | ||||
| 
 | ||||
|             for(auto& h : polygon.holes) { | ||||
|                 poly.Holes.emplace_back(); | ||||
|                 auto& hole = poly.Holes.back(); | ||||
|                 hole.reserve(h.points.size() + 1); | ||||
|                  | ||||
| 
 | ||||
|                 if(needreverse) | ||||
|                     for(auto it = h.points.rbegin(); it != h.points.rend(); ++it) | ||||
|                         hole.emplace_back(it->x(), it->y()); | ||||
|  | @ -577,42 +730,42 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o | |||
|                     for(auto& p : h.points) | ||||
|                         hole.emplace_back(p.x(), p.y()); | ||||
|             } | ||||
|              | ||||
| 
 | ||||
|             if(is_lefthanded) { | ||||
|                 for(auto& p : poly.Contour) p.X = -p.X; | ||||
|                 for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X; | ||||
|             } | ||||
|              | ||||
| 
 | ||||
|             sl::rotate(poly, double(instances[i].rotation)); | ||||
|             sl::translate(poly, ClipperPoint{instances[i].shift.x(), | ||||
|                                              instances[i].shift.y()}); | ||||
|              | ||||
| 
 | ||||
|             polygons.emplace_back(std::move(poly)); | ||||
|         } | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     return polygons; | ||||
| } | ||||
| 
 | ||||
| void SLAPrint::Steps::initialize_printer_input() | ||||
| { | ||||
|     auto &printer_input = m_print->m_printer_input; | ||||
|      | ||||
| 
 | ||||
|     // clear the rasterizer input
 | ||||
|     printer_input.clear(); | ||||
|      | ||||
| 
 | ||||
|     size_t mx = 0; | ||||
|     for(SLAPrintObject * o : m_print->m_objects) { | ||||
|         if(auto m = o->get_slice_index().size() > mx) mx = m; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     printer_input.reserve(mx); | ||||
|      | ||||
| 
 | ||||
|     auto eps = coord_t(SCALED_EPSILON); | ||||
|      | ||||
| 
 | ||||
|     for(SLAPrintObject * o : m_print->m_objects) { | ||||
|         coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs; | ||||
|          | ||||
| 
 | ||||
|         for(const SliceRecord& slicerecord : o->get_slice_index()) { | ||||
|             if (!slicerecord.is_valid()) | ||||
|                 throw Slic3r::SlicingError( | ||||
|  | @ -621,7 +774,7 @@ void SLAPrint::Steps::initialize_printer_input() | |||
|                       "objects printable.")); | ||||
| 
 | ||||
|             coord_t lvlid = slicerecord.print_level() - gndlvl; | ||||
|              | ||||
| 
 | ||||
|             // Neat trick to round the layer levels to the grid.
 | ||||
|             lvlid = eps * (lvlid / eps); | ||||
| 
 | ||||
|  | @ -631,8 +784,8 @@ void SLAPrint::Steps::initialize_printer_input() | |||
| 
 | ||||
|             if(it == printer_input.end() || it->level() != lvlid) | ||||
|                 it = printer_input.insert(it, PrintLayer(lvlid)); | ||||
|              | ||||
|              | ||||
| 
 | ||||
| 
 | ||||
|             it->add(slicerecord); | ||||
|         } | ||||
|     } | ||||
|  | @ -641,53 +794,53 @@ void SLAPrint::Steps::initialize_printer_input() | |||
| // Merging the slices from all the print objects into one slice grid and
 | ||||
| // calculating print statistics from the merge result.
 | ||||
| void SLAPrint::Steps::merge_slices_and_eval_stats() { | ||||
|      | ||||
| 
 | ||||
|     initialize_printer_input(); | ||||
|      | ||||
| 
 | ||||
|     auto &print_statistics = m_print->m_print_statistics; | ||||
|     auto &printer_config   = m_print->m_printer_config; | ||||
|     auto &material_config  = m_print->m_material_config; | ||||
|     auto &printer_input    = m_print->m_printer_input; | ||||
|      | ||||
| 
 | ||||
|     print_statistics.clear(); | ||||
|      | ||||
| 
 | ||||
|     // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
 | ||||
|     auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); }; | ||||
|      | ||||
| 
 | ||||
|     const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
 | ||||
|     const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0;
 | ||||
|     const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0;
 | ||||
|      | ||||
| 
 | ||||
|     const double init_exp_time = material_config.initial_exposure_time.getFloat(); | ||||
|     const double exp_time      = material_config.exposure_time.getFloat(); | ||||
|      | ||||
| 
 | ||||
|     const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20]
 | ||||
|      | ||||
| 
 | ||||
|     const auto width          = scaled<double>(printer_config.display_width.getFloat()); | ||||
|     const auto height         = scaled<double>(printer_config.display_height.getFloat()); | ||||
|     const double display_area = width*height; | ||||
|      | ||||
| 
 | ||||
|     double supports_volume(0.0); | ||||
|     double models_volume(0.0); | ||||
|      | ||||
| 
 | ||||
|     double estim_time(0.0); | ||||
|     std::vector<double> layers_times; | ||||
|     layers_times.reserve(printer_input.size()); | ||||
|      | ||||
| 
 | ||||
|     size_t slow_layers = 0; | ||||
|     size_t fast_layers = 0; | ||||
|      | ||||
| 
 | ||||
|     const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1); | ||||
|     double fade_layer_time = init_exp_time; | ||||
|      | ||||
| 
 | ||||
|     sla::ccr::SpinningMutex mutex; | ||||
|     using Lock = std::lock_guard<sla::ccr::SpinningMutex>; | ||||
|      | ||||
| 
 | ||||
|     // Going to parallel:
 | ||||
|     auto printlayerfn = [this, | ||||
|             // functions and read only vars
 | ||||
|             areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, | ||||
|              | ||||
| 
 | ||||
|             // write vars
 | ||||
|             &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, | ||||
|             &fast_layers, &fade_layer_time, &layers_times](size_t sliced_layer_cnt) | ||||
|  | @ -696,87 +849,87 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { | |||
| 
 | ||||
|         // vector of slice record references
 | ||||
|         auto& slicerecord_references = layer.slices(); | ||||
|          | ||||
| 
 | ||||
|         if(slicerecord_references.empty()) return; | ||||
|          | ||||
| 
 | ||||
|         // Layer height should match for all object slices for a given level.
 | ||||
|         const auto l_height = double(slicerecord_references.front().get().layer_height()); | ||||
|          | ||||
| 
 | ||||
|         // Calculation of the consumed material
 | ||||
|          | ||||
| 
 | ||||
|         ClipperPolygons model_polygons; | ||||
|         ClipperPolygons supports_polygons; | ||||
|          | ||||
| 
 | ||||
|         size_t c = std::accumulate(layer.slices().begin(), | ||||
|                                    layer.slices().end(), | ||||
|                                    size_t(0), | ||||
|                                    [](size_t a, const SliceRecord &sr) { | ||||
|             return a + sr.get_slice(soModel).size(); | ||||
|         }); | ||||
|          | ||||
| 
 | ||||
|         model_polygons.reserve(c); | ||||
|          | ||||
| 
 | ||||
|         c = std::accumulate(layer.slices().begin(), | ||||
|                             layer.slices().end(), | ||||
|                             size_t(0), | ||||
|                             [](size_t a, const SliceRecord &sr) { | ||||
|             return a + sr.get_slice(soModel).size(); | ||||
|         }); | ||||
|          | ||||
| 
 | ||||
|         supports_polygons.reserve(c); | ||||
|          | ||||
| 
 | ||||
|         for(const SliceRecord& record : layer.slices()) { | ||||
|              | ||||
| 
 | ||||
|             ClipperPolygons modelslices = get_all_polygons(record, soModel); | ||||
|             for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp)); | ||||
|          | ||||
| 
 | ||||
|             ClipperPolygons supportslices = get_all_polygons(record, soSupport); | ||||
|             for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp)); | ||||
|          | ||||
| 
 | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         model_polygons = polyunion(model_polygons); | ||||
|         double layer_model_area = 0; | ||||
|         for (const ClipperPolygon& polygon : model_polygons) | ||||
|             layer_model_area += areafn(polygon); | ||||
|          | ||||
| 
 | ||||
|         if (layer_model_area < 0 || layer_model_area > 0) { | ||||
|             Lock lck(mutex); models_volume += layer_model_area * l_height; | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         if(!supports_polygons.empty()) { | ||||
|             if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons); | ||||
|             else supports_polygons = polydiff(supports_polygons, model_polygons); | ||||
|             // allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
 | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         double layer_support_area = 0; | ||||
|         for (const ClipperPolygon& polygon : supports_polygons) | ||||
|             layer_support_area += areafn(polygon); | ||||
|          | ||||
| 
 | ||||
|         if (layer_support_area < 0 || layer_support_area > 0) { | ||||
|             Lock lck(mutex); supports_volume += layer_support_area * l_height; | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         // Here we can save the expensively calculated polygons for printing
 | ||||
|         ClipperPolygons trslices; | ||||
|         trslices.reserve(model_polygons.size() + supports_polygons.size()); | ||||
|         for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); | ||||
|         for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); | ||||
|          | ||||
| 
 | ||||
|         layer.transformed_slices(polyunion(trslices)); | ||||
|          | ||||
| 
 | ||||
|         // Calculation of the slow and fast layers to the future controlling those values on FW
 | ||||
|          | ||||
| 
 | ||||
|         const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill; | ||||
|         const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt; | ||||
|          | ||||
| 
 | ||||
|         { Lock lck(mutex); | ||||
|             if (is_fast_layer) | ||||
|                 fast_layers++; | ||||
|             else | ||||
|                 slow_layers++; | ||||
|              | ||||
| 
 | ||||
|             // Calculation of the printing time
 | ||||
| 
 | ||||
|             double layer_times = 0.0; | ||||
|  | @ -794,15 +947,15 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { | |||
|             estim_time += layer_times; | ||||
|         } | ||||
|     }; | ||||
|      | ||||
| 
 | ||||
|     // sequential version for debugging:
 | ||||
|     // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
 | ||||
|     sla::ccr::for_each(size_t(0), printer_input.size(), printlayerfn); | ||||
|      | ||||
| 
 | ||||
|     auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; | ||||
|     print_statistics.support_used_material = supports_volume * SCALING2; | ||||
|     print_statistics.objects_used_material = models_volume  * SCALING2; | ||||
|      | ||||
| 
 | ||||
|     // Estimated printing time
 | ||||
|     // A layers count o the highest object
 | ||||
|     if (printer_input.size() == 0) | ||||
|  | @ -811,10 +964,10 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { | |||
|         print_statistics.estimated_print_time = estim_time; | ||||
|         print_statistics.layers_times = layers_times; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     print_statistics.fast_layers_count = fast_layers; | ||||
|     print_statistics.slow_layers_count = slow_layers; | ||||
|      | ||||
| 
 | ||||
|     report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); | ||||
| } | ||||
| 
 | ||||
|  | @ -822,23 +975,23 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { | |||
| void SLAPrint::Steps::rasterize() | ||||
| { | ||||
|     if(canceled() || !m_print->m_printer) return; | ||||
|      | ||||
| 
 | ||||
|     // coefficient to map the rasterization state (0-99) to the allocated
 | ||||
|     // portion (slot) of the process state
 | ||||
|     double sd = (100 - max_objstatus) / 100.0; | ||||
|      | ||||
| 
 | ||||
|     // slot is the portion of 100% that is realted to rasterization
 | ||||
|     unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; | ||||
|      | ||||
| 
 | ||||
|     // pst: previous state
 | ||||
|     double pst = current_status(); | ||||
|      | ||||
| 
 | ||||
|     double increment = (slot * sd) / m_print->m_printer_input.size(); | ||||
|     double dstatus = current_status(); | ||||
|      | ||||
| 
 | ||||
|     sla::ccr::SpinningMutex slck; | ||||
|     using Lock = std::lock_guard<sla::ccr::SpinningMutex>; | ||||
|      | ||||
| 
 | ||||
|     // procedure to process one height level. This will run in parallel
 | ||||
|     auto lvlfn = | ||||
|         [this, &slck, increment, &dstatus, &pst] | ||||
|  | @ -846,10 +999,10 @@ void SLAPrint::Steps::rasterize() | |||
|     { | ||||
|         PrintLayer& printlayer = m_print->m_printer_input[idx]; | ||||
|         if(canceled()) return; | ||||
|          | ||||
| 
 | ||||
|         for (const ClipperLib::Polygon& poly : printlayer.transformed_slices()) | ||||
|             raster.draw(poly); | ||||
|          | ||||
| 
 | ||||
|         // Status indication guarded with the spinlock
 | ||||
|         { | ||||
|             Lock lck(slck); | ||||
|  | @ -861,10 +1014,10 @@ void SLAPrint::Steps::rasterize() | |||
|             } | ||||
|         } | ||||
|     }; | ||||
|      | ||||
| 
 | ||||
|     // last minute escape
 | ||||
|     if(canceled()) return; | ||||
|      | ||||
| 
 | ||||
|     // Print all the layers in parallel
 | ||||
|     m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn); | ||||
| } | ||||
|  |  | |||
|  | @ -112,8 +112,10 @@ SlicingParameters SlicingParameters::create_from_config( | |||
| 
 | ||||
|     if (! soluble_interface) { | ||||
|         params.gap_raft_object    = object_config.raft_contact_distance.value; | ||||
|         params.gap_object_support = object_config.support_material_contact_distance.value; | ||||
|         params.gap_object_support = object_config.support_material_bottom_contact_distance.value; | ||||
|         params.gap_support_object = object_config.support_material_contact_distance.value; | ||||
|         if (params.gap_object_support <= 0) | ||||
|             params.gap_object_support = params.gap_support_object; | ||||
|     } | ||||
| 
 | ||||
|     if (params.base_raft_layers > 0) { | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ class DynamicPrintConfig; | |||
| // (using a normal flow over a soluble support, using a bridging flow over a non-soluble support).
 | ||||
| struct SlicingParameters | ||||
| { | ||||
| 	SlicingParameters() { memset(this, 0, sizeof(SlicingParameters)); } | ||||
| 	SlicingParameters() = default; | ||||
| 
 | ||||
|     static SlicingParameters create_from_config( | ||||
|         const PrintConfig       &print_config,  | ||||
|  | @ -44,58 +44,58 @@ struct SlicingParameters | |||
|     // Height of the object to be printed. This value does not contain the raft height.
 | ||||
|     coordf_t    object_print_z_height() const { return object_print_z_max - object_print_z_min; } | ||||
| 
 | ||||
|     bool        valid; | ||||
|     bool        valid { false }; | ||||
| 
 | ||||
|     // Number of raft layers.
 | ||||
|     size_t      base_raft_layers; | ||||
|     size_t      base_raft_layers { 0 }; | ||||
|     // Number of interface layers including the contact layer.
 | ||||
|     size_t      interface_raft_layers; | ||||
|     size_t      interface_raft_layers { 0 }; | ||||
| 
 | ||||
|     // Layer heights of the raft (base, interface and a contact layer).
 | ||||
|     coordf_t    base_raft_layer_height; | ||||
|     coordf_t    interface_raft_layer_height; | ||||
|     coordf_t    contact_raft_layer_height; | ||||
|     coordf_t    base_raft_layer_height { 0 }; | ||||
|     coordf_t    interface_raft_layer_height { 0 }; | ||||
|     coordf_t    contact_raft_layer_height { 0 }; | ||||
| 
 | ||||
| 	// The regular layer height, applied for all but the first layer, if not overridden by layer ranges
 | ||||
| 	// or by the variable layer thickness table.
 | ||||
|     coordf_t    layer_height; | ||||
|     coordf_t    layer_height { 0 }; | ||||
|     // Minimum / maximum layer height, to be used for the automatic adaptive layer height algorithm,
 | ||||
|     // or by an interactive layer height editor.
 | ||||
|     coordf_t    min_layer_height; | ||||
|     coordf_t    max_layer_height; | ||||
|     coordf_t    max_suport_layer_height; | ||||
|     coordf_t    min_layer_height { 0 }; | ||||
|     coordf_t    max_layer_height { 0 }; | ||||
|     coordf_t    max_suport_layer_height { 0 }; | ||||
| 
 | ||||
|     // First layer height of the print, this may be used for the first layer of the raft
 | ||||
|     // or for the first layer of the print.
 | ||||
|     coordf_t    first_print_layer_height; | ||||
|     coordf_t    first_print_layer_height { 0 }; | ||||
| 
 | ||||
|     // Thickness of the first layer. This is either the first print layer thickness if printed without a raft,
 | ||||
|     // or a bridging flow thickness if printed over a non-soluble raft,
 | ||||
|     // or a normal layer height if printed over a soluble raft.
 | ||||
|     coordf_t    first_object_layer_height; | ||||
|     coordf_t    first_object_layer_height { 0 }; | ||||
| 
 | ||||
|     // If the object is printed over a non-soluble raft, the first layer may be printed with a briding flow.
 | ||||
|     bool 		first_object_layer_bridging; | ||||
|     bool 		first_object_layer_bridging { false }; | ||||
| 
 | ||||
|     // Soluble interface? (PLA soluble in water, HIPS soluble in lemonen)
 | ||||
|     // otherwise the interface must be broken off.
 | ||||
|     bool        soluble_interface; | ||||
|     bool        soluble_interface { false }; | ||||
|     // Gap when placing object over raft.
 | ||||
|     coordf_t    gap_raft_object; | ||||
|     coordf_t    gap_raft_object { 0 }; | ||||
|     // Gap when placing support over object.
 | ||||
|     coordf_t    gap_object_support; | ||||
|     coordf_t    gap_object_support { 0 }; | ||||
|     // Gap when placing object over support.
 | ||||
|     coordf_t    gap_support_object; | ||||
|     coordf_t    gap_support_object { 0 }; | ||||
| 
 | ||||
|     // Bottom and top of the printed object.
 | ||||
|     // If printed without a raft, object_print_z_min = 0 and object_print_z_max = object height.
 | ||||
|     // Otherwise object_print_z_min is equal to the raft height.
 | ||||
|     coordf_t    raft_base_top_z; | ||||
|     coordf_t    raft_interface_top_z; | ||||
|     coordf_t    raft_contact_top_z; | ||||
|     coordf_t    raft_base_top_z { 0 }; | ||||
|     coordf_t    raft_interface_top_z { 0 }; | ||||
|     coordf_t    raft_contact_top_z { 0 }; | ||||
|     // In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer.
 | ||||
|     coordf_t 	object_print_z_min; | ||||
|     coordf_t 	object_print_z_max; | ||||
|     coordf_t 	object_print_z_min { 0 }; | ||||
|     coordf_t 	object_print_z_max { 0 }; | ||||
| }; | ||||
| static_assert(IsTriviallyCopyable<SlicingParameters>::value, "SlicingParameters class is not POD (and it should be - see constructor)."); | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| #include <cmath> | ||||
| #include <memory> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/container/static_vector.hpp> | ||||
| 
 | ||||
| #include <tbb/parallel_for.h> | ||||
| #include <tbb/atomic.h> | ||||
|  | @ -334,7 +335,7 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object | |||
|     for (auto lh : m_print_config->min_layer_height.values) | ||||
|         m_support_layer_height_min = std::min(m_support_layer_height_min, std::max(0.01, lh)); | ||||
| 
 | ||||
|     if (m_object_config->support_material_interface_layers.value == 0) { | ||||
|     if (m_slicing_params.soluble_interface) { | ||||
|         // No interface layers allowed, print everything with the base support pattern.
 | ||||
|         m_support_material_interface_flow = m_support_material_flow; | ||||
|     } | ||||
|  | @ -342,11 +343,21 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object | |||
|     // Evaluate the XY gap between the object outer perimeters and the support structures.
 | ||||
|     // Evaluate the XY gap between the object outer perimeters and the support structures.
 | ||||
|     coordf_t external_perimeter_width = 0.; | ||||
|     size_t   num_nonempty_regions = 0; | ||||
|     coordf_t bridge_flow_ratio = 0; | ||||
|     for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) | ||||
|         if (! object->region_volumes[region_id].empty()) | ||||
|             external_perimeter_width = std::max(external_perimeter_width, | ||||
|                 (coordf_t)object->print()->get_region(region_id)->flow(frExternalPerimeter, slicing_params.layer_height, false, false, -1, *object).width); | ||||
|         if (! object->region_volumes[region_id].empty()) { | ||||
|             ++ num_nonempty_regions; | ||||
|             const PrintRegion ®ion = *object->print()->get_region(region_id); | ||||
|             external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width())); | ||||
|             bridge_flow_ratio += region.config().bridge_flow_ratio; | ||||
|         } | ||||
|     m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); | ||||
|     bridge_flow_ratio /= num_nonempty_regions; | ||||
| 
 | ||||
|     m_support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ? | ||||
|         m_support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : | ||||
|         Flow::bridging_flow(bridge_flow_ratio * m_support_material_interface_flow.nozzle_diameter(), m_support_material_interface_flow.nozzle_diameter()); | ||||
| 
 | ||||
|     m_can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value; | ||||
|     if (! m_can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) { | ||||
|  | @ -387,14 +398,6 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr | |||
|     dst.insert(dst.end(), src.begin(), src.end()); | ||||
| } | ||||
| 
 | ||||
| // Compare layers lexicographically.
 | ||||
| struct MyLayersPtrCompare | ||||
| { | ||||
|     bool operator()(const PrintObjectSupportMaterial::MyLayer* layer1, const PrintObjectSupportMaterial::MyLayer* layer2) const { | ||||
|         return *layer1 < *layer2; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| void PrintObjectSupportMaterial::generate(PrintObject &object) | ||||
| { | ||||
|     BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; | ||||
|  | @ -457,10 +460,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) | |||
|     MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( | ||||
|         object, bottom_contacts, top_contacts, layer_storage); | ||||
| 
 | ||||
| //    this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy);
 | ||||
|     this->trim_support_layers_by_object(object, top_contacts,  | ||||
|         m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,  | ||||
|         m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); | ||||
|     this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy); | ||||
| 
 | ||||
| #ifdef SLIC3R_DEBUG | ||||
|     for (const MyLayer *layer : top_contacts) | ||||
|  | @ -542,7 +542,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) | |||
|     layers_append(layers_sorted, interface_layers); | ||||
|     layers_append(layers_sorted, base_interface_layers); | ||||
|     // Sort the layers lexicographically by a raising print_z and a decreasing height.
 | ||||
|     std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare()); | ||||
|     std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); | ||||
|     int layer_id = 0; | ||||
|     assert(object.support_layers().empty()); | ||||
|     for (size_t i = 0; i < layers_sorted.size();) { | ||||
|  | @ -1231,8 +1231,8 @@ namespace SupportMaterialInternal { | |||
|             // since we're dealing with bridges, we can't assume width is larger than spacing,
 | ||||
|             // so we take the largest value and also apply safety offset to be ensure no gaps
 | ||||
|             // are left in between
 | ||||
|             Flow bridge_flow = layerm->flow(frPerimeter, true); | ||||
|             float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); | ||||
|             Flow perimeter_bridge_flow = layerm->bridging_flow(frPerimeter); | ||||
|             float w = float(std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())); | ||||
|             for (Polyline &polyline : overhang_perimeters) | ||||
|                 if (polyline.is_straight()) { | ||||
|                     // This is a bridge 
 | ||||
|  | @ -1542,16 +1542,20 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ | |||
|                                 } | ||||
|                             } | ||||
|                             // Offset the contact polygons outside.
 | ||||
| #if 0 | ||||
|                             for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { | ||||
|                                 diff_polygons = diff( | ||||
|                                     offset( | ||||
|                                         diff_polygons, | ||||
|                                         SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS, | ||||
|                                         scaled<float>(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS), | ||||
|                                         ClipperLib::jtRound, | ||||
|                                         // round mitter limit
 | ||||
|                                         scale_(0.05)), | ||||
|                                     slices_margin_cached); | ||||
|                             } | ||||
| #else | ||||
|                             diff_polygons = diff(diff_polygons, slices_margin_cached); | ||||
| #endif | ||||
|                         } | ||||
|                         polygons_append(contact_polygons, diff_polygons); | ||||
|                     } // for each layer.region
 | ||||
|  | @ -1571,11 +1575,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ | |||
|                     } else if (m_slicing_params.soluble_interface) { | ||||
|                         // Align the contact surface height with a layer immediately below the supported layer.
 | ||||
|                         // Interface layer will be synchronized with the object.
 | ||||
|                         new_layer.print_z  = layer.print_z - layer.height; | ||||
|                         new_layer.print_z  = layer.bottom_z(); | ||||
|                         new_layer.height   = object.layers()[layer_id - 1]->height; | ||||
|                         new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z; | ||||
|                     } else { | ||||
|                         new_layer.print_z  = layer.print_z - layer.height - m_object_config->support_material_contact_distance; | ||||
|                         new_layer.print_z  = layer.bottom_z() - m_slicing_params.gap_object_support; | ||||
|                         new_layer.bottom_z = new_layer.print_z; | ||||
|                         new_layer.height   = 0.; | ||||
|                         // Ignore this contact area if it's too low.
 | ||||
|  | @ -1597,12 +1601,12 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ | |||
| 
 | ||||
|                         // Contact layer will be printed with a normal flow, but
 | ||||
|                         // it will support layers printed with a bridging flow.
 | ||||
|                         if (SupportMaterialInternal::has_bridging_extrusions(layer)) { | ||||
|                         if (m_object_config->thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)) { | ||||
|                             coordf_t bridging_height = 0.; | ||||
|                             for (const LayerRegion *region : layer.regions()) | ||||
|                                 bridging_height += region->region()->bridging_height_avg(*m_print_config); | ||||
|                             bridging_height /= coordf_t(layer.regions().size()); | ||||
|                             coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance; | ||||
|                             coordf_t bridging_print_z = layer.print_z - bridging_height - m_slicing_params.gap_support_object; | ||||
|                             if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) { | ||||
|                                 // Not below the first layer height means this layer is printable.
 | ||||
|                                 if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { | ||||
|  | @ -1875,16 +1879,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta | |||
|                             layer_new.height  = m_slicing_params.soluble_interface ?  | ||||
|                                 // Align the interface layer with the object's layer height.
 | ||||
|                                 object.layers()[layer_id + 1]->height : | ||||
|                                 // Place a bridge flow interface layer over the top surface.
 | ||||
|                                 //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?)
 | ||||
|                                 // According to Jindrich the bottom surfaces work well.
 | ||||
|                                 //FIXME test the bridging flow instead?
 | ||||
|                                 m_support_material_interface_flow.nozzle_diameter; | ||||
|                                 // Place a bridge flow interface layer or the normal flow interface layer over the top surface.
 | ||||
|                                 m_support_material_bottom_interface_flow.height(); | ||||
|                             layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z : | ||||
|                                 layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; | ||||
|                                 layer.print_z + layer_new.height + m_slicing_params.gap_object_support; | ||||
|                             layer_new.bottom_z = layer.print_z; | ||||
|                             layer_new.idx_object_layer_below = layer_id; | ||||
|                             layer_new.bridging = ! m_slicing_params.soluble_interface; | ||||
|                             layer_new.bridging = ! m_slicing_params.soluble_interface && m_object_config->thick_bridges; | ||||
|                             //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
 | ||||
|                             //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks.
 | ||||
|                             layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); | ||||
|  | @ -2028,11 +2029,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta | |||
|             task_group.wait(); | ||||
|         } | ||||
|         std::reverse(bottom_contacts.begin(), bottom_contacts.end()); | ||||
| //        trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy);
 | ||||
|         trim_support_layers_by_object(object, bottom_contacts,  | ||||
|             m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,  | ||||
|             m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); | ||||
| 
 | ||||
|         trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy); | ||||
|     } // ! top_contacts.empty()
 | ||||
| 
 | ||||
|     return bottom_contacts; | ||||
|  | @ -2462,10 +2459,7 @@ void PrintObjectSupportMaterial::generate_base_layers( | |||
|     ++ iRun; | ||||
| #endif /* SLIC3R_DEBUG */ | ||||
| 
 | ||||
| //    trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy);
 | ||||
|     this->trim_support_layers_by_object(object, intermediate_layers,  | ||||
|         m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,  | ||||
|         m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); | ||||
|     this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy); | ||||
| } | ||||
| 
 | ||||
| void PrintObjectSupportMaterial::trim_support_layers_by_object( | ||||
|  | @ -2499,7 +2493,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( | |||
|                 // BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming non-empty layer " << idx_layer << " of " << nonempty_layers.size();
 | ||||
|                 assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON); | ||||
|                 // Find the overlapping object layers including the extra above / below gap.
 | ||||
|                 coordf_t z_threshold = support_layer.print_z - support_layer.height - gap_extra_below + EPSILON; | ||||
|                 coordf_t z_threshold = support_layer.bottom_print_z() - gap_extra_below + EPSILON; | ||||
|                 idx_object_layer_overlapping = idx_higher_or_equal( | ||||
|                     object.layers().begin(), object.layers().end(), idx_object_layer_overlapping, | ||||
|                     [z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; }); | ||||
|  | @ -2508,11 +2502,11 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( | |||
|                 size_t i = idx_object_layer_overlapping; | ||||
|                 for (; i < object.layers().size(); ++ i) { | ||||
|                     const Layer &object_layer = *object.layers()[i]; | ||||
|                     if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON) | ||||
|                     if (object_layer.bottom_z() > support_layer.print_z + gap_extra_above - EPSILON) | ||||
|                         break; | ||||
|                     polygons_append(polygons_trimming, offset(object_layer.lslices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); | ||||
|                 } | ||||
|                 if (! m_slicing_params.soluble_interface) { | ||||
|                 if (! m_slicing_params.soluble_interface && m_object_config->thick_bridges) { | ||||
|                     // Collect all bottom surfaces, which will be extruded with a bridging flow.
 | ||||
|                     for (; i < object.layers().size(); ++ i) { | ||||
|                         const Layer &object_layer = *object.layers()[i]; | ||||
|  | @ -2526,6 +2520,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( | |||
|                                 offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)),  | ||||
|                                        gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); | ||||
|                             if (region->region()->config().overhangs.value) | ||||
|                                 // Add bridging perimeters.
 | ||||
|                                 SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); | ||||
|                         } | ||||
|                         if (! some_region_overlaps) | ||||
|  | @ -2709,17 +2704,23 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M | |||
|         m_object_config->support_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) &&  | ||||
|         // Base extruder: Either "print with active extruder" not soluble.
 | ||||
|         (m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1)); | ||||
|     int num_interface_layers      = m_object_config->support_material_interface_layers.value; | ||||
|     int num_base_interface_layers = soluble_interface_non_soluble_base ? std::min(num_interface_layers / 2, 2) : 0; | ||||
|     int num_interface_layers_top         = m_object_config->support_material_interface_layers; | ||||
|     int num_interface_layers_bottom      = m_object_config->support_material_bottom_interface_layers; | ||||
|     if (num_interface_layers_bottom < 0) | ||||
|         num_interface_layers_bottom = num_interface_layers_top; | ||||
|     int num_base_interface_layers_top    = soluble_interface_non_soluble_base ? std::min(num_interface_layers_top / 2, 2) : 0; | ||||
|     int num_base_interface_layers_bottom = soluble_interface_non_soluble_base ? std::min(num_interface_layers_bottom / 2, 2) : 0; | ||||
| 
 | ||||
|     if (! intermediate_layers.empty() && num_interface_layers > 1) { | ||||
|     if (! intermediate_layers.empty() && (num_interface_layers_top > 1 || num_interface_layers_bottom > 1)) { | ||||
|         // For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers.
 | ||||
|         BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; | ||||
|         // Since the intermediate layer index starts at zero the number of interface layer needs to be reduced by 1.
 | ||||
|         -- num_interface_layers; | ||||
|         int num_interface_layers_only = num_interface_layers - num_base_interface_layers; | ||||
|         -- num_interface_layers_top; | ||||
|         -- num_interface_layers_bottom; | ||||
|         int num_interface_layers_only_top    = num_interface_layers_top    - num_base_interface_layers_top; | ||||
|         int num_interface_layers_only_bottom = num_interface_layers_bottom - num_base_interface_layers_bottom; | ||||
|         interface_layers.assign(intermediate_layers.size(), nullptr); | ||||
|         if (num_base_interface_layers) | ||||
|         if (num_base_interface_layers_top || num_base_interface_layers_bottom) | ||||
|             base_interface_layers.assign(intermediate_layers.size(), nullptr); | ||||
|         tbb::spin_mutex layer_storage_mutex; | ||||
|         // Insert a new layer into base_interface_layers, if intersection with base exists.
 | ||||
|  | @ -2743,7 +2744,8 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M | |||
|             return &layer_new; | ||||
|         }; | ||||
|         tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())), | ||||
|             [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, num_interface_layers, num_base_interface_layers, num_interface_layers_only, | ||||
|             [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer,  | ||||
|              num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, | ||||
|              &interface_layers, &base_interface_layers](const tbb::blocked_range<int>& range) {                 | ||||
|                 // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below
 | ||||
|                 // this intermediate layer.
 | ||||
|  | @ -2754,45 +2756,51 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M | |||
|                 auto num_intermediate = int(intermediate_layers.size()); | ||||
|                 for (int idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) { | ||||
|                     MyLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer]; | ||||
|                     // Top / bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
 | ||||
|                     coordf_t top_z              = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers - 1)]->print_z; | ||||
|                     coordf_t top_inteface_z     = std::numeric_limits<coordf_t>::max(); | ||||
|                     coordf_t bottom_z           = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers + 1)]->bottom_z; | ||||
|                     coordf_t bottom_interface_z = - std::numeric_limits<coordf_t>::max(); | ||||
|                     if (num_base_interface_layers > 0) { | ||||
|                         // Some base interface layers will be generated.
 | ||||
|                         if (num_interface_layers_only == 0) | ||||
|                             // Only base interface layers to generate.
 | ||||
|                             std::swap(top_inteface_z, bottom_interface_z); | ||||
|                         else { | ||||
|                             top_inteface_z     = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only - 1)]->print_z; | ||||
|                             bottom_interface_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only)]->bottom_z; | ||||
|                         } | ||||
|                     } | ||||
|                     // Move idx_top_contact_first up until above the current print_z.
 | ||||
|                     idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); //  - EPSILON
 | ||||
|                     // Collect the top contact areas above this intermediate layer, below top_z.
 | ||||
|                     Polygons polygons_top_contact_projected_interface; | ||||
|                     Polygons polygons_top_contact_projected_base; | ||||
|                     for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { | ||||
|                         const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; | ||||
|                         //FIXME maybe this adds one interface layer in excess?
 | ||||
|                         if (top_contact_layer.bottom_z - EPSILON > top_z) | ||||
|                             break; | ||||
|                         polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons); | ||||
|                     } | ||||
|                     // Move idx_bottom_contact_first up until touching bottom_z.
 | ||||
|                     idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); | ||||
|                     // Collect the top contact areas above this intermediate layer, below top_z.
 | ||||
|                     Polygons polygons_bottom_contact_projected_interface; | ||||
|                     Polygons polygons_bottom_contact_projected_base; | ||||
|                     for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { | ||||
|                         const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; | ||||
|                         if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) | ||||
|                             break; | ||||
|                         polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); | ||||
|                     if (num_interface_layers_top > 0) { | ||||
|                         // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
 | ||||
|                         coordf_t top_z              = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z; | ||||
|                         coordf_t top_inteface_z     = std::numeric_limits<coordf_t>::max(); | ||||
|                         if (num_base_interface_layers_top > 0) | ||||
|                             // Some top base interface layers will be generated.
 | ||||
|                             top_inteface_z = num_interface_layers_only_top == 0 ? | ||||
|                                 // Only base interface layers to generate.
 | ||||
|                                 - std::numeric_limits<coordf_t>::max() : | ||||
|                                 intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z; | ||||
|                         // Move idx_top_contact_first up until above the current print_z.
 | ||||
|                         idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); //  - EPSILON
 | ||||
|                         // Collect the top contact areas above this intermediate layer, below top_z.
 | ||||
|                         for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { | ||||
|                             const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; | ||||
|                             //FIXME maybe this adds one interface layer in excess?
 | ||||
|                             if (top_contact_layer.bottom_z - EPSILON > top_z) | ||||
|                                 break; | ||||
|                             polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons); | ||||
|                         } | ||||
|                     } | ||||
|                     if (num_interface_layers_bottom > 0) { | ||||
|                         // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
 | ||||
|                         coordf_t bottom_z           = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z; | ||||
|                         coordf_t bottom_interface_z = - std::numeric_limits<coordf_t>::max(); | ||||
|                         if (num_base_interface_layers_bottom > 0) | ||||
|                             // Some bottom base interface layers will be generated.
 | ||||
|                             bottom_interface_z = num_interface_layers_only_bottom == 0 ?  | ||||
|                                 // Only base interface layers to generate.
 | ||||
|                                 std::numeric_limits<coordf_t>::max() : | ||||
|                                 intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z; | ||||
|                         // Move idx_bottom_contact_first up until touching bottom_z.
 | ||||
|                         idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); | ||||
|                         // Collect the top contact areas above this intermediate layer, below top_z.
 | ||||
|                         for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { | ||||
|                             const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; | ||||
|                             if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) | ||||
|                                 break; | ||||
|                             polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     MyLayer *interface_layer = nullptr; | ||||
|                     if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) { | ||||
|                         interface_layer = insert_layer( | ||||
|  | @ -2810,7 +2818,7 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M | |||
|         // Compress contact_out, remove the nullptr items.
 | ||||
|         remove_nulls(interface_layers); | ||||
|         remove_nulls(base_interface_layers); | ||||
|         BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; | ||||
|         BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; | ||||
|     } | ||||
|      | ||||
|     return base_and_interface_layers; | ||||
|  | @ -2835,7 +2843,7 @@ static inline void fill_expolygon_generate_paths( | |||
|         dst, | ||||
|         std::move(polylines), | ||||
|         role, | ||||
|         flow.mm3_per_mm(), flow.width, flow.height); | ||||
|         flow.mm3_per_mm(), flow.width(), flow.height()); | ||||
| } | ||||
| 
 | ||||
| static inline void fill_expolygons_generate_paths( | ||||
|  | @ -2872,7 +2880,8 @@ static inline void fill_expolygons_with_sheath_generate_paths( | |||
|     float                    density, | ||||
|     ExtrusionRole            role, | ||||
|     const Flow              &flow, | ||||
|     bool                     with_sheath) | ||||
|     bool                     with_sheath, | ||||
|     bool                     no_sort) | ||||
| { | ||||
|     if (polygons.empty()) | ||||
|         return; | ||||
|  | @ -2892,8 +2901,12 @@ static inline void fill_expolygons_with_sheath_generate_paths( | |||
| 
 | ||||
|     for (ExPolygon &expoly : offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()))) { | ||||
|         // Don't reorder the skirt and its infills.
 | ||||
|         auto eec = std::make_unique<ExtrusionEntityCollection>(); | ||||
|         eec->no_sort = true; | ||||
|         std::unique_ptr<ExtrusionEntityCollection> eec; | ||||
|         if (no_sort) { | ||||
|             eec = std::make_unique<ExtrusionEntityCollection>(); | ||||
|             eec->no_sort = true; | ||||
|         } | ||||
|         ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; | ||||
|         // Draw the perimeters.
 | ||||
|         Polylines polylines; | ||||
|         polylines.reserve(expoly.holes.size() + 1); | ||||
|  | @ -2903,10 +2916,11 @@ static inline void fill_expolygons_with_sheath_generate_paths( | |||
|             pl.clip_end(clip_length); | ||||
|             polylines.emplace_back(std::move(pl)); | ||||
|         } | ||||
|         extrusion_entities_append_paths(eec->entities, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height); | ||||
|         extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); | ||||
|         // Fill in the rest.
 | ||||
|         fill_expolygons_generate_paths(eec->entities, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); | ||||
|         dst.emplace_back(eec.release()); | ||||
|         fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); | ||||
|         if (no_sort) | ||||
|             dst.emplace_back(eec.release()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -3011,8 +3025,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const | |||
|     if (n_contact_loops == 0 || top_contact_layer.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     Flow flow = interface_flow_src; | ||||
|     flow.height = float(top_contact_layer.layer->height); | ||||
|     Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); | ||||
| 
 | ||||
|     Polygons overhang_polygons; | ||||
|     if (top_contact_layer.layer->overhang_polygons != nullptr) | ||||
|  | @ -3209,7 +3222,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const | |||
|     extrusion_entities_append_paths( | ||||
|         top_contact_layer.extrusions, | ||||
|         std::move(loop_lines), | ||||
|         erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height); | ||||
|         erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); | ||||
| } | ||||
| 
 | ||||
| #ifdef SLIC3R_DEBUG | ||||
|  | @ -3232,6 +3245,9 @@ static std::string dbg_index_to_color(int idx) | |||
| // Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers,
 | ||||
| // leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers
 | ||||
| // to stick too firmly to the object.
 | ||||
| //
 | ||||
| // Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer
 | ||||
| // if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z.
 | ||||
| void modulate_extrusion_by_overlapping_layers( | ||||
|     // Extrusions generated for this_layer.
 | ||||
|     ExtrusionEntitiesPtr                               &extrusions_in_out, | ||||
|  | @ -3320,8 +3336,8 @@ void modulate_extrusion_by_overlapping_layers( | |||
|     // Collect the paths of this_layer.
 | ||||
|     { | ||||
|         Polylines &polylines = path_fragments.back().polylines; | ||||
|         for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { | ||||
|             ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(*it); | ||||
|         for (ExtrusionEntity *ee : extrusions_in_out) { | ||||
|             ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(ee); | ||||
|             assert(path != nullptr); | ||||
|             polylines.emplace_back(Polyline(std::move(path->polyline))); | ||||
|             path_ends.emplace_back(std::pair<Point, Point>(polylines.back().points.front(), polylines.back().points.back())); | ||||
|  | @ -3342,7 +3358,7 @@ void modulate_extrusion_by_overlapping_layers( | |||
|         // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter).
 | ||||
|         assert(this_layer.print_z > overlapping_layer.print_z); | ||||
|         frag.height = float(this_layer.print_z - overlapping_layer.print_z); | ||||
|         frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f, false).mm3_per_mm(); | ||||
|         frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); | ||||
| #ifdef SLIC3R_DEBUG | ||||
|         svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); | ||||
| #endif /* SLIC3R_DEBUG */ | ||||
|  | @ -3481,7 +3497,6 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|     const MyLayersPtr   &interface_layers, | ||||
|     const MyLayersPtr   &base_interface_layers) const | ||||
| { | ||||
| //    Slic3r::debugf "Generating patterns\n";
 | ||||
|     // loop_interface_processor with a given circle radius.
 | ||||
|     LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width()); | ||||
|     loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; | ||||
|  | @ -3492,7 +3507,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|     coordf_t interface_density  = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing); | ||||
|     coordf_t support_spacing    = m_object_config->support_material_spacing.value + m_support_material_flow.spacing(); | ||||
|     coordf_t support_density    = std::min(1., m_support_material_flow.spacing() / support_spacing); | ||||
|     if (m_object_config->support_material_interface_layers.value == 0) { | ||||
|     if (m_slicing_params.soluble_interface) { | ||||
|         // No interface layers allowed, print everything with the base support pattern.
 | ||||
|         interface_spacing = support_spacing; | ||||
|         interface_density = support_density; | ||||
|  | @ -3565,7 +3580,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                     //FIXME misusing contact_polygons for support columns.
 | ||||
|                     ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); | ||||
|                 if (! to_infill_polygons.empty()) { | ||||
|                     Flow flow(float(m_support_material_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging); | ||||
|                     assert(! raft_layer.bridging); | ||||
|                     Flow flow(float(m_support_material_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter()); | ||||
|                     Fill * filler = filler_support.get(); | ||||
|                     filler->angle = raft_angle_base; | ||||
|                     filler->spacing = m_support_material_flow.spacing(); | ||||
|  | @ -3579,7 +3595,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                         filler, float(support_density), | ||||
|                         // Extrusion parameters
 | ||||
|                         erSupportMaterial, flow, | ||||
|                         with_sheath); | ||||
|                         with_sheath, false); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -3596,7 +3612,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                 // We don't use $base_flow->spacing because we need a constant spacing
 | ||||
|                 // value that guarantees that all layers are correctly aligned.
 | ||||
|                 filler->spacing = m_support_material_flow.spacing(); | ||||
|                 flow          = Flow(float(m_support_material_interface_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging); | ||||
|                 assert(! raft_layer.bridging); | ||||
|                 flow          = Flow(float(m_support_material_interface_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter()); | ||||
|                 density       = float(interface_density); | ||||
|             } else | ||||
|                 continue; | ||||
|  | @ -3611,7 +3628,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                 // Extrusion parameters
 | ||||
|                 (support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow,  | ||||
|                 // sheath at first layer
 | ||||
|                 support_layer_id == 0); | ||||
|                 support_layer_id == 0, support_layer_id == 0); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|  | @ -3621,12 +3638,20 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|         std::vector<MyLayer*>    overlapping; | ||||
|     }; | ||||
|     struct LayerCache { | ||||
|         MyLayerExtruded                 bottom_contact_layer; | ||||
|         MyLayerExtruded                 top_contact_layer; | ||||
|         MyLayerExtruded                 base_layer; | ||||
|         MyLayerExtruded                 interface_layer; | ||||
|         MyLayerExtruded                 base_interface_layer; | ||||
|         std::vector<LayerCacheItem>     overlaps; | ||||
|         MyLayerExtruded                                     bottom_contact_layer; | ||||
|         MyLayerExtruded                                     top_contact_layer; | ||||
|         MyLayerExtruded                                     base_layer; | ||||
|         MyLayerExtruded                                     interface_layer; | ||||
|         MyLayerExtruded                                     base_interface_layer; | ||||
|         boost::container::static_vector<LayerCacheItem, 5>  nonempty; | ||||
| 
 | ||||
|         void add_nonempty_and_sort() { | ||||
|             for (MyLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) | ||||
|                 if (! item->empty()) | ||||
|                     this->nonempty.emplace_back(item); | ||||
|             // Sort the layers with the same print_z coordinate by their heights, thickest first.
 | ||||
|             std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); | ||||
|         } | ||||
|     }; | ||||
|     std::vector<LayerCache>             layer_caches(support_layers.size(), LayerCache()); | ||||
| 
 | ||||
|  | @ -3700,10 +3725,6 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                         base_layer.merge(std::move(top_contact_layer)); | ||||
|                     else if (base_layer.empty() && !top_contact_layer.empty() && !top_contact_layer.layer->bridging) | ||||
|                         std::swap(base_layer, top_contact_layer); | ||||
|                     if (base_layer.could_merge(bottom_contact_layer)) | ||||
|                         base_layer.merge(std::move(bottom_contact_layer)); | ||||
|                     else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging) | ||||
|                         std::swap(base_layer, bottom_contact_layer); | ||||
|                 } | ||||
|             } else { | ||||
|                 loop_interface_processor.generate(top_contact_layer, m_support_material_interface_flow); | ||||
|  | @ -3713,6 +3734,12 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                 if (top_contact_layer.could_merge(interface_layer)) | ||||
|                     top_contact_layer.merge(std::move(interface_layer)); | ||||
|             }  | ||||
|             if ((m_object_config->support_material_interface_layers == 0 || m_object_config->support_material_bottom_interface_layers == 0) && m_can_merge_support_regions) { | ||||
|                 if (base_layer.could_merge(bottom_contact_layer)) | ||||
|                     base_layer.merge(std::move(bottom_contact_layer)); | ||||
|                 else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging) | ||||
|                     std::swap(base_layer, bottom_contact_layer); | ||||
|             } | ||||
| 
 | ||||
| #if 0 | ||||
|             if ( ! interface_layer.empty() && ! base_layer.empty()) { | ||||
|  | @ -3731,14 +3758,12 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                 MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); | ||||
|                 if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) | ||||
|                     continue; | ||||
|                 bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0; | ||||
|                 bool interface_as_base = (&layer_ex == &interface_layer) && m_slicing_params.soluble_interface; | ||||
|                 //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
 | ||||
|                 // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
 | ||||
|                 Flow interface_flow( | ||||
|                     float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)), | ||||
|                     float(layer_ex.layer->height), | ||||
|                     m_support_material_interface_flow.nozzle_diameter, | ||||
|                     layer_ex.layer->bridging); | ||||
|                 auto interface_flow = layer_ex.layer->bridging ? | ||||
|                     Flow::bridging_flow(layer_ex.layer->height, m_support_material_bottom_interface_flow.nozzle_diameter()) : | ||||
|                     (interface_as_base ? &m_support_material_flow : &m_support_material_interface_flow)->with_height(float(layer_ex.layer->height)); | ||||
|                 filler_interface->angle = interface_as_base ? | ||||
|                         // If zero interface layers are configured, use the same angle as for the base layers.
 | ||||
|                         angles[support_layer_id % angles.size()] : | ||||
|  | @ -3762,11 +3787,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                 Fill *filler = filler_base_interface.get(); | ||||
|                 //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
 | ||||
|                 // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
 | ||||
|                 Flow interface_flow( | ||||
|                     float(base_interface_layer.layer->bridging ? base_interface_layer.layer->height : m_support_material_flow.width), // m_support_material_interface_flow.width)),
 | ||||
|                     float(base_interface_layer.layer->height), | ||||
|                     m_support_material_flow.nozzle_diameter, | ||||
|                     base_interface_layer.layer->bridging); | ||||
|                 assert(! base_interface_layer.layer->bridging); | ||||
|                 Flow interface_flow = m_support_material_flow.with_height(float(base_interface_layer.layer->height)); | ||||
|                 filler->angle   = interface_angle; | ||||
|                 filler->spacing = m_support_material_interface_flow.spacing(); | ||||
|                 filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density)); | ||||
|  | @ -3788,15 +3810,13 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                 filler->angle = angles[support_layer_id % angles.size()]; | ||||
|                 // We don't use $base_flow->spacing because we need a constant spacing
 | ||||
|                 // value that guarantees that all layers are correctly aligned.
 | ||||
|                 Flow flow( | ||||
|                     float(base_layer.layer->bridging ? base_layer.layer->height : m_support_material_flow.width),  | ||||
|                     float(base_layer.layer->height),  | ||||
|                     m_support_material_flow.nozzle_diameter,  | ||||
|                     base_layer.layer->bridging); | ||||
|                 assert(! base_layer.layer->bridging); | ||||
|                 auto flow = m_support_material_flow.with_height(float(base_layer.layer->height)); | ||||
|                 filler->spacing = m_support_material_flow.spacing(); | ||||
|                 filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); | ||||
|                 float density = float(support_density); | ||||
|                 bool  sheath  = with_sheath; | ||||
|                 bool  no_sort = false; | ||||
|                 if (base_layer.layer->bottom_z < EPSILON) { | ||||
|                     // Base flange (the 1st layer).
 | ||||
|                     filler = filler_first_layer; | ||||
|  | @ -3808,7 +3828,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                     //FIXME When paralellizing, each thread shall have its own copy of the fillers.
 | ||||
|                     filler->spacing = flow.spacing(); | ||||
|                     filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); | ||||
|                     sheath = true; | ||||
|                     sheath  = true; | ||||
|                     no_sort = true; | ||||
|                 } | ||||
|                 fill_expolygons_with_sheath_generate_paths( | ||||
|                     // Destination
 | ||||
|  | @ -3819,7 +3840,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                     filler, density, | ||||
|                     // Extrusion parameters
 | ||||
|                     erSupportMaterial, flow, | ||||
|                     sheath); | ||||
|                     sheath, no_sort); | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|  | @ -3828,24 +3849,13 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                 base_layer.could_merge(base_interface_layer)) | ||||
|                 base_layer.merge(std::move(base_interface_layer)); | ||||
| 
 | ||||
|             layer_cache.overlaps.reserve(5); | ||||
|             if (! bottom_contact_layer.empty()) | ||||
|                 layer_cache.overlaps.push_back(&bottom_contact_layer); | ||||
|             if (! top_contact_layer.empty()) | ||||
|                 layer_cache.overlaps.push_back(&top_contact_layer); | ||||
|             if (! interface_layer.empty()) | ||||
|                 layer_cache.overlaps.push_back(&interface_layer); | ||||
|             if (! base_interface_layer.empty()) | ||||
|                 layer_cache.overlaps.push_back(&base_interface_layer); | ||||
|             if (! base_layer.empty()) | ||||
|                 layer_cache.overlaps.push_back(&base_layer); | ||||
|             // Sort the layers with the same print_z coordinate by their heights, thickest first.
 | ||||
|             std::sort(layer_cache.overlaps.begin(), layer_cache.overlaps.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); | ||||
|             layer_cache.add_nonempty_and_sort(); | ||||
| 
 | ||||
|             // Collect the support areas with this print_z into islands, as there is no need
 | ||||
|             // for retraction over these islands.
 | ||||
|             Polygons polys; | ||||
|             // Collect the extrusions, sorted by the bottom extrusion height.
 | ||||
|             for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) { | ||||
|             for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { | ||||
|                 // Collect islands to polys.
 | ||||
|                 layer_cache_item.layer_extruded->polygons_append(polys); | ||||
|                 // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free"
 | ||||
|  | @ -3859,32 +3869,22 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|                 // Collect overlapping top/bottom surfaces.
 | ||||
|                 layer_cache_item.overlapping.reserve(20); | ||||
|                 coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; | ||||
|                 for (int i = int(idx_layer_bottom_contact) - 1; i >= 0 && bottom_contacts[i]->print_z > bottom_z; -- i) | ||||
|                     layer_cache_item.overlapping.push_back(bottom_contacts[i]); | ||||
|                 for (int i = int(idx_layer_top_contact) - 1; i >= 0 && top_contacts[i]->print_z > bottom_z; -- i) | ||||
|                     layer_cache_item.overlapping.push_back(top_contacts[i]); | ||||
|                 auto add_overlapping = [&layer_cache_item, bottom_z](const MyLayersPtr &layers, size_t idx_top) { | ||||
|                     for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) | ||||
|                         layer_cache_item.overlapping.push_back(layers[i]); | ||||
|                 }; | ||||
|                 add_overlapping(top_contacts, idx_layer_top_contact); | ||||
|                 if (layer_cache_item.layer_extruded->layer->layer_type == sltBottomContact) { | ||||
|                     // Bottom contact layer may overlap with a base layer, which may be changed to interface layer.
 | ||||
|                     for (int i = int(idx_layer_intermediate) - 1; i >= 0 && intermediate_layers[i]->print_z > bottom_z; -- i) | ||||
|                         layer_cache_item.overlapping.push_back(intermediate_layers[i]); | ||||
|                     for (int i = int(idx_layer_interface) - 1; i >= 0 && interface_layers[i]->print_z > bottom_z; -- i) | ||||
|                         layer_cache_item.overlapping.push_back(interface_layers[i]); | ||||
|                     for (int i = int(idx_layer_base_interface) - 1; i >= 0 && base_interface_layers[i]->print_z > bottom_z; -- i) | ||||
|                         layer_cache_item.overlapping.push_back(base_interface_layers[i]); | ||||
|                     add_overlapping(intermediate_layers,   idx_layer_intermediate); | ||||
|                     add_overlapping(interface_layers,      idx_layer_interface); | ||||
|                     add_overlapping(base_interface_layers, idx_layer_base_interface); | ||||
|                 } | ||||
|                 std::sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), MyLayersPtrCompare()); | ||||
|                 // Order the layers by lexicographically by an increasing print_z and a decreasing layer height.
 | ||||
|                 std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); | ||||
|             } | ||||
|             if (! polys.empty()) | ||||
|                 expolygons_append(support_layer.support_islands.expolygons, union_ex(polys)); | ||||
|             /* {
 | ||||
|                 require "Slic3r/SVG.pm"; | ||||
|                 Slic3r::SVG::output("islands_" . $z . ".svg", | ||||
|                     red_expolygons      => union_ex($contact), | ||||
|                     green_expolygons    => union_ex($interface), | ||||
|                     green_polylines     => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ], | ||||
|                     polylines           => [ map $_->unpack->polyline, @{$layer->support_fills} ], | ||||
|                 ); | ||||
|             } */ | ||||
|         } // for each support_layer_id
 | ||||
|     }); | ||||
| 
 | ||||
|  | @ -3895,7 +3895,9 @@ void PrintObjectSupportMaterial::generate_toolpaths( | |||
|         for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { | ||||
|             SupportLayer &support_layer = *support_layers[support_layer_id]; | ||||
|             LayerCache   &layer_cache   = layer_caches[support_layer_id]; | ||||
|             for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) { | ||||
|             // For all extrusion types at this print_z, ordered by decreasing layer height:
 | ||||
|             for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { | ||||
|                 // Trim the extrusion height from the bottom by the overlapping layers.
 | ||||
|                 modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); | ||||
|                 support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); | ||||
|             } | ||||
|  |  | |||
|  | @ -244,6 +244,7 @@ private: | |||
| 	Flow 			 	 m_first_layer_flow; | ||||
| 	Flow 			 	 m_support_material_flow; | ||||
| 	Flow 			 	 m_support_material_interface_flow; | ||||
| 	Flow 				 m_support_material_bottom_interface_flow; | ||||
| 	// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
 | ||||
| 	bool 				 m_can_merge_support_regions; | ||||
| 
 | ||||
|  |  | |||
|  | @ -2063,4 +2063,22 @@ TriangleMesh make_sphere(double radius, double fa) | |||
| 	return mesh; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::vector<size_t> > create_neighbor_index(const indexed_triangle_set &its) | ||||
| { | ||||
|     if (its.vertices.empty()) return {}; | ||||
| 
 | ||||
|     size_t res = its.indices.size() / its.vertices.size(); | ||||
|     std::vector< std::vector<size_t> > index(its.vertices.size(), | ||||
|                                              reserve_vector<size_t>(res)); | ||||
| 
 | ||||
|     for (size_t fi = 0; fi < its.indices.size(); ++fi) { | ||||
|         auto &face = its.indices[fi]; | ||||
|         index[face(0)].emplace_back(fi); | ||||
|         index[face(1)].emplace_back(fi); | ||||
|         index[face(2)].emplace_back(fi); | ||||
|     } | ||||
| 
 | ||||
|     return index; | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -89,6 +89,12 @@ private: | |||
|     std::deque<uint32_t> find_unvisited_neighbors(std::vector<unsigned char> &facet_visited) const; | ||||
| }; | ||||
| 
 | ||||
| // Create an index of faces belonging to each vertex. The returned vector can
 | ||||
| // be indexed with vertex indices and contains a list of face indices for each
 | ||||
| // vertex.
 | ||||
| std::vector< std::vector<size_t> > | ||||
| create_neighbor_index(const indexed_triangle_set &its); | ||||
| 
 | ||||
| enum FacetEdgeType {  | ||||
|     // A general case, the cutting plane intersect a face at two different edges.
 | ||||
|     feGeneral, | ||||
|  |  | |||
|  | @ -93,6 +93,8 @@ set(SLIC3R_GUI_SOURCES | |||
|     GUI/SavePresetDialog.cpp | ||||
|     GUI/PhysicalPrinterDialog.hpp | ||||
|     GUI/PhysicalPrinterDialog.cpp | ||||
|     GUI/GUI_Factories.cpp | ||||
|     GUI/GUI_Factories.hpp | ||||
|     GUI/GUI_ObjectList.cpp | ||||
|     GUI/GUI_ObjectList.hpp | ||||
|     GUI/GUI_ObjectManipulation.cpp | ||||
|  | @ -207,6 +209,8 @@ set(SLIC3R_GUI_SOURCES | |||
|     Utils/Bonjour.hpp | ||||
|     Utils/PresetUpdater.cpp | ||||
|     Utils/PresetUpdater.hpp | ||||
|     Utils/Platform.cpp | ||||
|     Utils/Platform.hpp | ||||
|     Utils/Process.cpp | ||||
|     Utils/Process.hpp | ||||
|     Utils/Profile.hpp | ||||
|  |  | |||
|  | @ -845,6 +845,57 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M | |||
|     return contained_min_one; | ||||
| } | ||||
| 
 | ||||
| bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut) | ||||
| { | ||||
|     if (config == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|     const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(config->option("bed_shape")); | ||||
|     if (opt == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|     BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); | ||||
|     BoundingBoxf3 print_volume(Vec3d(unscale<double>(bed_box_2D.min(0)), unscale<double>(bed_box_2D.min(1)), 0.0), Vec3d(unscale<double>(bed_box_2D.max(0)), unscale<double>(bed_box_2D.max(1)), config->opt_float("max_print_height"))); | ||||
|     // Allow the objects to protrude below the print bed
 | ||||
|     print_volume.min(2) = -1e10; | ||||
|     print_volume.min(0) -= BedEpsilon; | ||||
|     print_volume.min(1) -= BedEpsilon; | ||||
|     print_volume.max(0) += BedEpsilon; | ||||
|     print_volume.max(1) += BedEpsilon; | ||||
| 
 | ||||
|     bool contained_min_one = false; | ||||
| 
 | ||||
|     partlyOut = false; | ||||
|     fullyOut = false; | ||||
|     for (GLVolume* volume : this->volumes) | ||||
|     { | ||||
|         if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled)) | ||||
|             continue; | ||||
| 
 | ||||
|         const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); | ||||
|         bool contained = print_volume.contains(bb); | ||||
| 
 | ||||
|         volume->is_outside = !contained; | ||||
|         if (!volume->printable) | ||||
|             continue; | ||||
| 
 | ||||
|         if (contained) | ||||
|             contained_min_one = true; | ||||
| 
 | ||||
|         if (volume->is_outside) { | ||||
|             if (print_volume.intersects(bb)) | ||||
|                 partlyOut = true; | ||||
|             else  | ||||
|                 fullyOut = true; | ||||
|         } | ||||
|     } | ||||
|     /*
 | ||||
|     if (out_state != nullptr) | ||||
|         *out_state = state; | ||||
|     */ | ||||
|     return contained_min_one; | ||||
| } | ||||
| 
 | ||||
| void GLVolumeCollection::reset_outside_state() | ||||
| { | ||||
|     for (GLVolume* volume : this->volumes) | ||||
|  |  | |||
|  | @ -569,6 +569,7 @@ public: | |||
|     // returns true if all the volumes are completely contained in the print volume
 | ||||
|     // returns the containment state in the given out_state, if non-null
 | ||||
|     bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state); | ||||
|     bool check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut); | ||||
|     void reset_outside_state(); | ||||
| 
 | ||||
|     void update_colors_by_extruder(const DynamicPrintConfig* config); | ||||
|  |  | |||
|  | @ -285,8 +285,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) | |||
|                     "support_material_xy_spacing" }) | ||||
|         toggle_field(el, have_support_material); | ||||
|     toggle_field("support_material_threshold", have_support_material_auto); | ||||
|     toggle_field("support_material_bottom_contact_distance", have_support_material && ! have_support_soluble); | ||||
| 
 | ||||
|     for (auto el : { "support_material_interface_spacing", "support_material_interface_extruder", | ||||
|     for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder", | ||||
|                     "support_material_interface_speed", "support_material_interface_contact_loops" }) | ||||
|         toggle_field(el, have_support_material && have_support_interface); | ||||
|     toggle_field("support_material_synchronize_layers", have_support_soluble); | ||||
|  |  | |||
|  | @ -1317,7 +1317,12 @@ wxString Control::get_tooltip(int tick/*=-1*/) | |||
|                         "This code won't be processed during G-code generation."); | ||||
|          | ||||
|         // Show custom Gcode as a first string of tooltop
 | ||||
|         tooltip = "    "; | ||||
|         std::string space = "   "; | ||||
|         tooltip = space; | ||||
|         auto format_gcode = [space](std::string gcode) { | ||||
|             boost::replace_all(gcode, "\n", "\n" + space); | ||||
|             return gcode; | ||||
|         }; | ||||
|         tooltip +=   | ||||
|         	tick_code_it->type == ColorChange ? | ||||
|         		(m_mode == SingleExtruder ? | ||||
|  | @ -1329,7 +1334,7 @@ wxString Control::get_tooltip(int tick/*=-1*/) | |||
| 	                format_wxstr(_L("Custom template (\"%1%\")"), gcode(Template)) : | ||||
| 		            tick_code_it->type == ToolChange ? | ||||
| 		                format_wxstr(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) :                 | ||||
| 		                from_u8(tick_code_it->extra);// tick_code_it->type == Custom
 | ||||
| 		                from_u8(format_gcode(tick_code_it->extra));// tick_code_it->type == Custom
 | ||||
| 
 | ||||
|         // If tick is marked as a conflict (exclamation icon),
 | ||||
|         // we should to explain why
 | ||||
|  | @ -1925,6 +1930,8 @@ void Control::auto_color_change() | |||
|     double delta_area = scale_(scale_(25)); // equal to 25 mm2
 | ||||
| 
 | ||||
|     for (auto object : print.objects()) { | ||||
|         if (object->layer_count() == 0) | ||||
|             continue; | ||||
|         double prev_area = area(object->get_layer(0)->lslices); | ||||
| 
 | ||||
|         for (size_t i = 1; i < object->layers().size(); i++) { | ||||
|  |  | |||
|  | @ -902,7 +902,7 @@ void Choice::BUILD() { | |||
|     if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit); | ||||
| 
 | ||||
| 	choice_ctrl* temp; | ||||
|     if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0) { | ||||
|     if (m_opt.gui_type != ConfigOptionDef::GUIType::undefined && m_opt.gui_type != ConfigOptionDef::GUIType::select_open) { | ||||
|         m_is_editable = true; | ||||
|         temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size); | ||||
|     } | ||||
|  | @ -965,15 +965,27 @@ void Choice::BUILD() { | |||
|             } | ||||
| 
 | ||||
|             if (is_defined_input_value<choice_ctrl>(window, m_opt.type)) { | ||||
|                 if (m_opt.type == coFloatOrPercent) { | ||||
| 				switch (m_opt.type) { | ||||
| 				case coFloatOrPercent: | ||||
| 				{ | ||||
|                     std::string old_val = !m_value.empty() ? boost::any_cast<std::string>(m_value) : ""; | ||||
|                     if (old_val == boost::any_cast<std::string>(get_value())) | ||||
|                         return; | ||||
| 					break; | ||||
|                 } | ||||
|                 else { | ||||
|                     double old_val = !m_value.empty() ? boost::any_cast<double>(m_value) : -99999; | ||||
|                     if (fabs(old_val - boost::any_cast<double>(get_value())) <= 0.0001) | ||||
| 				case coInt: | ||||
| 				{ | ||||
|                     int old_val = !m_value.empty() ? boost::any_cast<int>(m_value) : 0; | ||||
|                     if (old_val == boost::any_cast<int>(get_value())) | ||||
|                         return; | ||||
| 					break; | ||||
| 				} | ||||
| 				default: | ||||
| 				{ | ||||
| 					double old_val = !m_value.empty() ? boost::any_cast<double>(m_value) : -99999; | ||||
| 					if (fabs(old_val - boost::any_cast<double>(get_value())) <= 0.0001) | ||||
| 						return; | ||||
| 				} | ||||
|                 } | ||||
|                 on_change_field(); | ||||
|             } | ||||
|  | @ -1225,7 +1237,7 @@ boost::any& Choice::get_value() | |||
|         else if (m_opt_id == "brim_type") | ||||
|             m_value = static_cast<BrimType>(ret_enum); | ||||
| 	} | ||||
|     else if (m_opt.gui_type == "f_enum_open") { | ||||
|     else if (m_opt.gui_type == ConfigOptionDef::GUIType::f_enum_open || m_opt.gui_type == ConfigOptionDef::GUIType::i_enum_open) { | ||||
|         const int ret_enum = field->GetSelection(); | ||||
|         if (ret_enum < 0 || m_opt.enum_values.empty() || m_opt.type == coStrings || | ||||
|             (ret_str != m_opt.enum_values[ret_enum] && ret_str != _(m_opt.enum_labels[ret_enum]))) | ||||
|  | @ -1233,6 +1245,8 @@ boost::any& Choice::get_value() | |||
|             get_value_by_opt_type(ret_str); | ||||
|         else if (m_opt.type == coFloatOrPercent) | ||||
|             m_value = m_opt.enum_values[ret_enum]; | ||||
|         else if (m_opt.type == coInt) | ||||
|             m_value = atoi(m_opt.enum_values[ret_enum].c_str()); | ||||
|         else | ||||
|             m_value = atof(m_opt.enum_values[ret_enum].c_str()); | ||||
|     } | ||||
|  |  | |||
|  | @ -641,6 +641,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool | |||
|         error = true; | ||||
|         break; | ||||
|     } | ||||
|     BOOST_LOG_TRIVIAL(error) << state << " : " << text ; | ||||
|     auto ¬ification_manager = *wxGetApp().plater()->get_notification_manager(); | ||||
|     if (state) { | ||||
|         if(error) | ||||
|  | @ -1620,9 +1621,6 @@ void GLCanvas3D::render() | |||
|         wxGetApp().plater()->init_environment_texture(); | ||||
| #endif // ENABLE_ENVIRONMENT_MAP
 | ||||
| 
 | ||||
|     m_render_timer.Stop(); | ||||
|     m_extra_frame_requested_delayed = std::numeric_limits<int>::max(); | ||||
| 
 | ||||
|     const Size& cnv_size = get_canvas_size(); | ||||
|     // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene
 | ||||
|     // to preview, this was called before canvas had its final size. It reported zero width
 | ||||
|  | @ -1754,7 +1752,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(get_overlay_window_width()); | ||||
| 
 | ||||
|     wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width()); | ||||
| 
 | ||||
|     wxGetApp().imgui()->render(); | ||||
| 
 | ||||
|  | @ -2238,24 +2237,24 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re | |||
| 
 | ||||
|     // checks for geometry outside the print volume to render it accordingly
 | ||||
|     if (!m_volumes.empty()) { | ||||
|         ModelInstanceEPrintVolumeState state; | ||||
| 
 | ||||
|         const bool contained_min_one = m_volumes.check_outside_state(m_config, &state); | ||||
|         bool partlyOut = false; | ||||
|         bool fullyOut = false; | ||||
|         const bool contained_min_one = m_volumes.check_outside_state(m_config, partlyOut, fullyOut); | ||||
| 
 | ||||
| #if ENABLE_WARNING_TEXTURE_REMOVAL | ||||
|         _set_warning_notification(EWarning::ObjectClashed, state == ModelInstancePVS_Partly_Outside); | ||||
|         _set_warning_notification(EWarning::ObjectOutside, state == ModelInstancePVS_Fully_Outside); | ||||
|         if (printer_technology != ptSLA || state == ModelInstancePVS_Inside) | ||||
|         _set_warning_notification(EWarning::ObjectClashed, partlyOut); | ||||
|         _set_warning_notification(EWarning::ObjectOutside, fullyOut); | ||||
|         if (printer_technology != ptSLA || !contained_min_one) | ||||
|             _set_warning_notification(EWarning::SlaSupportsOutside, false); | ||||
| #else | ||||
|         _set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstancePVS_Partly_Outside); | ||||
|         _set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstancePVS_Fully_Outside); | ||||
|         if(printer_technology != ptSLA || state == ModelInstancePVS_Inside) | ||||
|         _set_warning_texture(WarningTexture::ObjectClashed, partlyOut); | ||||
|         _set_warning_texture(WarningTexture::ObjectOutside, fullyOut); | ||||
|         if(printer_technology != ptSLA || !contained_min_one) | ||||
|             _set_warning_texture(WarningTexture::SlaSupportsOutside, false); | ||||
| #endif // ENABLE_WARNING_TEXTURE_REMOVAL
 | ||||
| 
 | ||||
|         post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS,  | ||||
|                                contained_min_one && !m_model->objects.empty() && state != ModelInstancePVS_Partly_Outside)); | ||||
|                                contained_min_one && !m_model->objects.empty() && !partlyOut)); | ||||
|     } | ||||
|     else { | ||||
| #if ENABLE_WARNING_TEXTURE_REMOVAL | ||||
|  | @ -2442,13 +2441,13 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) | |||
|     if (!m_initialized) | ||||
|         return; | ||||
| 
 | ||||
|     // FIXME
 | ||||
|     m_dirty |= m_main_toolbar.update_items_state(); | ||||
|     m_dirty |= m_undoredo_toolbar.update_items_state(); | ||||
|     m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); | ||||
|     m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); | ||||
|     bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); | ||||
|     m_dirty |= mouse3d_controller_applied; | ||||
|     m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); | ||||
| 
 | ||||
|     if (!m_dirty) | ||||
|         return; | ||||
|  | @ -2986,30 +2985,39 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt) | |||
| 
 | ||||
| void GLCanvas3D::on_render_timer(wxTimerEvent& evt) | ||||
| { | ||||
|     // If slicer is not top window -> restart timer with one second to try again
 | ||||
|     wxWindow* p = dynamic_cast<wxWindow*>(wxGetApp().plater()); | ||||
|     while (p->GetParent() != nullptr) | ||||
|         p = p->GetParent(); | ||||
|     wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p); | ||||
|     if (!top_level_wnd->IsActive()) { | ||||
|         request_extra_frame_delayed(1000); | ||||
|         return; | ||||
|     } | ||||
|     //render();
 | ||||
|     m_dirty = true; | ||||
|     wxWakeUpIdle(); | ||||
|     // no need to do anything here
 | ||||
|     // right after this event is recieved, idle event is fired
 | ||||
| 
 | ||||
|     //m_dirty = true;
 | ||||
|     //wxWakeUpIdle();  
 | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::request_extra_frame_delayed(int miliseconds) | ||||
| 
 | ||||
| void GLCanvas3D::schedule_extra_frame(int miliseconds) | ||||
| { | ||||
|     // Schedule idle event right now
 | ||||
|     if (miliseconds == 0) | ||||
|     { | ||||
|         // We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait
 | ||||
|         if (m_in_render) | ||||
|             miliseconds = 33; | ||||
|         else { | ||||
|             m_dirty = true; | ||||
|             wxWakeUpIdle(); | ||||
|             return; | ||||
|         } | ||||
|     }  | ||||
|     // Start timer
 | ||||
|     int64_t now = timestamp_now(); | ||||
|     // Timer is not running
 | ||||
|     if (! m_render_timer.IsRunning()) { | ||||
|         m_extra_frame_requested_delayed = miliseconds; | ||||
|         m_render_timer.StartOnce(miliseconds); | ||||
|         m_render_timer_start = now; | ||||
|     // Timer is running - restart only if new period is shorter than remaning period
 | ||||
|     } else { | ||||
|         const int64_t remaining_time = (m_render_timer_start + m_extra_frame_requested_delayed) - now; | ||||
|         if (miliseconds < remaining_time) { | ||||
|         if (miliseconds + 20 < remaining_time) { | ||||
|             m_render_timer.Stop();  | ||||
|             m_extra_frame_requested_delayed = miliseconds; | ||||
|             m_render_timer.StartOnce(miliseconds); | ||||
|  |  | |||
|  | @ -743,7 +743,8 @@ public: | |||
|     void msw_rescale(); | ||||
| 
 | ||||
|     void request_extra_frame() { m_extra_frame_requested = true; } | ||||
|     void request_extra_frame_delayed(int miliseconds); | ||||
|      | ||||
|     void schedule_extra_frame(int miliseconds); | ||||
| 
 | ||||
|     int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); } | ||||
|     void force_main_toolbar_left_action(int item_id) { m_main_toolbar.force_left_action(item_id, *this); } | ||||
|  |  | |||
|  | @ -1978,6 +1978,11 @@ wxNotebook* GUI_App::tab_panel() const | |||
|     return mainframe->m_tabpanel; | ||||
| } | ||||
| 
 | ||||
| NotificationManager* GUI_App::notification_manager()  | ||||
| { | ||||
|     return plater_->get_notification_manager(); | ||||
| } | ||||
| 
 | ||||
| // extruders count from selected printer preset
 | ||||
| int GUI_App::extruders_cnt() const | ||||
| { | ||||
|  |  | |||
|  | @ -43,6 +43,7 @@ class ObjectSettings; | |||
| class ObjectList; | ||||
| class ObjectLayers; | ||||
| class Plater; | ||||
| class NotificationManager; | ||||
| struct GUI_InitParams; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -226,14 +227,14 @@ public: | |||
|     void            MacOpenFiles(const wxArrayString &fileNames) override; | ||||
| #endif /* __APPLE */ | ||||
| 
 | ||||
|     Sidebar&            sidebar(); | ||||
|     ObjectManipulation* obj_manipul(); | ||||
|     ObjectSettings*     obj_settings(); | ||||
|     ObjectList*         obj_list(); | ||||
|     ObjectLayers*       obj_layers(); | ||||
|     Plater*             plater(); | ||||
|     Model&      		model(); | ||||
| 
 | ||||
|     Sidebar&             sidebar(); | ||||
|     ObjectManipulation*  obj_manipul(); | ||||
|     ObjectSettings*      obj_settings(); | ||||
|     ObjectList*          obj_list(); | ||||
|     ObjectLayers*        obj_layers(); | ||||
|     Plater*              plater(); | ||||
|     Model&      		 model(); | ||||
|     NotificationManager* notification_manager(); | ||||
| 
 | ||||
|     // Parameters extracted from the command line to be passed to GUI after initialization.
 | ||||
|     const GUI_InitParams* init_params { nullptr }; | ||||
|  |  | |||
							
								
								
									
										1027
									
								
								src/slic3r/GUI/GUI_Factories.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1027
									
								
								src/slic3r/GUI/GUI_Factories.cpp
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										104
									
								
								src/slic3r/GUI/GUI_Factories.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/slic3r/GUI/GUI_Factories.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,104 @@ | |||
| #ifndef slic3r_GUI_Factories_hpp_ | ||||
| #define slic3r_GUI_Factories_hpp_ | ||||
| 
 | ||||
| #include <map> | ||||
| #include <vector> | ||||
| #include <array> | ||||
| 
 | ||||
| #include <wx/bitmap.h> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| 
 | ||||
| class wxMenu; | ||||
| class wxMenuItem; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| enum class ModelVolumeType : int; | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| struct SettingsFactory | ||||
| { | ||||
| //				     category ->       vector ( option )
 | ||||
|     typedef std::map<std::string, std::vector<std::string>> Bundle; | ||||
|     static std::map<std::string, std::string>               CATEGORY_ICON; | ||||
| 
 | ||||
|     static wxBitmap                             get_category_bitmap(const std::string& category_name); | ||||
|     static Bundle                               get_bundle(const DynamicPrintConfig* config, bool is_object_settings); | ||||
|     static std::vector<std::string>             get_options(bool is_part); | ||||
| }; | ||||
| 
 | ||||
| class MenuFactory | ||||
| { | ||||
| public: | ||||
|     static std::vector<std::pair<std::string, std::string>> ADD_VOLUME_MENU_ITEMS; | ||||
|     static std::vector<wxBitmap>    get_volume_bitmaps(); | ||||
| 
 | ||||
|     MenuFactory(); | ||||
|     ~MenuFactory() = default; | ||||
| 
 | ||||
|     void    init(wxWindow* parent); | ||||
|     void    update_object_menu(); | ||||
|     void    msw_rescale(); | ||||
| 
 | ||||
|     wxMenu* default_menu(); | ||||
|     wxMenu* object_menu(); | ||||
|     wxMenu* sla_object_menu(); | ||||
|     wxMenu* part_menu(); | ||||
|     wxMenu* instance_menu(); | ||||
|     wxMenu* layer_menu(); | ||||
|     wxMenu* multi_selection_menu(); | ||||
| 
 | ||||
| private: | ||||
|     enum MenuType { | ||||
|         mtObjectFFF = 0, | ||||
|         mtObjectSLA, | ||||
|         mtCount | ||||
|     }; | ||||
| 
 | ||||
|     wxWindow* m_parent {nullptr}; | ||||
| 
 | ||||
|     MenuWithSeparators m_object_menu; | ||||
|     MenuWithSeparators m_part_menu; | ||||
|     MenuWithSeparators m_sla_object_menu; | ||||
|     MenuWithSeparators m_default_menu; | ||||
|     MenuWithSeparators m_instance_menu; | ||||
| 
 | ||||
|     // Removed/Prepended Items according to the view mode
 | ||||
|     std::array<wxMenuItem*, mtCount> items_increase; | ||||
|     std::array<wxMenuItem*, mtCount> items_decrease; | ||||
|     std::array<wxMenuItem*, mtCount> items_set_number_of_copies; | ||||
| 
 | ||||
|     void        create_default_menu(); | ||||
|     void        create_common_object_menu(wxMenu *menu); | ||||
|     void        create_object_menu(); | ||||
|     void        create_sla_object_menu(); | ||||
|     void        create_part_menu(); | ||||
| 
 | ||||
|     wxMenu*     append_submenu_add_generic(wxMenu* menu, ModelVolumeType type); | ||||
|     void        append_menu_items_add_volume(wxMenu* menu); | ||||
|     wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); | ||||
|     wxMenuItem* append_menu_item_settings(wxMenu* menu); | ||||
|     wxMenuItem* append_menu_item_change_type(wxMenu* menu); | ||||
|     wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu); | ||||
|     wxMenuItem* append_menu_item_printable(wxMenu* menu); | ||||
|     void        append_menu_items_osx(wxMenu* menu); | ||||
|     wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); | ||||
|     void        append_menu_item_export_stl(wxMenu* menu); | ||||
|     void        append_menu_item_reload_from_disk(wxMenu* menu); | ||||
|     void        append_menu_item_change_extruder(wxMenu* menu); | ||||
|     void        append_menu_item_delete(wxMenu* menu); | ||||
|     void        append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); | ||||
|     void        append_menu_items_convert_unit(wxMenu* menu, int insert_pos = 1); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk"
 | ||||
|     void        append_menu_item_merge_to_multipart_object(wxMenu *menu); | ||||
| //    void        append_menu_item_merge_to_single_object(wxMenu *menu);
 | ||||
|     void        append_menu_items_mirror(wxMenu *menu); | ||||
|     void        append_menu_items_instance_manipulation(wxMenu *menu); | ||||
|     void        update_menu_items_instance_manipulation(MenuType type); | ||||
| }; | ||||
| 
 | ||||
| }} | ||||
| 
 | ||||
| #endif //slic3r_GUI_Factories_hpp_
 | ||||
|  | @ -9,6 +9,7 @@ | |||
| #include "slic3r/GUI/format.hpp" | ||||
| #include "slic3r/GUI/MainFrame.hpp" | ||||
| #include "slic3r/GUI/Plater.hpp" | ||||
| #include "slic3r/Utils/Platform.hpp" | ||||
| 
 | ||||
| // To show a message box if GUI initialization ends up with an exception thrown.
 | ||||
| #include <wx/msgdlg.h> | ||||
|  | @ -36,6 +37,8 @@ int GUI_Run(GUI_InitParams ¶ms) | |||
|     signal(SIGCHLD, SIG_DFL); | ||||
| #endif // __APPLE__
 | ||||
| 
 | ||||
|     detect_platform(); | ||||
| 
 | ||||
|     try { | ||||
|         GUI::GUI_App* gui = new GUI::GUI_App(params.start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); | ||||
|         if (gui->get_app_mode() != GUI::GUI_App::EAppMode::GCodeViewer) { | ||||
|  |  | |||
|  | @ -260,16 +260,15 @@ void ObjectLayers::msw_rescale() | |||
|                     editor->msw_rescale(); | ||||
|             } | ||||
| 
 | ||||
|             const std::vector<size_t> btns = {2, 3};  // del_btn, add_btn
 | ||||
|             for (auto btn : btns) | ||||
|             { | ||||
|                 wxSizerItem* b_item = item->GetSizer()->GetItem(btn); | ||||
|                 if (b_item->IsWindow()) { | ||||
|                     auto button = dynamic_cast<PlusMinusButton*>(b_item->GetWindow()); | ||||
|                     if (button != nullptr) | ||||
|                         button->msw_rescale(); | ||||
|                 }                 | ||||
|             } | ||||
|             if (item->GetSizer()->GetItemCount() > 2) // if there are Add/Del buttons
 | ||||
|                 for (size_t btn : {2, 3}) { // del_btn, add_btn
 | ||||
|                     wxSizerItem* b_item = item->GetSizer()->GetItem(btn); | ||||
|                     if (b_item->IsWindow()) { | ||||
|                         auto button = dynamic_cast<PlusMinusButton*>(b_item->GetWindow()); | ||||
|                         if (button != nullptr) | ||||
|                             button->msw_rescale(); | ||||
|                     } | ||||
|                 } | ||||
|         } | ||||
|     } | ||||
|     m_grid_sizer->Layout(); | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -31,18 +31,11 @@ class TriangleMesh; | |||
| enum class ModelVolumeType : int; | ||||
| 
 | ||||
| // FIXME: broken build on mac os because of this is missing:
 | ||||
| typedef std::vector<std::string>    t_config_option_keys; | ||||
| 
 | ||||
| typedef std::map<std::string, std::vector<std::string>> SettingsBundle; | ||||
| 
 | ||||
| //				  category ->		vector 			 ( option	;  label )
 | ||||
| typedef std::map< std::string, std::vector< std::pair<std::string, std::string> > > settings_menu_hierarchy; | ||||
| 
 | ||||
| typedef std::vector<ModelVolume*> ModelVolumePtrs; | ||||
| 
 | ||||
| typedef double                                       coordf_t; | ||||
| typedef std::pair<coordf_t, coordf_t>                t_layer_height_range; | ||||
| typedef std::map<t_layer_height_range, ModelConfig>  t_layer_config_ranges; | ||||
| typedef std::vector<std::string>                    t_config_option_keys; | ||||
| typedef std::vector<ModelVolume*>                   ModelVolumePtrs; | ||||
| typedef double                                      coordf_t; | ||||
| typedef std::pair<coordf_t, coordf_t>               t_layer_height_range; | ||||
| typedef std::map<t_layer_height_range, ModelConfig> t_layer_config_ranges; | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
|  | @ -106,7 +99,7 @@ public: | |||
| 
 | ||||
| private: | ||||
|     SELECTION_MODE  m_selection_mode {smUndef}; | ||||
|     int             m_selected_layers_range_idx; | ||||
|     int             m_selected_layers_range_idx {-1}; | ||||
| 
 | ||||
|     Clipboard       m_clipboard; | ||||
| 
 | ||||
|  | @ -147,23 +140,6 @@ private: | |||
|     } m_dragged_data; | ||||
| 
 | ||||
|     wxBoxSizer          *m_sizer {nullptr}; | ||||
|     wxWindow            *m_parent {nullptr}; | ||||
| 
 | ||||
|     ScalableBitmap	    m_bmp_modifiermesh; | ||||
|     ScalableBitmap	    m_bmp_solidmesh; | ||||
|     ScalableBitmap	    m_bmp_support_enforcer; | ||||
|     ScalableBitmap	    m_bmp_support_blocker; | ||||
|     ScalableBitmap	    m_bmp_manifold_warning; | ||||
|     ScalableBitmap	    m_bmp_cog; | ||||
| 
 | ||||
|     MenuWithSeparators  m_menu_object; | ||||
|     MenuWithSeparators  m_menu_part; | ||||
|     MenuWithSeparators  m_menu_sla_object; | ||||
|     MenuWithSeparators  m_menu_instance; | ||||
|     MenuWithSeparators  m_menu_layer; | ||||
|     MenuWithSeparators  m_menu_default; | ||||
|     wxMenuItem* m_menu_item_settings { nullptr }; | ||||
|     wxMenuItem* m_menu_item_split_instances { nullptr }; | ||||
| 
 | ||||
|     ObjectDataViewModel         *m_objects_model{ nullptr }; | ||||
|     ModelConfig                 *m_config {nullptr}; | ||||
|  | @ -185,7 +161,6 @@ private: | |||
|                                                            // update_settings_items - updating canvas selection is undesirable,
 | ||||
|                                                            // because it would turn off the gizmos (mainly a problem for the SLA gizmo)
 | ||||
| 
 | ||||
|     int         m_selected_row = 0; | ||||
|     wxDataViewItem m_last_selected_item {nullptr}; | ||||
| #ifdef __WXMSW__ | ||||
|     // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
 | ||||
|  | @ -193,8 +168,8 @@ private: | |||
| #endif /* __MSW__ */ | ||||
| 
 | ||||
| #if 0 | ||||
|     SettingsBundle m_freq_settings_fff; | ||||
|     SettingsBundle m_freq_settings_sla; | ||||
|     SettingsFactory::Bundle m_freq_settings_fff; | ||||
|     SettingsFactory::Bundle m_freq_settings_sla; | ||||
| #endif | ||||
| 
 | ||||
|     size_t    m_items_count { size_t(-1) }; | ||||
|  | @ -212,8 +187,6 @@ public: | |||
|     void set_min_height(); | ||||
|     void update_min_height(); | ||||
| 
 | ||||
|     std::map<std::string, wxBitmap> CATEGORY_ICON; | ||||
| 
 | ||||
|     ObjectDataViewModel*        GetModel() const    { return m_objects_model; } | ||||
|     ModelConfig*                config() const      { return m_config; } | ||||
|     std::vector<ModelObject*>*  objects() const     { return m_objects; } | ||||
|  | @ -221,7 +194,6 @@ public: | |||
|     ModelObject*                object(const int obj_idx) const ; | ||||
| 
 | ||||
|     void                create_objects_ctrl(); | ||||
|     void                create_popup_menus(); | ||||
|     void                update_objects_list_extruder_column(size_t extruders_count); | ||||
|     void                update_extruder_colors(); | ||||
|     // show/hide "Extruder" column for Objects List
 | ||||
|  | @ -232,9 +204,6 @@ public: | |||
|     void                update_name_in_model(const wxDataViewItem& item) const; | ||||
|     void                update_extruder_values_for_items(const size_t max_extruder); | ||||
| 
 | ||||
|     void                init_icons(); | ||||
|     void                msw_rescale_icons(); | ||||
| 
 | ||||
|     // Get obj_idx and vol_idx values for the selected (by default) or an adjusted item
 | ||||
|     void                get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& item = wxDataViewItem(0)); | ||||
|     void                get_selection_indexes(std::vector<int>& obj_idxs, std::vector<int>& vol_idxs); | ||||
|  | @ -264,39 +233,11 @@ public: | |||
|     void                increase_instances(); | ||||
|     void                decrease_instances(); | ||||
| 
 | ||||
|     void                get_settings_choice(const wxString& category_name); | ||||
|     void                get_freq_settings_choice(const wxString& bundle_name); | ||||
|     void                add_category_to_settings_from_selection(const std::vector< std::pair<std::string, bool> >& category_options, wxDataViewItem item); | ||||
|     void                add_category_to_settings_from_frequent(const std::vector<std::string>& category_options, wxDataViewItem item); | ||||
|     void                show_settings(const wxDataViewItem settings_item); | ||||
|     bool                is_instance_or_object_selected(); | ||||
| 
 | ||||
|     wxMenu*             append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type); | ||||
|     void                append_menu_items_add_volume(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_split(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_layers_editing(wxMenu* menu, wxWindow* parent); | ||||
|     wxMenuItem*         append_menu_item_settings(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_change_type(wxMenu* menu, wxWindow* parent = nullptr); | ||||
|     wxMenuItem*         append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); | ||||
|     wxMenuItem*         append_menu_item_printable(wxMenu* menu, wxWindow* parent); | ||||
|     void                append_menu_items_osx(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_fix_through_netfabb(wxMenu* menu); | ||||
|     void                append_menu_item_export_stl(wxMenu* menu) const; | ||||
|     void                append_menu_item_reload_from_disk(wxMenu* menu) const; | ||||
|     void                append_menu_item_change_extruder(wxMenu* menu); | ||||
|     void                append_menu_item_delete(wxMenu* menu); | ||||
|     void                append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); | ||||
|     void                append_menu_items_convert_unit(wxMenu* menu, int insert_pos = 1); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk"
 | ||||
|     void                append_menu_item_merge_to_multipart_object(wxMenu *menu); | ||||
|     void                append_menu_item_merge_to_single_object(wxMenu *menu); | ||||
|     void                create_object_popupmenu(wxMenu *menu); | ||||
|     void                create_sla_object_popupmenu(wxMenu*menu); | ||||
|     void                create_part_popupmenu(wxMenu*menu); | ||||
|     void                create_instance_popupmenu(wxMenu*menu); | ||||
|     void                create_default_popupmenu(wxMenu *menu); | ||||
|     wxMenu*             create_settings_popupmenu(wxMenu *parent_menu); | ||||
|     void                create_freq_settings_popupmenu(wxMenu *parent_menu, const bool is_object_settings = true); | ||||
| 
 | ||||
|     void                update_opt_keys(t_config_option_keys& t_optopt_keys, const bool is_object); | ||||
| 
 | ||||
|     void                load_subobject(ModelVolumeType type); | ||||
|     void                load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type); | ||||
| 	void                load_generic_subobject(const std::string& type_name, const ModelVolumeType type); | ||||
|  | @ -318,7 +259,7 @@ public: | |||
| 
 | ||||
|     DynamicPrintConfig  get_default_layer_config(const int obj_idx); | ||||
|     bool                get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); | ||||
|     bool                is_splittable(); | ||||
|     bool                is_splittable(bool to_objects); | ||||
|     bool                selected_instances_of_same_object(); | ||||
|     bool                can_split_instances(); | ||||
|     bool                can_merge_to_multipart_object() const; | ||||
|  | @ -328,7 +269,6 @@ public: | |||
|     wxBoxSizer*         get_sizer() {return  m_sizer;} | ||||
|     int                 get_selected_obj_idx() const; | ||||
|     ModelConfig&        get_item_config(const wxDataViewItem& item) const; | ||||
|     SettingsBundle      get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_object_settings); | ||||
| 
 | ||||
|     void                changed_object(const int obj_idx = -1) const; | ||||
|     void                part_selection_changed(); | ||||
|  | @ -404,11 +344,9 @@ public: | |||
|     void change_part_type(); | ||||
| 
 | ||||
|     void last_volume_is_deleted(const int obj_idx); | ||||
|     void update_settings_items(); | ||||
|     void update_and_show_object_settings_item(); | ||||
|     void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); | ||||
|     void update_object_list_by_printer_technology(); | ||||
|     void update_object_menu(); | ||||
| 
 | ||||
|     void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx); | ||||
|     void instances_to_separated_objects(const int obj_idx); | ||||
|  | @ -433,7 +371,7 @@ public: | |||
|     void update_printable_state(int obj_idx, int instance_idx); | ||||
|     void toggle_printable_state(wxDataViewItem item); | ||||
| 
 | ||||
|     void show_multi_selection_menu(); | ||||
|     void set_extruder_for_selected_items(const int extruder) const ; | ||||
| 
 | ||||
| private: | ||||
| #ifdef __WXOSX__ | ||||
|  | @ -454,11 +392,6 @@ private: | |||
| #endif /* __WXMSW__ */ | ||||
|     void OnEditingDone(wxDataViewEvent &event); | ||||
|     void extruder_selection(); | ||||
|     void set_extruder_for_selected_items(const int extruder) const ; | ||||
| 
 | ||||
|     std::vector<std::string>        get_options(const bool is_part); | ||||
|     const std::vector<std::string>& get_options_for_bundle(const wxString& bundle_name); | ||||
|     void                            get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| #include "GUI_ObjectSettings.hpp" | ||||
| #include "GUI_ObjectList.hpp" | ||||
| #include "GUI_Factories.hpp" | ||||
| 
 | ||||
| #include "OptionsGroup.hpp" | ||||
| #include "GUI_App.hpp" | ||||
|  | @ -83,7 +84,7 @@ bool ObjectSettings::update_settings_list() | |||
|         return false; | ||||
| 
 | ||||
|     const bool is_object_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itObject; | ||||
| 	SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(&config->get(), is_object_settings); | ||||
|     SettingsFactory::Bundle cat_options = SettingsFactory::get_bundle(&config->get(), is_object_settings); | ||||
| 
 | ||||
|     if (!cat_options.empty()) | ||||
|     { | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| #include "libslic3r/libslic3r.h" | ||||
| #include "libslic3r/Layer.hpp" | ||||
| #include "GUI_Preview.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| #include "GUI.hpp" | ||||
|  | @ -24,6 +25,7 @@ | |||
| // this include must follow the wxWidgets ones or it won't compile on Windows -> see http://trac.wxwidgets.org/ticket/2421
 | ||||
| #include "libslic3r/Print.hpp" | ||||
| #include "libslic3r/SLAPrint.hpp" | ||||
| #include "NotificationManager.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
|  | @ -639,6 +641,53 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee | |||
|     else | ||||
|         m_layers_slider->SetLayersTimes(m_gcode_result->time_statistics.modes.front().layers_times); | ||||
| 
 | ||||
|     // Suggest the auto color change, if model looks like sign
 | ||||
|     if (ticks_info_from_model.gcodes.empty()) | ||||
|     { | ||||
|         NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); | ||||
|         notif_mngr->close_notification_of_type(NotificationType::SignDetected); | ||||
| 
 | ||||
|         const Print& print = wxGetApp().plater()->fff_print(); | ||||
|         double delta_area = scale_(scale_(25)); // equal to 25 mm2
 | ||||
| 
 | ||||
|         //bool is_possible_auto_color_change = false;
 | ||||
|         for (auto object : print.objects()) { | ||||
|             double height = object->height(); | ||||
|             coord_t longer_side = std::max(object->size().x(), object->size().y()); | ||||
|             if (height / longer_side > 0.3) | ||||
|                 continue; | ||||
| 
 | ||||
|             const ExPolygons& bottom = object->get_layer(0)->lslices; | ||||
|             if (bottom.size() > 1 || !bottom[0].holes.empty()) | ||||
|                 continue; | ||||
| 
 | ||||
|             double bottom_area = area(bottom); | ||||
|             int i; | ||||
|             for (i = 1; i < int(0.3 * object->layers().size()); i++) | ||||
|                 if (area(object->get_layer(1)->lslices) != bottom_area) | ||||
|                     break; | ||||
|             if (i < int(0.3 * object->layers().size())) | ||||
|                 continue; | ||||
| 
 | ||||
|             double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); | ||||
|             if( bottom_area - top_area > delta_area) { | ||||
|                 notif_mngr->push_notification( | ||||
|                     NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotification, | ||||
|                     _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", | ||||
|                     _u8L("Apply auto color change to print"), | ||||
|                     [this, notif_mngr](wxEvtHandler*) { | ||||
|                         notif_mngr->close_notification_of_type(NotificationType::SignDetected); | ||||
|                         m_layers_slider->auto_color_change(); | ||||
|                         return true; | ||||
|                     }); | ||||
| 
 | ||||
|                 notif_mngr->set_in_preview(true); | ||||
| 
 | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_layers_slider_sizer->Show((size_t)0); | ||||
|     Layout(); | ||||
| } | ||||
|  |  | |||
|  | @ -200,12 +200,20 @@ void HollowedMesh::on_update() | |||
|         if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { | ||||
|             size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; | ||||
|             if (timestamp > m_old_hollowing_timestamp) { | ||||
|                 const TriangleMesh& backend_mesh = print_object->get_mesh_to_print(); | ||||
|                 const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); | ||||
|                 if (! backend_mesh.empty()) { | ||||
|                     m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); | ||||
|                     Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse(); | ||||
|                     m_hollowed_mesh_transformed->transform(trafo_inv); | ||||
|                     m_old_hollowing_timestamp = timestamp; | ||||
| 
 | ||||
|                     const TriangleMesh &interior = print_object->hollowed_interior_mesh(); | ||||
|                     if (!interior.empty()) { | ||||
|                         m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(interior); | ||||
|                         m_hollowed_interior_transformed->repaired = false; | ||||
|                         m_hollowed_interior_transformed->repair(true); | ||||
|                         m_hollowed_interior_transformed->transform(trafo_inv); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                     m_hollowed_mesh_transformed.reset(nullptr); | ||||
|  | @ -230,6 +238,10 @@ const TriangleMesh* HollowedMesh::get_hollowed_mesh() const | |||
|     return m_hollowed_mesh_transformed.get(); | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh* HollowedMesh::get_hollowed_interior() const | ||||
| { | ||||
|     return m_hollowed_interior_transformed.get(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | @ -306,6 +318,10 @@ void ObjectClipper::on_update() | |||
|             m_clippers.back()->set_mesh(*mesh); | ||||
|         } | ||||
|         m_old_meshes = meshes; | ||||
| 
 | ||||
|         if (has_hollowed) | ||||
|             m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); | ||||
| 
 | ||||
|         m_active_inst_bb_radius = | ||||
|             mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); | ||||
|         //if (has_hollowed && m_clp_ratio != 0.)
 | ||||
|  |  | |||
|  | @ -199,6 +199,7 @@ public: | |||
| #endif // NDEBUG
 | ||||
| 
 | ||||
|     const TriangleMesh* get_hollowed_mesh() const; | ||||
|     const TriangleMesh* get_hollowed_interior() const; | ||||
| 
 | ||||
| protected: | ||||
|     void on_update() override; | ||||
|  | @ -206,6 +207,7 @@ protected: | |||
| 
 | ||||
| private: | ||||
|     std::unique_ptr<TriangleMesh> m_hollowed_mesh_transformed; | ||||
|     std::unique_ptr<TriangleMesh> m_hollowed_interior_transformed; | ||||
|     size_t m_old_hollowing_timestamp = 0; | ||||
|     int m_print_object_idx = -1; | ||||
|     int m_print_objects_count = 0; | ||||
|  |  | |||
|  | @ -51,7 +51,9 @@ static const std::map<const char, std::string> font_icons_large = { | |||
|     {ImGui::EjectButton            , "notification_eject_sd"         }, | ||||
|     {ImGui::EjectHoverButton       , "notification_eject_sd_hover"   }, | ||||
|     {ImGui::WarningMarker          , "notification_warning"          }, | ||||
|     {ImGui::ErrorMarker            , "notification_error"            } | ||||
|     {ImGui::ErrorMarker            , "notification_error"            }, | ||||
|     {ImGui::CancelButton           , "notification_cancel"           }, | ||||
|     {ImGui::CancelHoverButton      , "notification_cancel_hover"     }, | ||||
| }; | ||||
| 
 | ||||
| const ImVec4 ImGuiWrapper::COL_GREY_DARK         = { 0.333f, 0.333f, 0.333f, 1.0f }; | ||||
|  |  | |||
|  | @ -562,8 +562,6 @@ void MainFrame::init_tabpanel() | |||
| 
 | ||||
|     wxGetApp().plater_ = m_plater; | ||||
| 
 | ||||
|     wxGetApp().obj_list()->create_popup_menus(); | ||||
| 
 | ||||
|     if (wxGetApp().is_editor()) | ||||
|         create_preset_tabs(); | ||||
| 
 | ||||
|  | @ -1219,6 +1217,9 @@ void MainFrame::init_menubar_as_editor() | |||
|         append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse sidebar") + sep + "Shift+" + sep_space + "Tab", _L("Collapse sidebar"), | ||||
|             [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, | ||||
|             []() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); | ||||
|         append_menu_check_item(viewMenu, wxID_ANY, _L("&Full screen") + "\t" + "F11", _L("Full screen"), | ||||
|             [this](wxCommandEvent&) { this->ShowFullScreen(!this->IsFullScreen()); }, this, | ||||
|             []() { return true; }, [this]() { return this->IsFullScreen(); }, this); | ||||
|     } | ||||
| 
 | ||||
|     // Help menu
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| #include "libslic3r/Tesselate.hpp" | ||||
| #include "libslic3r/TriangleMesh.hpp" | ||||
| #include "libslic3r/ClipperUtils.hpp" | ||||
| 
 | ||||
| #include "slic3r/GUI/Camera.hpp" | ||||
| 
 | ||||
|  | @ -31,6 +32,15 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) | ||||
| { | ||||
|     if (m_negative_mesh != &mesh) { | ||||
|         m_negative_mesh = &mesh; | ||||
|         m_triangles_valid = false; | ||||
|         m_triangles2d.resize(0); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void MeshClipper::set_transformation(const Geometry::Transformation& trafo) | ||||
|  | @ -74,6 +84,15 @@ void MeshClipper::recalculate_triangles() | |||
|     std::vector<ExPolygons> list_of_expolys; | ||||
|     m_tms->set_up_direction(up.cast<float>()); | ||||
|     m_tms->slice(std::vector<float>{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){}); | ||||
| 
 | ||||
|     if (m_negative_mesh && !m_negative_mesh->empty()) { | ||||
|         TriangleMeshSlicer negative_tms{m_negative_mesh}; | ||||
|         negative_tms.set_up_direction(up.cast<float>()); | ||||
| 
 | ||||
|         std::vector<ExPolygons> neg_polys; | ||||
|         negative_tms.slice(std::vector<float>{height_mesh}, SlicingMode::Regular, 0.f, &neg_polys, [](){}); | ||||
|         list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front()); | ||||
|     } | ||||
|     m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); | ||||
| 
 | ||||
|     // Rotate the cut into world coords:
 | ||||
|  |  | |||
|  | @ -78,6 +78,8 @@ public: | |||
|     // must make sure that it stays valid.
 | ||||
|     void set_mesh(const TriangleMesh& mesh); | ||||
| 
 | ||||
|     void set_negative_mesh(const TriangleMesh &mesh); | ||||
| 
 | ||||
|     // Inform the MeshClipper about the transformation that transforms the mesh
 | ||||
|     // into world coordinates.
 | ||||
|     void set_transformation(const Geometry::Transformation& trafo); | ||||
|  | @ -91,6 +93,7 @@ private: | |||
| 
 | ||||
|     Geometry::Transformation m_trafo; | ||||
|     const TriangleMesh* m_mesh = nullptr; | ||||
|     const TriangleMesh* m_negative_mesh = nullptr; | ||||
|     ClippingPlane m_plane; | ||||
|     std::vector<Vec2f> m_triangles2d; | ||||
|     GLIndexedVertexArray m_vertex_array; | ||||
|  |  | |||
|  | @ -1,11 +1,9 @@ | |||
| #include "NotificationManager.hpp" | ||||
| 
 | ||||
| #include "GUI_App.hpp" | ||||
| #include "GUI.hpp" | ||||
| #include "Plater.hpp" | ||||
| #include "GLCanvas3D.hpp" | ||||
| #include "ImGuiWrapper.hpp" | ||||
| 
 | ||||
| #include "GUI.hpp" | ||||
| #include "ImGuiWrapper.hpp" | ||||
| #include "PrintHostDialogs.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| 
 | ||||
| #include <boost/algorithm/string.hpp> | ||||
|  | @ -22,9 +20,6 @@ static constexpr float SPACE_RIGHT_PANEL = 10.0f; | |||
| static constexpr float FADING_OUT_DURATION = 2.0f; | ||||
| // Time in Miliseconds after next render when fading out is requested
 | ||||
| static constexpr int   FADING_OUT_TIMEOUT = 100; | ||||
| // If timeout is changed to higher than 1 second, substract_time call should be revorked
 | ||||
| //static constexpr int   MAX_TIMEOUT_MILISECONDS = 1000; 
 | ||||
| //static constexpr int   MAX_TIMEOUT_SECONDS = 1;
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
|  | @ -131,35 +126,35 @@ void NotificationManager::NotificationIDProvider::release_id(int) {} | |||
| NotificationManager::PopNotification::PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler) : | ||||
| 	  m_data                (n) | ||||
| 	, m_id_provider   		(id_provider) | ||||
| 	, 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) | ||||
| 	, m_notification_start  (GLCanvas3D::timestamp_now()) | ||||
| { | ||||
| 	//init();
 | ||||
| } | ||||
| {} | ||||
| 
 | ||||
| void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) | ||||
| { | ||||
| 	if (!m_initialized) | ||||
| 
 | ||||
| 	if (m_state == EState::Unknown) | ||||
| 		init(); | ||||
| 
 | ||||
| 	if (m_hidden) { | ||||
| 	if (m_state == EState::Hidden) { | ||||
| 		m_top_y = initial_y - GAP_WIDTH; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (m_fading_out)  | ||||
| 		m_last_render_fading = GLCanvas3D::timestamp_now(); | ||||
| 	if (m_state == EState::ClosePending || m_state == EState::Finished) | ||||
| 	{ | ||||
| 		m_state = EState::Finished; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	Size cnv_size = canvas.get_canvas_size(); | ||||
| 	Size          cnv_size = canvas.get_canvas_size(); | ||||
| 	ImGuiWrapper& imgui = *wxGetApp().imgui(); | ||||
| 	ImVec2 mouse_pos = ImGui::GetMousePos(); | ||||
| 	float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0); | ||||
| 	ImVec2        mouse_pos = ImGui::GetMousePos(); | ||||
| 	float         right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0); | ||||
| 	bool          fading_pop = false; | ||||
| 
 | ||||
| 	if (m_line_height != ImGui::CalcTextSize("A").y) | ||||
| 		init(); | ||||
|  | @ -174,54 +169,46 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init | |||
| 	imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); | ||||
| 
 | ||||
| 	// find if hovered
 | ||||
| 	m_hovered = false; | ||||
| 	if (m_state == EState::Hovered)  | ||||
| 		m_state = EState::Shown; | ||||
| 
 | ||||
| 	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(); | ||||
| 		m_hovered = true; | ||||
| 		m_state = EState::Hovered; | ||||
| 	} | ||||
| 
 | ||||
| 	 | ||||
| 	// color change based on fading out
 | ||||
| 	bool fading_pop = false; | ||||
| 	if (m_fading_out) { | ||||
| 		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); | ||||
| 	if (m_state == EState::FadingOut) { | ||||
| 		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), true, m_current_fade_opacity); | ||||
| 		Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), true, 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); | ||||
| 		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, 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); | ||||
| 		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, 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); | ||||
| 		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	} | ||||
| 
 | ||||
| 	// name of window - probably indentifies window and is shown so last_end add whitespaces according to id
 | ||||
| 	 | ||||
| 	// name of window indentifies window - has to be unique string
 | ||||
| 	if (m_id == 0) | ||||
| 		m_id = m_id_provider.allocate_id(); | ||||
| 	std::string name = "!!Ntfctn" + std::to_string(m_id); | ||||
| 	 | ||||
| 	if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar)) { | ||||
| 		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()); | ||||
| 		*/ | ||||
| 
 | ||||
| 		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); | ||||
|  | @ -253,9 +240,14 @@ void NotificationManager::PopNotification::count_spaces() | |||
| 	m_window_width_offset = m_left_indentation + m_line_height * 3.f; | ||||
| 	m_window_width = m_line_height * 25; | ||||
| } | ||||
|   | ||||
| void NotificationManager::PopNotification::init() | ||||
| { | ||||
|     std::string text          = m_text1 + " " + m_hypertext; | ||||
| 	// Do not init closing notification
 | ||||
| 	if (is_finished()) | ||||
| 		return; | ||||
|      | ||||
| 	std::string text          = m_text1 + " " + m_hypertext; | ||||
|     size_t      last_end      = 0; | ||||
|     m_lines_count = 0; | ||||
| 
 | ||||
|  | @ -306,7 +298,9 @@ void NotificationManager::PopNotification::init() | |||
| 	} | ||||
| 	if (m_lines_count == 3) | ||||
| 		m_multiline = true; | ||||
| 	m_initialized = true; | ||||
| 	m_notification_start = GLCanvas3D::timestamp_now(); | ||||
| 	//if (m_state != EState::Hidden)
 | ||||
| 	//	m_state = EState::Shown;
 | ||||
| } | ||||
| void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) | ||||
| {  | ||||
|  | @ -375,12 +369,14 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons | |||
| 			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] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); | ||||
| 			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; | ||||
| 			if (m_text1.length() > m_endlines[0]) { | ||||
| 				std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); | ||||
| 				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); | ||||
|  | @ -423,8 +419,8 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, | |||
| 			m_multiline = true; | ||||
| 			set_next_window_size(imgui); | ||||
| 		} | ||||
| 		else { | ||||
| 			m_close_pending = on_text_click(); | ||||
| 		else if (on_text_click()) { | ||||
| 			close(); | ||||
| 		} | ||||
| 	} | ||||
| 	ImGui::PopStyleColor(); | ||||
|  | @ -432,12 +428,12 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, | |||
| 	ImGui::PopStyleColor(); | ||||
| 
 | ||||
| 	//hover color
 | ||||
| 	ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f);//ImGui::GetStyleColorVec4(ImGuiCol_Button);
 | ||||
| 	ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); | ||||
| 	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); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	ImGui::SetCursorPosX(text_x); | ||||
| 	ImGui::SetCursorPosY(text_y); | ||||
| 	imgui.text(text.c_str()); | ||||
|  | @ -448,7 +444,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, | |||
| 	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)))); | ||||
| 	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_state == EState::FadingOut ? m_current_fade_opacity : 1.f)))); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  | @ -458,12 +454,11 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img | |||
| 	ImVec2 win_pos(win_pos_x, win_pos_y);  | ||||
| 	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); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, 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::CloseNotifButton; | ||||
| 	 | ||||
|  | @ -479,7 +474,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img | |||
| 	ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); | ||||
| 	if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) | ||||
| 	{ | ||||
| 		m_close_pending = true; | ||||
| 		close(); | ||||
| 	} | ||||
| 
 | ||||
| 	//invisible large button
 | ||||
|  | @ -487,7 +482,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img | |||
| 	ImGui::SetCursorPosY(0); | ||||
| 	if (imgui.button(" ", m_line_height * 2.125, win_size.y - ( m_minimize_b_visible ? 2 * m_line_height : 0))) | ||||
| 	{ | ||||
| 		m_close_pending = true; | ||||
| 		close(); | ||||
| 	} | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
|  | @ -510,9 +505,9 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& | |||
| { | ||||
| 	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); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 
 | ||||
| 	 | ||||
| 	//button - if part if treggered
 | ||||
|  | @ -564,71 +559,62 @@ bool NotificationManager::PopNotification::compare_text(const std::string& text) | |||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::PopNotification::update_state() | ||||
| bool NotificationManager::PopNotification::update_state(bool paused, const int64_t delta) | ||||
| { | ||||
| 	if (!m_initialized) | ||||
| 		init(); | ||||
| 
 | ||||
| 	m_next_render = std::numeric_limits<int64_t>::max(); | ||||
| 
 | ||||
| 	if (m_hidden) { | ||||
| 		m_state = EState::Hidden; | ||||
| 		return; | ||||
| 	if (m_state == EState::Unknown) { | ||||
| 		init(); | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	if (m_state == EState::Hidden) { | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	int64_t now = GLCanvas3D::timestamp_now(); | ||||
| 
 | ||||
| 	if (m_hovered) { | ||||
| 		// reset fading
 | ||||
| 		m_fading_out = false; | ||||
| 	// reset timers - hovered state is set in render 
 | ||||
| 	if (m_state == EState::Hovered) {  | ||||
| 		m_current_fade_opacity = 1.0f; | ||||
| 		m_remaining_time = m_data.duration; | ||||
| 		m_notification_start = now; | ||||
| 	} | ||||
| 
 | ||||
| 	 | ||||
| 
 | ||||
| 	if (m_counting_down) { | ||||
| 	// Timers when not fading
 | ||||
| 	} else if (m_state != EState::FadingOut && m_data.duration != 0 && !paused) { | ||||
| 		int64_t up_time = now - m_notification_start; | ||||
| 		if (up_time >= m_data.duration * 1000) { | ||||
| 			m_state					= EState::FadingOut; | ||||
| 			m_fading_start			= now; | ||||
| 		} else { | ||||
| 			m_next_render = m_data.duration * 1000 - up_time; | ||||
| 		}	 | ||||
| 	} | ||||
| 	// Timers when fading
 | ||||
| 	if (m_state == EState::FadingOut && !paused) { | ||||
| 		int64_t curr_time		= now - m_fading_start; | ||||
| 		int64_t next_render		= FADING_OUT_TIMEOUT - delta; | ||||
| 		m_current_fade_opacity	= std::clamp(1.0f - 0.001f * static_cast<float>(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f); | ||||
| 		if (m_current_fade_opacity <= 0.0f) { | ||||
| 			m_state = EState::Finished; | ||||
| 			return true; | ||||
| 		} else if (next_render <= 20) { | ||||
| 			m_next_render = FADING_OUT_TIMEOUT; | ||||
| 			return true; | ||||
| 		} else { | ||||
| 			m_next_render = next_render; | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 		if (m_fading_out && m_current_fade_opacity <= 0.0f) | ||||
| 			m_finished = true; | ||||
| 		else if (!m_fading_out && /*m_remaining_time <=0*/up_time >= m_data.duration * 1000) { | ||||
| 			m_fading_out = true; | ||||
| 			m_fading_start = now; | ||||
| 			m_last_render_fading = now; | ||||
| 		} else if (!m_fading_out) { | ||||
| 			m_next_render = m_data.duration * 1000 - up_time;//std::min<int64_t>(/*m_data.duration * 1000 - up_time*/m_remaining_time * 1000, MAX_TIMEOUT_MILISECONDS);
 | ||||
| 		} | ||||
| 		 | ||||
| 	if (m_state == EState::Finished) { | ||||
| 		return true; | ||||
| 	} | ||||
| 	 | ||||
| 	if (m_finished) { | ||||
| 
 | ||||
| 	if (m_state == EState::ClosePending) { | ||||
| 		m_state = EState::Finished; | ||||
| 		m_next_render = 0; | ||||
| 		return; | ||||
| 	} | ||||
| 	if (m_close_pending) { | ||||
| 		m_finished = true; | ||||
| 		m_state = EState::ClosePending; | ||||
| 		m_next_render = 0; | ||||
| 		return; | ||||
| 	} | ||||
| 	if (m_fading_out) { | ||||
| 		if (!m_paused) { | ||||
| 			m_state = EState::FadingOutStatic; | ||||
| 			int64_t curr_time      = now - m_fading_start; | ||||
| 			int64_t no_render_time = now - m_last_render_fading; | ||||
| 			m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast<float>(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f); | ||||
| 			auto next_render = FADING_OUT_TIMEOUT - no_render_time; | ||||
| 			if (next_render <= 0) { | ||||
| 				//m_last_render_fading = GLCanvas3D::timestamp_now();
 | ||||
| 				m_state = EState::FadingOutRender; | ||||
| 				m_next_render = 0; | ||||
| 			} else  | ||||
| 				m_next_render = next_render; | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool large) : | ||||
|  | @ -672,9 +658,11 @@ void NotificationManager::SlicingCompleteLargeNotification::set_print_info(const | |||
| void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) | ||||
| { | ||||
| 	m_is_large = l; | ||||
| 	m_counting_down = !l; | ||||
| 	//FIXME this information should not be lost (change m_data?)
 | ||||
| //	m_counting_down = !l;
 | ||||
| 	m_hypertext = l ? _u8L("Export G-Code.") : std::string(); | ||||
| 	m_hidden = !l; | ||||
| 	m_state = l ? EState::Shown : EState::Hidden; | ||||
| 	init(); | ||||
| } | ||||
| //---------------ExportFinishedNotification-----------
 | ||||
| void NotificationManager::ExportFinishedNotification::count_spaces() | ||||
|  | @ -733,8 +721,8 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW | |||
| 	ImVec2 win_pos(win_pos_x, win_pos_y); | ||||
| 	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); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 
 | ||||
| 	std::string button_text; | ||||
|  | @ -768,7 +756,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW | |||
| 		assert(m_evt_handler != nullptr); | ||||
| 		if (m_evt_handler != nullptr) | ||||
| 			wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); | ||||
| 		m_close_pending = true; | ||||
| 		close(); | ||||
| 	} | ||||
| 
 | ||||
| 	//invisible large button
 | ||||
|  | @ -779,7 +767,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW | |||
| 		assert(m_evt_handler != nullptr); | ||||
| 		if (m_evt_handler != nullptr) | ||||
| 			wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); | ||||
| 		m_close_pending = true; | ||||
| 		close(); | ||||
| 	} | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
|  | @ -799,30 +787,157 @@ void NotificationManager::ProgressBarNotification::init() | |||
| 	m_lines_count++; | ||||
| 	m_endlines.push_back(m_endlines.back()); | ||||
| } | ||||
| void NotificationManager::ProgressBarNotification::count_spaces() | ||||
| { | ||||
| 	//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_line_height * (m_has_cancel_button ? 6 : 4); | ||||
| 	m_window_width = m_line_height * 25; | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 	// line1 - we do not print any more text than what fits on line 1. Line 2 is bar.
 | ||||
| 	ImGui::SetCursorPosX(m_left_indentation); | ||||
| 	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()); | ||||
| 	if (m_has_cancel_button) | ||||
| 	render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 	render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 	 | ||||
| } | ||||
| void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); | ||||
| 	float  invisible_length = 0;//((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 - m_window_width_offset, win_pos_y + win_size_y/2 + m_line_height / 2); | ||||
| 	ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y/2 + m_line_height / 2); | ||||
| 	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); | ||||
| 	/*
 | ||||
| 	//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++; | ||||
| 		*/ | ||||
| 	ImVec4 orange_color			= ImVec4(.99f, .313f, .0f, 1.0f); | ||||
| 	ImVec4 gray_color			= ImVec4(.34f, .34f, .34f, 1.0f); | ||||
| 	ImVec2 lineEnd				= ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + m_line_height / 4); | ||||
| 	ImVec2 lineStart			= ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + m_line_height / 4); | ||||
| 	ImVec2 midPoint				= ImVec2(lineStart.x + (lineEnd.x - lineStart.x) * m_percentage, lineStart.y); | ||||
| 	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); | ||||
| 	ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); | ||||
| } | ||||
| //------PrintHostUploadNotification----------------
 | ||||
| void NotificationManager::PrintHostUploadNotification::set_percentage(float percent) | ||||
| { | ||||
| 	if (m_uj_state == UploadJobState::PB_CANCELLED) | ||||
| 		return; | ||||
| 	m_percentage = percent; | ||||
| 	if (percent >= 1.0f) { | ||||
| 		m_uj_state = UploadJobState::PB_COMPLETED; | ||||
| 		m_has_cancel_button = false; | ||||
| 	} else if (percent < 0.0f) { | ||||
| 		error(); | ||||
| 	} else { | ||||
| 		m_uj_state = UploadJobState::PB_PROGRESS; | ||||
| 		m_has_cancel_button = true; | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	std::string text; | ||||
| 	switch (m_uj_state) { | ||||
| 	case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_PROGRESS: | ||||
| 	{ | ||||
| 		ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 		float uploaded = m_file_size / 100 * m_percentage; | ||||
| 		std::stringstream stream; | ||||
| 		stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded"; | ||||
| 		text = stream.str(); | ||||
| 		ImGui::SetCursorPosX(m_left_indentation); | ||||
| 		ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 /*- m_line_height / 4 * 3*/); | ||||
| 		break; | ||||
| 	} | ||||
| 	case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_ERROR: | ||||
| 		text = _u8L("ERROR"); | ||||
| 		ImGui::SetCursorPosX(m_left_indentation); | ||||
| 		ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_CANCELLED: | ||||
| 		text = _u8L("CANCELED"); | ||||
| 		ImGui::SetCursorPosX(m_left_indentation); | ||||
| 		ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_COMPLETED: | ||||
| 		text = _u8L("COMPLETED"); | ||||
| 		ImGui::SetCursorPosX(m_left_indentation); | ||||
| 		ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); | ||||
| 		break; | ||||
| 	} | ||||
| 	 | ||||
| 	imgui.text(text.c_str()); | ||||
| 
 | ||||
| } | ||||
| void NotificationManager::PrintHostUploadNotification::render_cancel_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); | ||||
| 	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_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 
 | ||||
| 	std::string button_text; | ||||
| 	button_text = ImGui::CancelButton; | ||||
| 
 | ||||
| 	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y), | ||||
| 		ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y), | ||||
| 		true)) | ||||
| 	{ | ||||
| 		button_text = ImGui::CancelHoverButton; | ||||
| 		// tooltip
 | ||||
| 		long time_now = wxGetLocalTime(); | ||||
| 		if (m_hover_time > 0 && m_hover_time < time_now) { | ||||
| 			ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); | ||||
| 			ImGui::BeginTooltip(); | ||||
| 			imgui.text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T"); | ||||
| 			ImGui::EndTooltip(); | ||||
| 			ImGui::PopStyleColor(); | ||||
| 		} | ||||
| 		if (m_hover_time == 0) | ||||
| 			m_hover_time = time_now; | ||||
| 	} | ||||
| 	else | ||||
| 		m_hover_time = 0; | ||||
| 
 | ||||
| 	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 * 5.0f); | ||||
| 	ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); | ||||
| 	if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) | ||||
| 	{ | ||||
| 		assert(m_evt_handler != nullptr); | ||||
| 		if (m_evt_handler != nullptr) { | ||||
| 			auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, m_job_id, m_job_id); | ||||
| 			wxQueueEvent(m_evt_handler, evt); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	//invisible large button
 | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); | ||||
| 	ImGui::SetCursorPosY(0); | ||||
| 	if (imgui.button("  ", m_line_height * 2.f, win_size.y)) | ||||
| 	{ | ||||
| 		assert(m_evt_handler != nullptr); | ||||
| 		if (m_evt_handler != nullptr) { | ||||
| 			auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, m_job_id, m_job_id); | ||||
| 			wxQueueEvent(m_evt_handler, evt); | ||||
| 		} | ||||
| 	} | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	 | ||||
| } | ||||
| //------NotificationManager--------
 | ||||
| NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : | ||||
|  | @ -881,12 +996,7 @@ void NotificationManager::push_plater_error_notification(const std::string& text | |||
| { | ||||
| 	push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, 0); | ||||
| } | ||||
| void NotificationManager::push_plater_warning_notification(const std::string& text) | ||||
| { | ||||
| 	push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text }, 0); | ||||
| 	// dissaper if in preview
 | ||||
| 	set_in_preview(m_in_preview); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::close_plater_error_notification(const std::string& text) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification> ¬ification : m_pop_notifications) { | ||||
|  | @ -895,11 +1005,32 @@ void NotificationManager::close_plater_error_notification(const std::string& tex | |||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::push_plater_warning_notification(const std::string& text) | ||||
| { | ||||
| 	// Find if was not hidden 
 | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { | ||||
| 			if (notification->get_state() == PopNotification::EState::Hidden) { | ||||
| 				//dynamic_cast<PlaterWarningNotification*>(notification.get())->show();
 | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	NotificationData data{ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text }; | ||||
| 
 | ||||
| 	auto notification = std::make_unique<NotificationManager::PlaterWarningNotification>(data, m_id_provider, m_evt_handler); | ||||
| 	push_notification_data(std::move(notification), 0); | ||||
| 	// dissaper if in preview
 | ||||
| 	set_in_preview(m_in_preview); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::close_plater_warning_notification(const std::string& text) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification> ¬ification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { | ||||
| 			notification->close(); | ||||
| 			dynamic_cast<PlaterWarningNotification*>(notification.get())->real_close(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -997,23 +1128,50 @@ void NotificationManager::push_exporting_finished_notification(const std::string | |||
| 	NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotification, on_removable ? 0 : 20,  _u8L("Exporting finished.") + "\n" + path }; | ||||
| 	push_notification_data(std::make_unique<NotificationManager::ExportFinishedNotification>(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); | ||||
| } | ||||
| void  NotificationManager::push_progress_bar_notification(const std::string& text, float percentage) | ||||
| 
 | ||||
| void  NotificationManager::push_upload_job_notification(wxEvtHandler* evt_handler, int id, float filesize, const std::string& filename, const std::string& host, float percentage) | ||||
| { | ||||
| 	NotificationData data{ NotificationType::ProgressBar, NotificationLevel::ProgressBarNotification, 0, text }; | ||||
| 	push_notification_data(std::make_unique<NotificationManager::ProgressBarNotification>(data, m_id_provider, m_evt_handler, 0), 0); | ||||
| 	std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); | ||||
| 	NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 0, text }; | ||||
| 	push_notification_data(std::make_unique<NotificationManager::PrintHostUploadNotification>(data, m_id_provider, evt_handler, 0, id, filesize), 0); | ||||
| } | ||||
| void NotificationManager::set_progress_bar_percentage(const std::string& text, float percentage) | ||||
| void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) | ||||
| { | ||||
| 	bool found = false; | ||||
| 	std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); | ||||
| //	bool found = false;
 | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { | ||||
| 			dynamic_cast<ProgressBarNotification*>(notification.get())->set_percentage(percentage); | ||||
| 			wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); | ||||
| 			found = true; | ||||
| 			dynamic_cast<PrintHostUploadNotification*>(notification.get())->set_percentage(percentage); | ||||
| 			wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); | ||||
| //			found = true;
 | ||||
| 		} | ||||
| 	} | ||||
| 	/*
 | ||||
| 	if (!found) { | ||||
| 		push_progress_bar_notification(text, percentage); | ||||
| 		push_upload_job_notification(id, filename, host, percentage); | ||||
| 	} | ||||
| 	*/ | ||||
| } | ||||
| void NotificationManager::upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host) | ||||
| { | ||||
| 	std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::PrintHostUpload && notification->compare_text(text)) { | ||||
| 			dynamic_cast<PrintHostUploadNotification*>(notification.get())->cancel(); | ||||
| 			wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::upload_job_notification_show_error(int id, const std::string& filename, const std::string& host) | ||||
| { | ||||
| 	std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::PrintHostUpload && notification->compare_text(text)) { | ||||
| 			dynamic_cast<PrintHostUploadNotification*>(notification.get())->error(); | ||||
| 			wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| bool NotificationManager::push_notification_data(const NotificationData& notification_data, int timestamp) | ||||
|  | @ -1035,20 +1193,19 @@ bool NotificationManager::push_notification_data(std::unique_ptr<NotificationMan | |||
| 
 | ||||
| 	if (this->activate_existing(notification.get())) { | ||||
| 		m_pop_notifications.back()->update(notification->get_data()); | ||||
| 		canvas.request_extra_frame_delayed(33); | ||||
| 		canvas.schedule_extra_frame(0); | ||||
| 		return false; | ||||
| 	} else { | ||||
| 		m_pop_notifications.emplace_back(std::move(notification)); | ||||
| 		canvas.request_extra_frame_delayed(33); | ||||
| 		canvas.schedule_extra_frame(0); | ||||
| 		return true; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::render_notifications(float overlay_width) | ||||
| void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay_width) | ||||
| { | ||||
| 	sort_notifications(); | ||||
| 
 | ||||
| 	GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D(); | ||||
| 	 | ||||
| 	float last_y = 0.0f; | ||||
| 
 | ||||
| 	for (const auto& notification : m_pop_notifications) { | ||||
|  | @ -1059,7 +1216,49 @@ void NotificationManager::render_notifications(float overlay_width) | |||
| 		} | ||||
| 		 | ||||
| 	} | ||||
| 	update_notifications(); | ||||
| 	m_last_render = GLCanvas3D::timestamp_now(); | ||||
| } | ||||
| 
 | ||||
| bool NotificationManager::update_notifications(GLCanvas3D& canvas) | ||||
| { | ||||
| 	// no update if not top window
 | ||||
| 	wxWindow* p = dynamic_cast<wxWindow*>(wxGetApp().plater()); | ||||
| 	while (p->GetParent() != nullptr) | ||||
| 		p = p->GetParent(); | ||||
| 	wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p); | ||||
| 	if (!top_level_wnd->IsActive()) | ||||
| 		return false; | ||||
| 
 | ||||
| 	// next_render() returns numeric_limits::max if no need for frame
 | ||||
| 	const int64_t max = std::numeric_limits<int64_t>::max(); | ||||
| 	int64_t       next_render = max; | ||||
| 	const int64_t time_since_render = GLCanvas3D::timestamp_now() - m_last_render; | ||||
| 	bool		  request_render = false; | ||||
| 	// During render, each notification detects if its currently hovered and changes its state to EState::Hovered
 | ||||
| 	// If any notification is hovered, all restarts its countdown 
 | ||||
| 	bool          hover = false; | ||||
| 	for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->is_hovered()) { | ||||
| 			hover = true; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	// update state of all notif and erase finished
 | ||||
| 	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { | ||||
| 		std::unique_ptr<PopNotification>& notification = *it; | ||||
| 		request_render |= notification->update_state(hover, time_since_render); | ||||
| 		next_render = std::min<int64_t>(next_render, notification->next_render()); | ||||
| 		if (notification->get_state() == PopNotification::EState::Finished) | ||||
| 			it = m_pop_notifications.erase(it); | ||||
| 		else  | ||||
| 			++it; | ||||
| 	} | ||||
| 
 | ||||
| 	// request next frame in future
 | ||||
| 	if (next_render < max) | ||||
| 		canvas.schedule_extra_frame(int(next_render)); | ||||
| 
 | ||||
| 	return request_render; | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::sort_notifications() | ||||
|  | @ -1080,9 +1279,11 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi | |||
| 	const std::string &new_text = notification->get_data().text1; | ||||
| 	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) { | ||||
| 		if ((*it)->get_type() == new_type && !(*it)->is_finished()) { | ||||
| 			if (new_type == NotificationType::CustomNotification || new_type == NotificationType::PlaterWarning) { | ||||
| 				if (!(*it)->compare_text(new_text)) | ||||
| 			if (std::find(m_multiple_types.begin(), m_multiple_types.end(), new_type) != m_multiple_types.end()) { | ||||
| 				// If found same type and same text, return true - update will be performed on the old notif
 | ||||
| 				if ((*it)->compare_text(new_text) == false) { | ||||
| 					continue; | ||||
| 				} | ||||
| 			} else if (new_type == NotificationType::SlicingWarning) { | ||||
| 				auto w1 = dynamic_cast<const SlicingWarningNotification*>(notification); | ||||
| 				auto w2 = dynamic_cast<const SlicingWarningNotification*>(it->get()); | ||||
|  | @ -1094,7 +1295,6 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi | |||
| 					continue; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (it != m_pop_notifications.end() - 1) | ||||
| 				std::rotate(it, it + 1, m_pop_notifications.end()); | ||||
| 			return true; | ||||
|  | @ -1108,107 +1308,12 @@ void NotificationManager::set_in_preview(bool preview) | |||
|     m_in_preview = preview; | ||||
|     for (std::unique_ptr<PopNotification> ¬ification : m_pop_notifications) { | ||||
|         if (notification->get_type() == NotificationType::PlaterWarning)  | ||||
|             notification->hide(preview);      | ||||
|             notification->hide(preview); | ||||
|         if (notification->get_type() == NotificationType::SignDetected) | ||||
|             notification->hide(!preview); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::update_notifications() | ||||
| { | ||||
| 	// no update if not top window
 | ||||
| 	wxWindow* p = dynamic_cast<wxWindow*>(wxGetApp().plater()); | ||||
| 	while (p->GetParent() != nullptr) | ||||
| 		p = p->GetParent(); | ||||
| 	wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p); | ||||
| 	if (!top_level_wnd->IsActive()) | ||||
| 		return; | ||||
| 
 | ||||
| 	//static size_t last_size = m_pop_notifications.size();
 | ||||
| 
 | ||||
| 	//request frames
 | ||||
| 	int64_t next_render = std::numeric_limits<int64_t>::max(); | ||||
| 	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { | ||||
| 		std::unique_ptr<PopNotification>& notification = *it; | ||||
| 		notification->set_paused(m_hovered); | ||||
| 		notification->update_state(); | ||||
| 		next_render = std::min<int64_t>(next_render, notification->next_render()); | ||||
| 		if (notification->get_state() == PopNotification::EState::Finished) | ||||
| 			it = m_pop_notifications.erase(it); | ||||
| 		else { | ||||
| 			 | ||||
| 			++it; | ||||
| 		} | ||||
| 	} | ||||
| 	/*
 | ||||
| 	m_requires_update = false; | ||||
| 	for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->requires_update()) { | ||||
| 			m_requires_update = true; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	*/ | ||||
| 	// update hovering state
 | ||||
| 	m_hovered = false; | ||||
| 	for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->is_hovered()) { | ||||
| 			m_hovered = true; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	// Reuire render if some notification was just deleted.
 | ||||
| 	size_t curr_size = m_pop_notifications.size(); | ||||
| 	m_requires_render = m_hovered || (last_size != curr_size); | ||||
| 	last_size = curr_size; | ||||
| 
 | ||||
| 	// Ask notification if it needs render
 | ||||
| 	if (!m_requires_render) { | ||||
| 		for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 			if (notification->requires_render()) { | ||||
| 				m_requires_render = true; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// Make sure there will be update after last notification erased
 | ||||
| 	if (m_requires_render) | ||||
| 		m_requires_update = true; | ||||
| 	*/ | ||||
| 	 | ||||
| 
 | ||||
| 	if (next_render == 0) | ||||
| 		wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(33); //few milliseconds to get from GLCanvas::render
 | ||||
| 	else if (next_render < std::numeric_limits<int64_t>::max()) | ||||
| 		wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(int(next_render)); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	// actualizate timers
 | ||||
| 	wxWindow* p = dynamic_cast<wxWindow*>(wxGetApp().plater()); | ||||
| 	while (p->GetParent() != nullptr) | ||||
| 		p = p->GetParent(); | ||||
| 	wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p); | ||||
| 	if (!top_level_wnd->IsActive()) | ||||
| 		return; | ||||
| 
 | ||||
| 	{ | ||||
| 		// Control the fade-out.
 | ||||
| 		// time in seconds
 | ||||
| 		long now = wxGetLocalTime(); | ||||
| 		// Pausing fade-out when the mouse is over some notification.
 | ||||
| 		if (!m_hovered && m_last_time < now) { | ||||
| 			if (now - m_last_time >= MAX_TIMEOUT_SECONDS) { | ||||
| 				for (auto& notification : m_pop_notifications) { | ||||
| 					//if (notification->get_state() != PopNotification::EState::Static)
 | ||||
| 						notification->substract_remaining_time(MAX_TIMEOUT_SECONDS); | ||||
| 				} | ||||
| 				m_last_time = now; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	*/ | ||||
| } | ||||
| 
 | ||||
| bool NotificationManager::has_slicing_error_notification() | ||||
| { | ||||
| 	return std::any_of(m_pop_notifications.begin(), m_pop_notifications.end(), [](auto &n) { | ||||
|  |  | |||
|  | @ -1,6 +1,9 @@ | |||
| #ifndef slic3r_GUI_NotificationManager_hpp_ | ||||
| #define slic3r_GUI_NotificationManager_hpp_ | ||||
| 
 | ||||
| #include "GUI_App.hpp" | ||||
| #include "Plater.hpp" | ||||
| #include "GLCanvas3D.hpp" | ||||
| #include "Event.hpp" | ||||
| #include "I18N.hpp" | ||||
| 
 | ||||
|  | @ -66,12 +69,16 @@ enum class NotificationType | |||
| 	PlaterWarning, | ||||
| 	// Progress bar instead of text.
 | ||||
| 	ProgressBar, | ||||
| 	// Progress bar with info from Print Host Upload Queue dialog.
 | ||||
| 	PrintHostUpload, | ||||
| 	// Notification, when Color Change G-code is empty and user try to add color change on DoubleSlider.
 | ||||
|     EmptyColorChangeCode, | ||||
|     // Notification that custom supports/seams were deleted after mesh repair.
 | ||||
|     CustomSupportsAndSeamRemovedAfterRepair, | ||||
|     // Notification that auto adding of color changes is impossible
 | ||||
|     EmptyAutoColorChange, | ||||
|     // Notification about detected sign
 | ||||
|     SignDetected, | ||||
|     // Notification emitted by Print::validate
 | ||||
|     PrintValidateWarning, | ||||
|     // Notification telling user to quit SLA supports manual editing
 | ||||
|  | @ -140,21 +147,24 @@ public: | |||
| 	// Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button
 | ||||
| 	void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); | ||||
| 	// notification with progress bar
 | ||||
| 	void push_progress_bar_notification(const std::string& text, float percentage = 0); | ||||
| 	void set_progress_bar_percentage(const std::string& text, float percentage); | ||||
| 	void push_upload_job_notification(wxEvtHandler* evt_handler, int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); | ||||
| 	void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); | ||||
| 	void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); | ||||
| 	void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); | ||||
| 	// Close old notification ExportFinished.
 | ||||
| 	void new_export_began(bool on_removable); | ||||
| 	// finds ExportFinished notification and closes it if it was to removable device
 | ||||
| 	void device_ejected(); | ||||
| 	// renders notifications in queue and deletes expired ones
 | ||||
| 	void render_notifications(float overlay_width); | ||||
| 	void render_notifications(GLCanvas3D& canvas, float overlay_width); | ||||
| 	// finds and closes all notifications of given type
 | ||||
| 	void close_notification_of_type(const NotificationType type); | ||||
| 	// Which view is active? Plater or G-code preview? Hide warnings in G-code preview.
 | ||||
|     void set_in_preview(bool preview); | ||||
| 	// Move to left to avoid colision with variable layer height gizmo.
 | ||||
| 	void set_move_from_overlay(bool move) { m_move_from_overlay = move; } | ||||
| 
 | ||||
| 	// perform update_state on each notification and ask for more frames if needed, return true for render needed
 | ||||
| 	bool update_notifications(GLCanvas3D& canvas); | ||||
| private: | ||||
| 	// duration 0 means not disapearing
 | ||||
| 	struct NotificationData { | ||||
|  | @ -192,23 +202,24 @@ private: | |||
| 
 | ||||
| 		enum class EState | ||||
| 		{ | ||||
| 			Unknown, | ||||
| 			Unknown,		  // NOT initialized
 | ||||
| 			Hidden, | ||||
| 			FadingOutRender,  // Requesting Render
 | ||||
| 			FadingOutStatic, | ||||
| 			Shown,			  // Requesting Render at some time if duration != 0
 | ||||
| 			FadingOut,        // Requesting Render at some time
 | ||||
| 			ClosePending,     // Requesting Render
 | ||||
| 			Finished,         // Requesting Render
 | ||||
| 			Hovered,		  // Followed by Shown 
 | ||||
| 			Paused | ||||
| 		}; | ||||
| 
 | ||||
| 		PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler); | ||||
| 		virtual ~PopNotification() { if (m_id) m_id_provider.release_id(m_id); } | ||||
| 		void                   render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width); | ||||
| 		virtual void           render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width); | ||||
| 		// close will dissapear notification on next render
 | ||||
| 		void                   close() { m_close_pending = true; } | ||||
| 		virtual void           close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);} | ||||
| 		// data from newer notification of same type
 | ||||
| 		void                   update(const NotificationData& n); | ||||
| 		bool                   is_finished() const { return m_finished || m_close_pending; } | ||||
| 		bool                   is_hovered() const { return m_hovered; } | ||||
| 		bool                   is_finished() const { return m_state == EState::ClosePending || m_state == EState::Finished; } | ||||
| 		// returns top after movement
 | ||||
| 		float                  get_top() const { return m_top_y; } | ||||
| 		//returns top in actual frame
 | ||||
|  | @ -216,23 +227,17 @@ private: | |||
| 		const NotificationType get_type() const { return m_data.type; } | ||||
| 		const NotificationData get_data() const { return m_data; } | ||||
| 		const bool             is_gray() const { return m_is_gray; } | ||||
| 		// Call equals one second down
 | ||||
| 		void                   substract_remaining_time(int seconds) { m_remaining_time -= seconds; } | ||||
| 		void                   set_gray(bool g) { m_is_gray = g; } | ||||
| 		void                   set_paused(bool p) { m_paused = p; } | ||||
| 		bool                   compare_text(const std::string& text); | ||||
|         void                   hide(bool h) { m_hidden = h; } | ||||
| 		// sets m_next_render with time of next mandatory rendering
 | ||||
| 		void                   update_state(); | ||||
| 		int64_t 		       next_render() const { return m_next_render; } | ||||
| 		/*
 | ||||
| 		bool				   requires_render() const { return m_state == EState::FadingOutRender || m_state == EState::ClosePending || m_state == EState::Finished; } | ||||
| 		bool				   requires_update() const { return m_state != EState::Hidden; } | ||||
| 		*/ | ||||
| 		EState                 get_state() const { return m_state; } | ||||
| 	protected: | ||||
|         void                   hide(bool h) { if (is_finished()) return; m_state = h ? EState::Hidden : EState::Unknown; } | ||||
| 		// sets m_next_render with time of next mandatory rendering. Delta is time since last render.
 | ||||
| 		bool                   update_state(bool paused, const int64_t delta); | ||||
| 		int64_t 		       next_render() const { return is_finished() ? 0 : m_next_render; } | ||||
| 		EState                 get_state()  const { return m_state; } | ||||
| 		bool				   is_hovered() const { return m_state == EState::Hovered; }  | ||||
| 	 | ||||
| 		// Call after every size change
 | ||||
| 		void         init(); | ||||
| 		virtual void init(); | ||||
| 		// Part of init() 
 | ||||
| 		virtual void count_spaces(); | ||||
| 		// Calculetes correct size but not se it in imgui!
 | ||||
|  | @ -254,45 +259,36 @@ private: | |||
| 		// Hypertext action, returns true if notification should close.
 | ||||
| 		// Action is stored in NotificationData::callback as std::function<bool(wxEvtHandler*)>
 | ||||
| 		virtual bool on_text_click(); | ||||
| 
 | ||||
| 	protected: | ||||
| 		const NotificationData m_data; | ||||
| 
 | ||||
| 		// For reusing ImGUI windows.
 | ||||
| 		NotificationIDProvider &m_id_provider; | ||||
| 		int              m_id{ 0 }; | ||||
| 
 | ||||
| 		// State for rendering
 | ||||
| 		EState           m_state                { EState::Unknown }; | ||||
| 
 | ||||
| 		int              m_id                   { 0 }; | ||||
| 		bool			 m_initialized          { false }; | ||||
| 		// Time values for rendering fade-out
 | ||||
| 
 | ||||
| 		int64_t		 	 m_fading_start{ 0LL }; | ||||
| 
 | ||||
| 		// first appereance of notification or last hover;
 | ||||
| 		int64_t		 	 m_notification_start; | ||||
| 		// time to next must-do render
 | ||||
| 		int64_t          m_next_render{ std::numeric_limits<int64_t>::max() }; | ||||
| 		float            m_current_fade_opacity{ 1.0f }; | ||||
| 
 | ||||
| 		// Notification data
 | ||||
| 
 | ||||
| 		// 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 }; | ||||
| 		int64_t		 	 m_fading_start         { 0LL }; | ||||
| 		// time of last done render when fading
 | ||||
| 		int64_t		 	 m_last_render_fading   { 0LL }; | ||||
| 		// first appereance of notification or last hover;
 | ||||
| 		int64_t		 	 m_notification_start; | ||||
| 		// time to next must-do render
 | ||||
| 		int64_t          m_next_render          { std::numeric_limits<int64_t>::max() }; | ||||
| 		float            m_current_fade_opacity { 1.0f }; | ||||
| 		// 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 }; | ||||
| 		bool             m_hovered              { false }; | ||||
| 		// variables to count positions correctly
 | ||||
| 
 | ||||
| 		// inner variables to position notification window, texts and buttons correctly
 | ||||
| 
 | ||||
| 		// all space without text
 | ||||
| 		float            m_window_width_offset; | ||||
| 		// Space on left side without text
 | ||||
|  | @ -302,9 +298,7 @@ private: | |||
| 		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!
 | ||||
| 		// Height of text - Used as basic scaling unit!
 | ||||
| 		float            m_line_height; | ||||
|         std::vector<size_t> m_endlines; | ||||
| 		// Gray are f.e. eorrors when its uknown if they are still valid
 | ||||
|  | @ -322,10 +316,16 @@ private: | |||
| 	{ | ||||
| 	public: | ||||
| 		SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool largeds); | ||||
| 		void set_large(bool l); | ||||
| 		bool get_large() { return m_is_large; } | ||||
| 
 | ||||
| 		void set_print_info(const std::string &info); | ||||
| 		void			set_large(bool l); | ||||
| 		bool			get_large() { return m_is_large; } | ||||
| 		void			set_print_info(const std::string &info); | ||||
| 		virtual void	render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) | ||||
| 		{ | ||||
| 			// This notification is always hidden if !large (means side bar is collapsed)
 | ||||
| 			if (!get_large() && !is_finished())  | ||||
| 				m_state = EState::Hidden; | ||||
| 			PopNotification::render(canvas, initial_y, move_from_overlay, overlay_width); | ||||
| 		} | ||||
| 	protected: | ||||
| 		virtual void render_text(ImGuiWrapper& imgui, | ||||
| 			                     const float win_size_x, const float win_size_y, | ||||
|  | @ -344,21 +344,78 @@ private: | |||
| 		int    		warning_step; | ||||
| 	}; | ||||
| 
 | ||||
| 	class PlaterWarningNotification : public PopNotification | ||||
| 	{ | ||||
| 	public: | ||||
| 		PlaterWarningNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) {} | ||||
| 		virtual void close()      { if(is_finished()) return; m_state = EState::Hidden; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } | ||||
| 		void		 real_close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } | ||||
| 		void         show()       { m_state = EState::Unknown; } | ||||
| 	}; | ||||
| 
 | ||||
| 	 | ||||
| 	class ProgressBarNotification : public PopNotification | ||||
| 	{ | ||||
| 	public: | ||||
| 		 | ||||
| 		ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); } | ||||
| 		void set_percentage(float percent) { m_percentage = percent; if (percent >= 1.0f) m_progress_complete = true; else m_progress_complete = false; } | ||||
| 		virtual void set_percentage(float percent) { m_percentage = percent; } | ||||
| 	protected: | ||||
| 		virtual void init(); | ||||
| 		virtual void init() override; | ||||
| 		virtual void count_spaces() override; | ||||
| 		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_bar(ImGuiWrapper& imgui, | ||||
| 			const float win_size_x, const float win_size_y, | ||||
| 			const float win_pos_x, const float win_pos_y); | ||||
| 		bool m_progress_complete{ false }; | ||||
| 		float m_percentage; | ||||
| 									const float win_size_x, const float win_size_y, | ||||
| 									const float win_pos_x, const float win_pos_y); | ||||
| 		virtual void render_bar(ImGuiWrapper& imgui, | ||||
| 									const float win_size_x, const float win_size_y, | ||||
| 									const float win_pos_x, const float win_pos_y); | ||||
| 		virtual void render_cancel_button(ImGuiWrapper& imgui, | ||||
| 									const float win_size_x, const float win_size_y, | ||||
| 									const float win_pos_x, const float win_pos_y) | ||||
| 		{} | ||||
| 		float				m_percentage; | ||||
| 		 | ||||
| 		bool				m_has_cancel_button {false}; | ||||
| 		// local time of last hover for showing tooltip
 | ||||
| 		 | ||||
| 	}; | ||||
| 
 | ||||
| 	 | ||||
| 
 | ||||
| 	class PrintHostUploadNotification : public ProgressBarNotification | ||||
| 	{ | ||||
| 	public: | ||||
| 		enum class UploadJobState | ||||
| 		{ | ||||
| 			PB_PROGRESS, | ||||
| 			PB_ERROR, | ||||
| 			PB_CANCELLED, | ||||
| 			PB_COMPLETED | ||||
| 		}; | ||||
| 		PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize) | ||||
| 			:ProgressBarNotification(n, id_provider, evt_handler, percentage) | ||||
| 			, m_job_id(job_id) | ||||
| 			, m_file_size(filesize) | ||||
| 		{ | ||||
| 			m_has_cancel_button = true; | ||||
| 		} | ||||
| 		static std::string	get_upload_job_text(int id, const std::string& filename, const std::string& host) { return "[" + std::to_string(id) + "] " + filename + " -> " + host; } | ||||
| 		virtual void		set_percentage(float percent); | ||||
| 		void				cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } | ||||
| 		void				error()  { m_uj_state = UploadJobState::PB_ERROR;     m_has_cancel_button = false; } | ||||
| 	protected: | ||||
| 		virtual void render_bar(ImGuiWrapper& imgui, | ||||
| 								const float win_size_x, const float win_size_y, | ||||
| 								const float win_pos_x, const float win_pos_y); | ||||
| 		virtual void render_cancel_button(ImGuiWrapper& imgui, | ||||
| 											const float win_size_x, const float win_size_y, | ||||
| 											const float win_pos_x, const float win_pos_y); | ||||
| 		// Identifies job in cancel callback
 | ||||
| 		int					m_job_id; | ||||
| 		// Size of uploaded size to be displayed in MB
 | ||||
| 		float			    m_file_size; | ||||
| 		long				m_hover_time{ 0 }; | ||||
| 		UploadJobState	m_uj_state{ UploadJobState::PB_PROGRESS }; | ||||
| 	}; | ||||
| 
 | ||||
| 	class ExportFinishedNotification : public PopNotification | ||||
|  | @ -405,32 +462,25 @@ private: | |||
| 	void sort_notifications(); | ||||
| 	// If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed.
 | ||||
|     bool has_slicing_error_notification(); | ||||
| 	// perform update_state on each notification and ask for more frames if needed
 | ||||
| 	void update_notifications(); | ||||
| 
 | ||||
|      | ||||
| 	// Target for wxWidgets events sent by clicking on the hyperlink available at some notifications.
 | ||||
| 	wxEvtHandler*                m_evt_handler; | ||||
| 	// Cache of IDs to identify and reuse ImGUI windows.
 | ||||
| 	NotificationIDProvider 		 m_id_provider; | ||||
| 	std::deque<std::unique_ptr<PopNotification>> m_pop_notifications; | ||||
| 	// Last render time in seconds for fade out control.
 | ||||
| 	long                         m_last_time { 0 }; | ||||
| 	// When mouse hovers over some notification, the fade-out of all notifications is suppressed.
 | ||||
| 	bool                         m_hovered { false }; | ||||
| 	//timestamps used for slicing finished - notification could be gone so it needs to be stored here
 | ||||
| 	std::unordered_set<int>      m_used_timestamps; | ||||
| 	// True if G-code preview is active. False if the Plater is active.
 | ||||
| 	bool                         m_in_preview { false }; | ||||
| 	// True if the layer editing is enabled in Plater, so that the notifications are shifted left of it.
 | ||||
| 	bool                         m_move_from_overlay { false }; | ||||
| 
 | ||||
| 	// Timestamp of last rendering
 | ||||
| 	int64_t						 m_last_render { 0LL }; | ||||
| 	// Notification types that can be shown multiple types at once (compared by text)
 | ||||
| 	const std::vector<NotificationType> m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload }; | ||||
| 	//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::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20,  _u8L("Configuration update is available."),  _u8L("See more."), | ||||
|              [](wxEvtHandler* evnthndlr) { | ||||
|                  if (evnthndlr != nullptr) | ||||
|  |  | |||
|  | @ -91,7 +91,7 @@ void OG_CustomCtrl::init_ctrl_lines() | |||
|             height = m_bmp_blinking_sz.GetHeight() + m_v_gap; | ||||
|             ctrl_lines.emplace_back(CtrlLine(height, this, line, true)); | ||||
|         } | ||||
|         else if (opt_group->label_width != 0 && (!line.label.IsEmpty() || option_set.front().opt.gui_type == "legend") ) | ||||
|         else if (opt_group->label_width != 0 && (!line.label.IsEmpty() || option_set.front().opt.gui_type == ConfigOptionDef::GUIType::legend) ) | ||||
|         { | ||||
|             wxSize label_sz = GetTextExtent(line.label); | ||||
|             height = label_sz.y * (label_sz.GetWidth() > int(opt_group->label_width * m_em_unit) ? 2 : 1) + m_v_gap; | ||||
|  | @ -186,11 +186,11 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) | |||
| #endif //__WXMSW__
 | ||||
|                     h_pos += label_w + 1 + m_h_gap; | ||||
|                 }                 | ||||
|                 h_pos += (opt.opt.gui_type == "legend" ? 1 : 3) * blinking_button_width; | ||||
|                 h_pos += (opt.opt.gui_type == ConfigOptionDef::GUIType::legend ? 1 : 3) * blinking_button_width; | ||||
|                  | ||||
|                 if (field == field_in) | ||||
|                     break; | ||||
|                 if (opt.opt.gui_type == "legend") | ||||
|                 if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend) | ||||
|                     h_pos += 2 * blinking_button_width; | ||||
| 
 | ||||
|                 h_pos += field->getWindow()->GetSize().x; | ||||
|  | @ -580,7 +580,7 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_mode_bmp(wxDC& dc, wxCoord v_pos) | |||
|     wxBitmap bmp = create_scaled_bitmap(bmp_name, ctrl, wxOSX ? 10 : 12); | ||||
|     wxCoord y_draw = v_pos + lround((height - get_bitmap_size(bmp).GetHeight()) / 2); | ||||
| 
 | ||||
|     if (og_line.get_options().front().opt.gui_type != "legend") | ||||
|     if (og_line.get_options().front().opt.gui_type != ConfigOptionDef::GUIType::legend) | ||||
|         dc.DrawBitmap(bmp, 0, y_draw); | ||||
| 
 | ||||
|     return get_bitmap_size(bmp).GetWidth() + ctrl->m_h_gap; | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| #include "wxExtensions.hpp" | ||||
| #include "BitmapCache.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| #include "GUI_ObjectList.hpp" | ||||
| #include "GUI_Factories.hpp" | ||||
| #include "I18N.hpp" | ||||
| 
 | ||||
| #include "libslic3r/Model.hpp" | ||||
|  | @ -44,8 +44,9 @@ void ObjectDataViewModelNode::init_container() | |||
| #endif  //__WXGTK__
 | ||||
| } | ||||
| 
 | ||||
| #define LAYER_ROOT_ICON "edit_layers_all" | ||||
| #define LAYER_ICON      "edit_layers_some" | ||||
| static constexpr char LayerRootIcon[]   = "edit_layers_all"; | ||||
| static constexpr char LayerIcon[]       = "edit_layers_some"; | ||||
| static constexpr char WarningIcon[]     = "exclamation"; | ||||
| 
 | ||||
| ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type) : | ||||
|     m_parent(parent), | ||||
|  | @ -65,7 +66,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent | |||
|     } | ||||
|     else if (type == itLayerRoot) | ||||
|     { | ||||
|         m_bmp = create_scaled_bitmap(LAYER_ROOT_ICON);    // FIXME: pass window ptr
 | ||||
|         m_bmp = create_scaled_bitmap(LayerRootIcon);    // FIXME: pass window ptr
 | ||||
|         m_name = _(L("Layers")); | ||||
|     } | ||||
| 
 | ||||
|  | @ -94,7 +95,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent | |||
|     } | ||||
|     const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); | ||||
|     m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; | ||||
|     m_bmp = create_scaled_bitmap(LAYER_ICON);    // FIXME: pass window ptr
 | ||||
|     m_bmp = create_scaled_bitmap(LayerIcon);    // FIXME: pass window ptr
 | ||||
| 
 | ||||
|     set_action_and_extruder_icons(); | ||||
|     init_container(); | ||||
|  | @ -140,17 +141,14 @@ void ObjectDataViewModelNode::update_settings_digest_bitmaps() | |||
| { | ||||
|     m_bmp = m_empty_bmp; | ||||
| 
 | ||||
|     std::map<std::string, wxBitmap>& categories_icon = Slic3r::GUI::wxGetApp().obj_list()->CATEGORY_ICON; | ||||
| 
 | ||||
|     std::string scaled_bitmap_name = m_name.ToUTF8().data(); | ||||
|     scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : ""); | ||||
| 
 | ||||
|     wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); | ||||
|     if (bmp == nullptr) { | ||||
|         std::vector<wxBitmap> bmps; | ||||
|         for (auto& cat : m_opt_categories) | ||||
|             bmps.emplace_back(  categories_icon.find(cat) == categories_icon.end() ? | ||||
|                                 wxNullBitmap : categories_icon.at(cat)); | ||||
|         for (auto& category : m_opt_categories) | ||||
|             bmps.emplace_back(SettingsFactory::get_category_bitmap(category)); | ||||
|         bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); | ||||
|     } | ||||
| 
 | ||||
|  | @ -249,6 +247,9 @@ static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType roo | |||
| ObjectDataViewModel::ObjectDataViewModel() | ||||
| { | ||||
|     m_bitmap_cache = new Slic3r::GUI::BitmapCache; | ||||
| 
 | ||||
|     m_volume_bmps = MenuFactory::get_volume_bitmaps(); | ||||
|     m_warning_bmp = create_scaled_bitmap(WarningIcon); | ||||
| } | ||||
| 
 | ||||
| ObjectDataViewModel::~ObjectDataViewModel() | ||||
|  | @ -267,7 +268,7 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name, | |||
| 	auto root = new ObjectDataViewModelNode(name, extruder_str); | ||||
|     // Add error icon if detected auto-repaire
 | ||||
|     if (has_errors) | ||||
|         root->m_bmp = *m_warning_bmp; | ||||
|         root->m_bmp = m_warning_bmp; | ||||
| 
 | ||||
|     m_objects.push_back(root); | ||||
| 	// notify control
 | ||||
|  | @ -317,7 +318,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent | |||
| 
 | ||||
|     // if part with errors is added, but object wasn't marked, then mark it
 | ||||
|     if (!obj_errors && has_errors) | ||||
|         root->SetBitmap(*m_warning_bmp); | ||||
|         root->SetBitmap(m_warning_bmp); | ||||
| 
 | ||||
| 	// notify control
 | ||||
|     const wxDataViewItem child((void*)node); | ||||
|  | @ -1434,10 +1435,20 @@ void ObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r | |||
|         return; | ||||
| 
 | ||||
|     ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID()); | ||||
|     node->SetBitmap(*m_volume_bmps[int(type)]); | ||||
|     node->SetVolumeType(type); | ||||
|     node->SetBitmap(m_volume_bmps[int(type)]); | ||||
|     ItemChanged(item); | ||||
| } | ||||
| 
 | ||||
| ModelVolumeType ObjectDataViewModel::GetVolumeType(const wxDataViewItem& item) | ||||
| { | ||||
|     if (!item.IsOk() || GetItemType(item) != itVolume)  | ||||
|         return ModelVolumeType::INVALID; | ||||
| 
 | ||||
|     ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID()); | ||||
|     return node->GetVolumeType(); | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::SetPrintableState( | ||||
|     PrintIndicator  printable, | ||||
|     int             obj_idx, | ||||
|  | @ -1480,6 +1491,9 @@ wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( | |||
| 
 | ||||
| void ObjectDataViewModel::Rescale() | ||||
| { | ||||
|     m_volume_bmps = MenuFactory::get_volume_bitmaps(); | ||||
|     m_warning_bmp = create_scaled_bitmap(WarningIcon); | ||||
| 
 | ||||
|     wxDataViewItemArray all_items; | ||||
|     GetAllChildren(wxDataViewItem(0), all_items); | ||||
| 
 | ||||
|  | @ -1494,15 +1508,15 @@ void ObjectDataViewModel::Rescale() | |||
|         switch (node->m_type) | ||||
|         { | ||||
|         case itObject: | ||||
|             if (node->m_bmp.IsOk()) node->m_bmp = *m_warning_bmp; | ||||
|             if (node->m_bmp.IsOk()) node->m_bmp = m_warning_bmp; | ||||
|             break; | ||||
|         case itVolume: | ||||
|             node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_bmp.GetWidth() != node->m_bmp.GetHeight()); | ||||
|             break; | ||||
|         case itLayerRoot: | ||||
|             node->m_bmp = create_scaled_bitmap(LAYER_ROOT_ICON); | ||||
|             node->m_bmp = create_scaled_bitmap(LayerRootIcon); | ||||
|         case itLayer: | ||||
|             node->m_bmp = create_scaled_bitmap(LAYER_ICON); | ||||
|             node->m_bmp = create_scaled_bitmap(LayerIcon); | ||||
|             break; | ||||
|         default: break; | ||||
|         } | ||||
|  | @ -1514,7 +1528,7 @@ void ObjectDataViewModel::Rescale() | |||
| wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked/* = false*/) | ||||
| { | ||||
|     if (!is_marked) | ||||
|         return *m_volume_bmps[static_cast<int>(vol_type)]; | ||||
|         return m_volume_bmps[static_cast<int>(vol_type)]; | ||||
| 
 | ||||
|     std::string scaled_bitmap_name = "warning" + std::to_string(static_cast<int>(vol_type)); | ||||
|     scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); | ||||
|  | @ -1523,8 +1537,8 @@ wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_ty | |||
|     if (bmp == nullptr) { | ||||
|         std::vector<wxBitmap> bmps; | ||||
| 
 | ||||
|         bmps.emplace_back(*m_warning_bmp); | ||||
|         bmps.emplace_back(*m_volume_bmps[static_cast<int>(vol_type)]); | ||||
|         bmps.emplace_back(m_warning_bmp); | ||||
|         bmps.emplace_back(m_volume_bmps[static_cast<int>(vol_type)]); | ||||
| 
 | ||||
|         bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); | ||||
|     } | ||||
|  | @ -1543,7 +1557,7 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo | |||
|         return; | ||||
| 
 | ||||
|     if (node->GetType() & itVolume) { | ||||
|         node->SetBitmap(*m_volume_bmps[static_cast<int>(node->volume_type())]); | ||||
|         node->SetBitmap(m_volume_bmps[static_cast<int>(node->volume_type())]); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -171,13 +171,14 @@ public: | |||
|     } | ||||
| 
 | ||||
|     bool            SetValue(const wxVariant &variant, unsigned int col); | ||||
| 
 | ||||
|     void            SetVolumeType(ModelVolumeType type) { m_volume_type = type; } | ||||
|     void            SetBitmap(const wxBitmap &icon) { m_bmp = icon; } | ||||
|     const wxBitmap& GetBitmap() const               { return m_bmp; } | ||||
|     const wxString& GetName() const                 { return m_name; } | ||||
|     ItemType        GetType() const                 { return m_type; } | ||||
| 	void			SetIdx(const int& idx); | ||||
| 	int             GetIdx() const                  { return m_idx; } | ||||
|     ModelVolumeType GetVolumeType()                 { return m_volume_type; } | ||||
| 	t_layer_height_range    GetLayerRange() const   { return m_layer_range; } | ||||
|     PrintIndicator  IsPrintable() const             { return m_printable; } | ||||
| 
 | ||||
|  | @ -241,8 +242,8 @@ wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); | |||
| class ObjectDataViewModel :public wxDataViewModel | ||||
| { | ||||
|     std::vector<ObjectDataViewModelNode*>       m_objects; | ||||
|     std::vector<wxBitmap*>                      m_volume_bmps; | ||||
|     wxBitmap*                                   m_warning_bmp { nullptr }; | ||||
|     std::vector<wxBitmap>                       m_volume_bmps; | ||||
|     wxBitmap                                    m_warning_bmp; | ||||
| 
 | ||||
|     wxDataViewCtrl*                             m_ctrl { nullptr }; | ||||
| 
 | ||||
|  | @ -348,9 +349,8 @@ public: | |||
|     void    UpdateObjectPrintable(wxDataViewItem parent_item); | ||||
|     void    UpdateInstancesPrintable(wxDataViewItem parent_item); | ||||
| 
 | ||||
|     void    SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; } | ||||
|     void    SetWarningBitmap(wxBitmap* bitmap)                          { m_warning_bmp = bitmap; } | ||||
|     void    SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); | ||||
|     ModelVolumeType GetVolumeType(const wxDataViewItem &item); | ||||
|     wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, | ||||
|                                       int subobj_idx = -1,  | ||||
|                                       ItemType subobj_type = itInstance); | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| #include "GUI.hpp" | ||||
| #include "I18N.hpp" | ||||
| #include "3DScene.hpp" | ||||
| #include "slic3r/Utils/Platform.hpp" | ||||
| 
 | ||||
| #include <GL/glew.h> | ||||
| 
 | ||||
|  | @ -319,7 +320,13 @@ void OpenGLManager::detect_multisample(int* attribList) | |||
| { | ||||
|     int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; | ||||
|     bool enable_multisample = wxVersion >= 30003; | ||||
|     s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? EMultisampleState::Enabled : EMultisampleState::Disabled; | ||||
|     s_multisample =  | ||||
|         enable_multisample && | ||||
|         // Disable multi-sampling on ChromeOS, as the OpenGL virtualization swaps Red/Blue channels with multi-sampling enabled,
 | ||||
|         // at least on some platforms.
 | ||||
|         (platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium) &&  | ||||
|         wxGLCanvas::IsDisplaySupported(attribList) | ||||
|         ? EMultisampleState::Enabled : EMultisampleState::Disabled; | ||||
|     // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows
 | ||||
|     // s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample");
 | ||||
| } | ||||
|  |  | |||
|  | @ -25,23 +25,27 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id) { | |||
| const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt) { | ||||
|     // Check the gui_type field first, fall through
 | ||||
|     // is the normal type.
 | ||||
|     if (opt.gui_type == "select") { | ||||
|     } else if (opt.gui_type == "select_open") { | ||||
|     switch (opt.gui_type) { | ||||
|     case ConfigOptionDef::GUIType::select_open: | ||||
|         m_fields.emplace(id, Choice::Create<Choice>(this->ctrl_parent(), opt, id)); | ||||
|     } else if (opt.gui_type == "color") { | ||||
|         break; | ||||
|     case ConfigOptionDef::GUIType::color: | ||||
|         m_fields.emplace(id, ColourPicker::Create<ColourPicker>(this->ctrl_parent(), opt, id)); | ||||
|     } else if (opt.gui_type == "f_enum_open" || | ||||
|                 opt.gui_type == "i_enum_open" || | ||||
|                 opt.gui_type == "i_enum_closed") { | ||||
|         break; | ||||
|     case ConfigOptionDef::GUIType::f_enum_open: | ||||
|     case ConfigOptionDef::GUIType::i_enum_open: | ||||
|         m_fields.emplace(id, Choice::Create<Choice>(this->ctrl_parent(), opt, id)); | ||||
|     } else if (opt.gui_type == "slider") { | ||||
|         break; | ||||
|     case ConfigOptionDef::GUIType::slider: | ||||
|         m_fields.emplace(id, SliderCtrl::Create<SliderCtrl>(this->ctrl_parent(), opt, id)); | ||||
|     } else if (opt.gui_type == "i_spin") { // Spinctrl
 | ||||
|     } else if (opt.gui_type == "legend") { // StaticText
 | ||||
|         break; | ||||
|     case ConfigOptionDef::GUIType::legend: // StaticText
 | ||||
|         m_fields.emplace(id, StaticText::Create<StaticText>(this->ctrl_parent(), opt, id)); | ||||
|     } else if (opt.gui_type == "one_string") { | ||||
|         break; | ||||
|     case ConfigOptionDef::GUIType::one_string: | ||||
|         m_fields.emplace(id, TextCtrl::Create<TextCtrl>(this->ctrl_parent(), opt, id)); | ||||
|     } else { | ||||
|         break; | ||||
|     default: | ||||
|         switch (opt.type) { | ||||
|             case coFloatOrPercent: | ||||
|             case coFloat: | ||||
|  | @ -122,7 +126,7 @@ bool OptionsGroup::is_legend_line() | |||
| { | ||||
| 	if (m_lines.size() == 1) { | ||||
| 		const std::vector<Option>& option_set = m_lines.front().get_options(); | ||||
| 		return !option_set.empty() && option_set.front().opt.gui_type == "legend"; | ||||
| 		return !option_set.empty() && option_set.front().opt.gui_type == ConfigOptionDef::GUIType::legend; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | @ -213,7 +217,7 @@ void OptionsGroup::activate_line(Line& line) | |||
|     } | ||||
| 
 | ||||
| 	auto option_set = line.get_options(); | ||||
| 	bool is_legend_line = option_set.front().opt.gui_type == "legend"; | ||||
| 	bool is_legend_line = option_set.front().opt.gui_type == ConfigOptionDef::GUIType::legend; | ||||
| 
 | ||||
|     if (!custom_ctrl && m_use_custom_ctrl) { | ||||
|         custom_ctrl = new OG_CustomCtrl(is_legend_line || !staticbox ? this->parent() : static_cast<wxWindow*>(this->stb), this); | ||||
|  |  | |||
|  | @ -50,6 +50,7 @@ | |||
| #include "GUI_ObjectManipulation.hpp" | ||||
| #include "GUI_ObjectLayers.hpp" | ||||
| #include "GUI_Utils.hpp" | ||||
| #include "GUI_Factories.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| #include "MainFrame.hpp" | ||||
| #include "format.hpp" | ||||
|  | @ -75,6 +76,7 @@ | |||
| #include "../Utils/FixModelByWin10.hpp" | ||||
| #include "../Utils/UndoRedo.hpp" | ||||
| #include "../Utils/PresetUpdater.hpp" | ||||
| #include "../Utils/Platform.hpp" | ||||
| #include "../Utils/Process.hpp" | ||||
| #include "RemovableDriveManager.hpp" | ||||
| #include "InstanceCheck.hpp" | ||||
|  | @ -356,7 +358,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : | |||
|     ConfigOptionDef support_def; | ||||
|     support_def.label = L("Supports"); | ||||
|     support_def.type = coStrings; | ||||
|     support_def.gui_type = "select_open"; | ||||
|     support_def.gui_type = ConfigOptionDef::GUIType::select_open; | ||||
|     support_def.tooltip = L("Select what kind of support do you need"); | ||||
|     support_def.enum_labels.push_back(L("None")); | ||||
|     support_def.enum_labels.push_back(L("Support on build plate only")); | ||||
|  | @ -396,7 +398,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : | |||
|     def.label = L("Brim"); | ||||
|     def.type = coBool; | ||||
|     def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer."); | ||||
|     def.gui_type = ""; | ||||
|     def.gui_type = ConfigOptionDef::GUIType::undefined; | ||||
|     def.set_default_value(new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }); | ||||
|     option = Option(def, "brim"); | ||||
|     option.opt.sidetext = ""; | ||||
|  | @ -499,7 +501,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : | |||
|     ConfigOptionDef pad_def; | ||||
|     pad_def.label = L("Pad"); | ||||
|     pad_def.type = coStrings; | ||||
|     pad_def.gui_type = "select_open"; | ||||
|     pad_def.gui_type = ConfigOptionDef::GUIType::select_open; | ||||
|     pad_def.tooltip = L("Select what kind of pad do you need"); | ||||
|     pad_def.enum_labels.push_back(L("None")); | ||||
|     pad_def.enum_labels.push_back(L("Below object")); | ||||
|  | @ -1322,7 +1324,7 @@ void Sidebar::update_mode() | |||
| 
 | ||||
|     p->object_list->unselect_objects(); | ||||
|     p->object_list->update_selections(); | ||||
|     p->object_list->update_object_menu(); | ||||
| //    p->object_list->update_object_menu();
 | ||||
| 
 | ||||
|     Layout(); | ||||
| } | ||||
|  | @ -1404,23 +1406,7 @@ struct Plater::priv | |||
|     Plater *q; | ||||
|     MainFrame *main_frame; | ||||
| 
 | ||||
|     // Object popup menu
 | ||||
|     MenuWithSeparators object_menu; | ||||
|     // Part popup menu
 | ||||
|     MenuWithSeparators part_menu; | ||||
|     // SLA-Object popup menu
 | ||||
|     MenuWithSeparators sla_object_menu; | ||||
|     // Default popup menu (when nothing is selected on 3DScene)
 | ||||
|     MenuWithSeparators default_menu; | ||||
| 
 | ||||
|     // Removed/Prepended Items according to the view mode
 | ||||
|     std::vector<wxMenuItem*> items_increase; | ||||
|     std::vector<wxMenuItem*> items_decrease; | ||||
|     std::vector<wxMenuItem*> items_set_number_of_copies; | ||||
|     enum MenuIdentifier { | ||||
|         miObjectFFF=0, | ||||
|         miObjectSLA | ||||
|     }; | ||||
|     MenuFactory menus; | ||||
| 
 | ||||
|     // Data
 | ||||
|     Slic3r::DynamicPrintConfig *config;        // FIXME: leak?
 | ||||
|  | @ -1669,7 +1655,6 @@ struct Plater::priv | |||
|     void on_update_geometry(Vec3dsEvent<2>&); | ||||
|     void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); | ||||
| 
 | ||||
|     void update_object_menu(); | ||||
|     void show_action_buttons(const bool is_ready_to_slice) const; | ||||
| 
 | ||||
|     // Set the bed shape to a single closed 2D polygon(array of two element arrays),
 | ||||
|  | @ -1690,12 +1675,11 @@ struct Plater::priv | |||
|     bool can_set_instance_to_object() const; | ||||
|     bool can_mirror() const; | ||||
|     bool can_reload_from_disk() const; | ||||
|     bool can_split(bool to_objects) const; | ||||
| 
 | ||||
|     void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); | ||||
|     void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); | ||||
| 
 | ||||
|     void msw_rescale_object_menu(); | ||||
| 
 | ||||
|     void bring_instance_forward() const; | ||||
| 
 | ||||
|     // returns the path to project file with the given extension (none if extension == wxEmptyString)
 | ||||
|  | @ -1712,13 +1696,6 @@ struct Plater::priv | |||
|     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); | ||||
|     bool complit_init_object_menu(); | ||||
|     bool complit_init_sla_object_menu(); | ||||
|     bool complit_init_part_menu(); | ||||
| 
 | ||||
|     bool can_split() const; | ||||
|     bool layers_height_allowed() const; | ||||
| 
 | ||||
|     void update_fff_scene(); | ||||
|  | @ -1762,7 +1739,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|         // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
 | ||||
|         "layer_height", "first_layer_height", "min_layer_height", "max_layer_height", | ||||
|         "brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers",  | ||||
|         "support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers" | ||||
|         "support_material", "support_material_extruder", "support_material_interface_extruder",  | ||||
|         "support_material_contact_distance", "support_material_bottom_contact_distance", "raft_layers" | ||||
|         })) | ||||
|     , sidebar(new Sidebar(q)) | ||||
|     , m_ui_jobs(this) | ||||
|  | @ -1827,7 +1805,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     hsizer->Add(sidebar, 0, wxEXPAND | wxLEFT | wxRIGHT, 0); | ||||
|     q->SetSizer(hsizer); | ||||
| 
 | ||||
|     init_object_menu(); | ||||
|     menus.init(q); | ||||
| 
 | ||||
|     // Events:
 | ||||
| 
 | ||||
|  | @ -2723,19 +2701,19 @@ void Plater::priv::split_object() | |||
|     Model new_model = model; | ||||
|     ModelObject* current_model_object = new_model.objects[obj_idx]; | ||||
| 
 | ||||
|     if (current_model_object->volumes.size() > 1) | ||||
|     { | ||||
|         Slic3r::GUI::warning_catcher(q, _L("The selected object can't be split because it contains more than one volume/material.")); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     wxBusyCursor wait; | ||||
|     ModelObjectPtrs new_objects; | ||||
|     current_model_object->split(&new_objects); | ||||
|     if (new_objects.size() == 1) | ||||
|         Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one part.")); | ||||
|         // #ysFIXME use notification
 | ||||
|         Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one solid part.")); | ||||
|     else | ||||
|     { | ||||
|         if (current_model_object->volumes.size() != new_objects.size()) | ||||
|             notification_manager->push_notification(NotificationType::CustomNotification, | ||||
|                 NotificationManager::NotificationLevel::RegularNotification, | ||||
|                 _u8L("All non-solid parts (modifiers) was deleted")); | ||||
| 
 | ||||
|         Plater::TakeSnapshot snapshot(q, _L("Split to Objects")); | ||||
| 
 | ||||
|         remove(obj_idx); | ||||
|  | @ -3563,6 +3541,7 @@ void Plater::priv::on_slicing_began() | |||
| { | ||||
| 	clear_warnings(); | ||||
| 	notification_manager->close_notification_of_type(NotificationType::SlicingComplete); | ||||
|     notification_manager->close_notification_of_type(NotificationType::SignDetected); | ||||
| } | ||||
| void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) | ||||
| { | ||||
|  | @ -3679,7 +3658,9 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) | |||
|         // If writing to removable drive was scheduled, show notification with eject button
 | ||||
|         if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) { | ||||
|             show_action_buttons(false); | ||||
|             notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, true); | ||||
|             notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, | ||||
|                 // Don't offer the "Eject" button on ChromeOS, the Linux side has no control over it.
 | ||||
|                 platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium); | ||||
|             wxGetApp().removable_drive_manager()->set_exporting_finished(true); | ||||
|         }else if (exporting_status == ExportingStatus::EXPORTING_TO_LOCAL && !has_error) | ||||
|             notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, false); | ||||
|  | @ -3727,70 +3708,25 @@ void Plater::priv::on_right_click(RBtnEvent& evt) | |||
| 
 | ||||
|     wxMenu* menu = nullptr; | ||||
| 
 | ||||
|     if (obj_idx == -1) // no one or several object are selected
 | ||||
|     {  | ||||
|     if (obj_idx == -1) { // no one or several object are selected
 | ||||
|         if (evt.data.second) // right button was clicked on empty space
 | ||||
|             menu = &default_menu; | ||||
|             menu = menus.default_menu(); | ||||
|         else | ||||
|         { | ||||
|             sidebar->obj_list()->show_multi_selection_menu(); | ||||
|             return; | ||||
|         } | ||||
|             menu = menus.multi_selection_menu(); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|     else { | ||||
|         // If in 3DScene is(are) selected volume(s), but right button was clicked on empty space
 | ||||
|         if (evt.data.second) | ||||
|             return;  | ||||
| 
 | ||||
|         int menu_item_convert_unit_position = 11; | ||||
|             return; | ||||
| 
 | ||||
|         if (printer_technology == ptSLA) | ||||
|             menu = &sla_object_menu; | ||||
|         else | ||||
|         { | ||||
|             menu = menus.sla_object_menu(); | ||||
|         else { | ||||
|             // show "Object menu" for each one or several FullInstance instead of FullObject
 | ||||
|             const bool is_some_full_instances = get_selection().is_single_full_instance() ||  | ||||
|                                                 get_selection().is_single_full_object() ||  | ||||
|                                                 get_selection().is_multiple_full_instance(); | ||||
|             menu = is_some_full_instances ? &object_menu : &part_menu; | ||||
|             if (!is_some_full_instances) | ||||
|                 menu_item_convert_unit_position = 2; | ||||
|         } | ||||
| 
 | ||||
|         sidebar->obj_list()->append_menu_items_convert_unit(menu, menu_item_convert_unit_position); | ||||
|         sidebar->obj_list()->append_menu_item_settings(menu); | ||||
| 
 | ||||
|         if (printer_technology != ptSLA) | ||||
|             sidebar->obj_list()->append_menu_item_change_extruder(menu); | ||||
| 
 | ||||
|         if (menu != &part_menu) | ||||
|         { | ||||
|             /* Remove/Prepend "increase/decrease instances" menu items according to the view mode.
 | ||||
|              * Suppress to show those items for a Simple mode | ||||
|              */ | ||||
|             const MenuIdentifier id = printer_technology == ptSLA ? miObjectSLA : miObjectFFF; | ||||
|             if (wxGetApp().get_mode() == comSimple) { | ||||
|                 if (menu->FindItem(_L("Add instance")) != wxNOT_FOUND) | ||||
|                 { | ||||
|                     /* Detach an items from the menu, but don't delete them
 | ||||
|                      * so that they can be added back later | ||||
|                      * (after switching to the Advanced/Expert mode) | ||||
|                      */ | ||||
|                     menu->Remove(items_increase[id]); | ||||
|                     menu->Remove(items_decrease[id]); | ||||
|                     menu->Remove(items_set_number_of_copies[id]); | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 if (menu->FindItem(_L("Add instance")) == wxNOT_FOUND) | ||||
|                 { | ||||
|                     // Prepend items to the menu, if those aren't not there
 | ||||
|                     menu->Prepend(items_set_number_of_copies[id]); | ||||
|                     menu->Prepend(items_decrease[id]); | ||||
|                     menu->Prepend(items_increase[id]); | ||||
|                 } | ||||
|             } | ||||
|             menu = is_some_full_instances ? menus.object_menu() : menus.part_menu(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -3837,26 +3773,6 @@ void Plater::priv::on_3dcanvas_mouse_dragging_finished(SimpleEvent&) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::init_object_menu() | ||||
| { | ||||
|     items_increase.reserve(2); | ||||
|     items_decrease.reserve(2); | ||||
|     items_set_number_of_copies.reserve(2); | ||||
| 
 | ||||
|     init_common_menu(&object_menu); | ||||
|     complit_init_object_menu(); | ||||
| 
 | ||||
|     init_common_menu(&sla_object_menu); | ||||
|     complit_init_sla_object_menu(); | ||||
| 
 | ||||
|     init_common_menu(&part_menu, true); | ||||
|     complit_init_part_menu(); | ||||
| 
 | ||||
|     sidebar->obj_list()->create_default_popupmenu(&default_menu); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) | ||||
| { | ||||
|     view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background); | ||||
|  | @ -3875,12 +3791,6 @@ void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::msw_rescale_object_menu() | ||||
| { | ||||
|     for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu, &default_menu }) | ||||
|         msw_rescale_menu(dynamic_cast<wxMenu*>(menu)); | ||||
| } | ||||
| 
 | ||||
| wxString Plater::priv::get_project_filename(const wxString& extension) const | ||||
| { | ||||
|     return m_project_filename.empty() ? "" : m_project_filename + extension; | ||||
|  | @ -3909,144 +3819,6 @@ void Plater::priv::set_project_filename(const wxString& filename) | |||
|         wxGetApp().mainframe->add_to_recent_projects(filename); | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/) | ||||
| { | ||||
|     if (is_part) { | ||||
|         append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"), | ||||
|             [this](wxCommandEvent&) { q->remove_selected();         }, "delete",            nullptr, [this]() { return can_delete(); }, q); | ||||
| 
 | ||||
|         append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected volumes from disk"), | ||||
|             [this](wxCommandEvent&) { q->reload_from_disk(); }, "", menu, [this]() { return can_reload_from_disk(); }, q); | ||||
| 
 | ||||
|         sidebar->obj_list()->append_menu_item_export_stl(menu); | ||||
|     } | ||||
|     else { | ||||
|         wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _L("Add instance") + "\t+", _L("Add one more instance of the selected object"), | ||||
|             [this](wxCommandEvent&) { q->increase_instances();      }, "add_copies",        nullptr, [this]() { return can_increase_instances(); }, q); | ||||
|         wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _L("Remove instance") + "\t-", _L("Remove one instance of the selected object"), | ||||
|             [this](wxCommandEvent&) { q->decrease_instances();      }, "remove_copies",     nullptr, [this]() { return can_decrease_instances(); }, q); | ||||
|         wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _L("Set number of instances") + dots, _L("Change the number of instances of the selected object"), | ||||
|             [this](wxCommandEvent&) { q->set_number_of_copies();    }, "number_of_copies",  nullptr, [this]() { return can_increase_instances(); }, q); | ||||
|         append_menu_item(menu, wxID_ANY, _L("Fill bed with instances") + dots, _L("Fill the remaining area of bed with instances of the selected object"), | ||||
|             [this](wxCommandEvent&) { q->fill_bed_with_instances();    }, "",  nullptr, [this]() { return can_increase_instances(); }, q); | ||||
| 
 | ||||
| 
 | ||||
|         items_increase.push_back(item_increase); | ||||
|         items_decrease.push_back(item_decrease); | ||||
|         items_set_number_of_copies.push_back(item_set_number_of_copies); | ||||
| 
 | ||||
|         // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake.
 | ||||
|         append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"), | ||||
|             [this](wxCommandEvent&) { q->remove_selected(); }, "delete",            nullptr, [this]() { return can_delete(); }, q); | ||||
| 
 | ||||
|         menu->AppendSeparator(); | ||||
|         sidebar->obj_list()->append_menu_item_instance_to_object(menu, q); | ||||
|         menu->AppendSeparator(); | ||||
| 
 | ||||
|         wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q); | ||||
|         menu->AppendSeparator(); | ||||
| 
 | ||||
|         append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected object from disk"), | ||||
|             [this](wxCommandEvent&) { reload_from_disk(); }, "", nullptr, [this]() { return can_reload_from_disk(); }, q); | ||||
| 
 | ||||
|         append_menu_item(menu, wxID_ANY, _L("Export as STL") + dots, _L("Export the selected object as STL file"), | ||||
|             [this](wxCommandEvent&) { q->export_stl(false, true); }, "", nullptr,  | ||||
|             [this]() { | ||||
|                 const Selection& selection = get_selection(); | ||||
|                 return selection.is_single_full_instance() || selection.is_single_full_object(); | ||||
|             }, q); | ||||
| 
 | ||||
|         menu->AppendSeparator(); | ||||
| 
 | ||||
|         // "Scale to print volume" makes a sense just for whole object
 | ||||
|         sidebar->obj_list()->append_menu_item_scale_selection_to_fit_print_volume(menu); | ||||
| 
 | ||||
|         q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { | ||||
|             const Selection& selection = get_selection(); | ||||
|             int instance_idx = selection.get_instance_idx(); | ||||
|             evt.Enable(selection.is_single_full_instance() || selection.is_single_full_object()); | ||||
|             if (instance_idx != -1) | ||||
|             { | ||||
|                 evt.Check(model.objects[selection.get_object_idx()]->instances[instance_idx]->printable); | ||||
|                 view3D->set_as_dirty(); | ||||
|             } | ||||
|             }, menu_item_printable->GetId()); | ||||
|     } | ||||
| 
 | ||||
|     sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); | ||||
| 
 | ||||
|     wxMenu* mirror_menu = new wxMenu(); | ||||
|     if (mirror_menu == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|     append_menu_item(mirror_menu, wxID_ANY, _L("Along X axis"), _L("Mirror the selected object along the X axis"), | ||||
|         [this](wxCommandEvent&) { mirror(X); }, "mark_X", menu); | ||||
|     append_menu_item(mirror_menu, wxID_ANY, _L("Along Y axis"), _L("Mirror the selected object along the Y axis"), | ||||
|         [this](wxCommandEvent&) { mirror(Y); }, "mark_Y", menu); | ||||
|     append_menu_item(mirror_menu, wxID_ANY, _L("Along Z axis"), _L("Mirror the selected object along the Z axis"), | ||||
|         [this](wxCommandEvent&) { mirror(Z); }, "mark_Z", menu); | ||||
| 
 | ||||
|     append_submenu(menu, mirror_menu, wxID_ANY, _L("Mirror"), _L("Mirror the selected object"), "", | ||||
|         [this]() { return can_mirror(); }, q); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::complit_init_object_menu() | ||||
| { | ||||
|     wxMenu* split_menu = new wxMenu(); | ||||
|     if (split_menu == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|     append_menu_item(split_menu, wxID_ANY, _L("To objects"), _L("Split the selected object into individual objects"), | ||||
|         [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL",  &object_menu, [this]() { return can_split(); }, q); | ||||
|     append_menu_item(split_menu, wxID_ANY, _L("To parts"), _L("Split the selected object into individual sub-parts"), | ||||
|         [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL",   &object_menu, [this]() { return can_split(); }, q); | ||||
| 
 | ||||
|     append_submenu(&object_menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "", | ||||
|         [this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q); | ||||
|     object_menu.AppendSeparator(); | ||||
| 
 | ||||
|     // Layers Editing for object
 | ||||
|     sidebar->obj_list()->append_menu_item_layers_editing(&object_menu, q); | ||||
|     object_menu.AppendSeparator(); | ||||
| 
 | ||||
|     // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume()
 | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::complit_init_sla_object_menu() | ||||
| { | ||||
|     append_menu_item(&sla_object_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual objects"), | ||||
|         [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", nullptr, [this]() { return can_split(); }, q); | ||||
| 
 | ||||
|     sla_object_menu.AppendSeparator(); | ||||
| 
 | ||||
|     // Add the automatic rotation sub-menu
 | ||||
|     append_menu_item( | ||||
|         &sla_object_menu, wxID_ANY, _(L("Optimize orientation")), | ||||
|         _(L("Optimize the rotation of the object for better print results.")), | ||||
|         [this](wxCommandEvent &) { | ||||
|             m_ui_jobs.optimize_rotation(); | ||||
|         }); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::complit_init_part_menu() | ||||
| { | ||||
|     append_menu_item(&part_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual sub-parts"), | ||||
|         [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", nullptr, [this]() { return can_split(); }, q); | ||||
| 
 | ||||
|     part_menu.AppendSeparator(); | ||||
| 
 | ||||
|     auto obj_list = sidebar->obj_list(); | ||||
|     obj_list->append_menu_item_change_type(&part_menu, q); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::set_current_canvas_as_dirty() | ||||
| { | ||||
|     if (current_panel == view3D) | ||||
|  | @ -4196,9 +3968,9 @@ bool Plater::priv::can_set_instance_to_object() const | |||
|     return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::can_split() const | ||||
| bool Plater::priv::can_split(bool to_objects) const | ||||
| { | ||||
|     return sidebar->obj_list()->is_splittable(); | ||||
|     return sidebar->obj_list()->is_splittable(to_objects); | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::layers_height_allowed() const | ||||
|  | @ -4314,12 +4086,12 @@ bool Plater::priv::can_decrease_instances() const | |||
| 
 | ||||
| bool Plater::priv::can_split_to_objects() const | ||||
| { | ||||
|     return can_split(); | ||||
|     return q->can_split(true); | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::can_split_to_volumes() const | ||||
| { | ||||
|     return (printer_technology != ptSLA) && can_split(); | ||||
|     return (printer_technology != ptSLA) && q->can_split(false); | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::can_arrange() const | ||||
|  | @ -4332,11 +4104,6 @@ bool Plater::priv::can_layers_editing() const | |||
|     return layers_height_allowed(); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::update_object_menu() | ||||
| { | ||||
|     sidebar->obj_list()->append_menu_items_add_volume(&object_menu); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::show_action_buttons(const bool ready_to_slice) const | ||||
| { | ||||
| 	// Cache this value, so that the callbacks from the RemovableDriveManager may repeat that value when calling show_action_buttons().
 | ||||
|  | @ -5363,7 +5130,7 @@ void Plater::export_stl(bool extended, bool selection_only) | |||
|                         inst_mesh.merge(inst_supports_mesh); | ||||
|                     } | ||||
| 
 | ||||
|                     TriangleMesh inst_object_mesh = object->get_mesh_to_print(); | ||||
|                     TriangleMesh inst_object_mesh = object->get_mesh_to_slice(); | ||||
|                     inst_object_mesh.transform(mesh_trafo_inv); | ||||
|                     inst_object_mesh.transform(inst_transform, is_left_handed); | ||||
| 
 | ||||
|  | @ -6056,9 +5823,12 @@ void Plater::suppress_background_process(const bool stop_background_process) | |||
| } | ||||
| 
 | ||||
| void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); } | ||||
| 
 | ||||
| void Plater::update_object_menu() { p->update_object_menu(); } | ||||
| void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); } | ||||
| void Plater::mirror(Axis axis)      { p->mirror(axis); } | ||||
| void Plater::split_object()         { p->split_object(); } | ||||
| void Plater::split_volume()         { p->split_volume(); } | ||||
| void Plater::optimize_rotation()    { p->m_ui_jobs.optimize_rotation();} | ||||
| void Plater::update_object_menu()   { p->menus.update_object_menu(); } | ||||
| void Plater::show_action_buttons(const bool ready_to_slice) const   { p->show_action_buttons(ready_to_slice); } | ||||
| 
 | ||||
| void Plater::copy_selection_to_clipboard() | ||||
| { | ||||
|  | @ -6115,7 +5885,7 @@ void Plater::msw_rescale() | |||
| 
 | ||||
|     p->sidebar->msw_rescale(); | ||||
| 
 | ||||
|     p->msw_rescale_object_menu(); | ||||
|     p->menus.msw_rescale(); | ||||
| 
 | ||||
|     Layout(); | ||||
|     GetParent()->Layout(); | ||||
|  | @ -6127,7 +5897,7 @@ void Plater::sys_color_changed() | |||
|     p->sidebar->sys_color_changed(); | ||||
| 
 | ||||
|     // msw_rescale_menu updates just icons, so use it
 | ||||
|     p->msw_rescale_object_menu(); | ||||
|     p->menus.msw_rescale(); | ||||
| 
 | ||||
|     Layout(); | ||||
|     GetParent()->Layout(); | ||||
|  | @ -6292,6 +6062,8 @@ bool Plater::can_copy_to_clipboard() const | |||
| bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); } | ||||
| bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); } | ||||
| bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } | ||||
| bool Plater::can_mirror() const { return p->can_mirror(); } | ||||
| bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); } | ||||
| const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } | ||||
| void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } | ||||
| void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } | ||||
|  | @ -6333,6 +6105,15 @@ void Plater::bring_instance_forward() | |||
|     p->bring_instance_forward(); | ||||
| } | ||||
| 
 | ||||
| wxMenu* Plater::object_menu()           { return p->menus.object_menu();            } | ||||
| wxMenu* Plater::part_menu()             { return p->menus.part_menu();              } | ||||
| wxMenu* Plater::sla_object_menu()       { return p->menus.sla_object_menu();        } | ||||
| wxMenu* Plater::default_menu()          { return p->menus.default_menu();           } | ||||
| wxMenu* Plater::instance_menu()         { return p->menus.instance_menu();          } | ||||
| wxMenu* Plater::layer_menu()            { return p->menus.layer_menu();             } | ||||
| wxMenu* Plater::multi_selection_menu()  { return p->menus.multi_selection_menu();   } | ||||
| 
 | ||||
| 
 | ||||
| SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() : | ||||
|     m_was_scheduled(wxGetApp().plater()->is_background_process_update_scheduled()) | ||||
| { | ||||
|  |  | |||
|  | @ -271,6 +271,10 @@ public: | |||
|     void copy_selection_to_clipboard(); | ||||
|     void paste_from_clipboard(); | ||||
|     void search(bool plater_is_active); | ||||
|     void mirror(Axis axis); | ||||
|     void split_object(); | ||||
|     void split_volume(); | ||||
|     void optimize_rotation(); | ||||
| 
 | ||||
|     bool can_delete() const; | ||||
|     bool can_delete_all() const; | ||||
|  | @ -287,6 +291,8 @@ public: | |||
|     bool can_undo() const; | ||||
|     bool can_redo() const; | ||||
|     bool can_reload_from_disk() const; | ||||
|     bool can_mirror() const; | ||||
|     bool can_split(bool to_objects) const; | ||||
| 
 | ||||
|     void msw_rescale(); | ||||
|     void sys_color_changed(); | ||||
|  | @ -375,6 +381,15 @@ public: | |||
| 	bool PopupMenu(wxMenu *menu, const wxPoint& pos = wxDefaultPosition); | ||||
|     bool PopupMenu(wxMenu *menu, int x, int y) { return this->PopupMenu(menu, wxPoint(x, y)); } | ||||
| 
 | ||||
|     // get same Plater/ObjectList menus
 | ||||
|     wxMenu* object_menu(); | ||||
|     wxMenu* part_menu(); | ||||
|     wxMenu* sla_object_menu(); | ||||
|     wxMenu* default_menu(); | ||||
|     wxMenu* instance_menu(); | ||||
|     wxMenu* layer_menu(); | ||||
|     wxMenu* multi_selection_menu(); | ||||
| 
 | ||||
| private: | ||||
|     struct priv; | ||||
|     std::unique_ptr<priv> p; | ||||
|  |  | |||
|  | @ -148,74 +148,33 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle | |||
|                 speed_normal = first_layer_speed.get_abs_value(speed_normal); | ||||
|             return (speed_normal > 0.) ? speed_normal : speed_max; | ||||
|         }; | ||||
|         auto test_flow = | ||||
|             [first_layer_extrusion_width_ptr, extrusion_width, nozzle_diameter, lh, bridging, bridge_speed, bridge_flow_ratio, limit_by_first_layer_speed, max_print_speed, &max_flow, &max_flow_extrusion_type] | ||||
|             (FlowRole flow_role, const ConfigOptionFloatOrPercent &this_extrusion_width, double speed, const char *err_msg) { | ||||
|             Flow flow = bridging ? | ||||
|                 Flow::new_from_config_width(flow_role, first_positive(first_layer_extrusion_width_ptr, this_extrusion_width, extrusion_width), nozzle_diameter, lh) : | ||||
|                 Flow::bridging_flow(nozzle_diameter * bridge_flow_ratio, nozzle_diameter); | ||||
|             double volumetric_flow = flow.mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(speed, max_print_speed)); | ||||
|             if (max_flow < volumetric_flow) { | ||||
|                 max_flow = volumetric_flow; | ||||
|                 max_flow_extrusion_type = _utf8(err_msg); | ||||
|             } | ||||
|         }; | ||||
|         if (perimeter_extruder_active) { | ||||
|             double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter,  | ||||
|                 first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width),  | ||||
|                 nozzle_diameter, lh, bfr).mm3_per_mm() * | ||||
|                 (bridging ? bridge_speed :  | ||||
|                     limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed)); | ||||
|             if (max_flow < external_perimeter_rate) { | ||||
|                 max_flow = external_perimeter_rate; | ||||
|                 max_flow_extrusion_type = _utf8(L("external perimeters")); | ||||
|             } | ||||
|             double perimeter_rate = Flow::new_from_config_width(frPerimeter,  | ||||
|                 first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width),  | ||||
|                 nozzle_diameter, lh, bfr).mm3_per_mm() * | ||||
|                 (bridging ? bridge_speed : | ||||
|                     limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed)); | ||||
|             if (max_flow < perimeter_rate) { | ||||
|                 max_flow = perimeter_rate; | ||||
|                 max_flow_extrusion_type = _utf8(L("perimeters")); | ||||
|             } | ||||
|         } | ||||
|         if (! bridging && infill_extruder_active) { | ||||
|             double infill_rate = Flow::new_from_config_width(frInfill,  | ||||
|                 first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width),  | ||||
|                 nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed); | ||||
|             if (max_flow < infill_rate) { | ||||
|                 max_flow = infill_rate; | ||||
|                 max_flow_extrusion_type = _utf8(L("infill")); | ||||
|             } | ||||
|             test_flow(frExternalPerimeter, external_perimeter_extrusion_width, std::max(external_perimeter_speed, small_perimeter_speed), L("external perimeters")); | ||||
|             test_flow(frPerimeter,         perimeter_extrusion_width,          std::max(perimeter_speed,          small_perimeter_speed), L("perimeters")); | ||||
|         } | ||||
|         if (! bridging && infill_extruder_active) | ||||
|             test_flow(frInfill, infill_extrusion_width, infill_speed, L("infill")); | ||||
|         if (solid_infill_extruder_active) { | ||||
|             double solid_infill_rate = Flow::new_from_config_width(frInfill,  | ||||
|                 first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width),  | ||||
|                 nozzle_diameter, lh, 0).mm3_per_mm() * | ||||
|                 (bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed)); | ||||
|             if (max_flow < solid_infill_rate) { | ||||
|                 max_flow = solid_infill_rate; | ||||
|                 max_flow_extrusion_type = _utf8(L("solid infill")); | ||||
|             } | ||||
|             if (! bridging) { | ||||
|                 double top_solid_infill_rate = Flow::new_from_config_width(frInfill,  | ||||
|                     first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width),  | ||||
|                     nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed); | ||||
|                 if (max_flow < top_solid_infill_rate) { | ||||
|                     max_flow = top_solid_infill_rate; | ||||
|                     max_flow_extrusion_type = _utf8(L("top solid infill")); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (support_material_extruder_active) { | ||||
|             double support_material_rate = Flow::new_from_config_width(frSupportMaterial, | ||||
|                 first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),  | ||||
|                 nozzle_diameter, lh, bfr).mm3_per_mm() * | ||||
|                 (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed)); | ||||
|             if (max_flow < support_material_rate) { | ||||
|                 max_flow = support_material_rate; | ||||
|                 max_flow_extrusion_type = _utf8(L("support")); | ||||
|             } | ||||
|         } | ||||
|         if (support_material_interface_extruder_active) { | ||||
|             double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface, | ||||
|                 first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width), | ||||
|                 nozzle_diameter, lh, bfr).mm3_per_mm() * | ||||
|                 (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed)); | ||||
|             if (max_flow < support_material_interface_rate) { | ||||
|                 max_flow = support_material_interface_rate; | ||||
|                 max_flow_extrusion_type = _utf8(L("support interface")); | ||||
|             } | ||||
|             test_flow(frInfill, solid_infill_extrusion_width, solid_infill_speed, L("solid infill")); | ||||
|             if (! bridging) | ||||
|                 test_flow(frInfill, top_infill_extrusion_width, top_solid_infill_speed, L("top solid infill")); | ||||
|         } | ||||
|         if (! bridging && support_material_extruder_active) | ||||
|             test_flow(frSupportMaterial, support_material_extrusion_width, support_material_speed, L("support")); | ||||
|         if (support_material_interface_extruder_active) | ||||
|             test_flow(frSupportMaterialInterface, support_material_extrusion_width, support_material_interface_speed, L("support interface")); | ||||
|         //FIXME handle gap_fill_speed
 | ||||
|         if (! out.empty()) | ||||
|             out += "\n"; | ||||
|  | @ -254,11 +213,11 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre | |||
|     Flow    external_perimeter_flow             = Flow::new_from_config_width( | ||||
|         frExternalPerimeter,  | ||||
|         *print_config.opt<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width"),  | ||||
|         nozzle_diameter, layer_height, false); | ||||
|         nozzle_diameter, layer_height); | ||||
|     Flow    perimeter_flow                      = Flow::new_from_config_width( | ||||
|         frPerimeter,  | ||||
|         *print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"),  | ||||
|         nozzle_diameter, layer_height, false); | ||||
|         nozzle_diameter, layer_height); | ||||
| 
 | ||||
|      | ||||
|     if (num_perimeters > 0) { | ||||
|  | @ -266,7 +225,7 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre | |||
|         out += (boost::format(_utf8(L("Recommended object thin wall thickness for layer height %.2f and"))) % layer_height).str() + " "; | ||||
|         // Start with the width of two closely spaced 
 | ||||
|         try { | ||||
| 	        double width = external_perimeter_flow.width + external_perimeter_flow.spacing(); | ||||
| 	        double width = external_perimeter_flow.width() + external_perimeter_flow.spacing(); | ||||
| 	        for (int i = 2; i <= num_lines; thin_walls ? ++ i : i += 2) { | ||||
| 	            if (i > 2) | ||||
| 	                out += ", "; | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| #include "PrintHostDialogs.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <iomanip> | ||||
| 
 | ||||
| #include <wx/frame.h> | ||||
| #include <wx/progdlg.h> | ||||
|  | @ -13,14 +14,18 @@ | |||
| #include <wx/wupdlock.h> | ||||
| #include <wx/debug.h> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/filesystem.hpp> | ||||
| #include <boost/nowide/convert.hpp> | ||||
| 
 | ||||
| #include "GUI.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| #include "MsgDialog.hpp" | ||||
| #include "I18N.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| #include "MainFrame.hpp" | ||||
| #include "libslic3r/AppConfig.hpp" | ||||
| #include "NotificationManager.hpp" | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| 
 | ||||
|  | @ -182,15 +187,24 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) | |||
| 
 | ||||
|     auto *topsizer = new wxBoxSizer(wxVERTICAL); | ||||
| 
 | ||||
|     std::vector<int> widths; | ||||
|     widths.reserve(6); | ||||
|     if (!load_user_data(UDT_COLS, widths)) { | ||||
|         widths.clear(); | ||||
|         for (size_t i = 0; i < 6; i++) | ||||
|             widths.push_back(-1); | ||||
|     } | ||||
| 
 | ||||
|     job_list = new wxDataViewListCtrl(this, wxID_ANY); | ||||
|     // Note: Keep these in sync with Column
 | ||||
|     job_list->AppendTextColumn(_L("ID"), wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn(_L("Status"), wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn(_L("Host"), wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn(_L("Filename"), wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn(_L("ID"), wxDATAVIEW_CELL_INERT, widths[0], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); | ||||
|     job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); | ||||
|     job_list->AppendTextColumn(_L("Status"), wxDATAVIEW_CELL_INERT, widths[2], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); | ||||
|     job_list->AppendTextColumn(_L("Host"), wxDATAVIEW_CELL_INERT, widths[3], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); | ||||
|     job_list->AppendTextColumn(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), wxDATAVIEW_CELL_INERT, widths[4], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); | ||||
|     job_list->AppendTextColumn(_L("Filename"), wxDATAVIEW_CELL_INERT, widths[5], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); | ||||
|     job_list->AppendTextColumn(_L("Error Message"), wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN); | ||||
| 
 | ||||
|   | ||||
|     auto *btnsizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected")); | ||||
|     btn_cancel->Disable(); | ||||
|  | @ -207,7 +221,21 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) | |||
|     topsizer->Add(btnsizer, 0, wxEXPAND); | ||||
|     SetSizer(topsizer); | ||||
| 
 | ||||
|     SetSize(wxSize(HEIGHT * em, WIDTH * em)); | ||||
|     std::vector<int> size; | ||||
|     SetSize(load_user_data(UDT_SIZE, size) ? wxSize(size[0] * em, size[1] * em) : wxSize(HEIGHT * em, WIDTH * em)); | ||||
| 
 | ||||
|     Bind(wxEVT_SIZE, [this, em](wxSizeEvent& evt) { | ||||
|         OnSize(evt); | ||||
|         save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); | ||||
|      }); | ||||
|      | ||||
|     std::vector<int> pos; | ||||
|     if (load_user_data(UDT_POSITION, pos)) | ||||
|         SetPosition(wxPoint(pos[0], pos[1])); | ||||
| 
 | ||||
|     Bind(wxEVT_MOVE, [this, em](wxMoveEvent& evt) { | ||||
|         save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); | ||||
|     }); | ||||
| 
 | ||||
|     job_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent&) { on_list_select(); }); | ||||
| 
 | ||||
|  | @ -238,11 +266,23 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job) | |||
|     fields.push_back(wxVariant(0)); | ||||
|     fields.push_back(wxVariant(_L("Enqueued"))); | ||||
|     fields.push_back(wxVariant(job.printhost->get_host())); | ||||
|     boost::system::error_code ec; | ||||
|     boost::uintmax_t size_i = boost::filesystem::file_size(job.upload_data.source_path, ec); | ||||
|     std::stringstream stream; | ||||
|     if (ec) { | ||||
|         stream << "unknown"; | ||||
|         size_i = 0; | ||||
|         BOOST_LOG_TRIVIAL(error) << ec.message(); | ||||
|     } else  | ||||
|         stream << std::fixed << std::setprecision(2) << ((float)size_i / 1024 / 1024) << "MB"; | ||||
|     fields.push_back(wxVariant(stream.str())); | ||||
|     fields.push_back(wxVariant(job.upload_data.upload_path.string())); | ||||
|     fields.push_back(wxVariant("")); | ||||
|     job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW)); | ||||
|     // Both strings are UTF-8 encoded.
 | ||||
|     upload_names.emplace_back(job.printhost->get_host(), job.upload_data.upload_path.string()); | ||||
| 
 | ||||
|     //wxGetApp().notification_manager()->push_upload_job_notification(this, job_list->GetItemCount(), 0, job.upload_data.upload_path.string(), job.printhost->get_host());
 | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect) | ||||
|  | @ -255,6 +295,8 @@ void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect) | |||
| 
 | ||||
|     Fit(); | ||||
|     Refresh(); | ||||
| 
 | ||||
|     save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); | ||||
| } | ||||
| 
 | ||||
| PrintHostQueueDialog::JobState PrintHostQueueDialog::get_state(int idx) | ||||
|  | @ -276,6 +318,8 @@ void PrintHostQueueDialog::set_state(int idx, JobState state) | |||
|         case ST_CANCELLED:  job_list->SetValue(_L("Cancelled"), idx, COL_STATUS); break; | ||||
|         case ST_COMPLETED:  job_list->SetValue(_L("Completed"), idx, COL_STATUS); break; | ||||
|     } | ||||
|     // This might be ambigous call, but user data needs to be saved time to time
 | ||||
|     save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_list_select() | ||||
|  | @ -304,6 +348,14 @@ void PrintHostQueueDialog::on_progress(Event &evt) | |||
|     } | ||||
| 
 | ||||
|     on_list_select(); | ||||
| 
 | ||||
|     if (evt.progress > 0) | ||||
|     { | ||||
|         wxVariant nm, hst; | ||||
|         job_list->GetValue(nm, evt.job_id, COL_FILENAME); | ||||
|         job_list->GetValue(hst, evt.job_id, COL_HOST); | ||||
|         wxGetApp().notification_manager()->set_upload_job_notification_percentage(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()), 100 / evt.progress); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_error(Event &evt) | ||||
|  | @ -319,6 +371,11 @@ void PrintHostQueueDialog::on_error(Event &evt) | |||
|     on_list_select(); | ||||
| 
 | ||||
|     GUI::show_error(nullptr, errormsg); | ||||
| 
 | ||||
|     wxVariant nm, hst; | ||||
|     job_list->GetValue(nm, evt.job_id, COL_FILENAME); | ||||
|     job_list->GetValue(hst, evt.job_id, COL_HOST); | ||||
|     wxGetApp().notification_manager()->upload_job_notification_show_error(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString())); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_cancel(Event &evt) | ||||
|  | @ -329,7 +386,13 @@ void PrintHostQueueDialog::on_cancel(Event &evt) | |||
|     job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS); | ||||
| 
 | ||||
|     on_list_select(); | ||||
| 
 | ||||
|     wxVariant nm, hst; | ||||
|     job_list->GetValue(nm, evt.job_id, COL_FILENAME); | ||||
|     job_list->GetValue(hst, evt.job_id, COL_HOST); | ||||
|     wxGetApp().notification_manager()->upload_job_notification_show_canceled(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString())); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::get_active_jobs(std::vector<std::pair<std::string, std::string>>& ret) | ||||
| { | ||||
|     int ic = job_list->GetItemCount(); | ||||
|  | @ -343,4 +406,60 @@ void PrintHostQueueDialog::get_active_jobs(std::vector<std::pair<std::string, st | |||
|     } | ||||
|     //job_list->data
 | ||||
| } | ||||
| void PrintHostQueueDialog::save_user_data(int udt) | ||||
| { | ||||
|     const auto em = GetTextExtent("m").x; | ||||
|     BOOST_LOG_TRIVIAL(error) << "save" << this->GetSize().x / em << " " << this->GetSize().y / em << " " << this->GetPosition().x << " " << this->GetPosition().y; | ||||
|     auto *app_config = wxGetApp().app_config; | ||||
|     if (udt & UserDataType::UDT_SIZE) { | ||||
|          | ||||
|         app_config->set("print_host_queue_dialog_height", std::to_string(this->GetSize().x / em)); | ||||
|         app_config->set("print_host_queue_dialog_width", std::to_string(this->GetSize().y / em)); | ||||
|     } | ||||
|     if (udt & UserDataType::UDT_POSITION) | ||||
|     { | ||||
|         app_config->set("print_host_queue_dialog_x", std::to_string(this->GetPosition().x)); | ||||
|         app_config->set("print_host_queue_dialog_y", std::to_string(this->GetPosition().y)); | ||||
|     } | ||||
|     if (udt & UserDataType::UDT_COLS) | ||||
|     { | ||||
|         for (size_t i = 0; i < job_list->GetColumnCount() - 1; i++) | ||||
|         { | ||||
|             app_config->set("print_host_queue_dialog_column_" + std::to_string(i), std::to_string(job_list->GetColumn(i)->GetWidth())); | ||||
|         } | ||||
|     }     | ||||
| } | ||||
| bool PrintHostQueueDialog::load_user_data(int udt, std::vector<int>& vector) | ||||
| { | ||||
|     auto* app_config = wxGetApp().app_config; | ||||
|     auto hasget = [app_config](const std::string& name, std::vector<int>& vector)->bool { | ||||
|         if (app_config->has(name)) { | ||||
|             vector.push_back(std::stoi(app_config->get(name))); | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     }; | ||||
|     if (udt & UserDataType::UDT_SIZE) { | ||||
|         if (!hasget("print_host_queue_dialog_height",vector)) | ||||
|             return false; | ||||
|         if (!hasget("print_host_queue_dialog_width", vector)) | ||||
|             return false; | ||||
|     } | ||||
|     if (udt & UserDataType::UDT_POSITION) | ||||
|     { | ||||
|         if (!hasget("print_host_queue_dialog_x", vector)) | ||||
|             return false; | ||||
|         if (!hasget("print_host_queue_dialog_y", vector)) | ||||
|             return false; | ||||
|     } | ||||
|     if (udt & UserDataType::UDT_COLS) | ||||
|     { | ||||
|         for (size_t i = 0; i < 6; i++) | ||||
|         { | ||||
|             if (!hasget("print_host_queue_dialog_column_" + std::to_string(i), vector)) | ||||
|                 return false; | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| }} | ||||
|  |  | |||
|  | @ -8,10 +8,8 @@ | |||
| #include <wx/event.h> | ||||
| #include <wx/dialog.h> | ||||
| 
 | ||||
| #include "GUI.hpp" | ||||
| #include "GUI_Utils.hpp" | ||||
| #include "MsgDialog.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
| 
 | ||||
| class wxButton; | ||||
| class wxTextCtrl; | ||||
|  | @ -65,6 +63,13 @@ public: | |||
| 
 | ||||
|     void append_job(const PrintHostJob &job); | ||||
|     void get_active_jobs(std::vector<std::pair<std::string, std::string>>& ret); | ||||
| 
 | ||||
|     virtual bool Show(bool show = true) override | ||||
|     { | ||||
|         if(!show) | ||||
|             save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); | ||||
|         return DPIDialog::Show(show); | ||||
|     } | ||||
| protected: | ||||
|     void on_dpi_changed(const wxRect &suggested_rect) override; | ||||
| 
 | ||||
|  | @ -74,8 +79,9 @@ private: | |||
|         COL_PROGRESS, | ||||
|         COL_STATUS, | ||||
|         COL_HOST, | ||||
|         COL_SIZE, | ||||
|         COL_FILENAME, | ||||
|         COL_ERRORMSG, | ||||
|         COL_ERRORMSG | ||||
|     }; | ||||
| 
 | ||||
|     enum JobState { | ||||
|  | @ -89,6 +95,12 @@ private: | |||
| 
 | ||||
|     enum { HEIGHT = 60, WIDTH = 30, SPACING = 5 }; | ||||
| 
 | ||||
|     enum UserDataType{ | ||||
|         UDT_SIZE = 1, | ||||
|         UDT_POSITION = 2, | ||||
|         UDT_COLS = 4 | ||||
|     }; | ||||
| 
 | ||||
|     wxButton *btn_cancel; | ||||
|     wxButton *btn_error; | ||||
|     wxDataViewListCtrl *job_list; | ||||
|  | @ -105,6 +117,8 @@ private: | |||
|     void on_cancel(Event&); | ||||
|     // This vector keep adress and filename of uploads. It is used when checking for running uploads during exit.
 | ||||
|     std::vector<std::pair<std::string, std::string>> upload_names; | ||||
|     void save_user_data(int); | ||||
|     bool load_user_data(int, std::vector<int>&); | ||||
| }; | ||||
| 
 | ||||
| wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| #include "RemovableDriveManager.hpp" | ||||
| #include "slic3r/Utils/Platform.hpp" | ||||
| #include <libslic3r/libslic3r.h> | ||||
| 
 | ||||
| #include <boost/nowide/convert.hpp> | ||||
|  | @ -231,22 +232,28 @@ std::vector<DriveData> RemovableDriveManager::search_for_removable_drives() cons | |||
| 
 | ||||
| #else | ||||
| 
 | ||||
|     //search /media/* folder
 | ||||
| 	search_for_drives_internal::search_path("/media/*", "/media", current_drives); | ||||
|    	if (platform() == Platform::Linux && platform_flavor() == PlatformFlavor::LinuxOnChromium) { | ||||
| 	    // ChromeOS specific: search /mnt/chromeos/removable/* folder
 | ||||
| 		search_for_drives_internal::search_path("/mnt/chromeos/removable/*", "/mnt/chromeos/removable", current_drives); | ||||
|    	} else { | ||||
| 	    //search /media/* folder
 | ||||
| 		search_for_drives_internal::search_path("/media/*", "/media", current_drives); | ||||
| 
 | ||||
| 	//search_path("/Volumes/*", "/Volumes");
 | ||||
|     std::string path(std::getenv("USER")); | ||||
| 	std::string pp(path); | ||||
| 		//search_path("/Volumes/*", "/Volumes");
 | ||||
| 	    std::string path(std::getenv("USER")); | ||||
| 		std::string pp(path); | ||||
| 
 | ||||
| 	//search /media/USERNAME/* folder
 | ||||
| 	pp = "/media/"+pp; | ||||
| 	path = "/media/" + path + "/*"; | ||||
| 	search_for_drives_internal::search_path(path, pp, current_drives); | ||||
| 		//search /media/USERNAME/* folder
 | ||||
| 		pp = "/media/"+pp; | ||||
| 		path = "/media/" + path + "/*"; | ||||
| 		search_for_drives_internal::search_path(path, pp, current_drives); | ||||
| 
 | ||||
| 		//search /run/media/USERNAME/* folder
 | ||||
| 		path = "/run" + path; | ||||
| 		pp = "/run"+pp; | ||||
| 		search_for_drives_internal::search_path(path, pp, current_drives); | ||||
| 	} | ||||
| 
 | ||||
| 	//search /run/media/USERNAME/* folder
 | ||||
| 	path = "/run" + path; | ||||
| 	pp = "/run"+pp; | ||||
| 	search_for_drives_internal::search_path(path, pp, current_drives); | ||||
| #endif | ||||
| 
 | ||||
| 	return current_drives; | ||||
|  | @ -443,7 +450,10 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status() | |||
| 	RemovableDriveManager::RemovableDrivesStatus out; | ||||
| 	{ | ||||
| 		tbb::mutex::scoped_lock lock(m_drives_mutex); | ||||
| 		out.has_eject = this->find_last_save_path_drive_data() != m_current_drives.end(); | ||||
| 		out.has_eject =  | ||||
| 			// Cannot control eject on Chromium.
 | ||||
| 			(platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium) && | ||||
| 			this->find_last_save_path_drive_data() != m_current_drives.end(); | ||||
| 		out.has_removable_drives = ! m_current_drives.empty(); | ||||
| 	} | ||||
| 	if (! out.has_eject)  | ||||
|  |  | |||
|  | @ -1433,6 +1433,7 @@ void TabPrint::build() | |||
|         optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters"); | ||||
|         optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour"); | ||||
|         optgroup->append_single_option_line("thin_walls", category_path + "detect-thin-walls"); | ||||
|         optgroup->append_single_option_line("thick_bridges", category_path + "thick_bridges"); | ||||
|         optgroup->append_single_option_line("overhangs", category_path + "detect-bridging-perimeters"); | ||||
| 
 | ||||
|         optgroup = page->new_optgroup(L("Advanced")); | ||||
|  | @ -1506,11 +1507,13 @@ void TabPrint::build() | |||
| 
 | ||||
|         optgroup = page->new_optgroup(L("Options for support material and raft")); | ||||
|         optgroup->append_single_option_line("support_material_contact_distance", category_path + "contact-z-distance"); | ||||
|         optgroup->append_single_option_line("support_material_bottom_contact_distance", category_path + "contact-z-distance"); | ||||
|         optgroup->append_single_option_line("support_material_pattern", category_path + "pattern"); | ||||
|         optgroup->append_single_option_line("support_material_with_sheath", category_path + "with-sheath-around-the-support"); | ||||
|         optgroup->append_single_option_line("support_material_spacing", category_path + "pattern-spacing-0-inf"); | ||||
|         optgroup->append_single_option_line("support_material_angle", category_path + "pattern-angle"); | ||||
|         optgroup->append_single_option_line("support_material_interface_layers", category_path + "interface-layers"); | ||||
|         optgroup->append_single_option_line("support_material_bottom_interface_layers", category_path + "interface-layers"); | ||||
|         optgroup->append_single_option_line("support_material_interface_pattern", category_path + "interface-pattern"); | ||||
|         optgroup->append_single_option_line("support_material_interface_spacing", category_path + "interface-pattern-spacing"); | ||||
|         optgroup->append_single_option_line("support_material_interface_contact_loops", category_path + "interface-loops"); | ||||
|  | @ -2528,7 +2531,7 @@ PageShp TabPrinter::build_kinematics_page() | |||
|         ConfigOptionDef def; | ||||
|         def.type = coString; | ||||
|         def.width = Field::def_width(); | ||||
|         def.gui_type = "legend"; | ||||
|         def.gui_type = ConfigOptionDef::GUIType::legend; | ||||
|         def.mode = comAdvanced; | ||||
|         def.tooltip = L("Values in this column are for Normal mode"); | ||||
|         def.set_default_value(new ConfigOptionString{ _(L("Normal")).ToUTF8().data() }); | ||||
|  |  | |||
|  | @ -363,7 +363,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) | |||
| 				ModelObject *model_object = model.add_object(); | ||||
| 				model_object->add_volume(*volumes[ivolume]); | ||||
| 				model_object->add_instance(); | ||||
| 				if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false)) { | ||||
| 				if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) { | ||||
| 					boost::filesystem::remove(path_src); | ||||
| 					throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed")); | ||||
| 				} | ||||
|  |  | |||
							
								
								
									
										78
									
								
								src/slic3r/Utils/Platform.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/slic3r/Utils/Platform.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,78 @@ | |||
| #include "Platform.hpp" | ||||
| 
 | ||||
| 
 | ||||
| // For starting another PrusaSlicer instance on OSX.
 | ||||
| // Fails to compile on Windows on the build server.
 | ||||
| 
 | ||||
| #include <wx/stdpaths.h> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| static auto s_platform 		  = Platform::Uninitialized; | ||||
| static auto s_platform_flavor = PlatformFlavor::Uninitialized; | ||||
| 
 | ||||
| void detect_platform() | ||||
| { | ||||
| #if defined(_WIN32) | ||||
|     BOOST_LOG_TRIVIAL(info) << "Platform: Windows"; | ||||
| 	s_platform 		  = Platform::Windows; | ||||
| 	s_platform_flavor = PlatformFlavor::Generic; | ||||
| #elif defined(__APPLE__) | ||||
|     BOOST_LOG_TRIVIAL(info) << "Platform: OSX"; | ||||
| 	s_platform 		  = Platform::OSX; | ||||
| 	s_platform_flavor = PlatformFlavor::Generic; | ||||
| #elif defined(__linux__) | ||||
|     BOOST_LOG_TRIVIAL(info) << "Platform: Linux"; | ||||
| 	s_platform 		  = Platform::Linux; | ||||
| 	s_platform_flavor = PlatformFlavor::GenericLinux; | ||||
| 	// Test for Chromium.
 | ||||
| 	{ | ||||
| 		FILE *f = ::fopen("/proc/version", "rt"); | ||||
| 		if (f) { | ||||
| 			char buf[4096]; | ||||
| 			// Read the 1st line.
 | ||||
| 			if (::fgets(buf, 4096, f)) { | ||||
| 				if (strstr(buf, "Chromium OS") != nullptr) { | ||||
| 					s_platform_flavor = PlatformFlavor::LinuxOnChromium; | ||||
| 				    BOOST_LOG_TRIVIAL(info) << "Platform flavor: LinuxOnChromium"; | ||||
| 				} else if (strstr(buf, "microsoft") != nullptr || strstr(buf, "Microsoft") != nullptr) { | ||||
| 					if (boost::filesystem::exists("/run/WSL") && getenv("WSL_INTEROP") != nullptr) { | ||||
| 						BOOST_LOG_TRIVIAL(info) << "Platform flavor: WSL2"; | ||||
| 						s_platform_flavor = PlatformFlavor::WSL2; | ||||
| 					} else { | ||||
| 						BOOST_LOG_TRIVIAL(info) << "Platform flavor: WSL"; | ||||
| 						s_platform_flavor = PlatformFlavor::WSL; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			::fclose(f); | ||||
| 		} | ||||
| 	} | ||||
| #elif defined(__OpenBSD__) | ||||
|     BOOST_LOG_TRIVIAL(info) << "Platform: OpenBSD"; | ||||
| 	s_platform 		  = Platform::BSDUnix; | ||||
| 	s_platform_flavor = PlatformFlavor::OpenBSD; | ||||
| #else | ||||
| 	// This should not happen.
 | ||||
|     BOOST_LOG_TRIVIAL(info) << "Platform: Unknown"; | ||||
| 	static_assert(false, "Unknown platform detected"); | ||||
| 	s_platform 		  = Platform::Unknown; | ||||
| 	s_platform_flavor = PlatformFlavor::Unknown; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| Platform platform() | ||||
| { | ||||
| 	return s_platform; | ||||
| } | ||||
| 
 | ||||
| PlatformFlavor platform_flavor() | ||||
| { | ||||
| 	return s_platform_flavor; | ||||
| } | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
							
								
								
									
										44
									
								
								src/slic3r/Utils/Platform.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/slic3r/Utils/Platform.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | |||
| #ifndef SLIC3R_GUI_Utils_Platform_HPP | ||||
| #define SLIC3R_GUI_Utils_Platform_HPP | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| enum class Platform | ||||
| { | ||||
| 	Uninitialized, | ||||
| 	Unknown, | ||||
| 	Windows, | ||||
| 	OSX, | ||||
| 	Linux, | ||||
| 	BSDUnix, | ||||
| }; | ||||
| 
 | ||||
| enum class PlatformFlavor | ||||
| { | ||||
| 	Uninitialized, | ||||
| 	Unknown, | ||||
| 	// For Windows and OSX, until we need to be more specific.
 | ||||
| 	Generic, | ||||
| 	// For Platform::Linux
 | ||||
| 	GenericLinux, | ||||
| 	LinuxOnChromium, | ||||
| 	// Microsoft's Windows on Linux (Linux kernel simulated on NTFS kernel)
 | ||||
| 	WSL, | ||||
| 	// Microsoft's Windows on Linux, version 2 (virtual machine)
 | ||||
| 	WSL2, | ||||
| 	// For Platform::BSDUnix
 | ||||
| 	OpenBSD, | ||||
| }; | ||||
| 
 | ||||
| // To be called on program start-up.
 | ||||
| void 			detect_platform(); | ||||
| 
 | ||||
| Platform 		platform(); | ||||
| PlatformFlavor 	platform_flavor(); | ||||
| 
 | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #endif // SLIC3R_GUI_Utils_Platform_HPP
 | ||||
|  | @ -31,7 +31,6 @@ use Slic3r::Test; | |||
|             role                => FLOW_ROLE_SUPPORT_MATERIAL, | ||||
|             nozzle_diameter     => $print->config->nozzle_diameter->[$object_config->support_material_extruder-1] // $print->config->nozzle_diameter->[0], | ||||
|             layer_height        => $object_config->layer_height, | ||||
|             bridge_flow_ratio   => 0, | ||||
|         ); | ||||
|         my $support = Slic3r::Print::SupportMaterial->new( | ||||
|             object_config       => $print->print->objects->[0]->config, | ||||
|  |  | |||
|  | @ -96,43 +96,41 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { | |||
|     GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") { | ||||
|         ConfigOptionFloatOrPercent	width(1.0, false); | ||||
|         float nozzle_diameter	= 0.4f; | ||||
|         float bridge_flow		= 0.f; | ||||
|         float layer_height		= 0.5f; | ||||
|         float layer_height		= 0.4f; | ||||
| 
 | ||||
|         // Spacing for non-bridges is has some overlap
 | ||||
|         THEN("External perimeter flow has spacing fixed to 1.125 * nozzle_diameter") { | ||||
|             auto flow = Flow::new_from_config_width(frExternalPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, bridge_flow); | ||||
|             auto flow = Flow::new_from_config_width(frExternalPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height); | ||||
|             REQUIRE(flow.spacing() == Approx(1.125 * nozzle_diameter - layer_height * (1.0 - PI / 4.0))); | ||||
|         } | ||||
| 
 | ||||
|         THEN("Internal perimeter flow has spacing fixed to 1.125 * nozzle_diameter") { | ||||
|             auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, bridge_flow); | ||||
|             auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height); | ||||
|             REQUIRE(flow.spacing() == Approx(1.125 *nozzle_diameter - layer_height * (1.0 - PI / 4.0))); | ||||
|         } | ||||
|         THEN("Spacing for supplied width is 0.8927f") { | ||||
|             auto flow = Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, bridge_flow); | ||||
|             auto flow = Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height); | ||||
|             REQUIRE(flow.spacing() == Approx(width.value - layer_height * (1.0 - PI / 4.0))); | ||||
|             flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, bridge_flow); | ||||
|             flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height); | ||||
|             REQUIRE(flow.spacing() == Approx(width.value - layer_height * (1.0 - PI / 4.0))); | ||||
|         } | ||||
|     } | ||||
|     /// Check the min/max
 | ||||
|     GIVEN("Nozzle Diameter of 0.25") { | ||||
|         float nozzle_diameter	= 0.25f; | ||||
|         float bridge_flow		= 0.f; | ||||
|         float layer_height		= 0.5f; | ||||
|         WHEN("layer height is set to 0.2") { | ||||
|             layer_height = 0.15f; | ||||
|             THEN("Max width is set.") { | ||||
|                 auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, bridge_flow); | ||||
|                 REQUIRE(flow.width == Approx(1.125 * nozzle_diameter)); | ||||
|                 auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height); | ||||
|                 REQUIRE(flow.width() == Approx(1.125 * nozzle_diameter)); | ||||
|             } | ||||
|         } | ||||
|         WHEN("Layer height is set to 0.2") { | ||||
|             layer_height = 0.3f; | ||||
|         WHEN("Layer height is set to 0.25") { | ||||
|             layer_height = 0.25f; | ||||
|             THEN("Min width is set.") { | ||||
|                 auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, bridge_flow); | ||||
|                 REQUIRE(flow.width == Approx(1.125 * nozzle_diameter)); | ||||
|                 auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height); | ||||
|                 REQUIRE(flow.width() == Approx(1.125 * nozzle_diameter)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -143,7 +141,7 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { | |||
|     GIVEN("Input spacing of 0.414159 and a total width of 2") { | ||||
|         double in_spacing = 0.414159; | ||||
|         double total_width = 2.0; | ||||
|         auto flow = Flow::new_from_spacing(1.0, 0.4, 0.3, false); | ||||
|         auto flow = Flow::new_from_spacing(1.0, 0.4, 0.3); | ||||
|         WHEN("solid_spacing() is called") { | ||||
|             double result = flow.solid_spacing(total_width, in_spacing); | ||||
|             THEN("Yielded spacing is greater than 0") { | ||||
|  | @ -158,41 +156,12 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") { | |||
| /// Spacing, width calculation for bridge extrusions
 | ||||
| SCENARIO("Flow: Flow math for bridges", "[Flow]") { | ||||
|     GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") { | ||||
|         auto width = ConfigOptionFloatOrPercent(1.0, false); | ||||
| 		float nozzle_diameter	= 0.4f; | ||||
| 		float bridge_flow		= 1.0f; | ||||
| 		float layer_height		= 0.5f; | ||||
|         WHEN("Flow role is frExternalPerimeter") { | ||||
|             auto flow = Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, bridge_flow); | ||||
|             auto flow = Flow::bridging_flow(nozzle_diameter * sqrt(bridge_flow), nozzle_diameter); | ||||
|             THEN("Bridge width is same as nozzle diameter") { | ||||
|                 REQUIRE(flow.width == Approx(nozzle_diameter)); | ||||
|             } | ||||
|             THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") { | ||||
|                 REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING)); | ||||
|             } | ||||
|         } | ||||
|         WHEN("Flow role is frInfill") { | ||||
|             auto flow = Flow::new_from_config_width(frInfill, width, nozzle_diameter, layer_height, bridge_flow); | ||||
|             THEN("Bridge width is same as nozzle diameter") { | ||||
|                 REQUIRE(flow.width == Approx(nozzle_diameter)); | ||||
|             } | ||||
|             THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") { | ||||
|                 REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING)); | ||||
|             } | ||||
|         } | ||||
|         WHEN("Flow role is frPerimeter") { | ||||
|             auto flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, bridge_flow); | ||||
|             THEN("Bridge width is same as nozzle diameter") { | ||||
|                 REQUIRE(flow.width == Approx(nozzle_diameter)); | ||||
|             } | ||||
|             THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") { | ||||
|                 REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING)); | ||||
|             } | ||||
|         } | ||||
|         WHEN("Flow role is frSupportMaterial") { | ||||
|             auto flow = Flow::new_from_config_width(frSupportMaterial, width, nozzle_diameter, layer_height, bridge_flow); | ||||
|             THEN("Bridge width is same as nozzle diameter") { | ||||
|                 REQUIRE(flow.width == Approx(nozzle_diameter)); | ||||
|                 REQUIRE(flow.width() == Approx(nozzle_diameter)); | ||||
|             } | ||||
|             THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") { | ||||
|                 REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING)); | ||||
|  |  | |||
|  | @ -53,8 +53,8 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t | |||
| 		config.set_deserialize({ | ||||
| 			{ "top_solid_layers",		2 }, | ||||
| 			{ "bottom_solid_layers",	1 }, | ||||
| 			{ "layer_height",			0.5 }, // get a known number of layers
 | ||||
| 			{ "first_layer_height",		0.5 } | ||||
| 			{ "layer_height",			0.25 }, // get a known number of layers
 | ||||
| 			{ "first_layer_height",		0.25 } | ||||
| 			}); | ||||
|         Slic3r::Print print; | ||||
|         Slic3r::Model model; | ||||
|  | @ -72,8 +72,8 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t | |||
| 		}; | ||||
|         print.process(); | ||||
|         test_is_solid_infill(0,  0); // should be solid
 | ||||
|         test_is_solid_infill(0, 39); // should be solid
 | ||||
|         test_is_solid_infill(0, 38); // should be solid
 | ||||
|         test_is_solid_infill(0, 79); // should be solid
 | ||||
|         test_is_solid_infill(0, 78); // should be solid
 | ||||
|         WHEN("Model is re-sliced with top_solid_layers == 3") { | ||||
| 			config.set("top_solid_layers", 3); | ||||
| 			print.apply(model, config); | ||||
|  | @ -82,9 +82,9 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t | |||
|                 test_is_solid_infill(0, 0); | ||||
|             } | ||||
|             AND_THEN("Print object has 3 top solid layers") { | ||||
|                 test_is_solid_infill(0, 39); | ||||
|                 test_is_solid_infill(0, 38); | ||||
|                 test_is_solid_infill(0, 37); | ||||
|                 test_is_solid_infill(0, 79); | ||||
|                 test_is_solid_infill(0, 78); | ||||
|                 test_is_solid_infill(0, 77); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -244,23 +244,24 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") { | |||
| 				{ "complete_objects",               true }, | ||||
|                 { "gcode_comments",                 true }, | ||||
|                 { "layer_gcode",                    ";Layer:[layer_num] ([layer_z] mm)" }, | ||||
|                 { "layer_height",                   1.0 }, | ||||
|                 { "first_layer_height",             1.0 } | ||||
|                 { "layer_height",                   0.1 }, | ||||
|                 { "first_layer_height",             0.1 } | ||||
|                 }); | ||||
| 			// End of the 1st object.
 | ||||
| 			size_t pos = gcode.find(";Layer:19 "); | ||||
|             std::string token = ";Layer:199 "; | ||||
| 			size_t pos = gcode.find(token); | ||||
| 			THEN("First and second object last layer is emitted") { | ||||
| 				// First object
 | ||||
| 				REQUIRE(pos != std::string::npos); | ||||
| 				pos += 10; | ||||
| 				pos += token.size(); | ||||
| 				REQUIRE(pos < gcode.size()); | ||||
| 				double z = 0; | ||||
| 				REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1)); | ||||
| 				REQUIRE(z == Approx(20.)); | ||||
| 				// Second object
 | ||||
| 				pos = gcode.find(";Layer:39 ", pos); | ||||
| 				pos = gcode.find(";Layer:399 ", pos); | ||||
| 				REQUIRE(pos != std::string::npos); | ||||
| 				pos += 10; | ||||
| 				pos += token.size(); | ||||
| 				REQUIRE(pos < gcode.size()); | ||||
| 				REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1)); | ||||
| 				REQUIRE(z == Approx(20.)); | ||||
|  |  | |||
|  | @ -434,7 +434,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
| 		ExPolygon expoly =  contour_with_hole(); | ||||
| 		WHEN("Compensated") { | ||||
| 			// Elephant foot compensation shall not pinch off bits from this contour.
 | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.2f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.2f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 			SVG::export_expolygons(debug_out_path("elephant_foot_compensation_with_hole.svg").c_str(), | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  | @ -449,7 +449,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
| 	GIVEN("Tiny contour") { | ||||
| 		ExPolygon expoly({ { 133382606, 94912473 }, { 134232493, 95001115 }, { 133783926, 95159440 }, { 133441897, 95180666 }, { 133408242, 95191984 }, { 133339012, 95166830 }, { 132991642, 95011087 }, { 133206549, 94908304 } }); | ||||
| 		WHEN("Compensated") { | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.2f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.2f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 			SVG::export_expolygons(debug_out_path("elephant_foot_compensation_tiny.svg").c_str(), | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  | @ -464,7 +464,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
| 	GIVEN("Large box") { | ||||
| 		ExPolygon expoly( { {50000000, 50000000 }, { 0, 50000000 }, { 0, 0 }, { 50000000, 0 } } ); | ||||
|         WHEN("Compensated") { | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.21f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.21f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 		    SVG::export_expolygons(debug_out_path("elephant_foot_compensation_large_box.svg").c_str(),  | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  | @ -479,7 +479,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
| 	GIVEN("Thin ring (GH issue #2085)") { | ||||
| 		ExPolygon expoly = thin_ring(); | ||||
|         WHEN("Compensated") { | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.25f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.25f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 		    SVG::export_expolygons(debug_out_path("elephant_foot_compensation_thin_ring.svg").c_str(),  | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  | @ -532,7 +532,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
| 		expoly = union_ex({ expoly, expoly2 }).front(); | ||||
| 
 | ||||
|         WHEN("Partially compensated") { | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f, false), 0.25f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f), 0.25f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 		    SVG::export_expolygons(debug_out_path("elephant_foot_compensation_0.svg").c_str(),  | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  | @ -543,7 +543,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
|             } | ||||
|         } | ||||
| 		WHEN("Fully compensated") { | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f, false), 0.17f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f), 0.17f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 		    SVG::export_expolygons(debug_out_path("elephant_foot_compensation_1.svg").c_str(),  | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  | @ -558,7 +558,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
| 	GIVEN("Box with hole close to wall (GH issue #2998)") { | ||||
| 		ExPolygon expoly = box_with_hole_close_to_wall(); | ||||
|         WHEN("Compensated") { | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.25f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.25f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 		    SVG::export_expolygons(debug_out_path("elephant_foot_compensation_2.svg").c_str(),  | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  | @ -575,7 +575,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
| 		ExPolygon expoly = spirograph_gear_1mm(); | ||||
| 
 | ||||
|         WHEN("Partially compensated") { | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f, false), 0.25f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f), 0.25f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 		    SVG::export_expolygons(debug_out_path("elephant_foot_compensation_2.svg").c_str(),  | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  | @ -586,7 +586,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
|             } | ||||
|         } | ||||
| 		WHEN("Fully compensated") { | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f, false), 0.17f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f), 0.17f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 		    SVG::export_expolygons(debug_out_path("elephant_foot_compensation_3.svg").c_str(),  | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  | @ -597,7 +597,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
| 			} | ||||
| 		} | ||||
|         WHEN("Brutally compensated") { | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f, false), 0.6f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f), 0.6f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 		    SVG::export_expolygons(debug_out_path("elephant_foot_compensation_4.svg").c_str(),  | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  | @ -612,7 +612,7 @@ SCENARIO("Elephant foot compensation", "[ElephantFoot]") { | |||
| 	GIVEN("Vase with fins") { | ||||
| 		ExPolygon expoly = vase_with_fins(); | ||||
|         WHEN("Compensated") { | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.41f); | ||||
| 			ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f), 0.41f); | ||||
| #ifdef TESTS_EXPORT_SVGS | ||||
| 		    SVG::export_expolygons(debug_out_path("elephant_foot_compensation_vase_with_fins.svg").c_str(),  | ||||
| 				{ { { expoly },             { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, | ||||
|  |  | |||
|  | @ -2,45 +2,21 @@ | |||
| #include <fstream> | ||||
| #include <catch2/catch.hpp> | ||||
| 
 | ||||
| #include <libslic3r/TriangleMesh.hpp> | ||||
| #include "libslic3r/SLA/Hollowing.hpp" | ||||
| #include <openvdb/tools/Filter.h> | ||||
| #include "libslic3r/Format/OBJ.hpp" | ||||
| 
 | ||||
| #include <libnest2d/tools/benchmark.h> | ||||
| TEST_CASE("Hollow two overlapping spheres") { | ||||
|     using namespace Slic3r; | ||||
| 
 | ||||
| #include <libslic3r/SimplifyMesh.hpp> | ||||
|     TriangleMesh sphere1 = make_sphere(10., 2 * PI / 20.), sphere2 = sphere1; | ||||
| 
 | ||||
| #if defined(WIN32) || defined(_WIN32) | ||||
| #define PATH_SEPARATOR R"(\)" | ||||
| #else | ||||
| #define PATH_SEPARATOR R"(/)" | ||||
| #endif | ||||
|     sphere1.translate(-5.f, 0.f, 0.f); | ||||
|     sphere2.translate( 5.f, 0.f, 0.f); | ||||
| 
 | ||||
| static Slic3r::TriangleMesh load_model(const std::string &obj_filename) | ||||
| { | ||||
|     Slic3r::TriangleMesh mesh; | ||||
|     auto fpath = TEST_DATA_DIR PATH_SEPARATOR + obj_filename; | ||||
|     Slic3r::load_obj(fpath.c_str(), &mesh); | ||||
|     return mesh; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| TEST_CASE("Negative 3D offset should produce smaller object.", "[Hollowing]") | ||||
| { | ||||
|     Slic3r::TriangleMesh in_mesh = load_model("20mm_cube.obj"); | ||||
|     Benchmark bench; | ||||
|     bench.start(); | ||||
|      | ||||
|     std::unique_ptr<Slic3r::TriangleMesh> out_mesh_ptr = | ||||
|         Slic3r::sla::generate_interior(in_mesh); | ||||
|      | ||||
|     bench.stop(); | ||||
|      | ||||
|     std::cout << "Elapsed processing time: " << bench.getElapsedSec() << std::endl; | ||||
|      | ||||
|     if (out_mesh_ptr) in_mesh.merge(*out_mesh_ptr); | ||||
|     in_mesh.require_shared_vertices(); | ||||
|     in_mesh.WriteOBJFile("merged_out.obj"); | ||||
|     sphere1.merge(sphere2); | ||||
|     sphere1.require_shared_vertices(); | ||||
| 
 | ||||
|     sla::hollow_mesh(sphere1, sla::HollowingConfig{}, sla::HollowingFlags::hfRemoveInsideTriangles); | ||||
| 
 | ||||
|     sphere1.WriteOBJFile("twospheres.obj"); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -46,7 +46,6 @@ SCENARIO("Iterators", "[MutablePolygon]") { | |||
|         } | ||||
|         WHEN("Deleting 1st point") { | ||||
|             auto it_2nd = p.begin().next(); | ||||
|             auto it_3rd = p.end(); | ||||
|             auto it     = p.begin().remove(); | ||||
|             THEN("Size is 2") { | ||||
|                 REQUIRE(p.size() == 2); | ||||
|  | @ -59,7 +58,6 @@ SCENARIO("Iterators", "[MutablePolygon]") { | |||
|         WHEN("Deleting 2nd point") { | ||||
|             auto it_1st = p.begin(); | ||||
|             auto it_2nd = it_1st.next(); | ||||
|             auto it_3rd = p.end(); | ||||
|             auto it = it_2nd.remove(); | ||||
|             THEN("Size is 2") { | ||||
|                 REQUIRE(p.size() == 2); | ||||
|  |  | |||
|  | @ -88,9 +88,9 @@ void test_supports(const std::string          &obj_filename, | |||
|     REQUIRE_FALSE(mesh.empty()); | ||||
|      | ||||
|     if (hollowingcfg.enabled) { | ||||
|         auto inside = sla::generate_interior(mesh, hollowingcfg); | ||||
|         REQUIRE(inside); | ||||
|         mesh.merge(*inside); | ||||
|         sla::InteriorPtr interior = sla::generate_interior(mesh, hollowingcfg); | ||||
|         REQUIRE(interior); | ||||
|         mesh.merge(sla::get_mesh(*interior)); | ||||
|         mesh.require_shared_vertices(); | ||||
|     } | ||||
|      | ||||
|  |  | |||
|  | @ -158,7 +158,6 @@ sub new { | |||
|     my $self = $class->_new( | ||||
|         @args{qw(width height nozzle_diameter)}, | ||||
|     ); | ||||
|     $self->set_bridge($args{bridge} // 0); | ||||
|     return $self; | ||||
| } | ||||
| 
 | ||||
|  | @ -166,7 +165,7 @@ sub new_from_width { | |||
|     my ($class, %args) = @_; | ||||
|      | ||||
|     return $class->_new_from_width( | ||||
|         @args{qw(role width nozzle_diameter layer_height bridge_flow_ratio)}, | ||||
|         @args{qw(role width nozzle_diameter layer_height)}, | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -170,19 +170,6 @@ print_config_def() | |||
|                 throw "Unknown option type"; | ||||
|             } | ||||
|             (void)hv_stores( hv, "type",        newSVpv(opt_type, 0) ); | ||||
|             (void)hv_stores( hv, "gui_type",    newSVpvn(optdef->gui_type.c_str(), optdef->gui_type.length()) ); | ||||
|             (void)hv_stores( hv, "gui_flags",   newSVpvn(optdef->gui_flags.c_str(), optdef->gui_flags.length()) ); | ||||
|             (void)hv_stores( hv, "label",       newSVpvn_utf8(optdef->label.c_str(), optdef->label.length(), true) ); | ||||
|             if (!optdef->full_label.empty()) | ||||
|                 (void)hv_stores( hv, "full_label",  newSVpvn_utf8(optdef->full_label.c_str(), optdef->full_label.length(), true) ); | ||||
|             (void)hv_stores( hv, "category",    newSVpvn_utf8(optdef->category.c_str(), optdef->category.length(), true) ); | ||||
|             (void)hv_stores( hv, "tooltip",     newSVpvn_utf8(optdef->tooltip.c_str(), optdef->tooltip.length(), true) ); | ||||
|             (void)hv_stores( hv, "sidetext",    newSVpvn_utf8(optdef->sidetext.c_str(), optdef->sidetext.length(), true) ); | ||||
|             (void)hv_stores( hv, "cli",         newSVpvn(optdef->cli.c_str(), optdef->cli.length()) ); | ||||
|             (void)hv_stores( hv, "ratio_over",  newSVpvn(optdef->ratio_over.c_str(), optdef->ratio_over.length()) ); | ||||
|             (void)hv_stores( hv, "multiline",   newSViv(optdef->multiline ? 1 : 0) ); | ||||
|             (void)hv_stores( hv, "full_width",  newSViv(optdef->full_width ? 1 : 0) ); | ||||
|             (void)hv_stores( hv, "readonly",    newSViv(optdef->readonly ? 1 : 0) ); | ||||
|             (void)hv_stores( hv, "height",      newSViv(optdef->height) ); | ||||
|             (void)hv_stores( hv, "width",       newSViv(optdef->width) ); | ||||
|             (void)hv_stores( hv, "min",         newSViv(optdef->min) ); | ||||
|  |  | |||
|  | @ -8,41 +8,30 @@ | |||
| %name{Slic3r::Flow} class Flow { | ||||
|     ~Flow(); | ||||
|     %name{_new} Flow(float width, float height, float nozzle_diameter); | ||||
|     void set_height(float height) | ||||
|         %code{% THIS->height = height; %}; | ||||
|     void set_bridge(bool bridge) | ||||
|         %code{% THIS->bridge = bridge; %}; | ||||
|     Clone<Flow> clone() | ||||
|         %code{% RETVAL = THIS; %}; | ||||
|      | ||||
|     float width() | ||||
|         %code{% RETVAL = THIS->width; %}; | ||||
|     float height() | ||||
|         %code{% RETVAL = THIS->height; %}; | ||||
|     float nozzle_diameter() | ||||
|         %code{% RETVAL = THIS->nozzle_diameter; %}; | ||||
|     bool bridge() | ||||
|         %code{% RETVAL = THIS->bridge; %}; | ||||
|     float width(); | ||||
|     float height(); | ||||
|     float nozzle_diameter(); | ||||
|     bool bridge(); | ||||
|     float spacing(); | ||||
|     float spacing_to(Flow* other) | ||||
|         %code{% RETVAL = THIS->spacing(*other); %}; | ||||
|     int   scaled_width(); | ||||
|     int   scaled_spacing(); | ||||
|     double mm3_per_mm(); | ||||
| %{ | ||||
| 
 | ||||
| Flow* | ||||
| _new_from_width(CLASS, role, width, nozzle_diameter, height, bridge_flow_ratio) | ||||
| _new_from_width(CLASS, role, width, nozzle_diameter, height) | ||||
|     char*           CLASS; | ||||
|     FlowRole        role; | ||||
|     std::string     width; | ||||
|     float           nozzle_diameter; | ||||
|     float           height; | ||||
|     float           bridge_flow_ratio; | ||||
|     CODE: | ||||
|         ConfigOptionFloatOrPercent optwidth; | ||||
|         optwidth.deserialize(width); | ||||
|         RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height, bridge_flow_ratio)); | ||||
|         RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height)); | ||||
|     OUTPUT: | ||||
|         RETVAL | ||||
| 
 | ||||
|  |  | |||
|  | @ -23,8 +23,8 @@ | |||
|     Ref<ExtrusionEntityCollection> fills() | ||||
|         %code%{ RETVAL = &THIS->fills; %}; | ||||
|      | ||||
|     Clone<Flow> flow(FlowRole role, bool bridge = false, double width = -1) | ||||
|         %code%{ RETVAL = THIS->flow(role, bridge, width); %}; | ||||
|     Clone<Flow> flow(FlowRole role) | ||||
|         %code%{ RETVAL = THIS->flow(role); %}; | ||||
|     void prepare_fill_surfaces(); | ||||
|     void make_perimeters(SurfaceCollection* slices, SurfaceCollection* fill_surfaces) | ||||
|         %code%{ THIS->make_perimeters(*slices, fill_surfaces); %}; | ||||
|  |  | |||
|  | @ -33,9 +33,6 @@ _constant() | |||
|     Ref<StaticPrintConfig> config() | ||||
|         %code%{ RETVAL = &THIS->config(); %}; | ||||
|     Ref<Print> print(); | ||||
|      | ||||
|     Clone<Flow> flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, PrintObject* object) | ||||
|         %code%{ RETVAL = THIS->flow(role, layer_height, bridge, first_layer, width, *object); %}; | ||||
| }; | ||||
| 
 | ||||
| %name{Slic3r::Print::Object} class PrintObject { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 enricoturri1966
						enricoturri1966