mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into ys_aliases
This commit is contained in:
		
						commit
						773dace33c
					
				
					 41 changed files with 2257 additions and 332 deletions
				
			
		|  | @ -1,5 +1,5 @@ | |||
| project(PrusaSlicer) | ||||
| cmake_minimum_required(VERSION 3.2) | ||||
| project(PrusaSlicer) | ||||
| 
 | ||||
| include("version.inc") | ||||
| include(GNUInstallDirs) | ||||
|  |  | |||
							
								
								
									
										4
									
								
								deps/deps-linux.cmake
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								deps/deps-linux.cmake
									
										
									
									
										vendored
									
									
								
							|  | @ -26,8 +26,8 @@ ExternalProject_Add(dep_boost | |||
| 
 | ||||
| ExternalProject_Add(dep_libopenssl | ||||
|     EXCLUDE_FROM_ALL 1 | ||||
|     URL "https://github.com/openssl/openssl/archive/OpenSSL_1_1_0g.tar.gz" | ||||
|     URL_HASH SHA256=8e9516b8635bb9113c51a7b5b27f9027692a56b104e75b709e588c3ffd6a0422 | ||||
|     URL "https://github.com/openssl/openssl/archive/OpenSSL_1_1_0l.tar.gz" | ||||
|     URL_HASH SHA256=e2acf0cf58d9bff2b42f2dc0aee79340c8ffe2c5e45d3ca4533dd5d4f5775b1d | ||||
|     BUILD_IN_SOURCE 1 | ||||
|     CONFIGURE_COMMAND ./config | ||||
|         "--prefix=${DESTDIR}/usr/local" | ||||
|  |  | |||
							
								
								
									
										12
									
								
								resources/icons/add_gcode.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								resources/icons/add_gcode.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> | ||||
| <g id="export_x5F_gcode"> | ||||
| 	<g> | ||||
| 		<path fill="#808080" d="M5.02,7.17H9v3.08c0,2.6-1.23,3.72-4.05,3.72S1,12.85,1,10.29V5.54C1,3.12,2.09,2,4.95,2S9,3,9,5.54H6.88 | ||||
| 			c0-1.11-0.28-1.66-1.92-1.66c-1.54,0-1.83,0.69-1.83,1.77v4.65c0,1.12,0.29,1.77,1.83,1.77c1.54,0,2.08-0.65,2.08-1.82V9.09H5.02 | ||||
| 			V7.17z"/> | ||||
| 	</g> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 656 B | 
							
								
								
									
										9
									
								
								resources/icons/change_extruder.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								resources/icons/change_extruder.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon --> | ||||
| <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||||
| <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve"> | ||||
| <metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata> | ||||
| <g> | ||||
| <path fill="#ED6B21" d="M59.7,265.7c0,0,89.2-138.9,230.3-213.4C431.1-22.2,605-0.7,719,71.5c114,72.3,152.4,133.2,152.4,133.2l98.2-56.5c0,0,20.3-10.2,20.3,13.5v354.5c0,0,0,31.6-23.7,20.3c-19.9-9.5-235.7-133.3-303.6-172.3c-37.3-16.8-4.5-30.4-4.5-30.4l94.8-54.7c0,0-54.1-68.3-133.2-104.5C535,130.3,455.7,125,358.6,162c-63.3,24.1-137.9,85.9-191.6,177.2L59.7,265.7L59.7,265.7z"/> | ||||
| <path fill="#ED6B21" d="M940.3,734.3c0,0-89.2,138.9-230.3,213.4c-141.1,74.5-315,53.1-429-19.2c-114-72.3-152.4-133.2-152.4-133.2l-98.2,56.4c0,0-20.3,10.2-20.3-13.5V483.6c0,0,0-31.6,23.7-20.3c19.9,9.5,235.7,133.3,303.6,172.3c37.3,16.8,4.5,30.4,4.5,30.4l-94.8,54.7c0,0,54.1,68.3,133.2,104.5c84.7,44.5,164,49.8,261.1,12.8c63.3-24.1,137.9-85.9,191.6-177.2L940.3,734.3L940.3,734.3z"/></g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										15
									
								
								resources/icons/edit_gcode.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								resources/icons/edit_gcode.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> | ||||
| <g id="edit_x5F_Gcode"> | ||||
| 	<g> | ||||
| 		<path fill="#808080" d="M8,1.85l5.29,3.53V7v3.62L8,14.15l-5.29-3.53V7V5.38L8,1.85 M8,1L2,5v2v4l6,4l6-4V7V5L8,1L8,1z"/> | ||||
| 	</g> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M7.97,7.47h2.65v2.05c0,1.73-0.82,2.48-2.69,2.48S5.3,11.25,5.3,9.55V6.39c0-1.61,0.73-2.36,2.63-2.36 | ||||
| 			s2.69,0.67,2.69,2.36H9.21c0-0.74-0.18-1.11-1.28-1.11c-1.02,0-1.22,0.46-1.22,1.18v3.09c0,0.75,0.19,1.18,1.22,1.18 | ||||
| 			c1.02,0,1.38-0.43,1.38-1.21V8.75H7.97V7.47z"/> | ||||
| 	</g> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 808 B | 
							
								
								
									
										20
									
								
								resources/icons/edit_uni.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								resources/icons/edit_uni.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> | ||||
| <g id="edit_x5F_uni"> | ||||
| 	<path fill="#808080" d="M8,1.85l5.29,3.53V7v3.62L8,14.15l-5.29-3.53V7V5.38L8,1.85 M8,1L2,5v2v4l6,4l6-4V7V5L8,1L8,1z"/> | ||||
| 	<path fill="#ED6B21" d="M11.87,7.36l-0.43-0.22c-0.07-0.04-0.16-0.13-0.18-0.21l-0.2-0.48c-0.04-0.07-0.04-0.2-0.02-0.28l0.15-0.46 | ||||
| 		c0.03-0.08,0-0.19-0.06-0.25l-0.6-0.6c-0.06-0.06-0.17-0.08-0.25-0.06L9.83,4.97c-0.08,0.03-0.2,0.02-0.28-0.02l-0.48-0.2 | ||||
| 		C8.99,4.72,8.89,4.64,8.85,4.57L8.64,4.13C8.6,4.06,8.5,4,8.42,4H7.58C7.5,4,7.4,4.06,7.36,4.13L7.15,4.57 | ||||
| 		C7.11,4.64,7.01,4.72,6.94,4.75l-0.48,0.2c-0.07,0.04-0.2,0.04-0.28,0.02L5.72,4.81c-0.08-0.03-0.19,0-0.25,0.06l-0.6,0.6 | ||||
| 		C4.82,5.53,4.79,5.64,4.81,5.72l0.15,0.46c0.03,0.08,0.02,0.2-0.02,0.28l-0.2,0.48C4.72,7.01,4.64,7.11,4.57,7.15L4.13,7.36 | ||||
| 		C4.06,7.4,4,7.5,4,7.58v0.84C4,8.5,4.06,8.6,4.13,8.64l0.43,0.22c0.07,0.04,0.16,0.13,0.18,0.21l0.2,0.48 | ||||
| 		c0.04,0.07,0.04,0.2,0.02,0.28l-0.15,0.46c-0.03,0.08,0,0.19,0.06,0.25l0.6,0.6c0.06,0.06,0.17,0.08,0.25,0.06l0.46-0.15 | ||||
| 		c0.08-0.03,0.2-0.02,0.28,0.02l0.48,0.2c0.08,0.03,0.17,0.11,0.21,0.18l0.22,0.43C7.4,11.94,7.5,12,7.58,12h0.84 | ||||
| 		c0.08,0,0.18-0.06,0.22-0.13l0.22-0.43c0.04-0.07,0.13-0.16,0.21-0.18l0.48-0.2c0.07-0.04,0.2-0.04,0.28-0.02l0.46,0.15 | ||||
| 		c0.08,0.03,0.19,0,0.25-0.06l0.6-0.6c0.06-0.06,0.08-0.17,0.06-0.25l-0.15-0.46c-0.03-0.08-0.02-0.2,0.02-0.28l0.2-0.48 | ||||
| 		c0.03-0.08,0.11-0.17,0.18-0.21l0.43-0.22C11.94,8.6,12,8.5,12,8.42V7.58C12,7.5,11.94,7.4,11.87,7.36z M8,10.29 | ||||
| 		c-1.26,0-2.29-1.03-2.29-2.29c0-1.26,1.03-2.29,2.29-2.29S10.29,6.74,10.29,8C10.29,9.26,9.26,10.29,8,10.29z"/> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								resources/icons/pause_add.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								resources/icons/pause_add.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.7 KiB | 
							
								
								
									
										18
									
								
								resources/icons/pause_print.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								resources/icons/pause_print.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> | ||||
| <g id="pause_x5F_print"> | ||||
| 	<g> | ||||
| 		<path fill="#808080" d="M8,1.85l5.29,3.53V7v3.62L8,14.15l-5.29-3.53V7V5.38L8,1.85 M8,1L2,5v2v4l6,4l6-4V7V5L8,1L8,1z"/> | ||||
| 	</g> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M6,11.71c-0.39,0-0.71-0.32-0.71-0.71V5c0-0.39,0.32-0.71,0.71-0.71S6.71,4.61,6.71,5v6 | ||||
| 			C6.71,11.39,6.39,11.71,6,11.71z"/> | ||||
| 	</g> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M10,11.71c-0.39,0-0.71-0.32-0.71-0.71V5c0-0.39,0.32-0.71,0.71-0.71S10.71,4.61,10.71,5v6 | ||||
| 			C10.71,11.39,10.39,11.71,10,11.71z"/> | ||||
| 	</g> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 833 B | 
|  | @ -51,6 +51,7 @@ const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; | |||
| const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; | ||||
| const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; | ||||
| const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; | ||||
| const std::string CUSTOM_GCODE_PER_HEIGHT_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_height.xml"; | ||||
| 
 | ||||
| const char* MODEL_TAG = "model"; | ||||
| const char* RESOURCES_TAG = "resources"; | ||||
|  | @ -417,6 +418,8 @@ namespace Slic3r { | |||
|         void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
|         void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
| 
 | ||||
|         void _extract_custom_gcode_per_height_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
| 
 | ||||
|         void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); | ||||
|         bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); | ||||
| 
 | ||||
|  | @ -626,6 +629,11 @@ namespace Slic3r { | |||
|                     // extract slic3r print config file
 | ||||
|                     _extract_print_config_from_archive(archive, stat, config, filename); | ||||
|                 } | ||||
|                 if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_HEIGHT_FILE)) | ||||
|                 { | ||||
|                     // extract slic3r layer config ranges file
 | ||||
|                     _extract_custom_gcode_per_height_from_archive(archive, stat); | ||||
|                 } | ||||
|                 else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) | ||||
|                 { | ||||
|                     // extract slic3r model config file
 | ||||
|  | @ -1056,6 +1064,43 @@ namespace Slic3r { | |||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     void _3MF_Importer::_extract_custom_gcode_per_height_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat) | ||||
|     { | ||||
|         if (stat.m_uncomp_size > 0) | ||||
|         { | ||||
|             std::string buffer((size_t)stat.m_uncomp_size, 0); | ||||
|             mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); | ||||
|             if (res == 0) { | ||||
|                 add_error("Error while reading custom Gcodes per height data to buffer"); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             std::istringstream iss(buffer); // wrap returned xml to istringstream
 | ||||
|             pt::ptree main_tree; | ||||
|             pt::read_xml(iss, main_tree); | ||||
| 
 | ||||
|             if (main_tree.front().first != "custom_gcodes_per_height") | ||||
|                 return; | ||||
|             pt::ptree code_tree = main_tree.front().second; | ||||
| 
 | ||||
|             if (!m_model->custom_gcode_per_height.empty()) | ||||
|                 m_model->custom_gcode_per_height.clear(); | ||||
| 
 | ||||
|             for (const auto& code : code_tree) | ||||
|             { | ||||
|                 if (code.first != "code") | ||||
|                     continue; | ||||
|                 pt::ptree tree = code.second; | ||||
|                 double height       = tree.get<double>("<xmlattr>.height"); | ||||
|                 std::string gcode   = tree.get<std::string>("<xmlattr>.gcode"); | ||||
|                 int extruder        = tree.get<int>("<xmlattr>.extruder"); | ||||
|                 std::string color   = tree.get<std::string>("<xmlattr>.color"); | ||||
| 
 | ||||
|                 m_model->custom_gcode_per_height.push_back(Model::CustomGCode(height, gcode, extruder, color)) ; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes) | ||||
|     { | ||||
|         if (m_xml_parser == nullptr) | ||||
|  | @ -1838,6 +1883,7 @@ namespace Slic3r { | |||
|         bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); | ||||
|         bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); | ||||
|         bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); | ||||
|         bool _add_custom_gcode_per_height_file_to_archive(mz_zip_archive& archive, Model& model); | ||||
|     }; | ||||
| 
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
|  | @ -1940,6 +1986,15 @@ namespace Slic3r { | |||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_height.xml").
 | ||||
|         // All custom gcode per height of whole Model are stored here
 | ||||
|         if (!_add_custom_gcode_per_height_file_to_archive(archive, model)) | ||||
|         { | ||||
|             close_zip_writer(&archive); | ||||
|             boost::filesystem::remove(filename); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Adds slic3r print config file ("Metadata/Slic3r_PE.config").
 | ||||
|         // This file contains the content of FullPrintConfing / SLAFullPrintConfig.
 | ||||
|         if (config != nullptr) | ||||
|  | @ -2324,7 +2379,7 @@ namespace Slic3r { | |||
|         if (!tree.empty()) | ||||
|         { | ||||
|             std::ostringstream oss; | ||||
|             boost::property_tree::write_xml(oss, tree); | ||||
|             pt::write_xml(oss, tree); | ||||
|             out = oss.str(); | ||||
| 
 | ||||
|             // Post processing("beautification") of the output string for a better preview
 | ||||
|  | @ -2510,7 +2565,49 @@ namespace Slic3r { | |||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) | ||||
| bool _3MF_Exporter::_add_custom_gcode_per_height_file_to_archive( mz_zip_archive& archive, Model& model) | ||||
| { | ||||
|     std::string out = ""; | ||||
| 
 | ||||
|     if (!model.custom_gcode_per_height.empty()) | ||||
|     { | ||||
|         pt::ptree tree; | ||||
|         pt::ptree& main_tree = tree.add("custom_gcodes_per_height", ""); | ||||
| 
 | ||||
|         for (const Model::CustomGCode& code : model.custom_gcode_per_height) | ||||
|         { | ||||
|             pt::ptree& code_tree = main_tree.add("code", ""); | ||||
|             // store minX and maxZ
 | ||||
|             code_tree.put("<xmlattr>.height"    , code.height   ); | ||||
|             code_tree.put("<xmlattr>.gcode"     , code.gcode    ); | ||||
|             code_tree.put("<xmlattr>.extruder"  , code.extruder ); | ||||
|             code_tree.put("<xmlattr>.color"     , code.color    ); | ||||
|         }        | ||||
| 
 | ||||
|         if (!tree.empty()) | ||||
|         { | ||||
|             std::ostringstream oss; | ||||
|             boost::property_tree::write_xml(oss, tree); | ||||
|             out = oss.str(); | ||||
| 
 | ||||
|             // Post processing("beautification") of the output string
 | ||||
|             boost::replace_all(out, "><", ">\n<"); | ||||
|         } | ||||
|     }  | ||||
| 
 | ||||
|     if (!out.empty()) | ||||
|     { | ||||
|         if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_HEIGHT_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) | ||||
|         { | ||||
|             add_error("Unable to add custom Gcodes per height file to archive"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) | ||||
|     { | ||||
|         if ((path == nullptr) || (config == nullptr) || (model == nullptr)) | ||||
|             return false; | ||||
|  |  | |||
|  | @ -16,6 +16,10 @@ | |||
| 
 | ||||
| #include "AMF.hpp" | ||||
| 
 | ||||
| #include <boost/property_tree/ptree.hpp> | ||||
| #include <boost/property_tree/xml_parser.hpp> | ||||
| namespace pt = boost::property_tree; | ||||
| 
 | ||||
| #include <boost/filesystem/operations.hpp> | ||||
| #include <boost/algorithm/string.hpp> | ||||
| #include <boost/nowide/fstream.hpp> | ||||
|  | @ -147,6 +151,8 @@ struct AMFParserContext | |||
|         NODE_TYPE_MIRRORY,              // amf/constellation/instance/mirrory
 | ||||
|         NODE_TYPE_MIRRORZ,              // amf/constellation/instance/mirrorz
 | ||||
|         NODE_TYPE_PRINTABLE,            // amf/constellation/instance/mirrorz
 | ||||
|         NODE_TYPE_CUSTOM_GCODE,         // amf/custom_code_per_height
 | ||||
|         NODE_TYPE_GCODE_PER_HEIGHT,     // amf/custom_code_per_height/code
 | ||||
|         NODE_TYPE_METADATA,             // anywhere under amf/*/metadata
 | ||||
|     }; | ||||
| 
 | ||||
|  | @ -227,7 +233,7 @@ struct AMFParserContext | |||
|     // Current instance allocated for an amf/constellation/instance subtree.
 | ||||
|     Instance                *m_instance; | ||||
|     // Generic string buffer for vertices, face indices, metadata etc.
 | ||||
|     std::string              m_value[3]; | ||||
|     std::string              m_value[4]; | ||||
|     // Pointer to config to update if config data are stored inside the amf file
 | ||||
|     DynamicPrintConfig      *m_config; | ||||
| 
 | ||||
|  | @ -268,6 +274,8 @@ void AMFParserContext::startElement(const char *name, const char **atts) | |||
|             } | ||||
|         } else if (strcmp(name, "constellation") == 0) { | ||||
|             node_type_new = NODE_TYPE_CONSTELLATION; | ||||
|         } else if (strcmp(name, "custom_gcodes_per_height") == 0) { | ||||
|             node_type_new = NODE_TYPE_CUSTOM_GCODE; | ||||
|         } | ||||
|         break; | ||||
|     case 2: | ||||
|  | @ -294,6 +302,13 @@ void AMFParserContext::startElement(const char *name, const char **atts) | |||
|             } | ||||
|             else | ||||
|                 this->stop(); | ||||
|         }  | ||||
|         else if (strcmp(name, "code") == 0 && m_path[1] == NODE_TYPE_CUSTOM_GCODE) { | ||||
|             node_type_new = NODE_TYPE_GCODE_PER_HEIGHT; | ||||
|             m_value[0] = get_attribute(atts, "height"); | ||||
|             m_value[1] = get_attribute(atts, "gcode"); | ||||
|             m_value[2] = get_attribute(atts, "extruder"); | ||||
|             m_value[3] = get_attribute(atts, "color"); | ||||
|         } | ||||
|         break; | ||||
|     case 3: | ||||
|  | @ -616,6 +631,19 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|         m_instance = nullptr; | ||||
|         break; | ||||
| 
 | ||||
|     case NODE_TYPE_GCODE_PER_HEIGHT: { | ||||
|         double height = double(atof(m_value[0].c_str())); | ||||
|         const std::string& gcode = m_value[1]; | ||||
|         int extruder = atoi(m_value[2].c_str()); | ||||
|         const std::string& color = m_value[3]; | ||||
| 
 | ||||
|         m_model.custom_gcode_per_height.push_back(Model::CustomGCode(height, gcode, extruder, color)); | ||||
| 
 | ||||
|         for (std::string& val: m_value) | ||||
|             val.clear(); | ||||
|         break; | ||||
|         } | ||||
| 
 | ||||
|     case NODE_TYPE_METADATA: | ||||
|         if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) | ||||
|             m_config->load_from_gcode_string(m_value[1].c_str()); | ||||
|  | @ -1190,6 +1218,42 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) | |||
|         stream << instances; | ||||
|         stream << "  </constellation>\n"; | ||||
|     } | ||||
| 
 | ||||
|     if (!model->custom_gcode_per_height.empty()) | ||||
|     { | ||||
|         std::string out = ""; | ||||
|         pt::ptree tree; | ||||
| 
 | ||||
|         pt::ptree& main_tree = tree.add("custom_gcodes_per_height", ""); | ||||
| 
 | ||||
|         for (const Model::CustomGCode& code : model->custom_gcode_per_height) | ||||
|         { | ||||
|             pt::ptree& code_tree = main_tree.add("code", ""); | ||||
|             // store minX and maxZ
 | ||||
|             code_tree.put("<xmlattr>.height", code.height); | ||||
|             code_tree.put("<xmlattr>.gcode", code.gcode); | ||||
|             code_tree.put("<xmlattr>.extruder", code.extruder); | ||||
|             code_tree.put("<xmlattr>.color", code.color); | ||||
|         } | ||||
| 
 | ||||
|         if (!tree.empty()) | ||||
|         { | ||||
|             std::ostringstream oss; | ||||
|             pt::write_xml(oss, tree); | ||||
|             out = oss.str(); | ||||
| 
 | ||||
|             int del_header_pos = out.find("<custom_gcodes_per_height"); | ||||
|             if (del_header_pos != std::string::npos) | ||||
|                 out.erase(out.begin(), out.begin() + del_header_pos); | ||||
| 
 | ||||
|             // Post processing("beautification") of the output string
 | ||||
|             boost::replace_all(out, "><code", ">\n  <code"); | ||||
|             boost::replace_all(out, "><", ">\n<"); | ||||
| 
 | ||||
|             stream << out << "\n"; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     stream << "</amf>\n"; | ||||
| 
 | ||||
|     std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(export_path).filename().string(), ".zip.amf", ".amf"); | ||||
|  |  | |||
|  | @ -924,8 +924,9 @@ void GCode::_do_export(Print& print, FILE* file) | |||
|     this->apply_print_config(print.config()); | ||||
|     this->set_extruders(print.extruders()); | ||||
| 
 | ||||
|     // Initialize colorprint.
 | ||||
|     m_colorprint_heights = cast<float>(print.config().colorprint_heights.values); | ||||
|     // Initialize custom gcode
 | ||||
|     Model* model = print.get_object(0)->model_object()->get_model(); | ||||
|     m_custom_g_code_heights = model->custom_gcode_per_height; | ||||
| 
 | ||||
|     // Initialize autospeed.
 | ||||
|     { | ||||
|  | @ -991,7 +992,7 @@ void GCode::_do_export(Print& print, FILE* file) | |||
|     { | ||||
|         const size_t max_row_length = 78; | ||||
|         ThumbnailsList thumbnails; | ||||
|         thumbnail_cb(thumbnails, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, false); | ||||
|         thumbnail_cb(thumbnails, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true); | ||||
|         for (const ThumbnailData& data : thumbnails) | ||||
|         { | ||||
|             if (data.is_valid()) | ||||
|  | @ -1113,6 +1114,47 @@ void GCode::_do_export(Print& print, FILE* file) | |||
|     } | ||||
|     print.throw_if_canceled(); | ||||
| 
 | ||||
|     /* To avoid change filament for non-used extruder for Multi-material,
 | ||||
|      * check model->custom_gcode_per_height using tool_ordering values | ||||
|      * */ | ||||
|     if (!m_custom_g_code_heights. empty()) | ||||
|     { | ||||
|         bool delete_executed = false; | ||||
|         auto it = m_custom_g_code_heights.end(); | ||||
|         while (it != m_custom_g_code_heights.begin()) | ||||
|         { | ||||
|             --it; | ||||
|             if (it->gcode != ColorChangeCode) | ||||
|                 continue; | ||||
| 
 | ||||
|             auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(it->height)); | ||||
| 
 | ||||
|             bool used_extruder = false; | ||||
|             for (; it_layer_tools != tool_ordering.end(); it_layer_tools++) | ||||
|             { | ||||
|                 const std::vector<unsigned>& extruders = it_layer_tools->extruders; | ||||
|                 if (std::find(extruders.begin(), extruders.end(), (unsigned)(it->extruder-1)) != extruders.end()) | ||||
|                 { | ||||
|                     used_extruder = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if (used_extruder) | ||||
|                 continue; | ||||
| 
 | ||||
|             /* If we are there, current extruder wouldn't be used,
 | ||||
|              * so this color change is a redundant move. | ||||
|              * Delete this item from m_custom_g_code_heights | ||||
|              * */ | ||||
|             it = m_custom_g_code_heights.erase(it); | ||||
|             delete_executed = true; | ||||
|         } | ||||
| 
 | ||||
|         if (delete_executed) | ||||
|             model->custom_gcode_per_height = m_custom_g_code_heights; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     m_cooling_buffer->set_current_extruder(initial_extruder_id); | ||||
| 
 | ||||
|     // Emit machine envelope limits for the Marlin firmware.
 | ||||
|  | @ -1779,19 +1821,66 @@ void GCode::process_layer( | |||
|     // In case there are more toolchange requests that weren't done yet and should happen simultaneously, erase them all.
 | ||||
|     // (Layers can be close to each other, model could have been resliced with bigger layer height, ...).
 | ||||
|     bool colorprint_change = false; | ||||
|     while (!m_colorprint_heights.empty() && m_colorprint_heights.front()-EPSILON < layer.print_z) { | ||||
|         m_colorprint_heights.erase(m_colorprint_heights.begin()); | ||||
| 
 | ||||
|     std::string custom_code = ""; | ||||
|     std::string pause_print_msg = ""; | ||||
|     int m600_before_extruder = -1; | ||||
|     while (!m_custom_g_code_heights.empty() && m_custom_g_code_heights.front().height-EPSILON < layer.print_z) { | ||||
|         custom_code = m_custom_g_code_heights.front().gcode; | ||||
| 
 | ||||
|         if (custom_code == ColorChangeCode && m_custom_g_code_heights.front().extruder > 0) | ||||
|             m600_before_extruder = m_custom_g_code_heights.front().extruder - 1; | ||||
|         if (custom_code == PausePrintCode) | ||||
|             pause_print_msg = m_custom_g_code_heights.front().color; | ||||
| 
 | ||||
|         m_custom_g_code_heights.erase(m_custom_g_code_heights.begin()); | ||||
|         colorprint_change = true; | ||||
|     } | ||||
| 
 | ||||
|     // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
 | ||||
|     if (colorprint_change && print./*extruders()*/config().nozzle_diameter.size()==1) | ||||
|     { | ||||
|         // add tag for analyzer
 | ||||
|         gcode += "; " + GCodeAnalyzer::Color_Change_Tag + "\n"; | ||||
|         // add tag for time estimator
 | ||||
|         gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n"; | ||||
|         gcode += "M600\n"; | ||||
| 
 | ||||
|     // don't save "tool_change"(ExtruderChangeCode) code to GCode
 | ||||
|     if (colorprint_change && custom_code != ExtruderChangeCode) { | ||||
|         const bool single_material_print = print.config().nozzle_diameter.size() == 1; | ||||
|          | ||||
|         if (custom_code == ColorChangeCode) // color change
 | ||||
|         { | ||||
|             // add tag for analyzer
 | ||||
|             gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_before_extruder) + "\n"; | ||||
|             // add tag for time estimator
 | ||||
|             gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n"; | ||||
| 
 | ||||
|             if (!single_material_print && m600_before_extruder >= 0 && first_extruder_id != m600_before_extruder | ||||
|                 // && !MMU1
 | ||||
|                 ) { | ||||
|                 //! FIXME_in_fw show message during print pause
 | ||||
|                 gcode += "M601\n"; // pause print
 | ||||
|                 gcode += "M117 Change filament for Extruder " + std::to_string(m600_before_extruder) + "\n"; | ||||
|             } | ||||
|             else  | ||||
|                 gcode += custom_code + "\n"; | ||||
|         }  | ||||
|         else | ||||
|         { | ||||
|             if (custom_code == PausePrintCode) // Pause print
 | ||||
|             { | ||||
|                 // add tag for analyzer
 | ||||
|                 gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n"; | ||||
|                 //! FIXME_in_fw show message during print pause
 | ||||
|                 if (!pause_print_msg.empty()) | ||||
|                     gcode += "M117 " + pause_print_msg + "\n"; | ||||
|                 // add tag for time estimator
 | ||||
|                 //gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n";
 | ||||
|             } | ||||
|             else // custom Gcode
 | ||||
|             { | ||||
|                 // add tag for analyzer
 | ||||
|                 gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n"; | ||||
|                 // add tag for time estimator
 | ||||
|                 //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n";
 | ||||
|             } | ||||
|             gcode += custom_code + "\n"; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -2101,6 +2190,12 @@ void GCode::process_layer( | |||
|     if (m_cooling_buffer) | ||||
|         gcode = m_cooling_buffer->process_layer(gcode, layer.id()); | ||||
| 
 | ||||
|     // add tag for analyzer
 | ||||
|     if (gcode.find(GCodeAnalyzer::Pause_Print_Tag) != gcode.npos) | ||||
|         gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n"; | ||||
|     else if (gcode.find(GCodeAnalyzer::Custom_Code_Tag) != gcode.npos) | ||||
|         gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n"; | ||||
| 
 | ||||
| #ifdef HAS_PRESSURE_EQUALIZER | ||||
|     // Apply pressure equalization if enabled;
 | ||||
|     // printf("G-code before filter:\n%s\n", gcode.c_str());
 | ||||
|  |  | |||
|  | @ -362,9 +362,12 @@ protected: | |||
|     bool                                m_second_layer_things_done; | ||||
|     // Index of a last object copy extruded.
 | ||||
|     std::pair<const PrintObject*, Point> m_last_obj_copy; | ||||
|     // Layer heights for colorprint - updated before the export and erased during the process
 | ||||
|     // so no toolchange occurs twice.
 | ||||
|     std::vector<float> m_colorprint_heights; | ||||
|     /* Extensions for colorprint - now it's not a just color_print_heights, 
 | ||||
|      * there can be some custom gcode. | ||||
|      * Updated before the export and erased during the process, | ||||
|      * so no toolchange occurs twice. | ||||
|      * */ | ||||
|     std::vector<Model::CustomGCode> m_custom_g_code_heights; | ||||
| 
 | ||||
|     // Time estimators
 | ||||
|     GCodeTimeEstimator m_normal_time_estimator; | ||||
|  |  | |||
|  | @ -29,6 +29,9 @@ const std::string GCodeAnalyzer::Mm3_Per_Mm_Tag = "_ANALYZER_MM3_PER_MM:"; | |||
| const std::string GCodeAnalyzer::Width_Tag = "_ANALYZER_WIDTH:"; | ||||
| const std::string GCodeAnalyzer::Height_Tag = "_ANALYZER_HEIGHT:"; | ||||
| const std::string GCodeAnalyzer::Color_Change_Tag = "_ANALYZER_COLOR_CHANGE"; | ||||
| const std::string GCodeAnalyzer::Pause_Print_Tag = "_ANALYZER_PAUSE_PRINT"; | ||||
| const std::string GCodeAnalyzer::Custom_Code_Tag = "_ANALYZER_CUSTOM_CODE"; | ||||
| const std::string GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag = "_ANALYZER_END_PAUSE_PRINT_OR_CUSTOM_CODE"; | ||||
| 
 | ||||
| const double GCodeAnalyzer::Default_mm3_per_mm = 0.0; | ||||
| const float GCodeAnalyzer::Default_Width = 0.0f; | ||||
|  | @ -118,6 +121,8 @@ void GCodeAnalyzer::set_extruder_offsets(const GCodeAnalyzer::ExtruderOffsetsMap | |||
| void GCodeAnalyzer::set_extruders_count(unsigned int count) | ||||
| { | ||||
|     m_extruders_count = count; | ||||
|     for (unsigned int i=0; i<m_extruders_count; i++) | ||||
|         m_extruder_color[i] = i; | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::set_gcode_flavor(const GCodeFlavor& flavor) | ||||
|  | @ -147,6 +152,7 @@ void GCodeAnalyzer::reset() | |||
|     m_moves_map.clear(); | ||||
|     m_extruder_offsets.clear(); | ||||
|     m_extruders_count = 1; | ||||
|     m_extruder_color.clear(); | ||||
| } | ||||
| 
 | ||||
| const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode) | ||||
|  | @ -595,7 +601,11 @@ void GCodeAnalyzer::_processT(const std::string& cmd) | |||
|                     BOOST_LOG_TRIVIAL(error) << "GCodeAnalyzer encountered an invalid toolchange, maybe from a custom gcode."; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _set_extruder_id(id); | ||||
|                 if (_get_cp_color_id() != INT_MAX) | ||||
|                     _set_cp_color_id(m_extruder_color[id]); | ||||
|             } | ||||
| 
 | ||||
|             // stores tool change move
 | ||||
|             _store_move(GCodeMove::Tool_change); | ||||
|  | @ -648,7 +658,33 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) | |||
|     pos = comment.find(Color_Change_Tag); | ||||
|     if (pos != comment.npos) | ||||
|     { | ||||
|         _process_color_change_tag(); | ||||
|         pos = comment.find_last_of(",T"); | ||||
|         int extruder = pos == comment.npos ? 0 : std::atoi(comment.substr(pos + 1, comment.npos).c_str()); | ||||
|         _process_color_change_tag(extruder); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // color change tag
 | ||||
|     pos = comment.find(Pause_Print_Tag); | ||||
|     if (pos != comment.npos) | ||||
|     { | ||||
|         _process_pause_print_or_custom_code_tag(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // color change tag
 | ||||
|     pos = comment.find(Custom_Code_Tag); | ||||
|     if (pos != comment.npos) | ||||
|     { | ||||
|         _process_pause_print_or_custom_code_tag(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // color change tag
 | ||||
|     pos = comment.find(End_Pause_Print_Or_Custom_Code_Tag); | ||||
|     if (pos != comment.npos) | ||||
|     { | ||||
|         _process_end_pause_print_or_custom_code_tag(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|  | @ -681,10 +717,24 @@ void GCodeAnalyzer::_process_height_tag(const std::string& comment, size_t pos) | |||
|     _set_height((float)::strtod(comment.substr(pos + Height_Tag.length()).c_str(), nullptr)); | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_process_color_change_tag() | ||||
| void GCodeAnalyzer::_process_color_change_tag(int extruder) | ||||
| { | ||||
|     m_state.cur_cp_color_id++; | ||||
|     _set_cp_color_id(m_state.cur_cp_color_id); | ||||
|     m_extruder_color[extruder] = m_extruders_count + m_state.cp_color_counter; // color_change position in list of color for preview
 | ||||
|     m_state.cp_color_counter++; | ||||
| 
 | ||||
|     if (_get_extruder_id() == extruder) | ||||
|         _set_cp_color_id(m_extruder_color[extruder]); | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_process_pause_print_or_custom_code_tag() | ||||
| { | ||||
|     _set_cp_color_id(INT_MAX); | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_process_end_pause_print_or_custom_code_tag() | ||||
| { | ||||
|     if (_get_cp_color_id() == INT_MAX) | ||||
|         _set_cp_color_id(m_extruder_color[_get_extruder_id()]); | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_set_units(GCodeAnalyzer::EUnits units) | ||||
|  |  | |||
|  | @ -20,6 +20,9 @@ public: | |||
|     static const std::string Width_Tag; | ||||
|     static const std::string Height_Tag; | ||||
|     static const std::string Color_Change_Tag; | ||||
|     static const std::string Pause_Print_Tag; | ||||
|     static const std::string Custom_Code_Tag; | ||||
|     static const std::string End_Pause_Print_Or_Custom_Code_Tag; | ||||
| 
 | ||||
|     static const double Default_mm3_per_mm; | ||||
|     static const float Default_Width; | ||||
|  | @ -89,6 +92,7 @@ public: | |||
|     typedef std::vector<GCodeMove> GCodeMovesList; | ||||
|     typedef std::map<GCodeMove::EType, GCodeMovesList> TypeToMovesMap; | ||||
|     typedef std::map<unsigned int, Vec2d> ExtruderOffsetsMap; | ||||
|     typedef std::map<unsigned int, unsigned int> ExtruderToColorMap; | ||||
| 
 | ||||
| private: | ||||
|     struct State | ||||
|  | @ -102,7 +106,7 @@ private: | |||
|         float start_extrusion; | ||||
|         float position[Num_Axis]; | ||||
|         float origin[Num_Axis]; | ||||
|         unsigned int cur_cp_color_id = 0; | ||||
|         unsigned int cp_color_counter = 0; | ||||
|     }; | ||||
| 
 | ||||
| private: | ||||
|  | @ -113,6 +117,8 @@ private: | |||
|     unsigned int m_extruders_count; | ||||
|     GCodeFlavor m_gcode_flavor; | ||||
| 
 | ||||
|     ExtruderToColorMap m_extruder_color; | ||||
| 
 | ||||
|     // The output of process_layer()
 | ||||
|     std::string m_process_output; | ||||
| 
 | ||||
|  | @ -212,7 +218,13 @@ private: | |||
|     void _process_height_tag(const std::string& comment, size_t pos); | ||||
| 
 | ||||
|     // Processes color change tag
 | ||||
|     void _process_color_change_tag(); | ||||
|     void _process_color_change_tag(int extruder); | ||||
| 
 | ||||
|     // Processes pause print and custom gcode tag
 | ||||
|     void _process_pause_print_or_custom_code_tag(); | ||||
| 
 | ||||
|     // Processes new layer tag
 | ||||
|     void _process_end_pause_print_or_custom_code_tag(); | ||||
| 
 | ||||
|     void _set_units(EUnits units); | ||||
|     EUnits _get_units() const; | ||||
|  |  | |||
|  | @ -379,7 +379,8 @@ std::string GCodePreviewData::get_legend_title() const | |||
|     return ""; | ||||
| } | ||||
| 
 | ||||
| GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::vector<float>& tool_colors, const std::vector</*double*/std::pair<double, double>>& cp_values) const | ||||
| GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::vector<float>& tool_colors,  | ||||
|                                                                      const std::vector<std::string>& cp_items) const | ||||
| { | ||||
|     struct Helper | ||||
|     { | ||||
|  | @ -455,31 +456,25 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: | |||
|     case Extrusion::ColorPrint: | ||||
|         { | ||||
|             const int color_cnt = (int)tool_colors.size()/4; | ||||
| 
 | ||||
|             const auto color_print_cnt = (int)cp_values.size(); | ||||
|             for (int i = color_print_cnt; i >= 0 ; --i) | ||||
|             const auto color_print_cnt = (int)cp_items.size(); | ||||
|             if (color_print_cnt == 1) // means "Default print color"
 | ||||
|             { | ||||
|                 GCodePreviewData::Color color; | ||||
|                 ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (i % color_cnt) * 4), 4 * sizeof(float)); | ||||
|                 Color color; | ||||
|                 ::memcpy((void*)color.rgba, (const void*)(tool_colors.data()), 4 * sizeof(float)); | ||||
| 
 | ||||
|                 items.emplace_back(cp_items[0], color); | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             if (color_cnt != color_print_cnt) | ||||
|                 break; | ||||
| 
 | ||||
|             for (int i = 0 ; i < color_print_cnt; ++i) | ||||
|             { | ||||
|                 Color color; | ||||
|                 ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float)); | ||||
|                  | ||||
|                 if (color_print_cnt == 0) { | ||||
|                     items.emplace_back(Slic3r::I18N::translate(L("Default print color")), color); | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 std::string id_str = std::to_string(i + 1) + ": "; | ||||
| 
 | ||||
|                 if (i == 0) { | ||||
|                     items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); | ||||
|                     break; | ||||
|                 } | ||||
|                 if (i == color_print_cnt) { | ||||
|                     items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i - 1].second).str(), color); | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
| //                 items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) %  cp_values[i-1] % cp_values[i]).str(), color);
 | ||||
|                 items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i - 1].second% cp_values[i].first).str(), color); | ||||
|                 items.emplace_back(cp_items[i], color); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|  |  | |||
|  | @ -237,7 +237,7 @@ public: | |||
|     void set_extrusion_paths_colors(const std::vector<std::string>& colors); | ||||
| 
 | ||||
|     std::string get_legend_title() const; | ||||
|     LegendItemsList get_legend_items(const std::vector<float>& tool_colors, const std::vector</*double*/std::pair<double, double>>& cp_values) const; | ||||
|     LegendItemsList get_legend_items(const std::vector<float>& tool_colors, const std::vector<std::string>& cp_items) const; | ||||
| 
 | ||||
|     // Return an estimate of the memory consumed by the time estimator.
 | ||||
|     size_t memory_used() const; | ||||
|  |  | |||
|  | @ -41,6 +41,9 @@ Model& Model::assign_copy(const Model &rhs) | |||
|         mo->set_model(this); | ||||
| 		this->objects.emplace_back(mo); | ||||
|     } | ||||
| 
 | ||||
|     // copy custom code per height
 | ||||
|     this->custom_gcode_per_height = rhs.custom_gcode_per_height; | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
|  | @ -59,6 +62,9 @@ Model& Model::assign_copy(Model &&rhs) | |||
|     for (ModelObject *model_object : this->objects) | ||||
|         model_object->set_model(this); | ||||
|     rhs.objects.clear(); | ||||
| 
 | ||||
|     // copy custom code per height
 | ||||
|     this->custom_gcode_per_height = rhs.custom_gcode_per_height; | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
|  | @ -586,6 +592,22 @@ std::string Model::propose_export_file_name_and_path(const std::string &new_exte | |||
|     return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string(); | ||||
| } | ||||
| 
 | ||||
| std::vector<std::pair<double, DynamicPrintConfig>> Model::get_custom_tool_changes(double default_layer_height, size_t num_extruders) const | ||||
| { | ||||
|     std::vector<std::pair<double, DynamicPrintConfig>> custom_tool_changes; | ||||
|     if (!custom_gcode_per_height.empty()) { | ||||
|         for (const CustomGCode& custom_gcode : custom_gcode_per_height) | ||||
|             if (custom_gcode.gcode == ExtruderChangeCode) { | ||||
|                 DynamicPrintConfig config; | ||||
|                 // If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
 | ||||
|                 config.set_key_value("extruder", new ConfigOptionInt(custom_gcode.extruder > num_extruders ? 0 : custom_gcode.extruder)); | ||||
|                 // For correct extruders(tools) changing, we should decrease custom_gcode.height value by one default layer height
 | ||||
|                 custom_tool_changes.push_back({ custom_gcode.height - default_layer_height, config }); | ||||
|             } | ||||
|     } | ||||
|     return custom_tool_changes; | ||||
| } | ||||
| 
 | ||||
| ModelObject::~ModelObject() | ||||
| { | ||||
|     this->clear_volumes(); | ||||
|  |  | |||
|  | @ -745,6 +745,37 @@ public: | |||
|     ModelObjectPtrs     objects; | ||||
|     // Wipe tower object.
 | ||||
|     ModelWipeTower	    wipe_tower; | ||||
| 
 | ||||
|     // Extensions for color print
 | ||||
|     struct CustomGCode | ||||
|     { | ||||
|         CustomGCode(double height, const std::string& code, int extruder, const std::string& color) : | ||||
|             height(height), gcode(code), extruder(extruder), color(color) {} | ||||
| 
 | ||||
|         bool operator<(const CustomGCode& other) const { return other.height > this->height; } | ||||
|         bool operator==(const CustomGCode& other) const | ||||
|         { | ||||
|             return (other.height    == this->height)     &&  | ||||
|                    (other.gcode     == this->gcode)      &&  | ||||
|                    (other.extruder  == this->extruder   )&&  | ||||
|                    (other.color     == this->color   ); | ||||
|         } | ||||
|         bool operator!=(const CustomGCode& other) const | ||||
|         { | ||||
|             return (other.height    != this->height)     ||  | ||||
|                    (other.gcode     != this->gcode)      ||  | ||||
|                    (other.extruder  != this->extruder   )||  | ||||
|                    (other.color     != this->color   ); | ||||
|         } | ||||
|          | ||||
|         double      height; | ||||
|         std::string gcode; | ||||
|         int         extruder;   // 0    - "gcode" will be applied for whole print
 | ||||
|                                 // else - "gcode" will be applied only for "extruder" print
 | ||||
|         std::string color;      // if gcode is equal to PausePrintCode, 
 | ||||
|                                 // this field is used for save a short message shown on Printer display 
 | ||||
|     }; | ||||
|     std::vector<CustomGCode> custom_gcode_per_height; | ||||
|      | ||||
|     // Default constructor assigns a new ID to the model.
 | ||||
|     Model() { assert(this->id().valid()); } | ||||
|  | @ -810,6 +841,9 @@ public: | |||
|     // Propose an output path, replace extension. The new_extension shall contain the initial dot.
 | ||||
|     std::string   propose_export_file_name_and_path(const std::string &new_extension) const; | ||||
| 
 | ||||
|     // from custom_gcode_per_height get just tool_change codes
 | ||||
|     std::vector<std::pair<double, DynamicPrintConfig>> get_custom_tool_changes(double default_layer_height, size_t num_extruders) const; | ||||
| 
 | ||||
| private: | ||||
| 	explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }; | ||||
| 	void assign_new_unique_ids_recursive(); | ||||
|  |  | |||
|  | @ -637,11 +637,59 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ | |||
|             else | ||||
|                 m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); | ||||
|         } | ||||
| 
 | ||||
|         // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs,
 | ||||
|         // considering custom_tool_change values
 | ||||
|         void assign(const t_layer_config_ranges &in, const std::vector<std::pair<double, DynamicPrintConfig>> &custom_tool_changes) { | ||||
|             m_ranges.clear(); | ||||
|             m_ranges.reserve(in.size()); | ||||
|             // Input ranges are sorted lexicographically. First range trims the other ranges.
 | ||||
|             coordf_t last_z = 0; | ||||
|             for (const std::pair<const t_layer_height_range, DynamicPrintConfig> &range : in) | ||||
| 				if (range.first.second > last_z) { | ||||
|                     coordf_t min_z = std::max(range.first.first, 0.); | ||||
|                     if (min_z > last_z + EPSILON) { | ||||
|                         m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); | ||||
|                         last_z = min_z; | ||||
|                     } | ||||
|                     if (range.first.second > last_z + EPSILON) { | ||||
| 						const DynamicPrintConfig* cfg = &range.second; | ||||
|                         m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg); | ||||
|                         last_z = range.first.second; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|             // add ranges for extruder changes from custom_tool_changes
 | ||||
|             for (size_t i = 0; i < custom_tool_changes.size(); i++) { | ||||
|                 const DynamicPrintConfig* cfg = &custom_tool_changes[i].second; | ||||
|                 coordf_t cur_Z = custom_tool_changes[i].first; | ||||
|                 coordf_t next_Z = i == custom_tool_changes.size()-1 ? DBL_MAX : custom_tool_changes[i+1].first; | ||||
|                 if (cur_Z > last_z + EPSILON) { | ||||
|                     if (i==0) | ||||
|                         m_ranges.emplace_back(t_layer_height_range(last_z, cur_Z), nullptr); | ||||
|                     m_ranges.emplace_back(t_layer_height_range(cur_Z, next_Z), cfg); | ||||
|                 } | ||||
|                 else if (next_Z > last_z + EPSILON) | ||||
|                     m_ranges.emplace_back(t_layer_height_range(last_z, next_Z), cfg); | ||||
|             } | ||||
| 
 | ||||
|             if (m_ranges.empty()) | ||||
|                 m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); | ||||
|             else if (m_ranges.back().second == nullptr) | ||||
|                 m_ranges.back().first.second = DBL_MAX; | ||||
|             else if (m_ranges.back().first.second != DBL_MAX) | ||||
|                 m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); | ||||
|         } | ||||
|         const DynamicPrintConfig* config(const t_layer_height_range &range) const { | ||||
|             auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr)); | ||||
|             assert(it != m_ranges.end()); | ||||
|             assert(it == m_ranges.end() || std::abs(it->first.first  - range.first ) < EPSILON); | ||||
|             assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); | ||||
|             // #ys_FIXME_COLOR
 | ||||
|             // assert(it != m_ranges.end());
 | ||||
|             // assert(it == m_ranges.end() || std::abs(it->first.first  - range.first ) < EPSILON);
 | ||||
|             // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON);
 | ||||
|             if (it == m_ranges.end() || | ||||
|                 std::abs(it->first.first - range.first) > EPSILON || | ||||
|                 std::abs(it->first.second - range.second) > EPSILON ) | ||||
|                 return nullptr; // desired range doesn't found
 | ||||
|             return (it == m_ranges.end()) ? nullptr : it->second; | ||||
|         } | ||||
|         std::vector<std::pair<t_layer_height_range, const DynamicPrintConfig*>>::const_iterator begin() const { return m_ranges.cbegin(); } | ||||
|  | @ -689,6 +737,13 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ | |||
|             // The object list did not change.
 | ||||
| 			for (const ModelObject *model_object : m_model.objects) | ||||
| 				model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); | ||||
| 
 | ||||
|             // But if custom gcode per layer height was changed
 | ||||
|             if (m_model.custom_gcode_per_height != model.custom_gcode_per_height) { | ||||
|                 // we should stop background processing
 | ||||
|                 update_apply_status(this->invalidate_step(psGCodeExport)); | ||||
|                 m_model.custom_gcode_per_height = model.custom_gcode_per_height; | ||||
|             } | ||||
|         } else if (model_object_list_extended(m_model, model)) { | ||||
|             // Add new objects. Their volumes and configs will be synchronized later.
 | ||||
|             update_apply_status(this->invalidate_step(psGCodeExport)); | ||||
|  | @ -780,6 +835,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ | |||
|     for (PrintObject *print_object : m_objects) | ||||
|         print_object_status.emplace(PrintObjectStatus(print_object)); | ||||
| 
 | ||||
|     std::vector<std::pair<double, DynamicPrintConfig>> custom_tool_changes =  | ||||
|         m_model.get_custom_tool_changes(m_default_object_config.layer_height, num_extruders); | ||||
| 
 | ||||
|     // 3) Synchronize ModelObjects & PrintObjects.
 | ||||
|     for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { | ||||
|         ModelObject &model_object = *m_model.objects[idx_model_object]; | ||||
|  | @ -787,7 +845,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ | |||
|         assert(it_status != model_object_status.end()); | ||||
|         assert(it_status->status != ModelObjectStatus::Deleted); | ||||
| 		const ModelObject& model_object_new = *model.objects[idx_model_object]; | ||||
| 		const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges); | ||||
|         // ys_FIXME_COLOR
 | ||||
| 		// const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges);
 | ||||
|         const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges, custom_tool_changes); | ||||
|         if (it_status->status == ModelObjectStatus::New) | ||||
|             // PrintObject instances will be added in the next loop.
 | ||||
|             continue; | ||||
|  | @ -955,6 +1015,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ | |||
|         PrintRegionConfig  this_region_config; | ||||
|         bool               this_region_config_set = false; | ||||
|         for (PrintObject *print_object : m_objects) { | ||||
|             if(m_force_update_print_regions && !custom_tool_changes.empty()) | ||||
|                 goto print_object_end; | ||||
|             const LayerRanges *layer_ranges; | ||||
|             { | ||||
|                 auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); | ||||
|  |  | |||
|  | @ -370,6 +370,9 @@ public: | |||
|     // Accessed by SupportMaterial
 | ||||
|     const PrintRegion*  get_region(size_t idx) const  { return m_regions[idx]; } | ||||
| 
 | ||||
|     // force update of PrintRegions, when custom_tool_change is not empty and (Re)Slicing is started
 | ||||
|     void set_force_update_print_regions(bool force_update_print_regions) { m_force_update_print_regions = force_update_print_regions; } | ||||
| 
 | ||||
| protected: | ||||
|     // methods for handling regions
 | ||||
|     PrintRegion*        get_region(size_t idx)        { return m_regions[idx]; } | ||||
|  | @ -412,6 +415,9 @@ private: | |||
|     // Estimated print time, filament consumed.
 | ||||
|     PrintStatistics                         m_print_statistics; | ||||
| 
 | ||||
|     // flag used
 | ||||
|     bool                                    m_force_update_print_regions = false; | ||||
| 
 | ||||
|     // To allow GCode to set the Print's GCodeExport step status.
 | ||||
|     friend class GCode; | ||||
|     // Allow PrintObject to access m_mutex and m_cancel_callback.
 | ||||
|  |  | |||
|  | @ -71,6 +71,12 @@ enum SLAPillarConnectionMode { | |||
|     slapcmDynamic | ||||
| }; | ||||
| 
 | ||||
| // ys_FIXME ! may be, it's not a best place
 | ||||
| // Additional Codes which can be set by user using DoubleSlider
 | ||||
| static const std::string ColorChangeCode    = "M600"; | ||||
| static const std::string PausePrintCode     = "M601"; | ||||
| static const std::string ExtruderChangeCode = "tool_change"; | ||||
| 
 | ||||
| template<> inline const t_config_enum_values& ConfigOptionEnum<PrinterTechnology>::get_enum_values() { | ||||
|     static t_config_enum_values keys_map; | ||||
|     if (keys_map.empty()) { | ||||
|  |  | |||
|  | @ -1333,6 +1333,82 @@ static inline std::pair<double, size_t> minimum_crossover_cost( | |||
| 	return std::make_pair(cost_min, flip_min); | ||||
| } | ||||
| 
 | ||||
| static inline std::pair<double, size_t> minimum_crossover_cost( | ||||
| 	const std::vector<FlipEdge>		  &edges, | ||||
| 	const std::pair<size_t, size_t>   &span1, const ConnectionCost &cost1, | ||||
| 	const std::pair<size_t, size_t>   &span2, const ConnectionCost &cost2, | ||||
| 	const std::pair<size_t, size_t>   &span3, const ConnectionCost &cost3, | ||||
| 	const std::pair<size_t, size_t>   &span4, const ConnectionCost &cost4, | ||||
| 	const double					   cost_current) | ||||
| { | ||||
| 	auto connection_cost = [&edges]( | ||||
| 		const std::pair<size_t, size_t> &span1, const ConnectionCost &cost1, bool reversed1, bool flipped1, | ||||
| 		const std::pair<size_t, size_t> &span2, const ConnectionCost &cost2, bool reversed2, bool flipped2, | ||||
| 		const std::pair<size_t, size_t> &span3, const ConnectionCost &cost3, bool reversed3, bool flipped3, | ||||
| 		const std::pair<size_t, size_t> &span4, const ConnectionCost &cost4, bool reversed4, bool flipped4) { | ||||
| 		auto first_point = [&edges](const std::pair<size_t, size_t> &span, bool flipped) { return flipped ? edges[span.first].p2 : edges[span.first].p1; }; | ||||
| 		auto last_point  = [&edges](const std::pair<size_t, size_t> &span, bool flipped) { return flipped ? edges[span.second - 1].p1 : edges[span.second - 1].p2; }; | ||||
| 		auto point       = [first_point, last_point](const std::pair<size_t, size_t> &span, bool start, bool flipped) { return start ? first_point(span, flipped) : last_point(span, flipped); }; | ||||
| 		auto cost        = [](const ConnectionCost &acost, bool flipped) {  | ||||
| 			assert(acost.cost >= 0. && acost.cost_flipped >= 0.); | ||||
| 			return flipped ? acost.cost_flipped : acost.cost; | ||||
| 		}; | ||||
| 		// Ignore reversed single segment spans.
 | ||||
| 		auto simple_span_ignore = [](const std::pair<size_t, size_t>& span, bool reversed) { | ||||
| 			return span.first + 1 == span.second && reversed; | ||||
| 		}; | ||||
| 		assert(span1.first < span1.second); | ||||
| 		assert(span2.first < span2.second); | ||||
| 		assert(span3.first < span3.second); | ||||
| 		assert(span4.first < span4.second); | ||||
| 		return  | ||||
| 			simple_span_ignore(span1, reversed1) || simple_span_ignore(span2, reversed2) || simple_span_ignore(span3, reversed3) || simple_span_ignore(span4, reversed4) ? | ||||
| 				// Don't perform unnecessary calculations simulating reversion of single segment spans.
 | ||||
| 				std::numeric_limits<double>::max() : | ||||
| 				// Calculate the cost of reverting chains and / or flipping segment orientations.
 | ||||
| 				cost(cost1, flipped1) + cost(cost2, flipped2) + cost(cost3, flipped3) + cost(cost4, flipped4) + | ||||
| 					(point(span2, ! reversed2, flipped2) - point(span1, reversed1, flipped1)).norm() +  | ||||
| 					(point(span3, ! reversed3, flipped3) - point(span2, reversed2, flipped2)).norm() + | ||||
| 					(point(span4, ! reversed4, flipped4) - point(span3, reversed3, flipped3)).norm(); | ||||
| 	}; | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| 	{ | ||||
| 		double c = connection_cost(span1, cost1, false, false, span2, cost2, false, false, span3, cost3, false, false, span4, cost4, false, false); | ||||
| 		assert(std::abs(c - cost_current) < SCALED_EPSILON); | ||||
| 	} | ||||
| #endif /* NDEBUG */ | ||||
| 
 | ||||
| 	double cost_min = cost_current; | ||||
| 	size_t flip_min = 0; // no flip, no improvement
 | ||||
| 	for (size_t i = 0; i < (1 << 8); ++ i) { | ||||
| 		// From the three combinations of 1,2,3 ordering, the other three are reversals of the first three.
 | ||||
| 		size_t permutation = 0; | ||||
| 		for (double c : { | ||||
| 				(i == 0) ? cost_current :  | ||||
| 				connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, cost2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, cost3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, cost2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, cost4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, cost3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, cost3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, cost2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, cost3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, cost4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span2, cost2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span4, cost4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, cost2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, cost3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span4, cost4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, cost3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span2, cost2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span2, cost2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, cost1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, cost3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span2, cost2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, cost1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, cost4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, cost3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span2, cost2, (i & 1) != 0, (i & (1 << 1)) != 0, span3, cost3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, cost1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span2, cost2, (i & 1) != 0, (i & (1 << 1)) != 0, span4, cost4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, cost1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, cost3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span3, cost3, (i & 1) != 0, (i & (1 << 1)) != 0, span1, cost1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, cost2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0), | ||||
| 				connection_cost(span3, cost3, (i & 1) != 0, (i & (1 << 1)) != 0, span2, cost2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, cost1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0) | ||||
| 			}) { | ||||
| 			if (c < cost_min) { | ||||
| 				cost_min = c; | ||||
| 				flip_min = i + (permutation << 8); | ||||
| 			} | ||||
| 			++ permutation; | ||||
| 		} | ||||
| 	} | ||||
| 	return std::make_pair(cost_min, flip_min); | ||||
| } | ||||
| 
 | ||||
| static inline void do_crossover(const std::vector<FlipEdge> &edges_in, std::vector<FlipEdge> &edges_out, | ||||
| 	const std::pair<size_t, size_t> &span1, const std::pair<size_t, size_t> &span2, const std::pair<size_t, size_t> &span3, | ||||
| 	size_t i) | ||||
|  | @ -1374,6 +1450,79 @@ static inline void do_crossover(const std::vector<FlipEdge> &edges_in, std::vect | |||
| 	assert(edges_in.size() == edges_out.size()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static inline void do_crossover(const std::vector<FlipEdge> &edges_in, std::vector<FlipEdge> &edges_out, | ||||
| 	const std::pair<size_t, size_t> &span1, const std::pair<size_t, size_t> &span2, const std::pair<size_t, size_t> &span3, const std::pair<size_t, size_t> &span4, | ||||
| 	size_t i) | ||||
| { | ||||
| 	assert(edges_in.size() == edges_out.size()); | ||||
| 	auto do_it = [&edges_in, &edges_out]( | ||||
| 		const std::pair<size_t, size_t> &span1, bool reversed1, bool flipped1, | ||||
| 		const std::pair<size_t, size_t> &span2, bool reversed2, bool flipped2, | ||||
| 		const std::pair<size_t, size_t> &span3, bool reversed3, bool flipped3, | ||||
| 		const std::pair<size_t, size_t> &span4, bool reversed4, bool flipped4) { | ||||
| 		auto it_edges_out = edges_out.begin(); | ||||
| 		auto copy_span = [&edges_in, &edges_out, &it_edges_out](std::pair<size_t, size_t> span, bool reversed, bool flipped) { | ||||
| 			assert(span.first < span.second); | ||||
| 			auto it = it_edges_out; | ||||
| 			if (reversed) | ||||
| 				std::reverse_copy(edges_in.begin() + span.first, edges_in.begin() + span.second, it_edges_out); | ||||
| 			else | ||||
| 				std::copy        (edges_in.begin() + span.first, edges_in.begin() + span.second, it_edges_out); | ||||
| 			it_edges_out += span.second - span.first; | ||||
| 			if (reversed != flipped) { | ||||
| 				for (; it != it_edges_out; ++ it) | ||||
| 					it->flip(); | ||||
| 			} | ||||
| 		}; | ||||
| 		copy_span(span1, reversed1, flipped1); | ||||
| 		copy_span(span2, reversed2, flipped2); | ||||
| 		copy_span(span3, reversed3, flipped3); | ||||
| 		copy_span(span4, reversed4, flipped4); | ||||
| 	}; | ||||
| 	switch (i >> 8) { | ||||
| 	case 0: | ||||
| 		assert(i != 0); // otherwise it would be a no-op
 | ||||
| 		do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	case 1: | ||||
| 		do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	case 2: | ||||
| 		do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	case 3: | ||||
| 		do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	case 4: | ||||
| 		do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	case 5: | ||||
| 		do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	case 6: | ||||
| 		do_it(span2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	case 7: | ||||
| 		do_it(span2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	case 8: | ||||
| 		do_it(span2, (i & 1) != 0, (i & (1 << 1)) != 0, span3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	case 9: | ||||
| 		do_it(span2, (i & 1) != 0, (i & (1 << 1)) != 0, span4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	case 10: | ||||
| 		do_it(span3, (i & 1) != 0, (i & (1 << 1)) != 0, span1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	default: | ||||
| 		assert((i >> 8) == 11); | ||||
| 		do_it(span3, (i & 1) != 0, (i & (1 << 1)) != 0, span2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0); | ||||
| 		break; | ||||
| 	} | ||||
| 	assert(edges_in.size() == edges_out.size()); | ||||
| } | ||||
| 
 | ||||
| static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges) | ||||
| { | ||||
| 	if (edges.size() < 2) | ||||
|  | @ -1448,6 +1597,90 @@ static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<Fl | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static inline void reorder_by_three_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges) | ||||
| { | ||||
| 	if (edges.size() < 3) { | ||||
| 		reorder_by_two_exchanges_with_segment_flipping(edges); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	std::vector<ConnectionCost> 			connections(edges.size()); | ||||
| 	std::vector<FlipEdge> 					edges_tmp(edges); | ||||
| 	std::vector<std::pair<double, size_t>>	connection_lengths(edges.size() - 1, std::pair<double, size_t>(0., 0)); | ||||
| 	std::vector<char>						connection_tried(edges.size(), false); | ||||
| 	for (size_t iter = 0; iter < edges.size(); ++ iter) { | ||||
| 		// Initialize connection costs and connection lengths.
 | ||||
| 		for (size_t i = 1; i < edges.size(); ++ i) { | ||||
| 			const FlipEdge   	 &e1 = edges[i - 1]; | ||||
| 			const FlipEdge   	 &e2 = edges[i]; | ||||
| 			ConnectionCost	     &c  = connections[i]; | ||||
| 			c = connections[i - 1]; | ||||
| 			double l = (e2.p1 - e1.p2).norm(); | ||||
| 			c.cost += l; | ||||
| 			c.cost_flipped += (e2.p2 - e1.p1).norm(); | ||||
| 			connection_lengths[i - 1] = std::make_pair(l, i); | ||||
| 		} | ||||
| 		std::sort(connection_lengths.begin(), connection_lengths.end(), [](const std::pair<double, size_t> &l, const std::pair<double, size_t> &r) { return l.first > r.first; }); | ||||
| 		std::fill(connection_tried.begin(), connection_tried.end(), false); | ||||
| 		size_t crossover1_pos_final = std::numeric_limits<size_t>::max(); | ||||
| 		size_t crossover2_pos_final = std::numeric_limits<size_t>::max(); | ||||
| 		size_t crossover3_pos_final = std::numeric_limits<size_t>::max(); | ||||
| 		size_t crossover_flip_final = 0; | ||||
| 		for (const std::pair<double, size_t> &first_crossover_candidate : connection_lengths) { | ||||
| 			double longest_connection_length = first_crossover_candidate.first; | ||||
| 			size_t longest_connection_idx    = first_crossover_candidate.second; | ||||
| 			connection_tried[longest_connection_idx] = true; | ||||
| 			// Find the second crossover connection with the lowest total chain cost.
 | ||||
| 			size_t crossover_pos_min  = std::numeric_limits<size_t>::max(); | ||||
| 			double crossover_cost_min = connections.back().cost; | ||||
| 			for (size_t j = 1; j < connections.size(); ++ j) | ||||
| 				if (! connection_tried[j]) { | ||||
| 					for (size_t k = j + 1; k < connections.size(); ++ k) | ||||
| 						if (! connection_tried[k]) { | ||||
| 							size_t a = longest_connection_idx; | ||||
| 							size_t b = j; | ||||
| 							size_t c = k; | ||||
| 							if (a > c) | ||||
| 								std::swap(a, c); | ||||
| 							if (a > b) | ||||
| 								std::swap(a, b); | ||||
| 							if (b > c) | ||||
| 								std::swap(b, c); | ||||
| 							std::pair<double, size_t> cost_and_flip = minimum_crossover_cost(edges,  | ||||
| 								std::make_pair(size_t(0), a), connections[a - 1], std::make_pair(a, b), connections[b - 1] - connections[a],  | ||||
| 								std::make_pair(b, c), connections[c - 1] - connections[b], std::make_pair(c, edges.size()), connections.back() - connections[c], | ||||
| 								connections.back().cost); | ||||
| 							if (cost_and_flip.second > 0 && cost_and_flip.first < crossover_cost_min) { | ||||
| 								crossover_cost_min   = cost_and_flip.first; | ||||
| 								crossover1_pos_final = a; | ||||
| 								crossover2_pos_final = b; | ||||
| 								crossover3_pos_final = c; | ||||
| 								crossover_flip_final = cost_and_flip.second; | ||||
| 								assert(crossover_cost_min < connections.back().cost + EPSILON); | ||||
| 							} | ||||
| 						} | ||||
| 				} | ||||
| 			if (crossover_flip_final > 0) { | ||||
| 				// The cost of the chain with the proposed two crossovers has a lower total cost than the current chain. Apply the crossover.
 | ||||
| 				break; | ||||
| 			} else { | ||||
| 				// Continue with another long candidate edge.
 | ||||
| 			} | ||||
| 		} | ||||
| 		if (crossover_flip_final > 0) { | ||||
| 			// Pair of cross over positions and flip / reverse constellation has been found, which improves the total cost of the connection.
 | ||||
| 			// Perform a crossover.
 | ||||
| 			do_crossover(edges, edges_tmp, std::make_pair(size_t(0), crossover1_pos_final), std::make_pair(crossover1_pos_final, crossover2_pos_final),  | ||||
| 				std::make_pair(crossover2_pos_final, crossover3_pos_final), std::make_pair(crossover3_pos_final, edges.size()), crossover_flip_final); | ||||
| 			edges.swap(edges_tmp); | ||||
| 		} else { | ||||
| 			// No valid pair of cross over positions was found improving the total cost. Giving up.
 | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Flip the sequences of polylines to lower the total length of connecting lines.
 | ||||
| static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polylines &polylines, bool fixed_start) | ||||
| { | ||||
|  | @ -1471,7 +1704,11 @@ static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polyl | |||
| 	edges.reserve(polylines.size()); | ||||
|     std::transform(polylines.begin(), polylines.end(), std::back_inserter(edges),  | ||||
|     	[&polylines](const Polyline &pl){ return FlipEdge(pl.first_point().cast<double>(), pl.last_point().cast<double>(), &pl - polylines.data()); }); | ||||
| #if 1 | ||||
| 	reorder_by_two_exchanges_with_segment_flipping(edges); | ||||
| #else | ||||
| 	reorder_by_three_exchanges_with_segment_flipping(edges); | ||||
| #endif | ||||
| 	Polylines out; | ||||
| 	out.reserve(polylines.size()); | ||||
| 	for (const FlipEdge &edge : edges) { | ||||
|  |  | |||
|  | @ -267,7 +267,7 @@ std::vector<coordf_t> layer_height_profile_adaptive( | |||
|     int current_facet = 0; | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     while (slice_z <= slicing_params.object_print_z_height()) { | ||||
|         double height = 999.0; | ||||
|         double height = slicing_params.max_layer_height; | ||||
| #else | ||||
|     double height = slicing_params.first_object_layer_height; | ||||
|     while ((slice_z - height) <= slicing_params.object_print_z_height()) { | ||||
|  |  | |||
|  | @ -94,8 +94,8 @@ float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet | |||
| 				continue; | ||||
| 			// compute cusp-height for this facet and store minimum of all heights
 | ||||
| 			float normal_z = m_face_normal_z[ordered_id]; | ||||
| 			height = std::min(height, (normal_z == 0.f) ? 9999.f : std::abs(cusp_value / normal_z)); | ||||
| 		} | ||||
|             height = std::min(height, (normal_z == 0.0f) ? (float)m_slicing_params.max_layer_height : std::abs(cusp_value / normal_z)); | ||||
|         } | ||||
| 	} | ||||
| 
 | ||||
| 	// lower height limit due to printer capabilities
 | ||||
|  | @ -115,13 +115,13 @@ float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet | |||
| 
 | ||||
| 			// Compute cusp-height for this facet and check against height.
 | ||||
| 			float normal_z = m_face_normal_z[ordered_id]; | ||||
| 			float cusp = (normal_z == 0) ? 9999 : abs(cusp_value / normal_z); | ||||
| 			 | ||||
|             float cusp = (normal_z == 0.0f) ? (float)m_slicing_params.max_layer_height : abs(cusp_value / normal_z); | ||||
| 
 | ||||
| 			float z_diff = zspan.first - z; | ||||
| 
 | ||||
| 			// handle horizontal facets
 | ||||
| 			if (m_face_normal_z[ordered_id] > 0.999) { | ||||
| 				// Slic3r::debugf "cusp computation, height is reduced from %f", $height;
 | ||||
|             if (normal_z > 0.999f) { | ||||
|                 // Slic3r::debugf "cusp computation, height is reduced from %f", $height;
 | ||||
| 				height = z_diff; | ||||
| 				// Slic3r::debugf "to %f due to near horizontal facet\n", $height;
 | ||||
| 			} else if (cusp > z_diff) { | ||||
|  | @ -139,7 +139,7 @@ float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet | |||
| 		// lower height limit due to printer capabilities again
 | ||||
| 		height = std::max(height, float(m_slicing_params.min_layer_height)); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| //	Slic3r::debugf "cusp computation, layer-bottom at z:%f, cusp_value:%f, resulting layer height:%f\n", unscale $z, $cusp_value, $height;	
 | ||||
| 	return height;  | ||||
| } | ||||
|  |  | |||
|  | @ -105,6 +105,8 @@ set(SLIC3R_GUI_SOURCES | |||
|     GUI/Camera.hpp | ||||
|     GUI/wxExtensions.cpp | ||||
|     GUI/wxExtensions.hpp | ||||
|     GUI/ExtruderSequenceDialog.cpp | ||||
|     GUI/ExtruderSequenceDialog.hpp | ||||
|     GUI/WipeTowerDialog.cpp | ||||
|     GUI/WipeTowerDialog.hpp | ||||
|     GUI/RammingChart.cpp | ||||
|  |  | |||
|  | @ -33,6 +33,7 @@ | |||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/nowide/cstdio.hpp> | ||||
| #include "I18N.hpp" | ||||
| #include "GUI.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -92,7 +93,15 @@ void BackgroundSlicingProcess::process_fff() | |||
| #else | ||||
|     m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); | ||||
| #endif // ENABLE_THUMBNAIL_GENERATOR
 | ||||
|     if (this->set_step_started(bspsGCodeFinalize)) { | ||||
| 
 | ||||
|     if (m_fff_print->model().custom_gcode_per_height != GUI::wxGetApp().model().custom_gcode_per_height) { | ||||
|         GUI::wxGetApp().model().custom_gcode_per_height = m_fff_print->model().custom_gcode_per_height; | ||||
|         // #ys_FIXME : controll text
 | ||||
|         GUI::show_info(nullptr, _(L("To except of redundant tool manipulation, \n" | ||||
|                                     "Color change(s) for unused extruder(s) was(were) deleted")), _(L("Info"))); | ||||
|     } | ||||
| 
 | ||||
| 	if (this->set_step_started(bspsGCodeFinalize)) { | ||||
| 	    if (! m_export_path.empty()) { | ||||
| 	    	//FIXME localize the messages
 | ||||
| 	    	// Perform the final post-processing of the export path by applying the print statistics over the file name.
 | ||||
|  | @ -108,7 +117,7 @@ void BackgroundSlicingProcess::process_fff() | |||
| 			m_print->set_status(100, _utf8(L("Slicing complete"))); | ||||
| 	    } | ||||
| 		this->set_step_done(bspsGCodeFinalize); | ||||
|     } | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
|  | @ -139,8 +148,8 @@ void BackgroundSlicingProcess::process_sla() | |||
|             if (m_thumbnail_cb != nullptr) | ||||
|             { | ||||
|                 ThumbnailsList thumbnails; | ||||
|                 m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, false); | ||||
| //                m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, false); // renders also supports and pad
 | ||||
|                 m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true); | ||||
| //                m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, true); // renders also supports and pad
 | ||||
|                 for (const ThumbnailData& data : thumbnails) | ||||
|                 { | ||||
|                     if (data.is_valid()) | ||||
|  | @ -464,8 +473,8 @@ void BackgroundSlicingProcess::prepare_upload() | |||
|         if (m_thumbnail_cb != nullptr) | ||||
|         { | ||||
|             ThumbnailsList thumbnails; | ||||
|             m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, false); | ||||
| //            m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, false); // renders also supports and pad
 | ||||
|             m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true); | ||||
| //            m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, true); // renders also supports and pad
 | ||||
|             for (const ThumbnailData& data : thumbnails) | ||||
|             { | ||||
|                 if (data.is_valid()) | ||||
|  |  | |||
|  | @ -132,6 +132,11 @@ public: | |||
|     // This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs),
 | ||||
|     // and it does not account for the OctoPrint scheduling.
 | ||||
|     bool    finished() const { return m_print->finished(); } | ||||
| 
 | ||||
|     void    set_force_update_print_regions(bool force_update_print_regions) { | ||||
|         if (m_fff_print) | ||||
| 	        m_fff_print->set_force_update_print_regions(force_update_print_regions); | ||||
| 	} | ||||
|      | ||||
| private: | ||||
| 	void 	thread_proc(); | ||||
|  |  | |||
							
								
								
									
										235
									
								
								src/slic3r/GUI/ExtruderSequenceDialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								src/slic3r/GUI/ExtruderSequenceDialog.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,235 @@ | |||
| #include "ExtruderSequenceDialog.hpp" | ||||
| 
 | ||||
| #include <wx/wx.h> | ||||
| #include <wx/stattext.h> | ||||
| #include <wx/dialog.h> | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/bmpcbox.h> | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <set> | ||||
| #include <functional> | ||||
| 
 | ||||
| #include "GUI.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| #include "I18N.hpp" | ||||
| #include "OptionsGroup.hpp" | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequence& sequence) | ||||
|     : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Set extruder sequence")), | ||||
|         wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), | ||||
|     m_sequence(sequence) | ||||
| { | ||||
|     SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); | ||||
|     SetDoubleBuffered(true); | ||||
|     SetFont(wxGetApp().normal_font()); | ||||
| 
 | ||||
|     auto main_sizer = new wxBoxSizer(wxVERTICAL); | ||||
|     const int em = wxGetApp().em_unit(); | ||||
| 
 | ||||
|     m_bmp_del = ScalableBitmap(this, "remove_copies"); | ||||
|     m_bmp_add = ScalableBitmap(this, "add_copies"); | ||||
| 
 | ||||
|     auto option_sizer = new wxBoxSizer(wxVERTICAL); | ||||
| 
 | ||||
|     auto intervals_box = new wxStaticBox(this, wxID_ANY, _(L("Set extruder change for every"))+ " : "); | ||||
|     auto intervals_box_sizer = new wxStaticBoxSizer(intervals_box, wxVERTICAL); | ||||
| 
 | ||||
|     m_intervals_grid_sizer = new wxFlexGridSizer(3, 5, em); | ||||
| 
 | ||||
|     auto editor_sz = wxSize(4*em, wxDefaultCoord); | ||||
| 
 | ||||
|     auto ID_RADIO_BUTTON = wxWindow::NewControlId(1); | ||||
| 
 | ||||
|     wxRadioButton* rb_by_layers = new wxRadioButton(this, ID_RADIO_BUTTON, "", wxDefaultPosition, wxDefaultSize, wxRB_GROUP); | ||||
|     rb_by_layers->Bind(wxEVT_RADIOBUTTON, [this](wxCommandEvent& event) { m_sequence.is_mm_intervals = false; }); | ||||
|     rb_by_layers->SetValue(!m_sequence.is_mm_intervals); | ||||
| 
 | ||||
|     wxStaticText* st_by_layers = new wxStaticText(this, wxID_ANY, _(L("layers"))); | ||||
|     m_interval_by_layers = new wxTextCtrl(this, wxID_ANY,  | ||||
|                                           wxString::Format("%d", m_sequence.interval_by_layers),  | ||||
|                                           wxDefaultPosition, editor_sz); | ||||
|     m_interval_by_layers->Bind(wxEVT_TEXT, [this, rb_by_layers](wxEvent&) | ||||
|     { | ||||
|         wxString str = m_interval_by_layers->GetValue(); | ||||
|         if (str.IsEmpty()) { | ||||
|             m_interval_by_layers->SetValue(wxString::Format("%d", m_sequence.interval_by_layers)); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         int val = wxAtoi(str); | ||||
|         if (val < 1) { | ||||
|             m_interval_by_layers->SetValue("1"); | ||||
|             val = 1; | ||||
|         } | ||||
|          | ||||
|         if (m_sequence.interval_by_layers == val) | ||||
|             return; | ||||
| 
 | ||||
|         m_sequence.interval_by_layers = val; | ||||
| 
 | ||||
|         m_sequence.is_mm_intervals = false; | ||||
|         rb_by_layers->SetValue(true); | ||||
|     }); | ||||
| 
 | ||||
|     m_intervals_grid_sizer->Add(rb_by_layers, 0, wxALIGN_CENTER_VERTICAL); | ||||
|     m_intervals_grid_sizer->Add(m_interval_by_layers,0, wxALIGN_CENTER_VERTICAL); | ||||
|     m_intervals_grid_sizer->Add(st_by_layers,0, wxALIGN_CENTER_VERTICAL); | ||||
| 
 | ||||
|     wxRadioButton* rb_by_mm = new wxRadioButton(this, ID_RADIO_BUTTON, ""); | ||||
|     rb_by_mm->Bind(wxEVT_RADIOBUTTON, [this](wxEvent&) { m_sequence.is_mm_intervals = true; }); | ||||
|     rb_by_mm->SetValue(m_sequence.is_mm_intervals); | ||||
| 
 | ||||
|     wxStaticText* st_by_mm = new wxStaticText(this, wxID_ANY, _(L("mm"))); | ||||
|     m_interval_by_mm = new wxTextCtrl(this, wxID_ANY,  | ||||
|                                       double_to_string(sequence.interval_by_mm),  | ||||
|                                       wxDefaultPosition, editor_sz, wxTE_PROCESS_ENTER); | ||||
| 
 | ||||
|     auto change_value = [this]() | ||||
|     { | ||||
|         wxString str = m_interval_by_mm->GetValue(); | ||||
|         if (str.IsEmpty()) { | ||||
|             m_interval_by_mm->SetValue(wxString::Format("%d", m_sequence.interval_by_mm)); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         str.Replace(",", ".", false); | ||||
|         double val; | ||||
|         if (str == "." || !str.ToCDouble(&val) || val <= 0.0) | ||||
|             val = 3.0; // default value
 | ||||
| 
 | ||||
|         if (fabs(m_sequence.interval_by_layers - val) < 0.001) | ||||
|             return; | ||||
| 
 | ||||
|         m_sequence.interval_by_mm = val; | ||||
|     }; | ||||
| 
 | ||||
|     m_interval_by_mm->Bind(wxEVT_TEXT, [this, rb_by_mm](wxEvent&) | ||||
|     { | ||||
|         m_sequence.is_mm_intervals = true; | ||||
|         rb_by_mm->SetValue(true); | ||||
|     }); | ||||
| 
 | ||||
|     m_interval_by_mm->Bind(wxEVT_KILL_FOCUS, [this, change_value](wxFocusEvent& event) | ||||
|     { | ||||
|         change_value(); | ||||
|         event.Skip(); | ||||
|     }); | ||||
| 
 | ||||
|     m_interval_by_mm->Bind(wxEVT_TEXT_ENTER, [this, change_value](wxEvent&) | ||||
|     { | ||||
|         change_value(); | ||||
|     }); | ||||
| 
 | ||||
|     m_intervals_grid_sizer->Add(rb_by_mm, 0, wxALIGN_CENTER_VERTICAL); | ||||
|     m_intervals_grid_sizer->Add(m_interval_by_mm,0, wxALIGN_CENTER_VERTICAL); | ||||
|     m_intervals_grid_sizer->Add(st_by_mm,0, wxALIGN_CENTER_VERTICAL); | ||||
| 
 | ||||
|     intervals_box_sizer->Add(m_intervals_grid_sizer, 0, wxLEFT, em); | ||||
|     option_sizer->Add(intervals_box_sizer, 0, wxEXPAND); | ||||
| 
 | ||||
|      | ||||
|     auto extruders_box = new wxStaticBox(this, wxID_ANY, _(L("Set extruder(tool) sequence"))+ " : "); | ||||
|     auto extruders_box_sizer = new wxStaticBoxSizer(extruders_box, wxVERTICAL); | ||||
| 
 | ||||
|     m_extruders_grid_sizer = new wxFlexGridSizer(3, 5, em); | ||||
| 
 | ||||
|     apply_extruder_sequence(); | ||||
| 
 | ||||
|     extruders_box_sizer->Add(m_extruders_grid_sizer, 0, wxALL, em); | ||||
|     option_sizer->Add(extruders_box_sizer, 0, wxEXPAND | wxTOP, em); | ||||
| 
 | ||||
|     main_sizer->Add(option_sizer, 0, wxEXPAND | wxALL, em); | ||||
| 
 | ||||
|     wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); | ||||
|     main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, em); | ||||
| 
 | ||||
|     SetSizer(main_sizer); | ||||
|     main_sizer->SetSizeHints(this); | ||||
| 
 | ||||
|     /* For this moment min sizes for dialog and its sizer are calculated.
 | ||||
|      * If we left them, it can cause a problem with layouts during deleting of extruders | ||||
|      */ | ||||
|     if (m_sequence.extruders.size()>1) | ||||
|     { | ||||
|         wxSize sz = wxSize(-1, 10 * em); | ||||
|         SetMinSize(sz); | ||||
|         GetSizer()->SetMinSize(sz); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ExtruderSequenceDialog::apply_extruder_sequence() | ||||
| { | ||||
|     m_extruders_grid_sizer->Clear(true); | ||||
| 
 | ||||
|     for (size_t extruder=0; extruder < m_sequence.extruders.size(); ++extruder) | ||||
|     { | ||||
|         wxBitmapComboBox* extruder_selector = nullptr; | ||||
|         apply_extruder_selector(&extruder_selector, this, "", wxDefaultPosition, wxSize(15*wxGetApp().em_unit(), -1)); | ||||
|         extruder_selector->SetSelection(m_sequence.extruders[extruder]); | ||||
| 
 | ||||
|         extruder_selector->Bind(wxEVT_COMBOBOX, [this, extruder_selector, extruder](wxCommandEvent& evt) | ||||
|         { | ||||
|             m_sequence.extruders[extruder] = extruder_selector->GetSelection(); | ||||
|             evt.StopPropagation(); | ||||
|         }); | ||||
| 
 | ||||
|         auto del_btn = new ScalableButton(this, wxID_ANY, m_bmp_del); | ||||
|         del_btn->SetToolTip(_(L("Remove extruder from sequence"))); | ||||
|         if (m_sequence.extruders.size()==1) | ||||
|             del_btn->Disable(); | ||||
| 
 | ||||
|         del_btn->Bind(wxEVT_BUTTON, [this, extruder](wxEvent&) { | ||||
|             m_sequence.delete_extruder(extruder); | ||||
|             apply_extruder_sequence(); | ||||
|         }); | ||||
| 
 | ||||
|         auto add_btn = new ScalableButton(this, wxID_ANY, m_bmp_add); | ||||
|         add_btn->SetToolTip(_(L("Add extruder to sequence"))); | ||||
| 
 | ||||
|         add_btn->Bind(wxEVT_BUTTON, [this, extruder](wxEvent&) { | ||||
|             m_sequence.add_extruder(extruder); | ||||
|             apply_extruder_sequence(); | ||||
|         }); | ||||
| 
 | ||||
|         m_extruders_grid_sizer->Add(extruder_selector, 0, wxALIGN_CENTER_VERTICAL); | ||||
|         m_extruders_grid_sizer->Add(del_btn, 0, wxALIGN_CENTER_VERTICAL); | ||||
|         m_extruders_grid_sizer->Add(add_btn, 0, wxALIGN_CENTER_VERTICAL); | ||||
|     } | ||||
|     m_extruders_grid_sizer->ShowItems(true); // show items hidden in apply_extruder_selector()
 | ||||
| 
 | ||||
|     Fit(); | ||||
|     Refresh(); | ||||
| } | ||||
| 
 | ||||
| void ExtruderSequenceDialog::on_dpi_changed(const wxRect& suggested_rect) | ||||
| { | ||||
|     SetFont(wxGetApp().normal_font()); | ||||
| 
 | ||||
|     m_bmp_add.msw_rescale(); | ||||
|     m_bmp_del.msw_rescale(); | ||||
| 
 | ||||
|     const int em = em_unit(); | ||||
| 
 | ||||
|     m_intervals_grid_sizer->SetHGap(em); | ||||
|     m_intervals_grid_sizer->SetVGap(em); | ||||
|     m_extruders_grid_sizer->SetHGap(em); | ||||
|     m_extruders_grid_sizer->SetVGap(em); | ||||
| 
 | ||||
|     msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); | ||||
| 
 | ||||
|     // wxSize size = get_size();
 | ||||
|     // SetMinSize(size);
 | ||||
| 
 | ||||
|     Fit(); | ||||
|     Refresh(); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										45
									
								
								src/slic3r/GUI/ExtruderSequenceDialog.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/slic3r/GUI/ExtruderSequenceDialog.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| #ifndef slic3r_GUI_ExtruderSequenceDialog_hpp_ | ||||
| #define slic3r_GUI_ExtruderSequenceDialog_hpp_ | ||||
| 
 | ||||
| #include "GUI_Utils.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| 
 | ||||
| class wxTextCtrl; | ||||
| class wxFlexGridSizer; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // ExtruderSequenceDialog: a node inside ObjectDataViewModel
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| class ExtruderSequenceDialog: public DPIDialog | ||||
| { | ||||
|     ScalableBitmap  m_bmp_del; | ||||
|     ScalableBitmap  m_bmp_add; | ||||
|     DoubleSlider::ExtrudersSequence m_sequence; | ||||
| 
 | ||||
|     wxTextCtrl* m_interval_by_layers {nullptr};  | ||||
|     wxTextCtrl* m_interval_by_mm {nullptr}; | ||||
| 
 | ||||
|     wxFlexGridSizer* m_intervals_grid_sizer {nullptr}; | ||||
|     wxFlexGridSizer* m_extruders_grid_sizer {nullptr}; | ||||
| public: | ||||
|     ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequence& sequence); | ||||
| 
 | ||||
|     ~ExtruderSequenceDialog() {} | ||||
| 
 | ||||
|     DoubleSlider::ExtrudersSequence GetValue() { return m_sequence; } | ||||
| 
 | ||||
| protected: | ||||
|     void apply_extruder_sequence(); | ||||
|     void on_dpi_changed(const wxRect& suggested_rect) override; | ||||
|      | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #endif // slic3r_GUI_ExtruderSequenceDialog_hpp_
 | ||||
|  | @ -22,6 +22,7 @@ | |||
| #include "slic3r/GUI/PresetBundle.hpp" | ||||
| #include "slic3r/GUI/Tab.hpp" | ||||
| #include "slic3r/GUI/GUI_Preview.hpp" | ||||
| 
 | ||||
| #include "GUI_App.hpp" | ||||
| #include "GUI_ObjectList.hpp" | ||||
| #include "GUI_ObjectManipulation.hpp" | ||||
|  | @ -277,9 +278,9 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const | |||
|     imgui.text(_(L("Cusp (mm)"))); | ||||
|     ImGui::SameLine(); | ||||
|     float widget_align = ImGui::GetCursorPosX(); | ||||
|     ImGui::PushItemWidth(120.0f); | ||||
|     m_adaptive_cusp = std::min(m_adaptive_cusp, (float)m_slicing_parameters->max_layer_height); | ||||
|     ImGui::SliderFloat("", &m_adaptive_cusp, 0.0f, (float)m_slicing_parameters->max_layer_height, "%.2f"); | ||||
|     ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); | ||||
|     m_adaptive_cusp = clamp((float)m_slicing_parameters->min_layer_height, (float)m_slicing_parameters->max_layer_height, m_adaptive_cusp); | ||||
|     ImGui::SliderFloat("", &m_adaptive_cusp, (float)m_slicing_parameters->min_layer_height, (float)m_slicing_parameters->max_layer_height, "%.2f"); | ||||
| 
 | ||||
|     ImGui::Separator(); | ||||
|     if (imgui.button(_(L("Smooth")))) | ||||
|  | @ -289,8 +290,8 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const | |||
|     ImGui::SetCursorPosX(text_align); | ||||
|     imgui.text(_(L("Radius"))); | ||||
|     ImGui::SameLine(); | ||||
|     ImGui::PushItemWidth(120.0f); | ||||
|     ImGui::SetCursorPosX(widget_align); | ||||
|     ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); | ||||
|     int radius = (int)m_smooth_params.radius; | ||||
|     if (ImGui::SliderInt("##1", &radius, 1, 10)) | ||||
|         m_smooth_params.radius = (unsigned int)radius; | ||||
|  | @ -298,8 +299,8 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const | |||
|     ImGui::SetCursorPosX(text_align); | ||||
|     imgui.text(_(L("Keep min"))); | ||||
|     ImGui::SameLine(); | ||||
|     ImGui::PushItemWidth(120.0f); | ||||
|     ImGui::SetCursorPosX(widget_align); | ||||
|     ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); | ||||
|     imgui.checkbox("##2", m_smooth_params.keep_min); | ||||
| 
 | ||||
|     ImGui::Separator(); | ||||
|  | @ -411,6 +412,35 @@ bool GLCanvas3D::LayersEditing::is_initialized() const | |||
|     return m_shader.is_initialized(); | ||||
| } | ||||
| 
 | ||||
| std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const | ||||
| { | ||||
|     std::string ret; | ||||
|     if (m_enabled && (m_layer_height_profile.size() >= 4)) | ||||
|     { | ||||
|         float z = get_cursor_z_relative(canvas); | ||||
|         if (z != -1000.0f) | ||||
|         { | ||||
|             z *= m_object_max_z; | ||||
| 
 | ||||
|             float h = 0.0f; | ||||
|             for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2) | ||||
|             { | ||||
|                 float zi = m_layer_height_profile[i]; | ||||
|                 float zi_1 = m_layer_height_profile[i - 2]; | ||||
|                 if ((zi_1 <= z) && (z <= zi)) | ||||
|                 { | ||||
|                     float dz = zi - zi_1; | ||||
|                     h = (dz != 0.0f) ? lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz) : m_layer_height_profile[i + 1]; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if (h > 0.0f) | ||||
|                 ret = std::to_string(h); | ||||
|         } | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| #if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const | ||||
| { | ||||
|  | @ -659,7 +689,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) | |||
| { | ||||
|     if (last_object_id >= 0) { | ||||
|         if (m_layer_height_profile_modified) { | ||||
|             wxGetApp().plater()->take_snapshot(_(L("Layers heights"))); | ||||
|             wxGetApp().plater()->take_snapshot(_(L("Layer height profile-Manual edit"))); | ||||
|             const_cast<ModelObject*>(m_model_object)->layer_height_profile = m_layer_height_profile; | ||||
| 			canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); | ||||
|         } | ||||
|  | @ -967,47 +997,125 @@ GLCanvas3D::LegendTexture::LegendTexture() | |||
| { | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas,  | ||||
|                                                                std::vector<std::pair<double, double>>& cp_legend_values) | ||||
| void GLCanvas3D::LegendTexture::fill_color_print_legend_items(  const GLCanvas3D& canvas, | ||||
|                                                                 const std::vector<float>& colors_in, | ||||
|                                                                 std::vector<float>& colors, | ||||
|                                                                 std::vector<std::string>& cp_legend_items) | ||||
| { | ||||
|     if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint &&  | ||||
|         wxGetApp().extruders_edited_cnt() == 1) // show color change legend only for single-material presets
 | ||||
|     std::vector<Model::CustomGCode> custom_gcode_per_height = wxGetApp().plater()->model().custom_gcode_per_height; | ||||
| 
 | ||||
|     const int extruders_cnt = wxGetApp().extruders_edited_cnt(); | ||||
|     if (extruders_cnt == 1)  | ||||
|     { | ||||
|         auto& config = wxGetApp().preset_bundle->project_config; | ||||
|         const std::vector<double>& color_print_values = config.option<ConfigOptionFloats>("colorprint_heights")->values; | ||||
|          | ||||
|         if (!color_print_values.empty()) { | ||||
|             std::vector<double> print_zs = canvas.get_current_print_zs(true); | ||||
|             for (auto cp_value : color_print_values) | ||||
|             { | ||||
|                 auto lower_b = std::lower_bound(print_zs.begin(), print_zs.end(), cp_value - DoubleSlider::epsilon()); | ||||
| 
 | ||||
|                 if (lower_b == print_zs.end()) | ||||
|                     continue; | ||||
| 
 | ||||
|                 double current_z    = *lower_b; | ||||
|                 double previous_z   = lower_b == print_zs.begin() ? 0.0 : *(--lower_b); | ||||
| 
 | ||||
|                 // to avoid duplicate values, check adding values
 | ||||
|                 if (cp_legend_values.empty() ||  | ||||
|                     !(cp_legend_values.back().first == previous_z && cp_legend_values.back().second == current_z) ) | ||||
|                     cp_legend_values.push_back(std::pair<double, double>(previous_z, current_z)); | ||||
|             } | ||||
|         if (custom_gcode_per_height.empty()) { | ||||
|             cp_legend_items.push_back(I18N::translate_utf8(L("Default print color"))); | ||||
|             colors = colors_in; | ||||
|             return; | ||||
|         } | ||||
|         std::vector<std::pair<double, double>> cp_values; | ||||
|          | ||||
|         std::vector<double> print_zs = canvas.get_current_print_zs(true); | ||||
|         for (auto custom_code : custom_gcode_per_height) | ||||
|         { | ||||
|             if (custom_code.gcode != ColorChangeCode) | ||||
|                 continue; | ||||
|             auto lower_b = std::lower_bound(print_zs.begin(), print_zs.end(), custom_code.height - DoubleSlider::epsilon()); | ||||
| 
 | ||||
|             if (lower_b == print_zs.end()) | ||||
|                 continue; | ||||
| 
 | ||||
|             double current_z = *lower_b; | ||||
|             double previous_z = lower_b == print_zs.begin() ? 0.0 : *(--lower_b); | ||||
| 
 | ||||
|             // to avoid duplicate values, check adding values
 | ||||
|             if (cp_values.empty() || | ||||
|                 !(cp_values.back().first == previous_z && cp_values.back().second == current_z)) | ||||
|                 cp_values.push_back(std::pair<double, double>(previous_z, current_z)); | ||||
|         } | ||||
| 
 | ||||
|         const auto items_cnt = (int)cp_values.size(); | ||||
|         if (items_cnt == 0) // There is no one color change, but there is/are some pause print or custom Gcode
 | ||||
|         { | ||||
|             cp_legend_items.push_back(I18N::translate_utf8(L("Default print color"))); | ||||
|             cp_legend_items.push_back(I18N::translate_utf8(L("Pause print or custom G-code"))); | ||||
|             colors = colors_in; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const int color_cnt = (int)colors_in.size() / 4; | ||||
|         colors.resize(colors_in.size(), 0.0); | ||||
|                  | ||||
|         ::memcpy((void*)(colors.data()), (const void*)(colors_in.data() + (color_cnt - 1) * 4), 4 * sizeof(float)); | ||||
|         cp_legend_items.push_back(I18N::translate_utf8(L("Pause print or custom G-code"))); | ||||
|         size_t color_pos = 4; | ||||
| 
 | ||||
|         for (int i = items_cnt; i >= 0; --i, color_pos+=4) | ||||
|         { | ||||
|             // update colors for color print item
 | ||||
|             ::memcpy((void*)(colors.data() + color_pos), (const void*)(colors_in.data() + i * 4), 4 * sizeof(float)); | ||||
| 
 | ||||
|             // create label for color print item
 | ||||
|             std::string id_str = std::to_string(i + 1) + ": "; | ||||
| 
 | ||||
|             if (i == 0) { | ||||
|                 cp_legend_items.push_back(id_str + (boost::format(I18N::translate_utf8(L("up to %.2f mm"))) % cp_values[0].first).str()); | ||||
|                 break; | ||||
|             } | ||||
|             if (i == items_cnt) { | ||||
|                 cp_legend_items.push_back(id_str + (boost::format(I18N::translate_utf8(L("above %.2f mm"))) % cp_values[i - 1].second).str()); | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             cp_legend_items.push_back(id_str + (boost::format(I18N::translate_utf8(L("%.2f - %.2f mm"))) % cp_values[i - 1].second % cp_values[i].first).str()); | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         // colors = colors_in;
 | ||||
|         const int color_cnt = (int)colors_in.size() / 4; | ||||
|         colors.resize(colors_in.size(), 0.0); | ||||
| 
 | ||||
|         ::memcpy((void*)(colors.data()), (const void*)(colors_in.data()), 4 * extruders_cnt * sizeof(float)); | ||||
|         size_t color_pos = 4 * extruders_cnt; | ||||
|         size_t color_in_pos = 4 * (color_cnt - 1); | ||||
|          | ||||
|         for (unsigned int i = 0; i < extruders_cnt; ++i) | ||||
|             cp_legend_items.push_back((boost::format(I18N::translate_utf8(L("Extruder %d"))) % (i + 1)).str()); | ||||
| 
 | ||||
|         ::memcpy((void*)(colors.data() + color_pos), (const void*)(colors_in.data() + color_in_pos), 4 * sizeof(float)); | ||||
|         color_pos += 4; | ||||
|         color_in_pos -= 4; | ||||
|         cp_legend_items.push_back(I18N::translate_utf8(L("Pause print or custom G-code"))); | ||||
| 
 | ||||
|         int cnt = custom_gcode_per_height.size(); | ||||
|         for (int i = cnt-1; i >= 0; --i) | ||||
|             if (custom_gcode_per_height[i].gcode == ColorChangeCode) { | ||||
|                 ::memcpy((void*)(colors.data() + color_pos), (const void*)(colors_in.data() + color_in_pos), 4 * sizeof(float)); | ||||
|                 color_pos += 4; | ||||
|                 color_in_pos -= 4; | ||||
|                 cp_legend_items.push_back((boost::format(I18N::translate_utf8(L("Color change for Extruder %d at %.2f mm"))) % custom_gcode_per_height[i].extruder % custom_gcode_per_height[i].height).str()); | ||||
|             } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas, bool compress) | ||||
| bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors_in, const GLCanvas3D& canvas, bool compress) | ||||
| { | ||||
|     reset(); | ||||
| 
 | ||||
|     // collects items to render
 | ||||
|     auto title = _(preview_data.get_legend_title()); | ||||
| 
 | ||||
|     std::vector<std::pair<double, double>> cp_legend_values; | ||||
|     fill_color_print_legend_values(preview_data, canvas, cp_legend_values); | ||||
|     std::vector<std::string> cp_legend_items; | ||||
|     std::vector<float> cp_colors; | ||||
| 
 | ||||
|     const GCodePreviewData::LegendItemsList& items = preview_data.get_legend_items(tool_colors, cp_legend_values); | ||||
|     if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint) | ||||
|     { | ||||
|         cp_legend_items.reserve(cp_colors.size()); | ||||
|         fill_color_print_legend_items(canvas, tool_colors_in, cp_colors, cp_legend_items); | ||||
|     } | ||||
| 
 | ||||
|     const std::vector<float>& tool_colors = preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint ? cp_colors : tool_colors_in; | ||||
|     const GCodePreviewData::LegendItemsList& items = preview_data.get_legend_items(tool_colors, cp_legend_items); | ||||
| 
 | ||||
|     unsigned int items_count = (unsigned int)items.size(); | ||||
|     if (items_count == 0) | ||||
|  | @ -1559,6 +1667,7 @@ bool GLCanvas3D::is_layers_editing_allowed() const | |||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| void GLCanvas3D::reset_layer_height_profile() | ||||
| { | ||||
|     wxGetApp().plater()->take_snapshot(_(L("Layer height profile-Reset"))); | ||||
|     m_layers_editing.reset_layer_height_profile(*this); | ||||
|     m_layers_editing.state = LayersEditing::Completed; | ||||
|     m_dirty = true; | ||||
|  | @ -1566,6 +1675,7 @@ void GLCanvas3D::reset_layer_height_profile() | |||
| 
 | ||||
| void GLCanvas3D::adaptive_layer_height_profile(float cusp) | ||||
| { | ||||
|     wxGetApp().plater()->take_snapshot(_(L("Layer height profile-Adaptive"))); | ||||
|     m_layers_editing.adaptive_layer_height_profile(*this, cusp); | ||||
|     m_layers_editing.state = LayersEditing::Completed; | ||||
|     m_dirty = true; | ||||
|  | @ -1573,6 +1683,7 @@ void GLCanvas3D::adaptive_layer_height_profile(float cusp) | |||
| 
 | ||||
| void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params) | ||||
| { | ||||
|     wxGetApp().plater()->take_snapshot(_(L("Layer height profile-Smooth all"))); | ||||
|     m_layers_editing.smooth_layer_height_profile(*this, smoothing_params); | ||||
|     m_layers_editing.state = LayersEditing::Completed; | ||||
|     m_dirty = true; | ||||
|  | @ -2408,7 +2519,7 @@ void GLCanvas3D::load_sla_preview() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<double>& color_print_values) | ||||
| void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<Model::CustomGCode>& color_print_values) | ||||
| { | ||||
|     const Print *print = this->fff_print(); | ||||
|     if (print == nullptr) | ||||
|  | @ -3136,6 +3247,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
| 
 | ||||
|         if ((m_layers_editing.state != LayersEditing::Unknown) && (layer_editing_object_idx != -1)) | ||||
|         { | ||||
|             set_tooltip(""); | ||||
|             if (m_layers_editing.state == LayersEditing::Editing) | ||||
|                 _perform_layer_editing_action(&evt); | ||||
|         } | ||||
|  | @ -3245,6 +3357,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|         m_mouse.position = pos.cast<double>(); | ||||
|         std::string tooltip = ""; | ||||
| 
 | ||||
|         if (tooltip.empty()) | ||||
|             tooltip = m_layers_editing.get_tooltip(*this); | ||||
| 
 | ||||
|         if (tooltip.empty()) | ||||
|             tooltip = m_gizmos.get_tooltip(); | ||||
| 
 | ||||
|  | @ -3810,7 +3925,7 @@ static void render_volumes_in_thumbnail(Shader& shader, const GLVolumePtrs& volu | |||
|     camera.apply_projection(box); | ||||
| 
 | ||||
|     if (transparent_background) | ||||
|         glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 0.0f)); | ||||
|         glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); | ||||
| 
 | ||||
|     glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); | ||||
|     glsafe(::glEnable(GL_DEPTH_TEST)); | ||||
|  | @ -5210,7 +5325,7 @@ void GLCanvas3D::_load_print_toolpaths() | |||
|     volume->indexed_vertex_array.finalize_geometry(m_initialized); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors, const std::vector<double>& color_print_values) | ||||
| void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors, const std::vector<Model::CustomGCode>& color_print_values) | ||||
| { | ||||
|     std::vector<float> tool_colors = _parse_colors(str_tool_colors); | ||||
| 
 | ||||
|  | @ -5222,11 +5337,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
|         bool                         has_infill; | ||||
|         bool                         has_support; | ||||
|         const std::vector<float>*    tool_colors; | ||||
|         const std::vector<double>*   color_print_values; | ||||
|         bool                         is_single_material_print; | ||||
|         int                          extruders_cnt; | ||||
|         const std::vector<Model::CustomGCode>*   color_print_values; | ||||
| 
 | ||||
|         static const float*          color_perimeters() { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow
 | ||||
|         static const float*          color_infill() { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish
 | ||||
|         static const float*          color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish
 | ||||
|         static const float*          color_pause_or_custom_code() { static float color[4] = { 0.5f, 0.5f, 0.5f, 1.f }; return color; } // gray
 | ||||
| 
 | ||||
|         // For cloring by a tool, return a parsed color.
 | ||||
|         bool                         color_by_tool() const { return tool_colors != nullptr; } | ||||
|  | @ -5236,9 +5354,106 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
|         // For coloring by a color_print(M600), return a parsed color.
 | ||||
|         bool                         color_by_color_print() const { return color_print_values!=nullptr; } | ||||
|         const size_t                 color_print_color_idx_by_layer_idx(const size_t layer_idx) const { | ||||
|             auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), layers[layer_idx]->print_z + EPSILON); | ||||
|             const Model::CustomGCode value(layers[layer_idx]->print_z + EPSILON, "", 0, ""); | ||||
|             auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); | ||||
|             return (it - color_print_values->begin()) % number_tools(); | ||||
|         } | ||||
| 
 | ||||
|         const size_t                 color_print_color_idx_by_layer_idx_and_extruder(const size_t layer_idx, const int extruder) const | ||||
|         { | ||||
|             const coordf_t print_z = layers[layer_idx]->print_z; | ||||
| 
 | ||||
|             auto it = std::find_if(color_print_values->begin(), color_print_values->end(), | ||||
|                 [print_z](const Model::CustomGCode& code) | ||||
|                 { return fabs(code.height - print_z) < EPSILON; }); | ||||
|             if (it != color_print_values->end()) | ||||
|             { | ||||
|                 const std::string& code = it->gcode; | ||||
|                 // pause print or custom Gcode
 | ||||
|                 if (code == PausePrintCode ||  | ||||
|                     (code != ColorChangeCode && code != ExtruderChangeCode)) | ||||
|                     return number_tools()-1; // last color item is a gray color for pause print or custom G-code 
 | ||||
| 
 | ||||
|                 // change tool (extruder) 
 | ||||
|                 if (code == ExtruderChangeCode) | ||||
|                     return get_color_idx_for_tool_change(it, extruder); | ||||
|                 // change color for current extruder
 | ||||
|                 if (code == ColorChangeCode) { | ||||
|                     int color_idx = get_color_idx_for_color_change(it, extruder); | ||||
|                     if (color_idx >= 0) | ||||
|                         return color_idx; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             const Model::CustomGCode value(print_z + EPSILON, "", 0, ""); | ||||
|             it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); | ||||
|             while (it != color_print_values->begin()) | ||||
|             { | ||||
|                 --it; | ||||
|                 // change color for current extruder
 | ||||
|                 if (it->gcode == ColorChangeCode) { | ||||
|                     int color_idx = get_color_idx_for_color_change(it, extruder); | ||||
|                     if (color_idx >= 0) | ||||
|                         return color_idx; | ||||
|                 } | ||||
|                 // change tool (extruder) 
 | ||||
|                 if (it->gcode == ExtruderChangeCode) | ||||
|                     return get_color_idx_for_tool_change(it, extruder); | ||||
|             } | ||||
| 
 | ||||
|             return std::min<int>(extruders_cnt - 1, std::max<int>(extruder - 1, 0));; | ||||
|         } | ||||
| 
 | ||||
|     private: | ||||
|         int get_m600_color_idx(std::vector<Model::CustomGCode>::const_iterator it) const  | ||||
|         { | ||||
|             int shift = 0; | ||||
|             while (it != color_print_values->begin()) { | ||||
|                 --it; | ||||
|                 if (it->gcode == ColorChangeCode) | ||||
|                     shift++; | ||||
|             } | ||||
|             return extruders_cnt + shift; | ||||
|         } | ||||
| 
 | ||||
|         int get_color_idx_for_tool_change(std::vector<Model::CustomGCode>::const_iterator it, const int extruder) const  | ||||
|         { | ||||
|             const int current_extruder = it->extruder == 0 ? extruder : it->extruder; | ||||
|             if (number_tools() == extruders_cnt + 1) // there is no one "M600"
 | ||||
|                 return std::min<int>(extruders_cnt - 1, std::max<int>(current_extruder - 1, 0)); | ||||
| 
 | ||||
|             auto it_n = it; | ||||
|             while (it_n != color_print_values->begin()) { | ||||
|                 --it_n; | ||||
|                 if (it_n->gcode == ColorChangeCode && it_n->extruder == current_extruder) | ||||
|                     return get_m600_color_idx(it_n); | ||||
|             } | ||||
| 
 | ||||
|             return std::min<int>(extruders_cnt - 1, std::max<int>(current_extruder - 1, 0)); | ||||
|         } | ||||
| 
 | ||||
|         int get_color_idx_for_color_change(std::vector<Model::CustomGCode>::const_iterator it, const int extruder) const  | ||||
|         { | ||||
|             if (extruders_cnt == 1) | ||||
|                 return get_m600_color_idx(it); | ||||
| 
 | ||||
|             auto it_n = it; | ||||
|             bool is_tool_change = false; | ||||
|             while (it_n != color_print_values->begin()) { | ||||
|                 --it_n; | ||||
|                 if (it_n->gcode == ExtruderChangeCode) { | ||||
|                     is_tool_change = true; | ||||
|                     if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder)) | ||||
|                         return get_m600_color_idx(it); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if (!is_tool_change && it->extruder == extruder) | ||||
|                 return get_m600_color_idx(it); | ||||
| 
 | ||||
|             return -1; | ||||
|         } | ||||
| 
 | ||||
|     } ctxt; | ||||
| 
 | ||||
|     ctxt.has_perimeters = print_object.is_step_done(posPerimeters); | ||||
|  | @ -5246,6 +5461,8 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
|     ctxt.has_support = print_object.is_step_done(posSupportMaterial); | ||||
|     ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; | ||||
|     ctxt.color_print_values = color_print_values.empty() ? nullptr : &color_print_values; | ||||
|     ctxt.is_single_material_print = this->fff_print()->extruders().size()==1; | ||||
|     ctxt.extruders_cnt = wxGetApp().extruders_edited_cnt(); | ||||
| 
 | ||||
|     ctxt.shifted_copies = &print_object.copies(); | ||||
| 
 | ||||
|  | @ -5269,6 +5486,8 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
|     // Maximum size of an allocation block: 32MB / sizeof(float)
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); | ||||
| 
 | ||||
|     const bool is_selected_separate_extruder = m_selected_extruder > 0 && ctxt.color_by_color_print(); | ||||
| 
 | ||||
|     //FIXME Improve the heuristics for a grain size.
 | ||||
|     size_t          grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); | ||||
|     tbb::spin_mutex new_volume_mutex; | ||||
|  | @ -5286,32 +5505,18 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
|     const size_t    volumes_cnt_initial = m_volumes.volumes.size(); | ||||
|     tbb::parallel_for( | ||||
|         tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size), | ||||
|         [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) { | ||||
|         [&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range<size_t>& range) { | ||||
|         GLVolumePtrs 		vols; | ||||
|         std::vector<size_t>	color_print_layer_to_glvolume; | ||||
|         auto                volume = [&ctxt, &vols, &color_print_layer_to_glvolume, &range](size_t layer_idx, int extruder, int feature) -> GLVolume& { | ||||
|             return *vols[ctxt.color_by_color_print() ? | ||||
|             	color_print_layer_to_glvolume[layer_idx - range.begin()] : | ||||
|         auto                volume = [&ctxt, &vols, &color_print_layer_to_glvolume, &range](size_t layer_idx, int extruder, int feature) -> GLVolume& {             | ||||
|             return *vols[ctxt.color_by_color_print()? | ||||
|                 ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) : | ||||
| 				ctxt.color_by_tool() ?  | ||||
| 					std::min<int>(ctxt.number_tools() - 1, std::max<int>(extruder - 1, 0)) :  | ||||
| 					feature | ||||
| 				]; | ||||
|         }; | ||||
|         if (ctxt.color_by_color_print()) { | ||||
|         	// Create a map from the layer index to a GLVolume, which is initialized with the correct layer span color.
 | ||||
|         	std::vector<int> color_print_tool_to_glvolume(ctxt.number_tools(), -1); | ||||
|         	color_print_layer_to_glvolume.reserve(range.end() - range.begin()); | ||||
|         	vols.reserve(ctxt.number_tools()); | ||||
| 	        for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { | ||||
| 	        	int idx_tool = (int)ctxt.color_print_color_idx_by_layer_idx(idx_layer); | ||||
| 	        	if (color_print_tool_to_glvolume[idx_tool] == -1) { | ||||
| 	        		color_print_tool_to_glvolume[idx_tool] = (int)vols.size(); | ||||
| 	        		vols.emplace_back(new_volume(ctxt.color_tool(idx_tool))); | ||||
| 	        	} | ||||
| 	        	color_print_layer_to_glvolume.emplace_back(color_print_tool_to_glvolume[idx_tool]); | ||||
| 	        } | ||||
|         } | ||||
|         else if (ctxt.color_by_tool()) { | ||||
|         if (ctxt.color_by_color_print() || ctxt.color_by_tool()) { | ||||
|             for (size_t i = 0; i < ctxt.number_tools(); ++i) | ||||
|                 vols.emplace_back(new_volume(ctxt.color_tool(i))); | ||||
|         } | ||||
|  | @ -5322,6 +5527,26 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
|         	vol->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6); | ||||
|         for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { | ||||
|             const Layer *layer = ctxt.layers[idx_layer]; | ||||
| 
 | ||||
|             if (is_selected_separate_extruder) | ||||
|             { | ||||
|                 bool at_least_one_has_correct_extruder = false; | ||||
|                 for (const LayerRegion* layerm : layer->regions()) | ||||
|                 { | ||||
|                     if (layerm->slices.surfaces.empty()) | ||||
|                         continue; | ||||
|                     const PrintRegionConfig& cfg = layerm->region()->config(); | ||||
|                     if (cfg.perimeter_extruder.value    == m_selected_extruder || | ||||
|                         cfg.infill_extruder.value       == m_selected_extruder || | ||||
|                         cfg.solid_infill_extruder.value == m_selected_extruder ) { | ||||
|                         at_least_one_has_correct_extruder = true; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if (!at_least_one_has_correct_extruder) | ||||
|                     continue; | ||||
|             } | ||||
| 
 | ||||
|             for (GLVolume *vol : vols) | ||||
|                 if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { | ||||
|                     vol->print_zs.push_back(layer->print_z); | ||||
|  | @ -5330,6 +5555,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
|                 } | ||||
|             for (const Point © : *ctxt.shifted_copies) { | ||||
|                 for (const LayerRegion *layerm : layer->regions()) { | ||||
|                     if (is_selected_separate_extruder) | ||||
|                     { | ||||
|                         const PrintRegionConfig& cfg = layerm->region()->config(); | ||||
|                         if (cfg.perimeter_extruder.value    != m_selected_extruder || | ||||
|                             cfg.infill_extruder.value       != m_selected_extruder || | ||||
|                             cfg.solid_infill_extruder.value != m_selected_extruder) | ||||
|                             continue; | ||||
|                     } | ||||
|                     if (ctxt.has_perimeters) | ||||
|                         _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, | ||||
|                         	volume(idx_layer, layerm->region()->config().perimeter_extruder.value, 0)); | ||||
|  | @ -5613,11 +5846,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat | |||
|             case GCodePreviewData::Extrusion::ColorPrint: | ||||
|             { | ||||
|                 int color_cnt = (int)tool_colors.size() / 4; | ||||
|                 int val = value > color_cnt ? color_cnt - 1 : value; | ||||
| 
 | ||||
|                 int val = int(value); | ||||
|                 while (val >= color_cnt) | ||||
|                     val -= color_cnt; | ||||
|                      | ||||
|                 GCodePreviewData::Color color; | ||||
|                 ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + val * 4), 4 * sizeof(float)); | ||||
| 
 | ||||
|  | @ -5678,10 +5908,13 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat | |||
| 	    BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - populate volumes" << m_volumes.log_memory_info() << log_memory_info(); | ||||
| 
 | ||||
| 	    // populates volumes
 | ||||
|         const bool is_selected_separate_extruder = m_selected_extruder > 0 && preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint; | ||||
| 		for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) | ||||
| 		{ | ||||
| 			for (const GCodePreviewData::Extrusion::Path& path : layer.paths) | ||||
| 			{ | ||||
|                 if (is_selected_separate_extruder && path.extruder_id != m_selected_extruder - 1) | ||||
|                     continue; | ||||
| 				std::vector<std::pair<float, GLVolume*>> &filters = roles_filters[size_t(path.extrusion_role)]; | ||||
| 				auto key = std::make_pair<float, GLVolume*>(Helper::path_filter(preview_data.extrusion.view_type, path), nullptr); | ||||
| 				auto it_filter = std::lower_bound(filters.begin(), filters.end(), key); | ||||
|  |  | |||
|  | @ -179,7 +179,7 @@ private: | |||
|         float                       m_object_max_z; | ||||
|         // Owned by LayersEditing.
 | ||||
|         SlicingParameters          *m_slicing_parameters; | ||||
|         std::vector<coordf_t>       m_layer_height_profile; | ||||
|         std::vector<double>         m_layer_height_profile; | ||||
|         bool                        m_layer_height_profile_modified; | ||||
| 
 | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|  | @ -254,6 +254,8 @@ private: | |||
| 
 | ||||
|         float object_max_z() const { return m_object_max_z; } | ||||
| 
 | ||||
|         std::string get_tooltip(const GLCanvas3D& canvas) const; | ||||
| 
 | ||||
|     private: | ||||
|         bool is_initialized() const; | ||||
|         void generate_layer_height_texture(); | ||||
|  | @ -382,8 +384,10 @@ private: | |||
| 
 | ||||
|     public: | ||||
|         LegendTexture(); | ||||
|         void fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas, | ||||
|                                      std::vector<std::pair<double, double>>& cp_legend_values); | ||||
|         void fill_color_print_legend_items(const GLCanvas3D& canvas, | ||||
|                                            const std::vector<float>& colors_in, | ||||
|                                            std::vector<float>& colors, | ||||
|                                            std::vector<std::string>& cp_legend_items); | ||||
| 
 | ||||
|         bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas, bool compress); | ||||
| 
 | ||||
|  | @ -473,6 +477,7 @@ private: | |||
| #endif // ENABLE_RENDER_STATISTICS
 | ||||
| 
 | ||||
|     int m_imgui_undo_redo_hovered_pos{ -1 }; | ||||
|     int m_selected_extruder; | ||||
| 
 | ||||
| public: | ||||
|     GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); | ||||
|  | @ -586,7 +591,7 @@ public: | |||
| 
 | ||||
|     void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors); | ||||
|     void load_sla_preview(); | ||||
|     void load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<double>& color_print_values); | ||||
|     void load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<Model::CustomGCode>& color_print_values); | ||||
|     void bind_event_handlers(); | ||||
|     void unbind_event_handlers(); | ||||
| 
 | ||||
|  | @ -625,6 +630,7 @@ public: | |||
| 
 | ||||
|     int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } | ||||
|     int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } | ||||
|     void set_selected_extruder(int extruder) { m_selected_extruder = extruder;} | ||||
|      | ||||
|     class WipeTowerInfo { | ||||
|     protected: | ||||
|  | @ -748,7 +754,7 @@ private: | |||
|     // Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
 | ||||
|     // one for perimeters, one for infill and one for supports.
 | ||||
|     void _load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors, | ||||
|                                       const std::vector<double>& color_print_values); | ||||
|                                       const std::vector<Model::CustomGCode>& color_print_values); | ||||
|     // Create 3D thick extrusion lines for wipe tower extrusions
 | ||||
|     void _load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors); | ||||
| 
 | ||||
|  |  | |||
|  | @ -895,10 +895,6 @@ void ObjectList::extruder_editing() | |||
|     if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject))) | ||||
|         return; | ||||
| 
 | ||||
|     std::vector<wxBitmap*> icons = get_extruder_color_icons(); | ||||
|     if (icons.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     const int column_width = GetColumn(colExtruder)->GetWidth() + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 5; | ||||
| 
 | ||||
|     wxPoint pos = get_mouse_position_in_control(); | ||||
|  | @ -906,29 +902,10 @@ void ObjectList::extruder_editing() | |||
|     pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5; | ||||
|     pos.y -= GetTextExtent("m").y; | ||||
| 
 | ||||
|     if (!m_extruder_editor) | ||||
|         m_extruder_editor = new wxBitmapComboBox(this, wxID_ANY, wxEmptyString, pos, size, | ||||
|                                                  0, nullptr, wxCB_READONLY); | ||||
|     else | ||||
|     { | ||||
|         m_extruder_editor->SetPosition(pos); | ||||
|         m_extruder_editor->SetMinSize(size); | ||||
|         m_extruder_editor->SetSize(size); | ||||
|         m_extruder_editor->Clear(); | ||||
|         m_extruder_editor->Show(); | ||||
|     } | ||||
|     apply_extruder_selector(&m_extruder_editor, this, L("default"), pos, size); | ||||
| 
 | ||||
|     int i = 0; | ||||
|     for (wxBitmap* bmp : icons) { | ||||
|         if (i == 0) { | ||||
|             m_extruder_editor->Append(_(L("default")), *bmp); | ||||
|             ++i; | ||||
|         } | ||||
| 
 | ||||
|         m_extruder_editor->Append(wxString::Format("%d", i), *bmp); | ||||
|         ++i; | ||||
|     } | ||||
|     m_extruder_editor->SetSelection(m_objects_model->GetExtruderNumber(item)); | ||||
|     m_extruder_editor->Show(); | ||||
| 
 | ||||
|     auto set_extruder = [this]() | ||||
|     { | ||||
|  | @ -940,6 +917,7 @@ void ObjectList::extruder_editing() | |||
|             m_objects_model->SetExtruder(m_extruder_editor->GetString(selection), item); | ||||
| 
 | ||||
|         m_extruder_editor->Hide(); | ||||
|         update_extruder_in_config(item); | ||||
|     }; | ||||
| 
 | ||||
|     // to avoid event propagation to other sidebar items
 | ||||
|  | @ -948,13 +926,6 @@ void ObjectList::extruder_editing() | |||
|         set_extruder(); | ||||
|         evt.StopPropagation(); | ||||
|     }); | ||||
|     /*
 | ||||
|     m_extruder_editor->Bind(wxEVT_KILL_FOCUS, [set_extruder](wxFocusEvent& evt) | ||||
|     { | ||||
|         set_extruder(); | ||||
|         evt.Skip(); | ||||
|     });*/ | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void ObjectList::copy() | ||||
|  |  | |||
|  | @ -243,11 +243,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
| 
 | ||||
|     // Add Axes labels with icons
 | ||||
|     static const char axes[] = { 'X', 'Y', 'Z' }; | ||||
| //    std::vector<wxString> axes_color = {"#990000", "#009900","#000099"};
 | ||||
|     for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) { | ||||
|         const char label = axes[axis_idx]; | ||||
| 
 | ||||
|         wxStaticText* axis_name = new wxStaticText(m_parent, wxID_ANY, wxString(label)); | ||||
|         set_font_and_background_style(axis_name, wxGetApp().bold_font()); | ||||
| //        axis_name->SetForegroundColour(wxColour(axes_color[axis_idx]));
 | ||||
| 
 | ||||
|         sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|         // Under OSX we use font, smaller than default font, so
 | ||||
|  |  | |||
|  | @ -492,18 +492,23 @@ void Preview::show_hide_ui_elements(const std::string& what) | |||
|     m_choice_view_type->Show(visible); | ||||
| } | ||||
| 
 | ||||
| void Preview::reset_sliders() | ||||
| void Preview::reset_sliders(bool reset_all) | ||||
| { | ||||
|     m_enabled = false; | ||||
| //    reset_double_slider();
 | ||||
|     m_double_slider_sizer->Hide((size_t)0); | ||||
|     if (reset_all) | ||||
|         m_double_slider_sizer->Hide((size_t)0); | ||||
|     else | ||||
|         m_double_slider_sizer->GetItem(size_t(0))->GetSizer()->Hide(1); | ||||
| } | ||||
| 
 | ||||
| void Preview::update_sliders(const std::vector<double>& layers_z, bool keep_z_range) | ||||
| { | ||||
|     m_enabled = true; | ||||
| 
 | ||||
|     update_double_slider(layers_z, keep_z_range); | ||||
|     m_double_slider_sizer->Show((size_t)0); | ||||
| 
 | ||||
|     Layout(); | ||||
| } | ||||
| 
 | ||||
|  | @ -560,12 +565,12 @@ void Preview::on_checkbox_legend(wxCommandEvent& evt) | |||
|     m_canvas_widget->Refresh(); | ||||
| } | ||||
| 
 | ||||
| void Preview::update_view_type() | ||||
| void Preview::update_view_type(bool slice_completed) | ||||
| { | ||||
|     const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config; | ||||
| 
 | ||||
|     const wxString& choice = !config.option<ConfigOptionFloats>("colorprint_heights")->values.empty() &&  | ||||
|                              wxGetApp().extruders_edited_cnt()==1 ?  | ||||
|     const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_height.empty() /*&&
 | ||||
|                              (wxGetApp().extruders_edited_cnt()==1 || !slice_completed) */?  | ||||
|                                 _(L("Color Print")) : | ||||
|                                 config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1 ? | ||||
|                                     _(L("Tool")) :  | ||||
|  | @ -583,6 +588,8 @@ void Preview::update_view_type() | |||
| void Preview::create_double_slider() | ||||
| { | ||||
|     m_slider = new DoubleSlider(this, wxID_ANY, 0, 0, 0, 100); | ||||
|     m_slider->EnableTickManipulation(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF); | ||||
| 
 | ||||
|     m_double_slider_sizer->Add(m_slider, 0, wxEXPAND, 0); | ||||
| 
 | ||||
|     // sizer, m_canvas_widget
 | ||||
|  | @ -592,10 +599,11 @@ void Preview::create_double_slider() | |||
| 
 | ||||
| 
 | ||||
|     Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { | ||||
|         wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights")->values = m_slider->GetTicksValues(); | ||||
|         Model& model = wxGetApp().plater()->model(); | ||||
|         model.custom_gcode_per_height = m_slider->GetTicksValues(); | ||||
|         m_schedule_background_process(); | ||||
| 
 | ||||
|         update_view_type(); | ||||
|         update_view_type(false); | ||||
| 
 | ||||
|         reload_print(); | ||||
|     }); | ||||
|  | @ -628,6 +636,24 @@ static int find_close_layer_idx(const std::vector<double>& zs, double &z, double | |||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| void Preview::check_slider_values(std::vector<Model::CustomGCode>& ticks_from_model, | ||||
|                                   const std::vector<double>& layers_z) | ||||
| { | ||||
|     // All ticks that would end up outside the slider range should be erased.
 | ||||
|     // TODO: this should be placed into more appropriate part of code,
 | ||||
|     // this function is e.g. not called when the last object is deleted
 | ||||
|     unsigned int old_size = ticks_from_model.size(); | ||||
|     ticks_from_model.erase(std::remove_if(ticks_from_model.begin(), ticks_from_model.end(), | ||||
|                      [layers_z](Model::CustomGCode val) | ||||
|         { | ||||
|             auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val.height - DoubleSlider::epsilon()); | ||||
|             return it == layers_z.end(); | ||||
|         }), | ||||
|         ticks_from_model.end()); | ||||
|     if (ticks_from_model.size() != old_size) | ||||
|         m_schedule_background_process(); | ||||
| } | ||||
| 
 | ||||
| void Preview::update_double_slider(const std::vector<double>& layers_z, bool keep_z_range) | ||||
| { | ||||
|     // Save the initial slider span.
 | ||||
|  | @ -643,8 +669,8 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee | |||
|     bool   snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min(); | ||||
| 	bool   snap_to_max  = force_sliders_full_range || m_slider->is_higher_at_max(); | ||||
| 
 | ||||
|     std::vector<double> &ticks_from_config = (wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights"))->values; | ||||
|     check_slider_values(ticks_from_config, layers_z); | ||||
|     std::vector<Model::CustomGCode> &ticks_from_model = wxGetApp().plater()->model().custom_gcode_per_height; | ||||
|     check_slider_values(ticks_from_model, layers_z); | ||||
| 
 | ||||
|     m_slider->SetSliderValues(layers_z); | ||||
|     assert(m_slider->GetMinValue() == 0); | ||||
|  | @ -666,33 +692,12 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee | |||
|     } | ||||
|     m_slider->SetSelectionSpan(idx_low, idx_high); | ||||
| 
 | ||||
|     m_slider->SetTicksValues(ticks_from_config); | ||||
|     m_slider->SetTicksValues(ticks_from_model); | ||||
| 
 | ||||
|     bool color_print_enable = (wxGetApp().plater()->printer_technology() == ptFFF); | ||||
|     if (color_print_enable) { | ||||
|         const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config; | ||||
|         if (cfg.opt<ConfigOptionFloats>("nozzle_diameter")->values.size() > 1)  | ||||
|             color_print_enable = false; | ||||
|     } | ||||
|     m_slider->EnableTickManipulation(color_print_enable); | ||||
| } | ||||
| 
 | ||||
| void Preview::check_slider_values(std::vector<double>& ticks_from_config, | ||||
|                                  const std::vector<double> &layers_z) | ||||
| { | ||||
|     // All ticks that would end up outside the slider range should be erased.
 | ||||
|     // TODO: this should be placed into more appropriate part of code,
 | ||||
|     // this function is e.g. not called when the last object is deleted
 | ||||
|     unsigned int old_size = ticks_from_config.size(); | ||||
|     ticks_from_config.erase(std::remove_if(ticks_from_config.begin(), ticks_from_config.end(), | ||||
|                                            [layers_z](double val) | ||||
|     { | ||||
|         auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val - DoubleSlider::epsilon()); | ||||
|         return it == layers_z.end(); | ||||
|     }), | ||||
|                             ticks_from_config.end()); | ||||
|     if (ticks_from_config.size() != old_size) | ||||
|         m_schedule_background_process(); | ||||
|     m_slider->EnableTickManipulation(color_print_enable); | ||||
|     m_slider->SetManipulationState(wxGetApp().extruders_edited_cnt()); | ||||
| } | ||||
| 
 | ||||
| void Preview::reset_double_slider() | ||||
|  | @ -753,7 +758,7 @@ void Preview::load_print_as_fff(bool keep_z_range) | |||
| 
 | ||||
|     if (! has_layers) | ||||
|     { | ||||
|         reset_sliders(); | ||||
|         reset_sliders(true); | ||||
|         m_canvas->reset_legend_texture(); | ||||
|         m_canvas_widget->Refresh(); | ||||
|         return; | ||||
|  | @ -776,51 +781,30 @@ void Preview::load_print_as_fff(bool keep_z_range) | |||
|     bool gcode_preview_data_valid = print->is_step_done(psGCodeExport) && ! m_gcode_preview_data->empty(); | ||||
|     // Collect colors per extruder.
 | ||||
|     std::vector<std::string> colors; | ||||
|     std::vector<double> color_print_values = {}; | ||||
|     std::vector<Model::CustomGCode> color_print_values = {}; | ||||
|     // set color print values, if it si selected "ColorPrint" view type
 | ||||
|     if (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint) | ||||
|     { | ||||
|         colors = GCodePreviewData::ColorPrintColors(); | ||||
|         if (! gcode_preview_data_valid) { | ||||
|             //FIXME accessing full_config() is pretty expensive.
 | ||||
|             // Only initialize color_print_values for the initial preview, not for the full preview where the color_print_values is extracted from the G-code.
 | ||||
|             const auto& config = wxGetApp().preset_bundle->project_config; | ||||
|             color_print_values = config.option<ConfigOptionFloats>("colorprint_heights")->values; | ||||
|         } | ||||
|         colors = wxGetApp().plater()->get_colors_for_color_print(); | ||||
|         colors.push_back("#808080"); // gray color for pause print or custom G-code 
 | ||||
| 
 | ||||
|         if (!gcode_preview_data_valid) | ||||
|             color_print_values = wxGetApp().plater()->model().custom_gcode_per_height; | ||||
|     } | ||||
|     else if (gcode_preview_data_valid || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool) ) | ||||
|     { | ||||
|         const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(m_config->option("extruder_colour")); | ||||
|         const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(m_config->option("filament_colour")); | ||||
|         unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size()); | ||||
| 
 | ||||
|         unsigned char rgb[3]; | ||||
|         for (unsigned int i = 0; i < colors_count; ++i) | ||||
|         { | ||||
|             std::string color = m_config->opt_string("extruder_colour", i); | ||||
|             if (!PresetBundle::parse_color(color, rgb)) | ||||
|             { | ||||
|                 color = m_config->opt_string("filament_colour", i); | ||||
|                 if (!PresetBundle::parse_color(color, rgb)) | ||||
|                     color = "#FFFFFF"; | ||||
|             } | ||||
| 
 | ||||
|             colors.emplace_back(color); | ||||
|         } | ||||
|         colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(); | ||||
|         color_print_values.clear(); | ||||
|     } | ||||
| 
 | ||||
|     if (IsShown()) | ||||
|     { | ||||
|         m_canvas->set_selected_extruder(0); | ||||
|         if (gcode_preview_data_valid) { | ||||
|             // Load the real G-code preview.
 | ||||
|             m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); | ||||
|             m_loaded = true; | ||||
|         } else { | ||||
|             // disable color change information for multi-material presets
 | ||||
|             if (wxGetApp().extruders_edited_cnt() > 1) | ||||
|                 color_print_values.clear(); | ||||
| 
 | ||||
|             // Load the initial preview based on slices, not the final G-code.
 | ||||
|             m_canvas->load_preview(colors, color_print_values); | ||||
|         } | ||||
|  | @ -829,7 +813,7 @@ void Preview::load_print_as_fff(bool keep_z_range) | |||
|         std::vector<double> zs = m_canvas->get_current_print_zs(true); | ||||
|         if (zs.empty()) { | ||||
|             // all layers filtered out
 | ||||
|             reset_sliders(); | ||||
|             reset_sliders(true); | ||||
|             m_canvas_widget->Refresh(); | ||||
|         } else | ||||
|             update_sliders(zs, keep_z_range); | ||||
|  | @ -860,7 +844,7 @@ void Preview::load_print_as_sla() | |||
|     n_layers = (unsigned int)zs.size(); | ||||
|     if (n_layers == 0) | ||||
|     { | ||||
|         reset_sliders(); | ||||
|         reset_sliders(true); | ||||
|         m_canvas_widget->Refresh(); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #include "libslic3r/Point.hpp" | ||||
| 
 | ||||
| #include <string> | ||||
| #include "libslic3r/Model.hpp" | ||||
| 
 | ||||
| class wxNotebook; | ||||
| class wxGLCanvas; | ||||
|  | @ -12,6 +13,7 @@ class wxBoxSizer; | |||
| class wxStaticText; | ||||
| class wxChoice; | ||||
| class wxComboCtrl; | ||||
| class wxBitmapComboBox; | ||||
| class wxCheckBox; | ||||
| class DoubleSlider; | ||||
| 
 | ||||
|  | @ -101,7 +103,7 @@ class Preview : public wxPanel | |||
|     bool m_loaded; | ||||
|     bool m_enabled; | ||||
| 
 | ||||
|     DoubleSlider* m_slider {nullptr}; | ||||
|     DoubleSlider*       m_slider {nullptr}; | ||||
| 
 | ||||
| public: | ||||
|     Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config,  | ||||
|  | @ -128,7 +130,7 @@ public: | |||
|     void move_double_slider(wxKeyEvent& evt); | ||||
|     void edit_double_slider(wxKeyEvent& evt); | ||||
| 
 | ||||
|     void update_view_type(); | ||||
|     void update_view_type(bool slice_completed); | ||||
| 
 | ||||
|     bool is_loaded() const { return m_loaded; } | ||||
| 
 | ||||
|  | @ -140,7 +142,7 @@ private: | |||
| 
 | ||||
|     void show_hide_ui_elements(const std::string& what); | ||||
| 
 | ||||
|     void reset_sliders(); | ||||
|     void reset_sliders(bool reset_all); | ||||
|     void update_sliders(const std::vector<double>& layers_z, bool keep_z_range = false); | ||||
| 
 | ||||
|     void on_size(wxSizeEvent& evt); | ||||
|  | @ -154,9 +156,9 @@ private: | |||
| 
 | ||||
|     // Create/Update/Reset double slider on 3dPreview
 | ||||
|     void create_double_slider(); | ||||
|     void check_slider_values(std::vector<Model::CustomGCode> &ticks_from_model, | ||||
|                              const std::vector<double> &layers_z); | ||||
|     void update_double_slider(const std::vector<double>& layers_z, bool keep_z_range = false); | ||||
|     void check_slider_values(std::vector<double> &ticks_from_config, | ||||
|                             const std::vector<double> &layers_z); | ||||
|     void reset_double_slider(); | ||||
|     // update DoubleSlider after keyDown in canvas
 | ||||
|     void update_double_slider_from_canvas(wxKeyEvent& event); | ||||
|  |  | |||
|  | @ -333,17 +333,16 @@ void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsign | |||
| 
 | ||||
| bool Mouse3DController::connect_device() | ||||
| { | ||||
|     static const long long DETECTION_TIME = 2; // seconds
 | ||||
|     static const long long DETECTION_TIME_MS = 2000; // seconds
 | ||||
| 
 | ||||
|     if (is_device_connected()) | ||||
|         return false; | ||||
| 
 | ||||
|     // check time since last detection took place
 | ||||
|     auto now = std::chrono::high_resolution_clock::now(); | ||||
|     if (std::chrono::duration_cast<std::chrono::seconds>(now - m_last_time).count() < DETECTION_TIME) | ||||
|     if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_last_time).count() < DETECTION_TIME_MS) | ||||
|         return false; | ||||
| 
 | ||||
|     m_last_time = now; | ||||
|     m_last_time = std::chrono::high_resolution_clock::now(); | ||||
| 
 | ||||
|     // Enumerates devices
 | ||||
|     hid_device_info* devices = hid_enumerate(0, 0); | ||||
|  |  | |||
|  | @ -131,7 +131,7 @@ class Mouse3DController | |||
|     std::string m_device_str; | ||||
|     bool m_running; | ||||
|     bool m_settings_dialog; | ||||
|     std::chrono::time_point<std::chrono::steady_clock> m_last_time; | ||||
|     std::chrono::time_point<std::chrono::high_resolution_clock> m_last_time; | ||||
| 
 | ||||
| public: | ||||
|     Mouse3DController(); | ||||
|  |  | |||
|  | @ -2325,6 +2325,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ | |||
|                         // and place the loaded config over the base.
 | ||||
|                         config += std::move(config_loaded); | ||||
|                     } | ||||
| 
 | ||||
|                     this->model.custom_gcode_per_height = model.custom_gcode_per_height; | ||||
|                 } | ||||
| 
 | ||||
|                 if (load_config) | ||||
|  | @ -2743,8 +2745,7 @@ void Plater::priv::reset() | |||
|     // The hiding of the slicing results, if shown, is not taken care by the background process, so we do it here
 | ||||
|     this->sidebar->show_sliced_info_sizer(false); | ||||
| 
 | ||||
|     auto& config = wxGetApp().preset_bundle->project_config; | ||||
|     config.option<ConfigOptionFloats>("colorprint_heights")->values.clear(); | ||||
|     model.custom_gcode_per_height.clear(); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::mirror(Axis axis) | ||||
|  | @ -2975,6 +2976,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool | |||
|     this->update_print_volume_state(); | ||||
|     // Apply new config to the possibly running background task.
 | ||||
|     bool               was_running = this->background_process.running(); | ||||
|     this->background_process.set_force_update_print_regions(force_validation); | ||||
|     Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config()); | ||||
| 
 | ||||
|     // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
 | ||||
|  | @ -3180,9 +3182,13 @@ void Plater::priv::reload_from_disk() | |||
|     for (unsigned int idx : selected_volumes_idxs) | ||||
|     { | ||||
|         const GLVolume* v = selection.get_volume(idx); | ||||
|         int o_idx = v->object_idx(); | ||||
|         int v_idx = v->volume_idx(); | ||||
|         selected_volumes.push_back({ o_idx, v_idx }); | ||||
|         if (v_idx >= 0) | ||||
|         { | ||||
|             int o_idx = v->object_idx(); | ||||
|             if ((0 <= o_idx) && (o_idx < (int)model.objects.size())) | ||||
|                 selected_volumes.push_back({ o_idx, v_idx }); | ||||
|         } | ||||
|     } | ||||
|     std::sort(selected_volumes.begin(), selected_volumes.end()); | ||||
|     selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end()); | ||||
|  | @ -4176,6 +4182,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator | |||
|     // Disable layer editing before the Undo / Redo jump.
 | ||||
|     if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) | ||||
|         view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); | ||||
| 
 | ||||
|     // Make a copy of the snapshot, undo/redo could invalidate the iterator
 | ||||
|     const UndoRedo::Snapshot snapshot_copy = *it_snapshot; | ||||
|     // Do the jump in time.
 | ||||
|  | @ -4782,7 +4789,7 @@ void Plater::reslice() | |||
|         p->show_action_buttons(true); | ||||
| 
 | ||||
|     // update type of preview
 | ||||
|     p->preview->update_view_type(); | ||||
|     p->preview->update_view_type(true); | ||||
| } | ||||
| 
 | ||||
| void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages) | ||||
|  | @ -5061,6 +5068,9 @@ std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const | |||
|         return extruder_colors; | ||||
| 
 | ||||
|     extruder_colors = (config->option<ConfigOptionStrings>("extruder_colour"))->values; | ||||
|     if (!wxGetApp().plater()) | ||||
|         return extruder_colors; | ||||
| 
 | ||||
|     const std::vector<std::string>& filament_colours = (p->config->option<ConfigOptionStrings>("filament_colour"))->values; | ||||
|     for (size_t i = 0; i < extruder_colors.size(); ++i) | ||||
|         if (extruder_colors[i] == "" && i < filament_colours.size()) | ||||
|  | @ -5069,6 +5079,17 @@ std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const | |||
|     return extruder_colors; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> Plater::get_colors_for_color_print() const | ||||
| { | ||||
|     std::vector<std::string> colors = get_extruder_colors_from_plater_config(); | ||||
| 
 | ||||
|     for (const Model::CustomGCode& code : p->model.custom_gcode_per_height) | ||||
|         if (code.gcode == ColorChangeCode) | ||||
|             colors.push_back(code.color); | ||||
| 
 | ||||
|     return colors; | ||||
| } | ||||
| 
 | ||||
| wxString Plater::get_project_filename(const wxString& extension) const | ||||
| { | ||||
|     return p->get_project_filename(extension); | ||||
|  |  | |||
|  | @ -223,6 +223,7 @@ public: | |||
|     void on_activate(); | ||||
|     const DynamicPrintConfig* get_plater_config() const; | ||||
|     std::vector<std::string> get_extruder_colors_from_plater_config() const; | ||||
|     std::vector<std::string> get_colors_for_color_print() const; | ||||
| 
 | ||||
|     void update_object_menu(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ | |||
| #include <wx/statline.h> | ||||
| #include <wx/dcclient.h> | ||||
| #include <wx/numformatter.h> | ||||
| #include <wx/colordlg.h> | ||||
| 
 | ||||
| #include <boost/algorithm/string/replace.hpp> | ||||
| 
 | ||||
|  | @ -22,9 +23,11 @@ | |||
| #include "I18N.hpp" | ||||
| #include "GUI_Utils.hpp" | ||||
| #include "PresetBundle.hpp" | ||||
| #include "ExtruderSequenceDialog.hpp" | ||||
| #include "../Utils/MacDarkMode.hpp" | ||||
| 
 | ||||
| using Slic3r::GUI::from_u8; | ||||
| using Slic3r::GUI::into_u8; | ||||
| 
 | ||||
| wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); | ||||
| wxDEFINE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); | ||||
|  | @ -404,6 +407,23 @@ int em_unit(wxWindow* win) | |||
|     return Slic3r::GUI::wxGetApp().em_unit(); | ||||
| } | ||||
| 
 | ||||
| static float get_svg_scale_factor(wxWindow *win) | ||||
| { | ||||
| #ifdef __APPLE__ | ||||
|     // Note: win->GetContentScaleFactor() is not used anymore here because it tends to
 | ||||
|     // return bogus results quite often (such as 1.0 on Retina or even 0.0).
 | ||||
|     // We're using the max scaling factor across all screens because it's very likely to be good enough.
 | ||||
| 
 | ||||
|     static float max_scaling_factor = NAN; | ||||
|     if (std::isnan(max_scaling_factor)) { | ||||
|         max_scaling_factor = Slic3r::GUI::mac_max_scaling_factor(); | ||||
|     } | ||||
|     return win != nullptr ? max_scaling_factor : 1.0f; | ||||
| #else | ||||
|     return 1.0f; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| // If an icon has horizontal orientation (width > height) call this function with is_horizontal = true
 | ||||
| wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in,  | ||||
|     const int px_cnt/* = 16*/, const bool is_horizontal /* = false*/, const bool grayscale/* = false*/) | ||||
|  | @ -449,7 +469,7 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, | |||
| 
 | ||||
| 
 | ||||
| Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr; | ||||
| /*static*/ std::vector<wxBitmap*> get_extruder_color_icons() | ||||
| std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon/* = false*/) | ||||
| { | ||||
|     // Create the bitmap with color bars.
 | ||||
|     std::vector<wxBitmap*> bmps; | ||||
|  | @ -465,16 +485,18 @@ Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr; | |||
|      * and scale them in respect to em_unit value | ||||
|      */ | ||||
|     const double em = Slic3r::GUI::wxGetApp().em_unit(); | ||||
|     const int icon_width = lround(3.2 * em); | ||||
|     const int icon_width = lround((thin_icon ? 1 : 3.2) * em); | ||||
|     const int icon_height = lround(1.6 * em); | ||||
| 
 | ||||
|     for (const std::string& color : colors) | ||||
|     { | ||||
|         wxBitmap* bitmap = m_bitmap_cache->find(color); | ||||
|         std::string bitmap_key = color + "-h" + std::to_string(icon_height) + "-w" + std::to_string(icon_width); | ||||
| 
 | ||||
|         wxBitmap* bitmap = m_bitmap_cache->find(bitmap_key); | ||||
|         if (bitmap == nullptr) { | ||||
|             // Paint the color icon.
 | ||||
|             Slic3r::PresetBundle::parse_color(color, rgb); | ||||
|             bitmap = m_bitmap_cache->insert(color, m_bitmap_cache->mksolid(icon_width, icon_height, rgb)); | ||||
|             bitmap = m_bitmap_cache->insert(bitmap_key, m_bitmap_cache->mksolid(icon_width, icon_height, rgb)); | ||||
|         } | ||||
|         bmps.emplace_back(bitmap); | ||||
|     } | ||||
|  | @ -483,16 +505,62 @@ Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr; | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| static wxBitmap get_extruder_color_icon(size_t extruder_idx) | ||||
| static wxBitmap get_extruder_color_icon(size_t extruder_idx, bool thin_icon = false) | ||||
| { | ||||
|     // Create the bitmap with color bars.
 | ||||
|     std::vector<wxBitmap*> bmps = get_extruder_color_icons(); | ||||
|     std::vector<wxBitmap*> bmps = get_extruder_color_icons(thin_icon); | ||||
|     if (bmps.empty()) | ||||
|         return wxNullBitmap; | ||||
| 
 | ||||
|     return *bmps[extruder_idx >= bmps.size() ? 0 : extruder_idx]; | ||||
| } | ||||
| 
 | ||||
| void apply_extruder_selector(wxBitmapComboBox** ctrl,  | ||||
|                              wxWindow* parent, | ||||
|                              const std::string& first_item/* = ""*/,  | ||||
|                              wxPoint pos/* = wxDefaultPosition*/, | ||||
|                              wxSize size/* = wxDefaultSize*/, | ||||
|                              bool use_thin_icon/* = false*/) | ||||
| { | ||||
|     std::vector<wxBitmap*> icons = get_extruder_color_icons(use_thin_icon); | ||||
| 
 | ||||
|     if (!*ctrl) | ||||
|         *ctrl = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, pos, size, | ||||
|             0, nullptr, wxCB_READONLY); | ||||
|     else | ||||
|     { | ||||
|         (*ctrl)->SetPosition(pos); | ||||
|         (*ctrl)->SetMinSize(size); | ||||
|         (*ctrl)->SetSize(size); | ||||
|         (*ctrl)->Clear(); | ||||
|     } | ||||
|     if (first_item.empty()) | ||||
|         (*ctrl)->Hide();    // to avoid unwanted rendering before layout (ExtruderSequenceDialog)
 | ||||
| 
 | ||||
|     if (icons.empty() && !first_item.empty()) { | ||||
|         (*ctrl)->Append(_(first_item), wxNullBitmap); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // For ObjectList we use short extruder name (just a number)
 | ||||
|     const bool use_full_item_name = dynamic_cast<Slic3r::GUI::ObjectList*>(parent) == nullptr; | ||||
| 
 | ||||
|     int i = 0; | ||||
|     wxString str = _(L("Extruder")); | ||||
|     for (wxBitmap* bmp : icons) { | ||||
|         if (i == 0) { | ||||
|             if (!first_item.empty()) | ||||
|                 (*ctrl)->Append(_(first_item), *bmp); | ||||
|             ++i; | ||||
|         } | ||||
| 
 | ||||
|         (*ctrl)->Append(use_full_item_name ? wxString::Format("%s %d", str, i) : std::to_string(i), *bmp); | ||||
|         ++i; | ||||
|     } | ||||
|     (*ctrl)->SetSelection(0); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // *****************************************************************************
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // ObjectDataViewModelNode
 | ||||
|  | @ -2230,6 +2298,8 @@ DoubleSlider::DoubleSlider( wxWindow *parent, | |||
|     if (!is_osx) | ||||
|         SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
 | ||||
| 
 | ||||
|     const float scale_factor = get_svg_scale_factor(this); | ||||
| 
 | ||||
|     m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "up_half_circle.png",   16, true)); | ||||
|     m_bmp_thumb_lower  = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "down_half_circle.png", 16, true)); | ||||
|     m_thumb_size = m_bmp_thumb_lower.bmp().GetSize(); | ||||
|  | @ -2240,16 +2310,19 @@ DoubleSlider::DoubleSlider( wxWindow *parent, | |||
|     m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_delete_off.png"); | ||||
|     m_tick_icon_dim = m_bmp_add_tick_on.bmp().GetSize().x; | ||||
| 
 | ||||
|     m_bmp_one_layer_lock_on    = ScalableBitmap(this, "one_layer_lock_on.png"); | ||||
|     m_bmp_one_layer_lock_off   = ScalableBitmap(this, "one_layer_lock_off.png"); | ||||
|     m_bmp_one_layer_unlock_on  = ScalableBitmap(this, "one_layer_unlock_on.png"); | ||||
|     m_bmp_one_layer_unlock_off = ScalableBitmap(this, "one_layer_unlock_off.png"); | ||||
|     m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x; | ||||
|     m_bmp_one_layer_lock_on    = ScalableBitmap(this, "lock_closed"); | ||||
|     m_bmp_one_layer_lock_off   = ScalableBitmap(this, "lock_closed_f"); | ||||
|     m_bmp_one_layer_unlock_on  = ScalableBitmap(this, "lock_open"); | ||||
|     m_bmp_one_layer_unlock_off = ScalableBitmap(this, "lock_open_f"); | ||||
|     m_lock_icon_dim   = int((float)m_bmp_one_layer_lock_on.bmp().GetSize().x / scale_factor); | ||||
| 
 | ||||
|     m_bmp_revert               = ScalableBitmap(this, "undo"); | ||||
|     m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x; | ||||
|     m_revert_icon_dim = int((float)m_bmp_revert.bmp().GetSize().x / scale_factor); | ||||
|     m_bmp_cog                  = ScalableBitmap(this, "cog"); | ||||
|     m_cog_icon_dim    = int((float)m_bmp_cog.bmp().GetSize().x / scale_factor); | ||||
| 
 | ||||
|     m_selection = ssUndef; | ||||
|     m_pause_print_msg = _utf8(L("Place bearings in slots and resume")); | ||||
| 
 | ||||
|     // slider events
 | ||||
|     Bind(wxEVT_PAINT,       &DoubleSlider::OnPaint,    this); | ||||
|  | @ -2306,6 +2379,8 @@ void DoubleSlider::msw_rescale() | |||
| 
 | ||||
|     m_bmp_revert.msw_rescale(); | ||||
|     m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x; | ||||
|     m_bmp_cog.msw_rescale(); | ||||
|     m_cog_icon_dim = m_bmp_cog.bmp().GetSize().x; | ||||
| 
 | ||||
|     SLIDER_MARGIN = 4 + Slic3r::GUI::wxGetApp().em_unit(); | ||||
| 
 | ||||
|  | @ -2453,41 +2528,45 @@ double DoubleSlider::get_double_value(const SelectedSlider& selection) | |||
|     return m_values[selection == ssLower ? m_lower_value : m_higher_value]; | ||||
| } | ||||
| 
 | ||||
| std::vector<double> DoubleSlider::GetTicksValues() const | ||||
| using t_custom_code = Slic3r::Model::CustomGCode; | ||||
| std::vector<t_custom_code> DoubleSlider::GetTicksValues() const | ||||
| { | ||||
|     std::vector<double> values; | ||||
|     std::vector<t_custom_code> values; | ||||
| 
 | ||||
|     const int val_size = m_values.size(); | ||||
|     if (!m_values.empty()) | ||||
|         for (int tick : m_ticks) { | ||||
|             if (tick > val_size) | ||||
|         for (const TICK_CODE& tick : m_ticks_) { | ||||
|             if (tick.tick > val_size) | ||||
|                 break; | ||||
|             values.push_back(m_values[tick]); | ||||
|             values.push_back(t_custom_code(m_values[tick.tick], tick.gcode, tick.extruder, tick.color)); | ||||
|         } | ||||
| 
 | ||||
|     return values; | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::SetTicksValues(const std::vector<double>& heights) | ||||
| void DoubleSlider::SetTicksValues(const std::vector<t_custom_code>& heights) | ||||
| { | ||||
|     if (m_values.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     const bool was_empty = m_ticks.empty(); | ||||
|     const bool was_empty = m_ticks_.empty(); | ||||
| 
 | ||||
|     m_ticks.clear(); | ||||
|     m_ticks_.clear(); | ||||
|     for (auto h : heights) { | ||||
|         auto it = std::lower_bound(m_values.begin(), m_values.end(), h - epsilon()); | ||||
|         auto it = std::lower_bound(m_values.begin(), m_values.end(), h.height - epsilon()); | ||||
| 
 | ||||
|         if (it == m_values.end()) | ||||
|             continue; | ||||
| 
 | ||||
|         m_ticks.insert(it-m_values.begin()); | ||||
|         m_ticks_.insert(TICK_CODE(it-m_values.begin(), h.gcode, h.extruder, h.color)); | ||||
|     } | ||||
|      | ||||
|     if (!was_empty && m_ticks.empty()) | ||||
|     if (!was_empty && m_ticks_.empty()) | ||||
|         // Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one
 | ||||
|         wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
| 
 | ||||
|     Refresh(); | ||||
|     Update(); | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos) | ||||
|  | @ -2538,17 +2617,20 @@ void DoubleSlider::render() | |||
| //     //higher slider:
 | ||||
| //     draw_thumb(dc, higher_pos, ssHigher);
 | ||||
| 
 | ||||
|     // draw both sliders
 | ||||
|     draw_thumbs(dc, lower_pos, higher_pos); | ||||
| 
 | ||||
|     //draw color print ticks
 | ||||
|     draw_ticks(dc); | ||||
| 
 | ||||
|     // draw both sliders
 | ||||
|     draw_thumbs(dc, lower_pos, higher_pos); | ||||
| 
 | ||||
|     //draw lock/unlock
 | ||||
|     draw_one_layer_icon(dc); | ||||
| 
 | ||||
|     //draw revert bitmap (if it's shown)
 | ||||
|     draw_revert_icon(dc); | ||||
| 
 | ||||
|     //draw cog bitmap (if it's shown)
 | ||||
|     draw_cog_icon(dc); | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end) | ||||
|  | @ -2560,7 +2642,7 @@ void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoin | |||
|         return; | ||||
| 
 | ||||
|     wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp(); | ||||
|     if (m_ticks.find(tick) != m_ticks.end()) | ||||
|     if (m_ticks_.find(tick) != m_ticks_.end()) | ||||
|         icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp(); | ||||
| 
 | ||||
|     wxCoord x_draw, y_draw; | ||||
|  | @ -2703,14 +2785,26 @@ void DoubleSlider::draw_ticks(wxDC& dc) | |||
|     int height, width; | ||||
|     get_size(&width, &height); | ||||
|     const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width; | ||||
|     for (auto tick : m_ticks) | ||||
|     for (auto tick : m_ticks_) | ||||
|     { | ||||
|         const wxCoord pos = get_position_from_value(tick); | ||||
|         const wxCoord pos = get_position_from_value(tick.tick); | ||||
| 
 | ||||
|         is_horizontal() ?   dc.DrawLine(pos, mid-14, pos, mid-9) : | ||||
|                             dc.DrawLine(mid - 14, pos/* - 1*/, mid - 9, pos/* - 1*/); | ||||
|         is_horizontal() ?   dc.DrawLine(pos, mid+14, pos, mid+9) : | ||||
|                             dc.DrawLine(mid + 14, pos/* - 1*/, mid + 9, pos/* - 1*/); | ||||
| 
 | ||||
|         // Draw icon for "Pause print" or "Custom Gcode"
 | ||||
|         if (tick.gcode != Slic3r::ColorChangeCode && tick.gcode != Slic3r::ExtruderChangeCode) | ||||
|         { | ||||
|             wxBitmap icon = create_scaled_bitmap(nullptr, tick.gcode == Slic3r::PausePrintCode ? "pause_print" : "edit_gcode"); | ||||
| 
 | ||||
|             wxCoord x_draw, y_draw; | ||||
|             is_horizontal() ? x_draw = pos - 0.5 * m_tick_icon_dim : y_draw = pos - 0.5 * m_tick_icon_dim; | ||||
|             is_horizontal() ? y_draw = mid + 22 : x_draw = mid + 22 ; | ||||
| 
 | ||||
|             dc.DrawBitmap(icon, x_draw, y_draw); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -2735,34 +2829,34 @@ void DoubleSlider::draw_colored_band(wxDC& dc) | |||
|         main_band.SetBottom(height - SLIDER_MARGIN + 1); | ||||
|     } | ||||
| 
 | ||||
|     if (m_ticks.empty()) { | ||||
|         dc.SetPen(GetParent()->GetBackgroundColour()); | ||||
|         dc.SetBrush(GetParent()->GetBackgroundColour()); | ||||
|         dc.DrawRectangle(main_band); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const std::vector<std::string>& colors = Slic3r::GCodePreviewData::ColorPrintColors(); | ||||
|     const size_t colors_cnt = colors.size(); | ||||
| 
 | ||||
|     wxColour clr(colors[0]); | ||||
|     dc.SetPen(clr); | ||||
|     dc.SetBrush(clr); | ||||
|     dc.DrawRectangle(main_band); | ||||
| 
 | ||||
|     size_t i = 1; | ||||
|     for (auto tick : m_ticks) | ||||
|     { | ||||
|         if (i == colors_cnt) | ||||
|             i = 0; | ||||
|         const wxCoord pos = get_position_from_value(tick); | ||||
|         is_horizontal() ?   main_band.SetLeft(SLIDER_MARGIN + pos) : | ||||
|                             main_band.SetBottom(pos-1); | ||||
| 
 | ||||
|         clr = wxColour(colors[i]); | ||||
|     auto draw_band = [](wxDC& dc, const wxColour& clr, const wxRect& band_rc) { | ||||
|         dc.SetPen(clr); | ||||
|         dc.SetBrush(clr); | ||||
|         dc.DrawRectangle(main_band); | ||||
|         dc.DrawRectangle(band_rc); | ||||
|     }; | ||||
| 
 | ||||
|     const std::vector<std::string>& colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); | ||||
|     int colors_cnt = colors.size(); | ||||
| 
 | ||||
|     const wxColour bg_clr = GetParent()->GetBackgroundColour(); | ||||
| 
 | ||||
|     wxColour clr = m_state == msSingleExtruder ? wxColour(colors[0]) : bg_clr; | ||||
|     draw_band(dc, clr, main_band); | ||||
| 
 | ||||
|     size_t i = 1; | ||||
|     for (auto tick : m_ticks_) | ||||
|     { | ||||
|         if ( (m_state == msSingleExtruder && tick.gcode != Slic3r::ColorChangeCode) || | ||||
|              (m_state == msMultiExtruder && tick.gcode != Slic3r::ExtruderChangeCode) ) | ||||
|             continue; | ||||
| 
 | ||||
|         const wxCoord pos = get_position_from_value(tick.tick); | ||||
|         is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) : | ||||
|             main_band.SetBottom(pos - 1); | ||||
| 
 | ||||
|         clr = (m_state == msMultiExtruder && tick.color.empty()) ? bg_clr : | ||||
|                m_state == msMultiExtruder ? wxColour(colors[std::min<int>(colors_cnt - 1, tick.extruder-1)]) : wxColour(tick.color); | ||||
|         draw_band(dc, clr, main_band); | ||||
|         i++; | ||||
|     } | ||||
| } | ||||
|  | @ -2788,7 +2882,7 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc) | |||
| 
 | ||||
| void DoubleSlider::draw_revert_icon(wxDC& dc) | ||||
| { | ||||
|     if (m_ticks.empty() || !m_is_enabled_tick_manipulation) | ||||
|     if (m_ticks_.empty() || !m_is_enabled_tick_manipulation) | ||||
|         return; | ||||
| 
 | ||||
|     int width, height; | ||||
|  | @ -2804,6 +2898,24 @@ void DoubleSlider::draw_revert_icon(wxDC& dc) | |||
|     m_rect_revert_icon = wxRect(x_draw, y_draw, m_revert_icon_dim, m_revert_icon_dim); | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::draw_cog_icon(wxDC& dc) | ||||
| { | ||||
|     if (m_state != msMultiExtruder) | ||||
|         return; | ||||
| 
 | ||||
|     int width, height; | ||||
|     get_size(&width, &height); | ||||
| 
 | ||||
|     wxCoord x_draw, y_draw; | ||||
|     is_horizontal() ? x_draw = width-2 : x_draw = width - m_cog_icon_dim - 2; | ||||
|     is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height-2; | ||||
| 
 | ||||
|     dc.DrawBitmap(m_bmp_cog.bmp(), x_draw, y_draw); | ||||
| 
 | ||||
|     //update rect of the lock/unlock icon
 | ||||
|     m_rect_cog_icon = wxRect(x_draw, y_draw, m_cog_icon_dim, m_cog_icon_dim); | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection) | ||||
| { | ||||
|     const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y); | ||||
|  | @ -2840,16 +2952,16 @@ bool DoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect) | |||
| 
 | ||||
| int DoubleSlider::is_point_near_tick(const wxPoint& pt) | ||||
| { | ||||
|     for (auto tick : m_ticks) { | ||||
|         const wxCoord pos = get_position_from_value(tick); | ||||
|     for (auto tick : m_ticks_) { | ||||
|         const wxCoord pos = get_position_from_value(tick.tick); | ||||
| 
 | ||||
|         if (is_horizontal()) { | ||||
|             if (pos - 4 <= pt.x && pt.x <= pos + 4) | ||||
|                 return tick; | ||||
|                 return tick.tick; | ||||
|         } | ||||
|         else { | ||||
|             if (pos - 4 <= pt.y && pt.y <= pos + 4)  | ||||
|                 return tick; | ||||
|                 return tick.tick; | ||||
|         } | ||||
|     } | ||||
|     return -1; | ||||
|  | @ -2901,9 +3013,13 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event) | |||
|         m_selection == ssLower ? correct_lower_value() : correct_higher_value(); | ||||
|         if (!m_selection) m_selection = ssHigher; | ||||
| 
 | ||||
|         m_ticks.clear(); | ||||
|         m_ticks_.clear(); | ||||
|         wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
|     } | ||||
|     else if (is_point_in_rect(pos, m_rect_cog_icon) && m_state == msMultiExtruder) { | ||||
|         // show dialog for set extruder sequence
 | ||||
|         m_edit_extruder_sequence = true; | ||||
|     } | ||||
|     else | ||||
|         detect_selected_slider(pos); | ||||
| 
 | ||||
|  | @ -2956,6 +3072,38 @@ void DoubleSlider::correct_higher_value() | |||
|         m_lower_value = m_higher_value; | ||||
| } | ||||
| 
 | ||||
| wxString DoubleSlider::get_tooltip(IconFocus icon_focus) | ||||
| { | ||||
|     wxString tooltip(wxEmptyString); | ||||
|     if (m_is_one_layer_icon_focesed) | ||||
|         tooltip = _(L("One layer mode")); | ||||
| 
 | ||||
|     if (icon_focus == ifRevert) | ||||
|         tooltip = _(L("Discard all custom changes")); | ||||
|     if (icon_focus == ifCog) | ||||
|         tooltip = _(L("Set extruder sequence for whole print")); | ||||
|     else if (m_is_action_icon_focesed) | ||||
|     { | ||||
|         const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; | ||||
|         const auto tick_code_it = m_ticks_.find(tick); | ||||
|         tooltip = tick_code_it == m_ticks_.end()            ? (m_state == msSingleExtruder ? | ||||
|                         _(L("For add color change use left mouse button click")) : | ||||
|                         _(L("For add change extruder use left mouse button click"))) + "\n" + | ||||
|                         _(L("For add another code use right mouse button click")) : | ||||
|                   tick_code_it->gcode == Slic3r::ColorChangeCode             ? ( m_state == msSingleExtruder ? | ||||
|                       _(L("For Delete color change use left mouse button click\n" | ||||
|                           "For Edit color use right mouse button click")) : | ||||
|                       from_u8((boost::format(_utf8(L("Delete color change for Extruder %1%"))) % tick_code_it->extruder).str()) ): | ||||
| //                  tick_code_it->gcode == Slic3r::PausePrintCode             ? _(L("Delete pause")) :
 | ||||
|                   tick_code_it->gcode == Slic3r::ExtruderChangeCode         ? | ||||
|                       from_u8((boost::format(_utf8(L("Delete extruder change to \"%1%\""))) % tick_code_it->extruder).str()) : | ||||
|                       from_u8((boost::format(_utf8(L("For Delete \"%1%\" code use left mouse button click\n" | ||||
|                                                        "For Edit \"%1%\" code use right mouse button click"))) % tick_code_it->gcode ).str()); | ||||
|     } | ||||
| 
 | ||||
|     return tooltip; | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::OnMotion(wxMouseEvent& event) | ||||
| { | ||||
|     bool action = false; | ||||
|  | @ -2964,11 +3112,14 @@ void DoubleSlider::OnMotion(wxMouseEvent& event) | |||
|     const wxPoint pos = event.GetLogicalPosition(dc); | ||||
| 
 | ||||
|     m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon); | ||||
|     bool is_revert_icon_focused = false; | ||||
|     IconFocus icon_focus = ifNone; | ||||
| 
 | ||||
|     if (!m_is_left_down && !m_is_one_layer) { | ||||
|         m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action); | ||||
|         is_revert_icon_focused = !m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon); | ||||
|         if (!m_ticks_.empty() && is_point_in_rect(pos, m_rect_revert_icon)) | ||||
|             icon_focus = ifRevert; | ||||
|         else if (is_point_in_rect(pos, m_rect_cog_icon)) | ||||
|             icon_focus = ifCog; | ||||
|     } | ||||
|     else if (m_is_left_down || m_is_right_down) { | ||||
|         if (m_selection == ssLower) { | ||||
|  | @ -2989,10 +3140,7 @@ void DoubleSlider::OnMotion(wxMouseEvent& event) | |||
|     event.Skip(); | ||||
| 
 | ||||
|     // Set tooltips with information for each icon
 | ||||
|     const wxString tooltip = m_is_one_layer_icon_focesed    ? _(L("One layer mode"))    : | ||||
|                              m_is_action_icon_focesed       ? _(L("Add/Del color change")) : | ||||
|                              is_revert_icon_focused         ? _(L("Discard all color changes")) : ""; | ||||
|     this->SetToolTip(tooltip); | ||||
|     this->SetToolTip(get_tooltip(icon_focus)); | ||||
| 
 | ||||
|     if (action) | ||||
|     { | ||||
|  | @ -3009,6 +3157,55 @@ void DoubleSlider::OnLeftUp(wxMouseEvent& event) | |||
|         return; | ||||
|     this->ReleaseMouse(); | ||||
|     m_is_left_down = false; | ||||
| 
 | ||||
|     if (m_show_context_menu) | ||||
|     { | ||||
|         if (m_state == msMultiExtruder) | ||||
|         { | ||||
|             wxMenu menu; | ||||
|             const int extruders_cnt = Slic3r::GUI::wxGetApp().extruders_edited_cnt(); | ||||
|             if (extruders_cnt > 1) | ||||
|             { | ||||
|                 /*
 | ||||
|                 wxMenu* add_color_change_menu = new wxMenu(); | ||||
| 
 | ||||
|                 for (int i = 1; i <= extruders_cnt; i++) | ||||
|                     append_menu_item(add_color_change_menu, wxID_ANY, wxString::Format(_(L("Extruder %d")), i), "", | ||||
|                         [this, i](wxCommandEvent&) { add_code(Slic3r::ColorChangeCode, i); }, "", &menu); | ||||
| 
 | ||||
|                 const wxString menu_name = from_u8((boost::format(_utf8(L("Add color change (%1%) for:"))) % Slic3r::ColorChangeCode).str()); | ||||
|                 wxMenuItem* add_color_change_menu_item = menu.AppendSubMenu(add_color_change_menu, menu_name, ""); | ||||
|                 add_color_change_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "colorchange_add_off.png")); | ||||
|             */ | ||||
| 
 | ||||
|                 const int initial_extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); | ||||
| 
 | ||||
|                 wxMenu* change_extruder_menu = new wxMenu(); | ||||
| 
 | ||||
|                 for (int i = 0; i <= extruders_cnt; i++) { | ||||
|                     const wxString item_name = i == 0 ? _(L("Default")) : wxString::Format(_(L("Extruder %d")), i); | ||||
| 
 | ||||
|                     append_menu_radio_item(change_extruder_menu, wxID_ANY, item_name, "", | ||||
|                         [this, i](wxCommandEvent&) { change_extruder(i); }, &menu)->Check(i == initial_extruder); | ||||
|                 } | ||||
| 
 | ||||
|                 wxMenuItem* change_extruder_menu_item = menu.AppendSubMenu(change_extruder_menu, _(L("Change extruder")), _(L("Use another extruder"))); | ||||
|                 change_extruder_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "change_extruder")); | ||||
|             } | ||||
| 
 | ||||
|             Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu); | ||||
|         } | ||||
|         else | ||||
|             add_code(Slic3r::ColorChangeCode); | ||||
|          | ||||
|         m_show_context_menu = false; | ||||
|     } | ||||
| 
 | ||||
|     if (m_edit_extruder_sequence) { | ||||
|         edit_extruder_sequence(); | ||||
|         m_edit_extruder_sequence = false; | ||||
|     } | ||||
| 
 | ||||
|     Refresh(); | ||||
|     Update(); | ||||
|     event.Skip(); | ||||
|  | @ -3059,21 +3256,36 @@ void DoubleSlider::action_tick(const TicksAction action) | |||
| 
 | ||||
|     const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; | ||||
| 
 | ||||
|     if (action == taOnIcon) { | ||||
|         if (!m_ticks.insert(tick).second) | ||||
|             m_ticks.erase(tick); | ||||
|     const auto it = m_ticks_.find(tick); | ||||
| 
 | ||||
|     if (it != m_ticks_.end()) // erase this tick
 | ||||
|     { | ||||
|         if (action == taAdd) | ||||
|             return; | ||||
|         m_ticks_.erase(TICK_CODE(tick)); | ||||
| 
 | ||||
|         wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
|         Refresh(); | ||||
|         Update(); | ||||
|         return; | ||||
|     } | ||||
|     else { | ||||
|         const auto it = m_ticks.find(tick); | ||||
|         if (it == m_ticks.end() && action == taAdd) | ||||
|             m_ticks.insert(tick); | ||||
|         else if (it != m_ticks.end() && action == taDel) | ||||
|             m_ticks.erase(tick); | ||||
|      | ||||
|     if (action == taDel) | ||||
|         return; | ||||
|     if (action == taAdd) | ||||
|     { | ||||
|         // OnChar() is called immediately after OnKeyDown(), which can cause call of add_code() twice.
 | ||||
|         // To avoid this case we should suppress second add_code() call.
 | ||||
|         if (m_suppress_add_code) | ||||
|             return; | ||||
|         m_suppress_add_code = true; | ||||
|         if (m_state != msMultiExtruder) | ||||
|             add_code(Slic3r::ColorChangeCode); | ||||
|         m_suppress_add_code = false; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
|     Refresh(); | ||||
|     Update(); | ||||
|     m_show_context_menu = true; | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::OnWheel(wxMouseEvent& event) | ||||
|  | @ -3148,6 +3360,27 @@ void DoubleSlider::OnRightDown(wxMouseEvent& event) | |||
|     this->CaptureMouse(); | ||||
| 
 | ||||
|     const wxClientDC dc(this); | ||||
| 
 | ||||
|     wxPoint pos = event.GetLogicalPosition(dc); | ||||
|     if (is_point_in_rect(pos, m_rect_tick_action) && m_is_enabled_tick_manipulation) | ||||
|     { | ||||
|         const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; | ||||
|         // if on this Z doesn't exist tick
 | ||||
|         auto it = m_ticks_.find(tick); | ||||
|         if (it == m_ticks_.end()) | ||||
|         { | ||||
|             // show context menu on OnRightUp()
 | ||||
|             m_show_context_menu = true; | ||||
|             return; | ||||
|         } | ||||
|         if (it->gcode != Slic3r::ExtruderChangeCode) | ||||
|         { | ||||
|             // show "Edit" and "Delete" menu on OnRightUp()
 | ||||
|             m_show_edit_menu = true; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     detect_selected_slider(event.GetLogicalPosition(dc)); | ||||
|     if (!m_selection) | ||||
|         return; | ||||
|  | @ -3157,13 +3390,29 @@ void DoubleSlider::OnRightDown(wxMouseEvent& event) | |||
|     else | ||||
|         m_lower_value = m_higher_value; | ||||
| 
 | ||||
|     m_is_right_down = m_is_one_layer = true; | ||||
|     // set slider to "one layer" mode
 | ||||
|     m_is_right_down = m_is_one_layer = true;  | ||||
| 
 | ||||
|     Refresh(); | ||||
|     Update(); | ||||
|     event.Skip(); | ||||
| } | ||||
| 
 | ||||
| int DoubleSlider::get_extruder_for_tick(int tick) | ||||
| { | ||||
|     if (m_ticks_.empty()) | ||||
|         return 0; | ||||
|      | ||||
|     auto it = m_ticks_.lower_bound(tick); | ||||
|     while (it != m_ticks_.begin()) { | ||||
|         --it; | ||||
|         if(it->gcode == Slic3r::ExtruderChangeCode) | ||||
|             return it->extruder; | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::OnRightUp(wxMouseEvent& event) | ||||
| { | ||||
|     if (!HasCapture()) | ||||
|  | @ -3171,11 +3420,290 @@ void DoubleSlider::OnRightUp(wxMouseEvent& event) | |||
|     this->ReleaseMouse(); | ||||
|     m_is_right_down = m_is_one_layer = false; | ||||
| 
 | ||||
|     if (m_show_context_menu) { | ||||
|         wxMenu menu; | ||||
| 
 | ||||
|         if (m_state == msMultiExtruder) | ||||
|         { | ||||
|             const int extruders_cnt = Slic3r::GUI::wxGetApp().extruders_edited_cnt(); | ||||
|             if (extruders_cnt > 1) | ||||
|             { | ||||
|                 const int initial_extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); | ||||
| 
 | ||||
|                 wxMenu* change_extruder_menu = new wxMenu(); | ||||
|                 wxMenu* add_color_change_menu = new wxMenu(); | ||||
| 
 | ||||
|                 for (int i = 0; i <= extruders_cnt; i++) { | ||||
|                     const wxString item_name = i == 0 ? _(L("Default")) : wxString::Format(_(L("Extruder %d")), i); | ||||
| 
 | ||||
|                     append_menu_radio_item(change_extruder_menu, wxID_ANY, item_name, "", | ||||
|                         [this, i](wxCommandEvent&) { change_extruder(i); }, &menu)->Check(i == initial_extruder); | ||||
| 
 | ||||
|                     if (i==0)       // don't use M600 for default extruder, if multimaterial print is selected 
 | ||||
|                         continue; | ||||
|                     append_menu_item(add_color_change_menu, wxID_ANY, item_name, "", | ||||
|                         [this, i](wxCommandEvent&) { add_code(Slic3r::ColorChangeCode, i); }, "", &menu); | ||||
|                 } | ||||
| 
 | ||||
|                 wxMenuItem* change_extruder_menu_item = menu.AppendSubMenu(change_extruder_menu, _(L("Change extruder")), _(L("Use another extruder"))); | ||||
|                 change_extruder_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "change_extruder")); | ||||
| 
 | ||||
|                 const wxString menu_name = from_u8((boost::format(_utf8(L("Add color change (%1%) for:"))) % Slic3r::ColorChangeCode).str()); | ||||
|                 wxMenuItem* add_color_change_menu_item = menu.AppendSubMenu(add_color_change_menu, menu_name, ""); | ||||
|                 add_color_change_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "colorchange_add_off.png")); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         append_menu_item(&menu, wxID_ANY, _(L("Add color change")) + " (M600)", "", | ||||
|             [this](wxCommandEvent&) { add_code(Slic3r::ColorChangeCode); }, "colorchange_add_off.png", &menu); | ||||
| 
 | ||||
|         append_menu_item(&menu, wxID_ANY, _(L("Add pause print")) + " (M601)", "", | ||||
|             [this](wxCommandEvent&) { add_code(Slic3r::PausePrintCode); }, "pause_print", &menu); | ||||
|      | ||||
|         append_menu_item(&menu, wxID_ANY, _(L("Add custom G-code")), "", | ||||
|             [this](wxCommandEvent&) { add_code(""); }, "edit_gcode", &menu); | ||||
|      | ||||
|         Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu); | ||||
| 
 | ||||
|         m_show_context_menu = false; | ||||
|     } | ||||
|     else if (m_show_edit_menu) { | ||||
|         wxMenu menu; | ||||
| 
 | ||||
|         std::set<TICK_CODE>::iterator it = m_ticks_.find(m_selection == ssLower ? m_lower_value : m_higher_value); | ||||
|         const bool is_color_change = it->gcode == Slic3r::ColorChangeCode; | ||||
| 
 | ||||
|         append_menu_item(&menu, wxID_ANY, it->gcode == Slic3r::ColorChangeCode ? _(L("Edit color")) : | ||||
|                                           it->gcode == Slic3r::PausePrintCode ? _(L("Edit pause print message")) : | ||||
|                                           _(L("Edit custom G-code")), "", | ||||
|             [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu); | ||||
| 
 | ||||
|         append_menu_item(&menu, wxID_ANY, it->gcode == Slic3r::ColorChangeCode ? _(L("Delete color change")) :  | ||||
|                                           it->gcode == Slic3r::PausePrintCode ? _(L("Delete pause print")) : | ||||
|                                           _(L("Delete custom G-code")), "", | ||||
|             [this](wxCommandEvent&) { action_tick(taDel); }, "colorchange_delete_off.png", &menu); | ||||
| 
 | ||||
|         Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu); | ||||
| 
 | ||||
|         m_show_edit_menu = false; | ||||
|     } | ||||
| 
 | ||||
|     Refresh(); | ||||
|     Update(); | ||||
|     event.Skip(); | ||||
| } | ||||
| 
 | ||||
| static std::string get_new_color(const std::string& color) | ||||
| { | ||||
|     wxColour clr(color); | ||||
|     if (!clr.IsOk()) | ||||
|         clr = wxColour(0, 0, 0); // Don't set alfa to transparence
 | ||||
| 
 | ||||
|     auto data = new wxColourData(); | ||||
|     data->SetChooseFull(1); | ||||
|     data->SetColour(clr); | ||||
| 
 | ||||
|     wxColourDialog dialog(nullptr, data); | ||||
|     dialog.CenterOnParent(); | ||||
|     if (dialog.ShowModal() == wxID_OK) | ||||
|         return dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); | ||||
|     return ""; | ||||
| } | ||||
| 
 | ||||
| static std::string get_custom_code(const std::string& code_in, double height) | ||||
| { | ||||
|     wxString msg_text = from_u8(_utf8(L("Enter custom G-code used on current layer"))) + " :"; | ||||
|     wxString msg_header = from_u8((boost::format(_utf8(L("Custom Gcode on current layer (%1% mm)."))) % height).str()); | ||||
| 
 | ||||
|     // get custom gcode
 | ||||
|     wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in, | ||||
|         wxTextEntryDialogStyle | wxTE_MULTILINE); | ||||
|     if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) | ||||
|         return ""; | ||||
| 
 | ||||
|     return dlg.GetValue().ToStdString(); | ||||
| } | ||||
| 
 | ||||
| static std::string get_pause_print_msg(const std::string& msg_in, double height) | ||||
| { | ||||
|     wxString msg_text = from_u8(_utf8(L("Enter short message shown on Printer display during pause print"))) + " :"; | ||||
|     wxString msg_header = from_u8((boost::format(_utf8(L("Message for pause print on current layer (%1% mm)."))) % height).str()); | ||||
| 
 | ||||
|     // get custom gcode
 | ||||
|     wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in), | ||||
|         wxTextEntryDialogStyle); | ||||
|     if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) | ||||
|         return ""; | ||||
| 
 | ||||
|     return into_u8(dlg.GetValue()); | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::add_code(std::string code, int selected_extruder/* = -1*/) | ||||
| { | ||||
|     const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; | ||||
|     // if on this Z doesn't exist tick
 | ||||
|     auto it = m_ticks_.find(tick); | ||||
|     if (it == m_ticks_.end()) | ||||
|     { | ||||
|         std::string color = ""; | ||||
|         if (code == Slic3r::ColorChangeCode) | ||||
|         { | ||||
|             std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); | ||||
| 
 | ||||
|             if (m_state == msSingleExtruder && !m_ticks_.empty()) { | ||||
|                 auto before_tick_it = std::lower_bound(m_ticks_.begin(), m_ticks_.end(), tick); | ||||
|                 while (before_tick_it != m_ticks_.begin()) { | ||||
|                     --before_tick_it; | ||||
|                     if (before_tick_it->gcode == Slic3r::ColorChangeCode) { | ||||
|                         color = before_tick_it->color; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (color.empty()) | ||||
|                     color = colors[0]; | ||||
|             } | ||||
|             else | ||||
|                 color = colors[selected_extruder > 0 ? selected_extruder-1 : 0]; | ||||
| 
 | ||||
|             color = get_new_color(color); | ||||
|             if (color.empty()) | ||||
|                 return; | ||||
|         } | ||||
|         else if (code == Slic3r::PausePrintCode) | ||||
|         { | ||||
|             /* PausePrintCode doesn't need a color, so
 | ||||
|              * this field is used for save a short message shown on Printer display  | ||||
|              * */ | ||||
|             m_pause_print_msg = color = get_pause_print_msg(m_pause_print_msg, m_values[tick]); | ||||
|         } | ||||
|         else if (code.empty()) | ||||
|         { | ||||
|             m_custom_gcode = code = get_custom_code(m_custom_gcode, m_values[tick]); | ||||
|         } | ||||
| 
 | ||||
|         int extruder = 1; | ||||
|         if (m_state == msMultiExtruder) {  | ||||
|             if (code == Slic3r::ColorChangeCode && selected_extruder >= 0) | ||||
|                 extruder = selected_extruder; | ||||
|             else | ||||
|                 extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); | ||||
|         } | ||||
| 
 | ||||
|         m_ticks_.insert(TICK_CODE(tick, code, extruder, color)); | ||||
| 
 | ||||
|         wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
|         Refresh(); | ||||
|         Update(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::edit_tick() | ||||
| { | ||||
|     const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; | ||||
|     // if on this Z exists tick
 | ||||
|     std::set<TICK_CODE>::iterator it = m_ticks_.find(tick); | ||||
|     if (it != m_ticks_.end()) | ||||
|     { | ||||
|         std::string edited_value; | ||||
|         if (it->gcode == Slic3r::ColorChangeCode) | ||||
|             edited_value = get_new_color(it->color); | ||||
|         else if (it->gcode == Slic3r::PausePrintCode) | ||||
|             edited_value = get_pause_print_msg(it->color, m_values[it->tick]); | ||||
|         else | ||||
|             edited_value = get_custom_code(it->gcode, m_values[it->tick]); | ||||
| 
 | ||||
|         if (edited_value.empty()) | ||||
|             return; | ||||
| 
 | ||||
|         TICK_CODE changed_tick = *it; | ||||
|         if (it->gcode == Slic3r::ColorChangeCode || it->gcode == Slic3r::PausePrintCode) { | ||||
|             if (it->color == edited_value) | ||||
|                 return; | ||||
|             changed_tick.color = edited_value; | ||||
|         } | ||||
|         else { | ||||
|             if (it->gcode == edited_value) | ||||
|                 return; | ||||
|             changed_tick.gcode = edited_value; | ||||
|         } | ||||
|          | ||||
|         m_ticks_.erase(it); | ||||
|         m_ticks_.insert(changed_tick); | ||||
| 
 | ||||
|         wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::change_extruder(int extruder) | ||||
| { | ||||
|     const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; | ||||
| 
 | ||||
|     std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); | ||||
| 
 | ||||
|     // if on this Y doesn't exist tick
 | ||||
|     if (m_ticks_.find(tick) == m_ticks_.end()) | ||||
|     {         | ||||
|         m_ticks_.insert(TICK_CODE(tick, Slic3r::ExtruderChangeCode, extruder, extruder == 0 ? "" : colors[extruder-1])); | ||||
| 
 | ||||
|         wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
|         Refresh(); | ||||
|         Update(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DoubleSlider::edit_extruder_sequence() | ||||
| { | ||||
|     Slic3r::GUI::ExtruderSequenceDialog dlg(m_extruders_sequence); | ||||
|     if (dlg.ShowModal() != wxID_OK) | ||||
|         return; | ||||
| 
 | ||||
|     const ExtrudersSequence& from_dlg_val = dlg.GetValue(); | ||||
|     if (m_extruders_sequence == from_dlg_val) | ||||
|         return; | ||||
| 
 | ||||
|     m_extruders_sequence = from_dlg_val; | ||||
| 
 | ||||
|     auto it = m_ticks_.begin(); | ||||
|     while (it != m_ticks_.end()) { | ||||
|         if (it->gcode == Slic3r::ExtruderChangeCode) | ||||
|             it = m_ticks_.erase(it); | ||||
|         else | ||||
|             ++it; | ||||
|     } | ||||
| 
 | ||||
|     int tick = 0; | ||||
|     double value = 0.0; | ||||
|     int extruder = 0; | ||||
|     const int extr_cnt = m_extruders_sequence.extruders.size(); | ||||
| 
 | ||||
|     std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); | ||||
| 
 | ||||
|     while (tick <= m_max_value) | ||||
|     { | ||||
|         int cur_extruder = m_extruders_sequence.extruders[extruder]; | ||||
|         m_ticks_.insert(TICK_CODE(tick, Slic3r::ExtruderChangeCode, cur_extruder + 1, colors[cur_extruder])); | ||||
| 
 | ||||
|         extruder++; | ||||
|         if (extruder == extr_cnt) | ||||
|             extruder = 0; | ||||
|         if (m_extruders_sequence.is_mm_intervals) | ||||
|         { | ||||
|             value += m_extruders_sequence.interval_by_mm; | ||||
|             auto it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon()); | ||||
| 
 | ||||
|             if (it == m_values.end()) | ||||
|                 break; | ||||
| 
 | ||||
|             tick = it - m_values.begin(); | ||||
|         } | ||||
|         else | ||||
|             tick += m_extruders_sequence.interval_by_layers; | ||||
|     } | ||||
| 
 | ||||
|     wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // LockButton
 | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| #include <vector> | ||||
| #include <set> | ||||
| #include <functional> | ||||
| #include "libslic3r/Model.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
|     enum class ModelVolumeType : int; | ||||
|  | @ -48,6 +49,8 @@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, | |||
|     std::function<void(wxCommandEvent& event)> cb, wxEvtHandler* event_handler); | ||||
| 
 | ||||
| class wxDialog; | ||||
| class wxBitmapComboBox; | ||||
| 
 | ||||
| void    edit_tooltip(wxString& tooltip); | ||||
| void    msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector<int>& btn_ids); | ||||
| int     em_unit(wxWindow* win); | ||||
|  | @ -55,7 +58,13 @@ int     em_unit(wxWindow* win); | |||
| wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name, | ||||
|     const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false); | ||||
| 
 | ||||
| std::vector<wxBitmap*> get_extruder_color_icons(); | ||||
| std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon = false); | ||||
| void apply_extruder_selector(wxBitmapComboBox** ctrl, | ||||
|                              wxWindow* parent, | ||||
|                              const std::string& first_item = "", | ||||
|                              wxPoint pos = wxDefaultPosition, | ||||
|                              wxSize size = wxDefaultSize, | ||||
|                              bool use_thin_icon = false); | ||||
| 
 | ||||
| class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup | ||||
| { | ||||
|  | @ -749,6 +758,11 @@ enum TicksAction{ | |||
| 
 | ||||
| class DoubleSlider : public wxControl | ||||
| { | ||||
|     enum IconFocus { | ||||
|         ifNone, | ||||
|         ifRevert, | ||||
|         ifCog | ||||
|     }; | ||||
| public: | ||||
|     DoubleSlider( | ||||
|         wxWindow *parent, | ||||
|  | @ -794,8 +808,8 @@ public: | |||
|         m_values = values; | ||||
|     } | ||||
|     void ChangeOneLayerLock(); | ||||
|     std::vector<double> GetTicksValues() const; | ||||
|     void SetTicksValues(const std::vector<double>& heights); | ||||
|     std::vector<Slic3r::Model::CustomGCode> GetTicksValues() const; | ||||
|     void SetTicksValues(const std::vector<Slic3r::Model::CustomGCode> &heights); | ||||
|     void EnableTickManipulation(bool enable = true) { | ||||
|         m_is_enabled_tick_manipulation = enable; | ||||
|     } | ||||
|  | @ -803,6 +817,18 @@ public: | |||
|         EnableTickManipulation(false); | ||||
|     } | ||||
| 
 | ||||
|     enum ManipulationState { | ||||
|         msSingleExtruder,   // single extruder printer preset is selected
 | ||||
|         msMultiExtruder     // multiple extruder printer preset is selected, and "Whole print" is selected 
 | ||||
|     }; | ||||
|     void SetManipulationState(ManipulationState state) { | ||||
|         m_state = state; | ||||
|     } | ||||
|     void SetManipulationState(int extruders_cnt) { | ||||
|         m_state = extruders_cnt ==1 ? msSingleExtruder : msMultiExtruder; | ||||
|     } | ||||
|     ManipulationState GetManipulationState() const { return m_state; } | ||||
| 
 | ||||
|     bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } | ||||
|     bool is_one_layer() const { return m_is_one_layer; } | ||||
|     bool is_lower_at_min() const { return m_lower_value == m_min_value; } | ||||
|  | @ -820,7 +846,12 @@ public: | |||
|     void OnKeyUp(wxKeyEvent &event); | ||||
|     void OnChar(wxKeyEvent &event); | ||||
|     void OnRightDown(wxMouseEvent& event); | ||||
|     int  get_extruder_for_tick(int tick); | ||||
|     void OnRightUp(wxMouseEvent& event); | ||||
|     void add_code(std::string code, int selected_extruder = -1); | ||||
|     void edit_tick(); | ||||
|     void change_extruder(int extruder); | ||||
|     void edit_extruder_sequence(); | ||||
| 
 | ||||
| protected: | ||||
| 
 | ||||
|  | @ -834,6 +865,7 @@ protected: | |||
|     void    draw_colored_band(wxDC& dc); | ||||
|     void    draw_one_layer_icon(wxDC& dc); | ||||
|     void    draw_revert_icon(wxDC& dc); | ||||
|     void    draw_cog_icon(wxDC &dc); | ||||
|     void    draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection); | ||||
|     void    draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection); | ||||
|     void    draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const; | ||||
|  | @ -842,6 +874,7 @@ protected: | |||
|     void    detect_selected_slider(const wxPoint& pt); | ||||
|     void    correct_lower_value(); | ||||
|     void    correct_higher_value(); | ||||
|     wxString get_tooltip(IconFocus icon_focus); | ||||
|     void    move_current_thumb(const bool condition); | ||||
|     void    action_tick(const TicksAction action); | ||||
|     void    enter_window(wxMouseEvent& event, const bool enter); | ||||
|  | @ -876,6 +909,7 @@ private: | |||
|     ScalableBitmap    m_bmp_one_layer_unlock_on; | ||||
|     ScalableBitmap    m_bmp_one_layer_unlock_off; | ||||
|     ScalableBitmap    m_bmp_revert; | ||||
|     ScalableBitmap    m_bmp_cog; | ||||
|     SelectedSlider  m_selection; | ||||
|     bool        m_is_left_down = false; | ||||
|     bool        m_is_right_down = false; | ||||
|  | @ -884,16 +918,25 @@ private: | |||
|     bool        m_is_action_icon_focesed = false; | ||||
|     bool        m_is_one_layer_icon_focesed = false; | ||||
|     bool        m_is_enabled_tick_manipulation = true; | ||||
|     bool        m_show_context_menu = false; | ||||
|     bool        m_show_edit_menu = false; | ||||
|     bool        m_edit_extruder_sequence = false; | ||||
|     bool        m_suppress_add_code = false; | ||||
|     ManipulationState m_state = msSingleExtruder; | ||||
|     std::string m_custom_gcode = ""; | ||||
|     std::string m_pause_print_msg; | ||||
| 
 | ||||
|     wxRect      m_rect_lower_thumb; | ||||
|     wxRect      m_rect_higher_thumb; | ||||
|     wxRect      m_rect_tick_action; | ||||
|     wxRect      m_rect_one_layer_icon; | ||||
|     wxRect      m_rect_revert_icon; | ||||
|     wxRect      m_rect_cog_icon; | ||||
|     wxSize      m_thumb_size; | ||||
|     int         m_tick_icon_dim; | ||||
|     int         m_lock_icon_dim; | ||||
|     int         m_revert_icon_dim; | ||||
|     int         m_cog_icon_dim; | ||||
|     long        m_style; | ||||
|     float       m_label_koef = 1.0; | ||||
| 
 | ||||
|  | @ -912,6 +955,88 @@ private: | |||
|     std::vector<wxPen*> m_segm_pens; | ||||
|     std::set<int>       m_ticks; | ||||
|     std::vector<double> m_values; | ||||
| 
 | ||||
|     struct TICK_CODE | ||||
|     { | ||||
|         TICK_CODE(int tick):tick(tick), gcode(Slic3r::ColorChangeCode), extruder(0), color("") {} | ||||
|         TICK_CODE(int tick, const std::string& code) :  | ||||
|                             tick(tick), gcode(code), extruder(0) {} | ||||
|         TICK_CODE(int tick, int extruder) : | ||||
|                             tick(tick), gcode(Slic3r::ColorChangeCode), extruder(extruder) {} | ||||
|         TICK_CODE(int tick, const std::string& code, int extruder, const std::string& color) :  | ||||
|                             tick(tick), gcode(code), extruder(extruder), color(color) {} | ||||
| 
 | ||||
|         bool operator<(const TICK_CODE& other) const { return other.tick > this->tick; } | ||||
|         bool operator>(const TICK_CODE& other) const { return other.tick < this->tick; } | ||||
|         TICK_CODE operator=(const TICK_CODE& other) const { | ||||
|             TICK_CODE ret_val(other.tick, other.gcode, other.extruder, other.color); | ||||
|             return ret_val; | ||||
|         } | ||||
| 
 | ||||
|         int         tick; | ||||
|         std::string gcode; | ||||
|         int         extruder; | ||||
|         std::string color; | ||||
|     }; | ||||
| 
 | ||||
|     std::set<TICK_CODE> m_ticks_; | ||||
| 
 | ||||
| public: | ||||
|     struct ExtrudersSequence | ||||
|     { | ||||
|         bool            is_mm_intervals; | ||||
|         double          interval_by_mm; | ||||
|         int             interval_by_layers; | ||||
|         std::vector<size_t>  extruders; | ||||
| 
 | ||||
|         ExtrudersSequence() : | ||||
|             is_mm_intervals(true), | ||||
|             interval_by_mm(3.0), | ||||
|             interval_by_layers(10), | ||||
|             extruders({ 0 }) {} | ||||
| 
 | ||||
|         ExtrudersSequence(const ExtrudersSequence& other) : | ||||
|             is_mm_intervals(other.is_mm_intervals), | ||||
|             interval_by_mm(other.interval_by_mm), | ||||
|             interval_by_layers(other.interval_by_layers), | ||||
|             extruders(other.extruders) {} | ||||
| 
 | ||||
|         ExtrudersSequence& operator=(const ExtrudersSequence& other) { | ||||
|             this->is_mm_intervals   = other.is_mm_intervals; | ||||
|             this->interval_by_mm    = other.interval_by_mm; | ||||
|             this->interval_by_layers= other.interval_by_layers; | ||||
|             this->extruders         = other.extruders; | ||||
| 
 | ||||
|             return *this; | ||||
|         } | ||||
|         bool operator==(const ExtrudersSequence& other) const | ||||
|         { | ||||
|             return  (other.is_mm_intervals      == this->is_mm_intervals    ) && | ||||
|                     (other.interval_by_mm       == this->interval_by_mm     ) && | ||||
|                     (other.interval_by_layers   == this->interval_by_layers ) && | ||||
|                     (other.extruders            == this->extruders          ) ; | ||||
|         } | ||||
|         bool operator!=(const ExtrudersSequence& other) const | ||||
|         { | ||||
|             return  (other.is_mm_intervals      != this->is_mm_intervals    ) && | ||||
|                     (other.interval_by_mm       != this->interval_by_mm     ) && | ||||
|                     (other.interval_by_layers   != this->interval_by_layers ) && | ||||
|                     (other.extruders            != this->extruders          ) ; | ||||
|         } | ||||
| 
 | ||||
|         void add_extruder(size_t pos) | ||||
|         { | ||||
|             extruders.insert(extruders.begin() + pos+1, size_t(0)); | ||||
|         } | ||||
| 
 | ||||
|         void delete_extruder(size_t pos) | ||||
|         {             | ||||
|             if (extruders.size() == 1) | ||||
|                 return;// last item can't be deleted
 | ||||
|             extruders.erase(extruders.begin() + pos); | ||||
|         } | ||||
|     } | ||||
|     m_extruders_sequence; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 YuSanka
						YuSanka