mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'master' into lm_tm_hollowing
This commit is contained in:
		
						commit
						b3f15b1c98
					
				
					 68 changed files with 1399 additions and 917 deletions
				
			
		|  | @ -1,8 +1,5 @@ | |||
| cmake_minimum_required(VERSION 3.0) | ||||
| 
 | ||||
| set(CMAKE_CXX_STANDARD 11) | ||||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||||
| 
 | ||||
| add_definitions(-D_BSD_SOURCE -D_DEFAULT_SOURCE)   # To enable various useful macros and functions on Unices | ||||
| remove_definitions(-D_UNICODE -DUNICODE) | ||||
| set(CMAKE_POSITION_INDEPENDENT_CODE ON) | ||||
|  |  | |||
|  | @ -15,5 +15,5 @@ add_library(hidapi STATIC ${HIDAPI_IMPL}) | |||
| if (CMAKE_SYSTEM_NAME STREQUAL "Linux") | ||||
| 	# Don't link the udev library, as there are two versions out there (libudev.so.0, libudev.so.1), so they are linked explicitely. | ||||
| #	target_link_libraries(hidapi udev) | ||||
| 	target_link_libraries(hidapi) | ||||
| 	target_link_libraries(hidapi dl) | ||||
| endif() | ||||
|  |  | |||
							
								
								
									
										5
									
								
								src/hidapi/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/hidapi/README.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| ** hidapi is a c++ library for communicating with USB and Bluetooth HID devices on Linux, Mac and Windows.** | ||||
| 
 | ||||
| For more information go to https://github.com/libusb/hidapi | ||||
| 
 | ||||
| THIS DIRECTORY CONTAINS THE hidapi-0.9.0 7da5cc9 SOURCE DISTRIBUTION. | ||||
|  | @ -679,133 +679,73 @@ ClipperLib::PolyTree union_pt(ExPolygons &&subject, bool safety_offset_) | |||
|     return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_); | ||||
| } | ||||
| 
 | ||||
| Polygons | ||||
| union_pt_chained(const Polygons &subject, bool safety_offset_) | ||||
| { | ||||
|     ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_); | ||||
|      | ||||
|     Polygons retval; | ||||
|     traverse_pt(polytree.Childs, &retval); | ||||
|     return retval; | ||||
| } | ||||
| 
 | ||||
| static ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes) | ||||
| // Simple spatial ordering of Polynodes
 | ||||
| ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes) | ||||
| { | ||||
|     // collect ordering points
 | ||||
|     Points ordering_points; | ||||
|     ordering_points.reserve(nodes.size()); | ||||
|      | ||||
|     for (const ClipperLib::PolyNode *node : nodes) | ||||
|         ordering_points.emplace_back(Point(node->Contour.front().X, node->Contour.front().Y)); | ||||
|      | ||||
|         ordering_points.emplace_back( | ||||
|             Point(node->Contour.front().X, node->Contour.front().Y)); | ||||
| 
 | ||||
|     // perform the ordering
 | ||||
|     ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes); | ||||
|      | ||||
|     ClipperLib::PolyNodes ordered_nodes = | ||||
|         chain_clipper_polynodes(ordering_points, nodes); | ||||
| 
 | ||||
|     return ordered_nodes; | ||||
| } | ||||
| 
 | ||||
| enum class e_ordering { | ||||
|     ORDER_POLYNODES, | ||||
|     DONT_ORDER_POLYNODES | ||||
| }; | ||||
| 
 | ||||
| template<e_ordering o> | ||||
| void foreach_node(const ClipperLib::PolyNodes &nodes, | ||||
|                   std::function<void(const ClipperLib::PolyNode *)> fn); | ||||
| 
 | ||||
| template<> void foreach_node<e_ordering::DONT_ORDER_POLYNODES>( | ||||
|     const ClipperLib::PolyNodes &                     nodes, | ||||
|     std::function<void(const ClipperLib::PolyNode *)> fn) | ||||
| static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *out) | ||||
| { | ||||
|     for (auto &n : nodes) fn(n); | ||||
|     foreach_node<e_ordering::ON>(nodes, [&out](const ClipperLib::PolyNode *node)  | ||||
|     { | ||||
|         traverse_pt_noholes(node->Childs, out); | ||||
|         out->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); | ||||
|         if (node->IsHole()) out->back().reverse(); // ccw
 | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| template<> void foreach_node<e_ordering::ORDER_POLYNODES>( | ||||
|     const ClipperLib::PolyNodes &                     nodes, | ||||
|     std::function<void(const ClipperLib::PolyNode *)> fn) | ||||
| { | ||||
|     auto ordered_nodes = order_nodes(nodes); | ||||
|     for (auto &n : ordered_nodes) fn(n); | ||||
| } | ||||
| 
 | ||||
| template<e_ordering o> | ||||
| void _traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval) | ||||
| static void traverse_pt_old(ClipperLib::PolyNodes &nodes, Polygons* retval) | ||||
| { | ||||
|     /* use a nearest neighbor search to order these children
 | ||||
|        TODO: supply start_near to chained_path() too? */ | ||||
|      | ||||
|     // collect ordering points
 | ||||
|     Points ordering_points; | ||||
|     ordering_points.reserve(nodes.size()); | ||||
|     for (ClipperLib::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { | ||||
|         Point p((*it)->Contour.front().X, (*it)->Contour.front().Y); | ||||
|         ordering_points.push_back(p); | ||||
|     } | ||||
|      | ||||
|     // perform the ordering
 | ||||
|     ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes); | ||||
|      | ||||
|     // push results recursively
 | ||||
|     foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) { | ||||
|     for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) { | ||||
|         // traverse the next depth
 | ||||
|         _traverse_pt<o>(node->Childs, retval); | ||||
|         retval->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); | ||||
|         if (node->IsHole()) retval->back().reverse();  // ccw
 | ||||
|     }); | ||||
|         traverse_pt_old((*it)->Childs, retval); | ||||
|         retval->push_back(ClipperPath_to_Slic3rPolygon((*it)->Contour)); | ||||
|         if ((*it)->IsHole()) retval->back().reverse();  // ccw
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| template<e_ordering o> | ||||
| void _traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval) | ||||
| Polygons union_pt_chained(const Polygons &subject, bool safety_offset_) | ||||
| { | ||||
|     if (!retval || !tree) return; | ||||
|     ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_); | ||||
|      | ||||
|     ExPolygons &retv = *retval; | ||||
|     Polygons retval; | ||||
|     traverse_pt_old(polytree.Childs, &retval); | ||||
|     return retval; | ||||
|      | ||||
|     std::function<void(const ClipperLib::PolyNode*, ExPolygon&)> hole_fn; | ||||
| // TODO: This needs to be tested:
 | ||||
| //    ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_);
 | ||||
|      | ||||
|     auto contour_fn = [&retv, &hole_fn](const ClipperLib::PolyNode *pptr) { | ||||
|         ExPolygon poly; | ||||
|         poly.contour.points = ClipperPath_to_Slic3rPolygon(pptr->Contour); | ||||
|         auto fn = std::bind(hole_fn, std::placeholders::_1, poly); | ||||
|         foreach_node<o>(pptr->Childs, fn); | ||||
|         retv.push_back(poly); | ||||
|     }; | ||||
|      | ||||
|     hole_fn = [&contour_fn](const ClipperLib::PolyNode *pptr, ExPolygon& poly) | ||||
|     {    | ||||
|         poly.holes.emplace_back(); | ||||
|         poly.holes.back().points = ClipperPath_to_Slic3rPolygon(pptr->Contour); | ||||
|         foreach_node<o>(pptr->Childs, contour_fn); | ||||
|     }; | ||||
|      | ||||
|     contour_fn(tree); | ||||
| } | ||||
| 
 | ||||
| template<e_ordering o> | ||||
| void _traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) | ||||
| { | ||||
|     // Here is the actual traverse
 | ||||
|     foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) { | ||||
|         _traverse_pt<o>(node, retval); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::ORDER_POLYNODES>(tree, retval); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNode *tree, ExPolygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(tree, retval); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::ORDER_POLYNODES>(nodes, retval); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::ORDER_POLYNODES>(nodes, retval); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Polygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(nodes, retval); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) | ||||
| { | ||||
|     _traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(nodes, retval); | ||||
| //    Polygons retval;
 | ||||
| //    traverse_pt_noholes(polytree.Childs, &retval);
 | ||||
| //    return retval;
 | ||||
| } | ||||
| 
 | ||||
| Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear) | ||||
|  |  | |||
|  | @ -214,7 +214,6 @@ inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_ | |||
|     return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false); | ||||
| ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject, bool safety_offset_ = false); | ||||
| ClipperLib::PolyTree union_pt(Slic3r::Polygons &&subject, bool safety_offset_ = false); | ||||
|  | @ -222,13 +221,95 @@ ClipperLib::PolyTree union_pt(Slic3r::ExPolygons &&subject, bool safety_offset_ | |||
| 
 | ||||
| Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false); | ||||
| 
 | ||||
| void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval); | ||||
| void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval); | ||||
| void traverse_pt(const ClipperLib::PolyNode  *tree,  Slic3r::ExPolygons *retval); | ||||
| ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes); | ||||
| 
 | ||||
| // Implementing generalized loop (foreach) over a list of nodes which can be
 | ||||
| // ordered or unordered (performance gain) based on template parameter
 | ||||
| enum class e_ordering { | ||||
|     ON, | ||||
|     OFF | ||||
| }; | ||||
| 
 | ||||
| // Create a template struct, template functions can not be partially specialized
 | ||||
| template<e_ordering o, class Fn> struct _foreach_node { | ||||
|     void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn); | ||||
| }; | ||||
| 
 | ||||
| // Specialization with NO ordering
 | ||||
| template<class Fn> struct _foreach_node<e_ordering::OFF, Fn> { | ||||
|     void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn) | ||||
|     { | ||||
|         for (auto &n : nodes) fn(n);     | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // Specialization with ordering
 | ||||
| template<class Fn> struct _foreach_node<e_ordering::ON, Fn> { | ||||
|     void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn) | ||||
|     { | ||||
|         auto ordered_nodes = order_nodes(nodes); | ||||
|         for (auto &n : nodes) fn(n);     | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // Wrapper function for the foreach_node which can deduce arguments automatically
 | ||||
| template<e_ordering o, class Fn> | ||||
| void foreach_node(const ClipperLib::PolyNodes &nodes, Fn &&fn) | ||||
| { | ||||
|     _foreach_node<o, Fn>()(nodes, std::forward<Fn>(fn)); | ||||
| } | ||||
| 
 | ||||
| // Collecting polygons of the tree into a list of Polygons, holes have clockwise
 | ||||
| // orientation.
 | ||||
| template<e_ordering ordering = e_ordering::OFF> | ||||
| void traverse_pt(const ClipperLib::PolyNode *tree, Polygons *out) | ||||
| { | ||||
|     if (!tree) return; // terminates recursion
 | ||||
|      | ||||
|     // Push the contour of the current level
 | ||||
|     out->emplace_back(ClipperPath_to_Slic3rPolygon(tree->Contour)); | ||||
|      | ||||
|     // Do the recursion for all the children.
 | ||||
|     traverse_pt<ordering>(tree->Childs, out); | ||||
| } | ||||
| 
 | ||||
| // Collecting polygons of the tree into a list of ExPolygons.
 | ||||
| template<e_ordering ordering = e_ordering::OFF> | ||||
| void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *out) | ||||
| { | ||||
|     if (!tree) return; | ||||
|     else if(tree->IsHole()) { | ||||
|         // Levels of holes are skipped and handled together with the
 | ||||
|         // contour levels.
 | ||||
|         traverse_pt<ordering>(tree->Childs, out); | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     ExPolygon level; | ||||
|     level.contour = ClipperPath_to_Slic3rPolygon(tree->Contour); | ||||
|      | ||||
|     foreach_node<ordering>(tree->Childs,  | ||||
|                            [out, &level] (const ClipperLib::PolyNode *node) { | ||||
|          | ||||
|         // Holes are collected here. 
 | ||||
|         level.holes.emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); | ||||
|          | ||||
|         // By doing a recursion, a new level expoly is created with the contour
 | ||||
|         // and holes of the lower level. Doing this for all the childs.
 | ||||
|         traverse_pt<ordering>(node->Childs, out); | ||||
|     });  | ||||
|      | ||||
|     out->emplace_back(level); | ||||
| } | ||||
| 
 | ||||
| template<e_ordering o = e_ordering::OFF, class ExOrJustPolygons> | ||||
| void traverse_pt(const ClipperLib::PolyNodes &nodes, ExOrJustPolygons *retval) | ||||
| { | ||||
|     foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) { | ||||
|         traverse_pt<o>(node, retval); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval); | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval); | ||||
| void traverse_pt_unordered(const ClipperLib::PolyNode  *tree,  Slic3r::ExPolygons *retval); | ||||
| 
 | ||||
| /* OTHER */ | ||||
| Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false); | ||||
|  |  | |||
|  | @ -612,7 +612,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file) | |||
| 	} | ||||
|     ifs.seekg(0, ifs.end); | ||||
| 	auto file_length = ifs.tellg(); | ||||
| 	auto data_length = std::min<std::fstream::streampos>(65535, file_length); | ||||
| 	auto data_length = std::min<std::fstream::pos_type>(65535, file_length); | ||||
| 	ifs.seekg(file_length - data_length, ifs.beg); | ||||
|     std::vector<char> data(size_t(data_length) + 1, 0); | ||||
|     ifs.read(data.data(), data_length); | ||||
|  |  | |||
|  | @ -472,7 +472,7 @@ static inline void smooth_compensation_banded(const Points &contour, float band, | |||
| 			float	l2    = (pthis - pprev).squaredNorm(); | ||||
| 			if (l2 < dist_min2) { | ||||
| 				float l = sqrt(l2); | ||||
| 				int jprev = exchange(j, prev_idx_modulo(j, contour)); | ||||
| 				int jprev = std::exchange(j, prev_idx_modulo(j, contour)); | ||||
| 				while (j != i) { | ||||
| 					const Vec2f pp = contour[j].cast<float>(); | ||||
| 					const float lthis = (pp - pprev).norm(); | ||||
|  | @ -487,7 +487,7 @@ static inline void smooth_compensation_banded(const Points &contour, float band, | |||
| 					prev  = use_min ? std::min(prev, compensation[j]) : compensation[j]; | ||||
| 					pprev = pp; | ||||
| 					l     = lnext; | ||||
| 					jprev = exchange(j, prev_idx_modulo(j, contour)); | ||||
| 					jprev = std::exchange(j, prev_idx_modulo(j, contour)); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
|  | @ -497,7 +497,7 @@ static inline void smooth_compensation_banded(const Points &contour, float band, | |||
| 			l2 = (pprev - pthis).squaredNorm(); | ||||
| 			if (l2 < dist_min2) { | ||||
| 				float l = sqrt(l2); | ||||
| 				int jprev = exchange(j, next_idx_modulo(j, contour)); | ||||
| 				int jprev = std::exchange(j, next_idx_modulo(j, contour)); | ||||
| 				while (j != i) { | ||||
| 					const Vec2f pp = contour[j].cast<float>(); | ||||
| 					const float lthis = (pp - pprev).norm(); | ||||
|  | @ -512,7 +512,7 @@ static inline void smooth_compensation_banded(const Points &contour, float band, | |||
| 					next  = use_min ? std::min(next, compensation[j]) : compensation[j]; | ||||
| 					pprev = pp; | ||||
| 					l     = lnext; | ||||
| 					jprev = exchange(j, next_idx_modulo(j, contour)); | ||||
| 					jprev = std::exchange(j, next_idx_modulo(j, contour)); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
|  |  | |||
|  | @ -34,8 +34,12 @@ namespace pt = boost::property_tree; | |||
| // VERSION NUMBERS
 | ||||
| // 0 : .3mf, files saved by older slic3r or other applications. No version definition in them.
 | ||||
| // 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files.
 | ||||
| // 2 : Meshes saved in their local system; Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file.
 | ||||
| const unsigned int VERSION_3MF = 2; | ||||
| // 2 : Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file, meshes transformed back to their coordinate system on loading.
 | ||||
| // WARNING !! -> the version number has been rolled back to 1
 | ||||
| //               the next change should use 3
 | ||||
| const unsigned int VERSION_3MF = 1; | ||||
| // Allow loading version 2 file as well.
 | ||||
| const unsigned int VERSION_3MF_COMPATIBLE = 2; | ||||
| const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file
 | ||||
| 
 | ||||
| const std::string MODEL_FOLDER = "3D/"; | ||||
|  | @ -52,7 +56,7 @@ const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights | |||
| 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 SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; | ||||
| const std::string CUSTOM_GCODE_PER_HEIGHT_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_height.xml"; | ||||
| const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"; | ||||
| 
 | ||||
| const char* MODEL_TAG = "model"; | ||||
| const char* RESOURCES_TAG = "resources"; | ||||
|  | @ -422,7 +426,7 @@ namespace Slic3r { | |||
|         void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); | ||||
|         void _extract_sla_drain_holes_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_custom_gcode_per_print_z_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); | ||||
|  | @ -638,10 +642,10 @@ 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)) | ||||
|                 if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) | ||||
|                 { | ||||
|                     // extract slic3r layer config ranges file
 | ||||
|                     _extract_custom_gcode_per_height_from_archive(archive, stat); | ||||
|                     _extract_custom_gcode_per_print_z_from_archive(archive, stat); | ||||
|                 } | ||||
|                 else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) | ||||
|                 { | ||||
|  | @ -1163,7 +1167,7 @@ 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) | ||||
|     void _3MF_Importer::_extract_custom_gcode_per_print_z_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat) | ||||
|     { | ||||
|         if (stat.m_uncomp_size > 0) | ||||
|         { | ||||
|  | @ -1178,24 +1182,23 @@ namespace Slic3r { | |||
|             pt::ptree main_tree; | ||||
|             pt::read_xml(iss, main_tree); | ||||
| 
 | ||||
|             if (main_tree.front().first != "custom_gcodes_per_height") | ||||
|             if (main_tree.front().first != "custom_gcodes_per_print_z") | ||||
|                 return; | ||||
|             pt::ptree code_tree = main_tree.front().second; | ||||
| 
 | ||||
|             if (!m_model->custom_gcode_per_height.empty()) | ||||
|                 m_model->custom_gcode_per_height.clear(); | ||||
|             m_model->custom_gcode_per_print_z.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"); | ||||
|                 double print_z      = tree.get<double>      ("<xmlattr>.print_z"    ); | ||||
|                 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)) ; | ||||
|                 m_model->custom_gcode_per_print_z.push_back(Model::CustomGCode{print_z, gcode, extruder, color}) ; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -1611,7 +1614,7 @@ namespace Slic3r { | |||
|         { | ||||
|             m_version = (unsigned int)atoi(m_curr_characters.c_str()); | ||||
| 
 | ||||
|             if (m_check_version && (m_version > VERSION_3MF)) | ||||
|             if (m_check_version && (m_version > VERSION_3MF_COMPATIBLE)) | ||||
|             { | ||||
|                 // std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
 | ||||
|                 // throw version_error(msg.c_str());
 | ||||
|  | @ -1797,20 +1800,19 @@ namespace Slic3r { | |||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             Slic3r::Geometry::Transformation transform; | ||||
|             if (m_version > 1) | ||||
|             Transform3d volume_matrix_to_object = Transform3d::Identity(); | ||||
|             bool        has_transform 		    = false; | ||||
|             // extract the volume transformation from the volume's metadata, if present
 | ||||
|             for (const Metadata& metadata : volume_data.metadata) | ||||
|             { | ||||
|                 // extract the volume transformation from the volume's metadata, if present
 | ||||
|                 for (const Metadata& metadata : volume_data.metadata) | ||||
|                 if (metadata.key == MATRIX_KEY) | ||||
|                 { | ||||
|                     if (metadata.key == MATRIX_KEY) | ||||
|                     { | ||||
|                         transform.set_from_string(metadata.value); | ||||
|                         break; | ||||
|                     } | ||||
|                     volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); | ||||
|                     has_transform 			= ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             Transform3d inv_matrix = transform.get_matrix().inverse(); | ||||
|             Transform3d inv_matrix = volume_matrix_to_object.inverse(); | ||||
| 
 | ||||
|             // splits volume out of imported geometry
 | ||||
| 			TriangleMesh triangle_mesh; | ||||
|  | @ -1831,10 +1833,10 @@ namespace Slic3r { | |||
|                 { | ||||
|                     unsigned int tri_id = geometry.triangles[src_start_id + ii + v] * 3; | ||||
|                     Vec3f vertex(geometry.vertices[tri_id + 0], geometry.vertices[tri_id + 1], geometry.vertices[tri_id + 2]); | ||||
|                     if (m_version > 1) | ||||
|                     facet.vertex[v] = has_transform ? | ||||
|                         // revert the vertices to the original mesh reference system
 | ||||
|                         vertex = (inv_matrix * vertex.cast<double>()).cast<float>(); | ||||
|                     ::memcpy(facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float)); | ||||
|                         (inv_matrix * vertex.cast<double>()).cast<float>() : | ||||
|                         vertex; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -1843,8 +1845,8 @@ namespace Slic3r { | |||
| 
 | ||||
| 			ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); | ||||
|             // apply the volume matrix taken from the metadata, if present
 | ||||
|             if (m_version > 1) | ||||
|                 volume->set_transformation(transform); | ||||
|             if (has_transform) | ||||
|                 volume->set_transformation(Slic3r::Geometry::Transformation(volume_matrix_to_object)); | ||||
|             volume->calculate_convex_hull(); | ||||
| 
 | ||||
|             // apply the remaining volume's metadata
 | ||||
|  | @ -1985,7 +1987,7 @@ namespace Slic3r { | |||
|         bool _add_sla_drain_holes_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); | ||||
|         bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model); | ||||
|     }; | ||||
| 
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
|  | @ -2096,9 +2098,9 @@ namespace Slic3r { | |||
|         } | ||||
|          | ||||
| 
 | ||||
|         // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_height.xml").
 | ||||
|         // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
 | ||||
|         // All custom gcode per height of whole Model are stored here
 | ||||
|         if (!_add_custom_gcode_per_height_file_to_archive(archive, model)) | ||||
|         if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model)) | ||||
|         { | ||||
|             close_zip_writer(&archive); | ||||
|             boost::filesystem::remove(filename); | ||||
|  | @ -2622,6 +2624,9 @@ namespace Slic3r { | |||
|     bool _3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data) | ||||
|     { | ||||
|         std::stringstream stream; | ||||
|         // Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back
 | ||||
|         // when loaded as accurately as possible.
 | ||||
| 		stream << std::setprecision(std::numeric_limits<double>::max_digits10); | ||||
|         stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; | ||||
|         stream << "<" << CONFIG_TAG << ">\n"; | ||||
| 
 | ||||
|  | @ -2719,20 +2724,20 @@ namespace Slic3r { | |||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| bool _3MF_Exporter::_add_custom_gcode_per_height_file_to_archive( mz_zip_archive& archive, Model& model) | ||||
| bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model) | ||||
| { | ||||
|     std::string out = ""; | ||||
| 
 | ||||
|     if (!model.custom_gcode_per_height.empty()) | ||||
|     if (!model.custom_gcode_per_print_z.empty()) | ||||
|     { | ||||
|         pt::ptree tree; | ||||
|         pt::ptree& main_tree = tree.add("custom_gcodes_per_height", ""); | ||||
|         pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", ""); | ||||
| 
 | ||||
|         for (const Model::CustomGCode& code : model.custom_gcode_per_height) | ||||
|         for (const Model::CustomGCode& code : model.custom_gcode_per_print_z) | ||||
|         { | ||||
|             pt::ptree& code_tree = main_tree.add("code", ""); | ||||
|             // store minX and maxZ
 | ||||
|             code_tree.put("<xmlattr>.height"    , code.height   ); | ||||
|             code_tree.put("<xmlattr>.print_z"   , code.print_z  ); | ||||
|             code_tree.put("<xmlattr>.gcode"     , code.gcode    ); | ||||
|             code_tree.put("<xmlattr>.extruder"  , code.extruder ); | ||||
|             code_tree.put("<xmlattr>.color"     , code.color    ); | ||||
|  | @ -2751,9 +2756,9 @@ bool _3MF_Exporter::_add_custom_gcode_per_height_file_to_archive( mz_zip_archive | |||
| 
 | ||||
|     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)) | ||||
|         if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) | ||||
|         { | ||||
|             add_error("Unable to add custom Gcodes per height file to archive"); | ||||
|             add_error("Unable to add custom Gcodes per print_z file to archive"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -41,8 +41,11 @@ namespace pt = boost::property_tree; | |||
| //     Added x and y components of rotation
 | ||||
| //     Added x, y and z components of scale
 | ||||
| //     Added x, y and z components of mirror
 | ||||
| // 3 : Meshes saved in their local system; Added volumes' matrices and source data
 | ||||
| const unsigned int VERSION_AMF = 3; | ||||
| // 3 : Added volumes' matrices and source data, meshes transformed back to their coordinate system on loading.
 | ||||
| // WARNING !! -> the version number has been rolled back to 2
 | ||||
| //               the next change should use 4
 | ||||
| const unsigned int VERSION_AMF = 2; | ||||
| const unsigned int VERSION_AMF_COMPATIBLE = 3; | ||||
| const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version"; | ||||
| 
 | ||||
| const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config"; | ||||
|  | @ -228,6 +231,8 @@ struct AMFParserContext | |||
|     ModelVolume             *m_volume; | ||||
|     // Faces collected for the current m_volume.
 | ||||
|     std::vector<int>         m_volume_facets; | ||||
|     // Transformation matrix of a volume mesh from its coordinate system to Object's coordinate system.
 | ||||
|     Transform3d 			 m_volume_transform; | ||||
|     // Current material allocated for an amf/metadata subtree.
 | ||||
|     ModelMaterial           *m_material; | ||||
|     // Current instance allocated for an amf/constellation/instance subtree.
 | ||||
|  | @ -319,6 +324,7 @@ void AMFParserContext::startElement(const char *name, const char **atts) | |||
| 			else if (strcmp(name, "volume") == 0) { | ||||
| 				assert(! m_volume); | ||||
|                 m_volume = m_object->add_volume(TriangleMesh()); | ||||
|                 m_volume_transform = Transform3d::Identity(); | ||||
|                 node_type_new = NODE_TYPE_VOLUME; | ||||
| 			} | ||||
|         } else if (m_path[2] == NODE_TYPE_INSTANCE) { | ||||
|  | @ -578,27 +584,25 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|         stl.stats.original_num_facets = stl.stats.number_of_facets; | ||||
|         stl_allocate(&stl); | ||||
| 
 | ||||
|         Slic3r::Geometry::Transformation transform; | ||||
|         if (m_version > 2) | ||||
|             transform = m_volume->get_transformation(); | ||||
| 
 | ||||
|         Transform3d inv_matrix = transform.get_matrix().inverse(); | ||||
| 
 | ||||
|         bool has_transform = ! m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); | ||||
|         Transform3d inv_matrix = m_volume_transform.inverse(); | ||||
|         for (size_t i = 0; i < m_volume_facets.size();) { | ||||
|             stl_facet &facet = stl.facet_start[i/3]; | ||||
|             for (unsigned int v = 0; v < 3; ++v) | ||||
|             { | ||||
|                 unsigned int tri_id = m_volume_facets[i++] * 3; | ||||
|                 Vec3f vertex(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]); | ||||
|                 if (m_version > 2) | ||||
|                 facet.vertex[v] = has_transform ? | ||||
|                     // revert the vertices to the original mesh reference system
 | ||||
|                     vertex = (inv_matrix * vertex.cast<double>()).cast<float>(); | ||||
|                 ::memcpy((void*)facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float)); | ||||
|                     (inv_matrix * vertex.cast<double>()).cast<float>() : | ||||
|                     vertex; | ||||
|             } | ||||
|         } | ||||
|         }         | ||||
|         stl_get_size(&stl); | ||||
|         mesh.repair(); | ||||
| 		m_volume->set_mesh(std::move(mesh)); | ||||
| 		if (has_transform) | ||||
| 			m_volume->set_transformation(m_volume_transform); | ||||
|         if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) | ||||
|         { | ||||
|             m_volume->source.object_idx = (int)m_model.objects.size() - 1; | ||||
|  | @ -637,7 +641,7 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|         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)); | ||||
|         m_model.custom_gcode_per_print_z.push_back(Model::CustomGCode{height, gcode, extruder, color}); | ||||
| 
 | ||||
|         for (std::string& val: m_value) | ||||
|             val.clear(); | ||||
|  | @ -718,9 +722,7 @@ void AMFParserContext::endElement(const char * /* name */) | |||
|                     m_volume->set_type(ModelVolume::type_from_string(m_value[1])); | ||||
|                 } | ||||
|                 else if (strcmp(opt_key, "matrix") == 0) { | ||||
|                     Geometry::Transformation transform; | ||||
|                     transform.set_from_string(m_value[1]); | ||||
|                     m_volume->set_transformation(transform); | ||||
|                     m_volume_transform = Slic3r::Geometry::transform3d_from_string(m_value[1]); | ||||
|                 } | ||||
|                 else if (strcmp(opt_key, "source_file") == 0) { | ||||
|                     m_volume->source.input_file = m_value[1]; | ||||
|  | @ -910,7 +912,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi | |||
| 
 | ||||
|     ctx.endDocument(); | ||||
| 
 | ||||
|     if (check_version && (ctx.m_version > VERSION_AMF)) | ||||
|     if (check_version && (ctx.m_version > VERSION_AMF_COMPATIBLE)) | ||||
|     { | ||||
|         // std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
 | ||||
|         // throw std::runtime_error(msg.c_str());
 | ||||
|  | @ -1146,6 +1148,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) | |||
|             stream << "        <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n"; | ||||
|             stream << "        <metadata type=\"slic3r.matrix\">"; | ||||
|             const Transform3d& matrix = volume->get_matrix(); | ||||
| 			stream << std::setprecision(std::numeric_limits<double>::max_digits10); | ||||
|             for (int r = 0; r < 4; ++r) | ||||
|             { | ||||
|                 for (int c = 0; c < 4; ++c) | ||||
|  | @ -1165,6 +1168,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) | |||
|                 stream << "        <metadata type=\"slic3r.source_offset_y\">" << volume->source.mesh_offset(1) << "</metadata>\n"; | ||||
|                 stream << "        <metadata type=\"slic3r.source_offset_z\">" << volume->source.mesh_offset(2) << "</metadata>\n"; | ||||
|             } | ||||
| 			stream << std::setprecision(std::numeric_limits<float>::max_digits10); | ||||
|             const indexed_triangle_set &its = volume->mesh().its; | ||||
|             for (size_t i = 0; i < its.indices.size(); ++i) { | ||||
|                 stream << "        <triangle>\n"; | ||||
|  | @ -1221,21 +1225,21 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) | |||
|         stream << "  </constellation>\n"; | ||||
|     } | ||||
| 
 | ||||
|     if (!model->custom_gcode_per_height.empty()) | ||||
|     if (!model->custom_gcode_per_print_z.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) | ||||
|         for (const Model::CustomGCode& code : model->custom_gcode_per_print_z) | ||||
|         { | ||||
|             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); | ||||
|             code_tree.put("<xmlattr>.print_z"   , code.print_z  ); | ||||
|             code_tree.put("<xmlattr>.gcode"     , code.gcode    ); | ||||
|             code_tree.put("<xmlattr>.extruder"  , code.extruder ); | ||||
|             code_tree.put("<xmlattr>.color"     , code.color    ); | ||||
|         } | ||||
| 
 | ||||
|         if (!tree.empty()) | ||||
|  |  | |||
|  | @ -936,7 +936,7 @@ void GCode::_do_export(Print& print, FILE* file) | |||
| 
 | ||||
|     // Initialize custom gcode
 | ||||
|     Model* model = print.get_object(0)->model_object()->get_model(); | ||||
|     m_custom_g_code_heights = model->custom_gcode_per_height; | ||||
|     m_custom_gcode_per_print_z = model->custom_gcode_per_print_z; | ||||
| 
 | ||||
|     // Initialize autospeed.
 | ||||
|     { | ||||
|  | @ -1124,20 +1124,22 @@ void GCode::_do_export(Print& print, FILE* file) | |||
|     } | ||||
|     print.throw_if_canceled(); | ||||
| 
 | ||||
| // #ys_FIXME_no_exported_codes
 | ||||
|     /*
 | ||||
|     /* 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()) | ||||
|      * check model->custom_gcode_per_print_z using tool_ordering values | ||||
|      * / | ||||
|     if (!m_custom_gcode_per_print_z. empty()) | ||||
|     { | ||||
|         bool delete_executed = false; | ||||
|         auto it = m_custom_g_code_heights.end(); | ||||
|         while (it != m_custom_g_code_heights.begin()) | ||||
|         auto it = m_custom_gcode_per_print_z.end(); | ||||
|         while (it != m_custom_gcode_per_print_z.begin()) | ||||
|         { | ||||
|             --it; | ||||
|             if (it->gcode != ColorChangeCode) | ||||
|                 continue; | ||||
| 
 | ||||
|             auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(it->height)); | ||||
|             auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(it->print_z)); | ||||
| 
 | ||||
|             bool used_extruder = false; | ||||
|             for (; it_layer_tools != tool_ordering.end(); it_layer_tools++) | ||||
|  | @ -1154,16 +1156,16 @@ void GCode::_do_export(Print& print, FILE* file) | |||
| 
 | ||||
|             /* 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 this item from m_custom_gcode_per_print_z | ||||
|              * / | ||||
|             it = m_custom_gcode_per_print_z.erase(it); | ||||
|             delete_executed = true; | ||||
|         } | ||||
| 
 | ||||
|         if (delete_executed) | ||||
|             model->custom_gcode_per_height = m_custom_g_code_heights; | ||||
|             model->custom_gcode_per_print_z = m_custom_gcode_per_print_z; | ||||
|     } | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
|     m_cooling_buffer->set_current_extruder(initial_extruder_id); | ||||
| 
 | ||||
|  | @ -1461,7 +1463,7 @@ void GCode::_do_export(Print& print, FILE* file) | |||
|                 dst.first += buf; | ||||
|                 ++ dst.second; | ||||
|             }; | ||||
|             print.m_print_statistics.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament)); | ||||
|             print.m_print_statistics.filament_stats.insert(std::pair<size_t, float>{extruder.id(), (float)used_filament}); | ||||
|             append(out_filament_used_mm,  "%.1lf", used_filament); | ||||
|             append(out_filament_used_cm3, "%.1lf", extruded_volume * 0.001); | ||||
|             if (filament_weight > 0.) { | ||||
|  | @ -1835,15 +1837,15 @@ void GCode::process_layer( | |||
|     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; | ||||
|     while (!m_custom_gcode_per_print_z.empty() && m_custom_gcode_per_print_z.front().print_z - EPSILON < layer.print_z) { | ||||
|         custom_code = m_custom_gcode_per_print_z.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 == ColorChangeCode && m_custom_gcode_per_print_z.front().extruder > 0) | ||||
|             m600_before_extruder = m_custom_gcode_per_print_z.front().extruder - 1; | ||||
|         if (custom_code == PausePrintCode) | ||||
|             pause_print_msg = m_custom_g_code_heights.front().color; | ||||
|             pause_print_msg = m_custom_gcode_per_print_z.front().color; | ||||
| 
 | ||||
|         m_custom_g_code_heights.erase(m_custom_g_code_heights.begin()); | ||||
|         m_custom_gcode_per_print_z.erase(m_custom_gcode_per_print_z.begin()); | ||||
|         colorprint_change = true; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -367,7 +367,7 @@ protected: | |||
|      * Updated before the export and erased during the process, | ||||
|      * so no toolchange occurs twice. | ||||
|      * */ | ||||
|     std::vector<Model::CustomGCode> m_custom_g_code_heights; | ||||
|     std::vector<Model::CustomGCode> m_custom_gcode_per_print_z; | ||||
| 
 | ||||
|     // Time estimators
 | ||||
|     GCodeTimeEstimator m_normal_time_estimator; | ||||
|  |  | |||
|  | @ -10,6 +10,11 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| // Additional Codes which can be set by user using DoubleSlider
 | ||||
| static constexpr char ColorChangeCode[]     = "M600"; | ||||
| static constexpr char PausePrintCode[]      = "M601"; | ||||
| static constexpr char ExtruderChangeCode[]  = "tool_change"; | ||||
| 
 | ||||
| class GCodeWriter { | ||||
| public: | ||||
|     GCodeConfig config; | ||||
|  |  | |||
|  | @ -1187,14 +1187,12 @@ MedialAxis::validate_edge(const VD::edge_type* edge) | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| const Line& | ||||
| MedialAxis::retrieve_segment(const VD::cell_type* cell) const | ||||
| const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const | ||||
| { | ||||
|     return this->lines[cell->source_index()]; | ||||
| } | ||||
| 
 | ||||
| const Point& | ||||
| MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const | ||||
| const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const | ||||
| { | ||||
|     const Line& line = this->retrieve_segment(cell); | ||||
|     if (cell->source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) { | ||||
|  | @ -1208,11 +1206,8 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation, const | |||
| { | ||||
|     transform = Transform3d::Identity(); | ||||
|     transform.translate(translation); | ||||
|     transform.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); | ||||
|     transform.rotate(Eigen::AngleAxisd(rotation(1), Vec3d::UnitY())); | ||||
|     transform.rotate(Eigen::AngleAxisd(rotation(0), Vec3d::UnitX())); | ||||
|     transform.scale(scale); | ||||
|     transform.scale(mirror); | ||||
|     transform.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation(1), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation(0), Vec3d::UnitX())); | ||||
|     transform.scale(scale.cwiseProduct(mirror)); | ||||
| } | ||||
| 
 | ||||
| Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, const Vec3d& scale, const Vec3d& mirror) | ||||
|  | @ -1420,32 +1415,6 @@ void Transformation::set_from_transform(const Transform3d& transform) | |||
| //        std::cout << "something went wrong in extracting data from matrix" << std::endl;
 | ||||
| } | ||||
| 
 | ||||
| void Transformation::set_from_string(const std::string& transform_str) | ||||
| { | ||||
|     Transform3d transform = Transform3d::Identity(); | ||||
| 
 | ||||
|     if (!transform_str.empty()) | ||||
|     { | ||||
|         std::vector<std::string> mat_elements_str; | ||||
|         boost::split(mat_elements_str, transform_str, boost::is_any_of(" "), boost::token_compress_on); | ||||
| 
 | ||||
|         unsigned int size = (unsigned int)mat_elements_str.size(); | ||||
|         if (size == 16) | ||||
|         { | ||||
|             unsigned int i = 0; | ||||
|             for (unsigned int r = 0; r < 4; ++r) | ||||
|             { | ||||
|                 for (unsigned int c = 0; c < 4; ++c) | ||||
|                 { | ||||
|                     transform(r, c) = ::atof(mat_elements_str[i++].c_str()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     set_from_transform(transform); | ||||
| } | ||||
| 
 | ||||
| void Transformation::reset() | ||||
| { | ||||
|     m_offset = Vec3d::Zero(); | ||||
|  | @ -1536,6 +1505,33 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation | |||
|     return out; | ||||
| } | ||||
| 
 | ||||
| // For parsing a transformation matrix from 3MF / AMF.
 | ||||
| Transform3d transform3d_from_string(const std::string& transform_str) | ||||
| { | ||||
|     Transform3d transform = Transform3d::Identity(); | ||||
| 
 | ||||
|     if (!transform_str.empty()) | ||||
|     { | ||||
|         std::vector<std::string> mat_elements_str; | ||||
|         boost::split(mat_elements_str, transform_str, boost::is_any_of(" "), boost::token_compress_on); | ||||
| 
 | ||||
|         unsigned int size = (unsigned int)mat_elements_str.size(); | ||||
|         if (size == 16) | ||||
|         { | ||||
|             unsigned int i = 0; | ||||
|             for (unsigned int r = 0; r < 4; ++r) | ||||
|             { | ||||
|                 for (unsigned int c = 0; c < 4; ++c) | ||||
|                 { | ||||
|                     transform(r, c) = ::atof(mat_elements_str[i++].c_str()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return transform; | ||||
| } | ||||
| 
 | ||||
| Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) | ||||
| { | ||||
|     return | ||||
|  |  | |||
|  | @ -360,7 +360,6 @@ public: | |||
|     void set_mirror(Axis axis, double mirror); | ||||
| 
 | ||||
|     void set_from_transform(const Transform3d& transform); | ||||
|     void set_from_string(const std::string& transform_str); | ||||
| 
 | ||||
|     void reset(); | ||||
| 
 | ||||
|  | @ -385,6 +384,9 @@ private: | |||
| 	} | ||||
| }; | ||||
| 
 | ||||
| // For parsing a transformation matrix from 3MF / AMF.
 | ||||
| extern Transform3d transform3d_from_string(const std::string& transform_str); | ||||
| 
 | ||||
| // Rotation when going from the first coordinate system with rotation rot_xyz_from applied
 | ||||
| // to a coordinate system with rot_xyz_to applied.
 | ||||
| extern Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to); | ||||
|  |  | |||
|  | @ -18,6 +18,8 @@ | |||
| 
 | ||||
| #include "SVG.hpp" | ||||
| #include <Eigen/Dense> | ||||
| #include "GCodeWriter.hpp" | ||||
| #include "GCode/PreviewData.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -43,7 +45,7 @@ Model& Model::assign_copy(const Model &rhs) | |||
|     } | ||||
| 
 | ||||
|     // copy custom code per height
 | ||||
|     this->custom_gcode_per_height = rhs.custom_gcode_per_height; | ||||
|     this->custom_gcode_per_print_z = rhs.custom_gcode_per_print_z; | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
|  | @ -64,7 +66,7 @@ Model& Model::assign_copy(Model &&rhs) | |||
|     rhs.objects.clear(); | ||||
| 
 | ||||
|     // copy custom code per height
 | ||||
|     this->custom_gcode_per_height = rhs.custom_gcode_per_height; | ||||
|     this->custom_gcode_per_print_z = rhs.custom_gcode_per_print_z; | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
|  | @ -124,6 +126,8 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c | |||
|     if (add_default_instances) | ||||
|         model.add_default_instances(); | ||||
| 
 | ||||
|     update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); | ||||
| 
 | ||||
|     return model; | ||||
| } | ||||
| 
 | ||||
|  | @ -159,6 +163,8 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig | |||
|     if (add_default_instances) | ||||
|         model.add_default_instances(); | ||||
| 
 | ||||
|     update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); | ||||
| 
 | ||||
|     return model; | ||||
| } | ||||
| 
 | ||||
|  | @ -595,16 +601,15 @@ std::string Model::propose_export_file_name_and_path(const std::string &new_exte | |||
| 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 }); | ||||
|             } | ||||
|     } | ||||
|     for (const CustomGCode& custom_gcode : custom_gcode_per_print_z) | ||||
|         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.print_z - default_layer_height, config }); | ||||
|         } | ||||
| 
 | ||||
|     return custom_tool_changes; | ||||
| } | ||||
| 
 | ||||
|  | @ -1304,6 +1309,8 @@ void ModelObject::split(ModelObjectPtrs* new_objects) | |||
|         } | ||||
| 
 | ||||
|         new_vol->set_offset(Vec3d::Zero()); | ||||
|         // reset the source to disable reload from disk
 | ||||
|         new_vol->source = ModelVolume::Source(); | ||||
|         new_objects->emplace_back(new_object); | ||||
|         delete mesh; | ||||
|     } | ||||
|  | @ -1359,6 +1366,8 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) | |||
|         model_volume->set_mirror(Vec3d(1., 1., 1.)); | ||||
|         // Move the reference point of the volume to compensate for the change of the instance trafo.
 | ||||
|         model_volume->set_offset(volume_offset_correction * volume_trafo.get_offset()); | ||||
|         // reset the source to disable reload from disk
 | ||||
|         model_volume->source = ModelVolume::Source(); | ||||
|     } | ||||
| 
 | ||||
|     this->invalidate_bounding_box(); | ||||
|  | @ -1670,6 +1679,8 @@ size_t ModelVolume::split(unsigned int max_extruders) | |||
|             this->calculate_convex_hull(); | ||||
|             // Assign a new unique ID, so that a new GLVolume will be generated.
 | ||||
|             this->set_new_unique_id(); | ||||
|             // reset the source to disable reload from disk
 | ||||
|             this->source = ModelVolume::Source(); | ||||
|         } | ||||
|         else | ||||
|             this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(*mesh))); | ||||
|  | @ -1940,6 +1951,30 @@ extern bool model_has_advanced_features(const Model &model) | |||
|     return false; | ||||
| } | ||||
| 
 | ||||
| extern void update_custom_gcode_per_print_z_from_config(std::vector<Model::CustomGCode>& custom_gcode_per_print_z, DynamicPrintConfig* config) | ||||
| { | ||||
|     if (!config->has("colorprint_heights")) | ||||
|         return; | ||||
| 
 | ||||
|     const std::vector<std::string>& colors = GCodePreviewData::ColorPrintColors(); | ||||
| 
 | ||||
|     const auto& colorprint_values = config->option<ConfigOptionFloats>("colorprint_heights")->values; | ||||
|      | ||||
|     if (!colorprint_values.empty()) | ||||
|     { | ||||
|         custom_gcode_per_print_z.clear(); | ||||
|         custom_gcode_per_print_z.reserve(colorprint_values.size()); | ||||
|         int i = 0; | ||||
|         for (auto val : colorprint_values) | ||||
|             custom_gcode_per_print_z.emplace_back(Model::CustomGCode{ val, ColorChangeCode, 1, colors[(++i)%7] }); | ||||
|     } | ||||
| 
 | ||||
|     /* There is one and only place this configuration option is used now.
 | ||||
|      * It wouldn't be used in the future, so erase it. | ||||
|      * */ | ||||
|     config->erase("colorprint_heights"); | ||||
| } | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
 | ||||
| void check_model_ids_validity(const Model &model) | ||||
|  |  | |||
|  | @ -470,6 +470,7 @@ public: | |||
| 
 | ||||
|     const Geometry::Transformation& get_transformation() const { return m_transformation; } | ||||
|     void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } | ||||
|     void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } | ||||
| 
 | ||||
|     const Vec3d& get_offset() const { return m_transformation.get_offset(); } | ||||
|     double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } | ||||
|  | @ -753,33 +754,30 @@ public: | |||
|     // 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.print_z > this->print_z; } | ||||
|         bool operator==(const CustomGCode& other) const | ||||
|         { | ||||
|             return (other.height    == this->height)     &&  | ||||
|                    (other.gcode     == this->gcode)      &&  | ||||
|                    (other.extruder  == this->extruder   )&&  | ||||
|                    (other.color     == this->color   ); | ||||
|             return (other.print_z   == this->print_z    ) &&  | ||||
|                    (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   ); | ||||
|             return (other.print_z   != this->print_z    ) ||  | ||||
|                    (other.gcode     != this->gcode      ) ||  | ||||
|                    (other.extruder  != this->extruder   ) ||  | ||||
|                    (other.color     != this->color      ); | ||||
|         } | ||||
|          | ||||
|         double      height; | ||||
|         double      print_z; | ||||
|         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; | ||||
|     std::vector<CustomGCode> custom_gcode_per_print_z; | ||||
|      | ||||
|     // Default constructor assigns a new ID to the model.
 | ||||
|     Model() { assert(this->id().valid()); } | ||||
|  | @ -845,7 +843,7 @@ 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
 | ||||
|     // from custom_gcode_per_print_z 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: | ||||
|  | @ -881,6 +879,10 @@ extern bool model_volume_list_changed(const ModelObject &model_object_old, const | |||
| extern bool model_has_multi_part_objects(const Model &model); | ||||
| // If the model has advanced features, then it cannot be processed in simple mode.
 | ||||
| extern bool model_has_advanced_features(const Model &model); | ||||
| /* If loaded configuration has a "colorprint_heights" option (if it was imported from older Slicer), 
 | ||||
|  * then model.custom_gcode_per_print_z should be updated considering this option | ||||
|  * */ | ||||
| extern void update_custom_gcode_per_print_z_from_config(std::vector<Model::CustomGCode>& custom_gcode_per_print_z, DynamicPrintConfig* config); | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
 | ||||
|  |  | |||
|  | @ -739,10 +739,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ | |||
| 				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) { | ||||
|             if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { | ||||
|                 // we should stop background processing
 | ||||
|                 update_apply_status(this->invalidate_step(psGCodeExport)); | ||||
|                 m_model.custom_gcode_per_height = model.custom_gcode_per_height; | ||||
|                 m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; | ||||
|             } | ||||
|         } else if (model_object_list_extended(m_model, model)) { | ||||
|             // Add new objects. Their volumes and configs will be synchronized later.
 | ||||
|  |  | |||
|  | @ -71,12 +71,6 @@ 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()) { | ||||
|  |  | |||
|  | @ -339,18 +339,15 @@ PadSkeleton divide_blueprint(const ExPolygons &bp) | |||
|     for (ClipperLib::PolyTree::PolyNode *node : ptree.Childs) { | ||||
|         ExPolygon poly(ClipperPath_to_Slic3rPolygon(node->Contour)); | ||||
|         for (ClipperLib::PolyTree::PolyNode *child : node->Childs) { | ||||
|             if (child->IsHole()) { | ||||
|                 poly.holes.emplace_back( | ||||
|                     ClipperPath_to_Slic3rPolygon(child->Contour)); | ||||
|             poly.holes.emplace_back( | ||||
|                 ClipperPath_to_Slic3rPolygon(child->Contour)); | ||||
| 
 | ||||
|                 traverse_pt_unordered(child->Childs, &ret.inner); | ||||
|             } | ||||
|             else traverse_pt_unordered(child, &ret.inner); | ||||
|             traverse_pt(child->Childs, &ret.inner); | ||||
|         } | ||||
| 
 | ||||
|         ret.outer.emplace_back(poly); | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
|  | @ -432,9 +429,11 @@ public: | |||
| 
 | ||||
|         ExPolygons fullpad = diff_ex(fullcvh, model_bp_sticks); | ||||
| 
 | ||||
|         remove_redundant_parts(fullpad); | ||||
| 
 | ||||
|         PadSkeleton divided = divide_blueprint(fullpad); | ||||
|          | ||||
|         remove_redundant_parts(divided.outer); | ||||
|         remove_redundant_parts(divided.inner); | ||||
| 
 | ||||
|         outer = std::move(divided.outer); | ||||
|         inner = std::move(divided.inner); | ||||
|     } | ||||
|  |  | |||
|  | @ -43,6 +43,7 @@ std::vector<std::pair<size_t, bool>> chain_segments_closest_point(std::vector<En | |||
| 		assert(next_idx < end_points.size()); | ||||
| 		EndPointType &end_point = end_points[next_idx]; | ||||
| 		end_point.chain_id = 1; | ||||
| 		out.emplace_back(next_idx / 2, (next_idx & 1) != 0); | ||||
| 		this_idx = next_idx ^ 1; | ||||
| 	} | ||||
| #ifndef NDEBUG | ||||
|  | @ -72,7 +73,7 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals | |||
| 	else if (num_segments == 1) | ||||
| 	{ | ||||
| 		// Just sort the end points so that the first point visited is closest to start_near.
 | ||||
| 		out.emplace_back(0, start_near != nullptr &&  | ||||
| 		out.emplace_back(0, could_reverse_func(0) && start_near != nullptr &&  | ||||
|             (end_point_func(0, true) - *start_near).template cast<double>().squaredNorm() < (end_point_func(0, false) - *start_near).template cast<double>().squaredNorm()); | ||||
| 	}  | ||||
| 	else | ||||
|  | @ -999,13 +1000,13 @@ std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<Extrus | |||
| 	auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); }; | ||||
| 	auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); }; | ||||
| 	std::vector<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(segment_end_point, could_reverse, entities.size(), start_near); | ||||
| 	for (size_t i = 0; i < entities.size(); ++ i) { | ||||
| 		ExtrusionEntity *ee = entities[i]; | ||||
| 	for (std::pair<size_t, bool> &segment : out) { | ||||
| 		ExtrusionEntity *ee = entities[segment.first]; | ||||
| 		if (ee->is_loop()) | ||||
| 			// Ignore reversals for loops, as the start point equals the end point.
 | ||||
| 			out[i].second = false; | ||||
| 			segment.second = false; | ||||
| 		// Is can_reverse() respected by the reversals?
 | ||||
| 		assert(entities[i]->can_reverse() || ! out[i].second); | ||||
| 		assert(ee->can_reverse() || ! segment.second); | ||||
| 	} | ||||
| 	return out; | ||||
| } | ||||
|  |  | |||
|  | @ -224,38 +224,14 @@ std::vector<coordf_t> layer_height_profile_from_ranges( | |||
| 
 | ||||
| // Based on the work of @platsch
 | ||||
| // Fill layer_height_profile by heights ensuring a prescribed maximum cusp height.
 | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| std::vector<double> layer_height_profile_adaptive(const SlicingParameters& slicing_params, | ||||
|     const ModelObject& object, float cusp_value) | ||||
| #else | ||||
| std::vector<coordf_t> layer_height_profile_adaptive( | ||||
|     const SlicingParameters     &slicing_params, | ||||
|     const t_layer_config_ranges & /* layer_config_ranges */, | ||||
|     const ModelVolumePtrs		&volumes) | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| std::vector<double> layer_height_profile_adaptive(const SlicingParameters& slicing_params, const ModelObject& object, float quality_factor) | ||||
| { | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     // 1) Initialize the SlicingAdaptive class with the object meshes.
 | ||||
|     SlicingAdaptive as; | ||||
|     as.set_slicing_parameters(slicing_params); | ||||
|     as.set_object(object); | ||||
| #else | ||||
|     // 1) Initialize the SlicingAdaptive class with the object meshes.
 | ||||
|     SlicingAdaptive as; | ||||
|     as.set_slicing_parameters(slicing_params); | ||||
|     for (const ModelVolume* volume : volumes) | ||||
|         if (volume->is_model_part()) | ||||
|             as.add_mesh(&volume->mesh()); | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
|     as.prepare(); | ||||
|     as.prepare(object); | ||||
| 
 | ||||
|     // 2) Generate layers using the algorithm of @platsch 
 | ||||
|     // loop until we have at least one layer and the max slice_z reaches the object height
 | ||||
| #if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     double cusp_value = 0.2; | ||||
| #endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
|     std::vector<double> layer_height_profile; | ||||
|     layer_height_profile.push_back(0.0); | ||||
|     layer_height_profile.push_back(slicing_params.first_object_layer_height); | ||||
|  | @ -263,39 +239,41 @@ std::vector<coordf_t> layer_height_profile_adaptive( | |||
|         layer_height_profile.push_back(slicing_params.first_object_layer_height); | ||||
|         layer_height_profile.push_back(slicing_params.first_object_layer_height); | ||||
|     } | ||||
|     double slice_z = slicing_params.first_object_layer_height; | ||||
|     int current_facet = 0; | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     while (slice_z <= slicing_params.object_print_z_height()) { | ||||
|         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()) { | ||||
|         height = 999.0; | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
|     double print_z = slicing_params.first_object_layer_height; | ||||
|     // last facet visited by the as.next_layer_height() function, where the facets are sorted by their increasing Z span.
 | ||||
|     size_t current_facet = 0; | ||||
|     // loop until we have at least one layer and the max slice_z reaches the object height
 | ||||
|     while (print_z + EPSILON < slicing_params.object_print_z_height()) { | ||||
|         float height = slicing_params.max_layer_height; | ||||
|         // Slic3r::debugf "\n Slice layer: %d\n", $id;
 | ||||
|         // determine next layer height
 | ||||
|         double cusp_height = as.cusp_height((float)slice_z, cusp_value, current_facet); | ||||
|         float cusp_height = as.next_layer_height(float(print_z), quality_factor, current_facet); | ||||
| 
 | ||||
| #if 0 | ||||
|         // check for horizontal features and object size
 | ||||
|         /*
 | ||||
|         if($self->config->get_value('match_horizontal_surfaces')) { | ||||
|             my $horizontal_dist = $adaptive_slicing[$region_id]->horizontal_facet_distance(scale $slice_z+$cusp_height, $min_height); | ||||
|             if(($horizontal_dist < $min_height) && ($horizontal_dist > 0)) { | ||||
|                 Slic3r::debugf "Horizontal feature ahead, distance: %f\n", $horizontal_dist; | ||||
|                 # can we shrink the current layer a bit? | ||||
|                 if($cusp_height-($min_height-$horizontal_dist) > $min_height) { | ||||
|                     # yes we can | ||||
|                     $cusp_height = $cusp_height-($min_height-$horizontal_dist); | ||||
|                     Slic3r::debugf "Shrink layer height to %f\n", $cusp_height; | ||||
|                 }else{ | ||||
|                     # no, current layer would become too thin | ||||
|                     $cusp_height = $cusp_height+$horizontal_dist; | ||||
|                     Slic3r::debugf "Widen layer height to %f\n", $cusp_height; | ||||
|         if (this->config.match_horizontal_surfaces.value) { | ||||
|             coordf_t horizontal_dist = as.horizontal_facet_distance(print_z + height, min_layer_height); | ||||
|             if ((horizontal_dist < min_layer_height) && (horizontal_dist > 0)) { | ||||
|                 #ifdef SLIC3R_DEBUG | ||||
|                 std::cout << "Horizontal feature ahead, distance: " << horizontal_dist << std::endl; | ||||
|                 #endif | ||||
|                 // can we shrink the current layer a bit?
 | ||||
|                 if (height-(min_layer_height - horizontal_dist) > min_layer_height) { | ||||
|                     // yes we can
 | ||||
|                     height -= (min_layer_height - horizontal_dist); | ||||
|                     #ifdef SLIC3R_DEBUG | ||||
|                     std::cout << "Shrink layer height to " << height << std::endl; | ||||
|                     #endif | ||||
|                 } else { | ||||
|                     // no, current layer would become too thin
 | ||||
|                     height += horizontal_dist; | ||||
|                     #ifdef SLIC3R_DEBUG | ||||
|                     std::cout << "Widen layer height to " << height << std::endl; | ||||
|                     #endif | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         */ | ||||
| #endif | ||||
|         height = std::min(cusp_height, height); | ||||
| 
 | ||||
|         // apply z-gradation
 | ||||
|  | @ -308,22 +286,22 @@ std::vector<coordf_t> layer_height_profile_adaptive( | |||
|      | ||||
|         // look for an applicable custom range
 | ||||
|         /*
 | ||||
|         if (my $range = first { $_->[0] <= $slice_z && $_->[1] > $slice_z } @{$self->layer_height_ranges}) { | ||||
|         if (my $range = first { $_->[0] <= $print_z && $_->[1] > $print_z } @{$self->layer_height_ranges}) { | ||||
|             $height = $range->[2]; | ||||
|      | ||||
|             # if user set custom height to zero we should just skip the range and resume slicing over it | ||||
|             if ($height == 0) { | ||||
|                 $slice_z += $range->[1] - $range->[0]; | ||||
|                 $print_z += $range->[1] - $range->[0]; | ||||
|                 next; | ||||
|             } | ||||
|         } | ||||
|         */ | ||||
|          | ||||
|         layer_height_profile.push_back(slice_z); | ||||
|         layer_height_profile.push_back(print_z); | ||||
|         layer_height_profile.push_back(height); | ||||
|         slice_z += height; | ||||
|         print_z += height; | ||||
| #if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|         layer_height_profile.push_back(slice_z); | ||||
|         layer_height_profile.push_back(print_z); | ||||
|         layer_height_profile.push_back(height); | ||||
| #endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
|     } | ||||
|  | @ -722,11 +700,7 @@ int generate_layer_height_texture( | |||
|             const Vec3crd &color1 = palette_raw[idx1]; | ||||
|             const Vec3crd &color2 = palette_raw[idx2]; | ||||
|             coordf_t z = cell_to_z * coordf_t(cell); | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|             assert((lo - EPSILON <= z) && (z <= hi + EPSILON)); | ||||
| #else | ||||
|             assert(z >= lo && z <= hi); | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
|             assert(lo - EPSILON <= z && z <= hi + EPSILON); | ||||
|             // Intensity profile to visualize the layers.
 | ||||
|             coordf_t intensity = cos(M_PI * 0.7 * (mid - z) / h); | ||||
|             // Color mapping from layer height to RGB.
 | ||||
|  |  | |||
|  | @ -18,12 +18,7 @@ namespace Slic3r | |||
| 
 | ||||
| class PrintConfig; | ||||
| class PrintObjectConfig; | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| class ModelObject; | ||||
| #else | ||||
| class ModelVolume; | ||||
| typedef std::vector<ModelVolume*> ModelVolumePtrs; | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
| // Parameters to guide object slicing and support generation.
 | ||||
| // The slicing parameters account for a raft and whether the 1st object layer is printed with a normal or a bridging flow
 | ||||
|  | @ -142,10 +137,9 @@ extern std::vector<coordf_t> layer_height_profile_from_ranges( | |||
|     const SlicingParameters     &slicing_params, | ||||
|     const t_layer_config_ranges &layer_config_ranges); | ||||
| 
 | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| extern std::vector<double> layer_height_profile_adaptive( | ||||
|     const SlicingParameters& slicing_params, | ||||
|     const ModelObject& object, float cusp_value); | ||||
|     const ModelObject& object, float quality_factor); | ||||
| 
 | ||||
| struct HeightProfileSmoothingParams | ||||
| { | ||||
|  | @ -159,12 +153,6 @@ struct HeightProfileSmoothingParams | |||
| extern std::vector<double> smooth_height_profile( | ||||
|     const std::vector<double>& profile, const SlicingParameters& slicing_params, | ||||
|     const HeightProfileSmoothingParams& smoothing_params); | ||||
| #else | ||||
| extern std::vector<coordf_t> layer_height_profile_adaptive( | ||||
|     const SlicingParameters     &slicing_params, | ||||
|     const t_layer_config_ranges &layer_config_ranges, | ||||
|     const ModelVolumePtrs       &volumes); | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
| enum LayerHeightEditActionType : unsigned int { | ||||
|     LAYER_HEIGHT_EDIT_ACTION_INCREASE = 0, | ||||
|  |  | |||
|  | @ -1,156 +1,211 @@ | |||
| #include "libslic3r.h" | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| #include "Model.hpp" | ||||
| #else | ||||
| #include "TriangleMesh.hpp" | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| #include "SlicingAdaptive.hpp" | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
| // Based on the work of Florens Waserfall (@platch on github)
 | ||||
| // and his paper
 | ||||
| // Florens Wasserfall, Norman Hendrich, Jianwei Zhang:
 | ||||
| // Adaptive Slicing for the FDM Process Revisited
 | ||||
| // 13th IEEE Conference on Automation Science and Engineering (CASE-2017), August 20-23, Xi'an, China. DOI: 10.1109/COASE.2017.8256074
 | ||||
| // https://tams.informatik.uni-hamburg.de/publications/2017/Adaptive%20Slicing%20for%20the%20FDM%20Process%20Revisited.pdf
 | ||||
| 
 | ||||
| // Vojtech believes that there is a bug in @platch's derivation of the triangle area error metric.
 | ||||
| // Following Octave code paints graphs of recommended layer height versus surface slope angle.
 | ||||
| #if 0 | ||||
| adeg=0:1:85; | ||||
| a=adeg*pi/180; | ||||
| t=tan(a); | ||||
| tsqr=sqrt(tan(a)); | ||||
| lerr=1./cos(a); | ||||
| lerr2=1./(0.3+cos(a)); | ||||
| plot(adeg, t, 'b', adeg, sqrt(t), 'g', adeg, 0.5 * lerr, 'm', adeg, 0.5 * lerr2, 'r') | ||||
| xlabel("angle(deg), 0 - horizontal wall, 90 - vertical wall"); | ||||
| ylabel("layer height"); | ||||
| legend("tan(a) as cura - topographic lines distance limit", "sqrt(tan(a)) as PrusaSlicer - error triangle area limit", "old slic3r - max distance metric", "new slic3r - Waserfall paper"); | ||||
| #endif | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| 	#define ADAPTIVE_LAYER_HEIGHT_DEBUG | ||||
| #endif /* NDEBUG */ | ||||
| 
 | ||||
| namespace Slic3r | ||||
| { | ||||
| 
 | ||||
| #if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| void SlicingAdaptive::clear() | ||||
| { | ||||
|     m_meshes.clear(); | ||||
| 	m_faces.clear(); | ||||
| 	m_face_normal_z.clear(); | ||||
| } | ||||
| #endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
| std::pair<float, float> face_z_span(const stl_facet *f) | ||||
| static inline std::pair<float, float> face_z_span(const stl_facet &f) | ||||
| { | ||||
| 	return std::pair<float, float>( | ||||
| 		std::min(std::min(f->vertex[0](2), f->vertex[1](2)), f->vertex[2](2)), | ||||
| 		std::max(std::max(f->vertex[0](2), f->vertex[1](2)), f->vertex[2](2))); | ||||
| 		std::min(std::min(f.vertex[0](2), f.vertex[1](2)), f.vertex[2](2)), | ||||
| 		std::max(std::max(f.vertex[0](2), f.vertex[1](2)), f.vertex[2](2))); | ||||
| } | ||||
| 
 | ||||
| void SlicingAdaptive::prepare() | ||||
| // By Florens Waserfall aka @platch:
 | ||||
| // This constant essentially describes the volumetric error at the surface which is induced 
 | ||||
| // by stacking "elliptic" extrusion threads. It is empirically determined by
 | ||||
| // 1. measuring the surface profile of printed parts to find
 | ||||
| // the ratio between layer height and profile height and then
 | ||||
| // 2. computing the geometric difference between the model-surface and the elliptic profile.
 | ||||
| //
 | ||||
| // The definition of the roughness formula is in 
 | ||||
| // https://tams.informatik.uni-hamburg.de/publications/2017/Adaptive%20Slicing%20for%20the%20FDM%20Process%20Revisited.pdf
 | ||||
| // (page 51, formula (8))
 | ||||
| // Currenty @platch's error metric formula is not used.
 | ||||
| static constexpr double SURFACE_CONST = 0.18403; | ||||
| 
 | ||||
| // for a given facet, compute maximum height within the allowed surface roughness / stairstepping deviation
 | ||||
| static inline float layer_height_from_slope(const SlicingAdaptive::FaceZ &face, float max_surface_deviation) | ||||
| { | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     if (m_object == nullptr) | ||||
|         return; | ||||
| // @platch's formula, see his paper "Adaptive Slicing for the FDM Process Revisited".
 | ||||
| //    return float(max_surface_deviation / (SURFACE_CONST + 0.5 * std::abs(normal_z)));
 | ||||
| 	 | ||||
| // Constant stepping in horizontal direction, as used by Cura.
 | ||||
| //    return (face.n_cos > 1e-5) ? float(max_surface_deviation * face.n_sin / face.n_cos) : FLT_MAX;
 | ||||
| 
 | ||||
|     m_faces.clear(); | ||||
|     m_face_normal_z.clear(); | ||||
| // Constant error measured as an area of the surface error triangle, Vojtech's formula.
 | ||||
| //    return (face.n_cos > 1e-5) ? float(1.44 * max_surface_deviation * sqrt(face.n_sin / face.n_cos)) : FLT_MAX;
 | ||||
| 
 | ||||
|     m_mesh = m_object->raw_mesh(); | ||||
|     const ModelInstance* first_instance = m_object->instances.front(); | ||||
|     m_mesh.transform(first_instance->get_matrix(), first_instance->is_left_handed()); | ||||
| // Constant error measured as an area of the surface error triangle, Vojtech's formula with clamping to roughness at 90 degrees.
 | ||||
|     return std::min(max_surface_deviation / 0.184f, (face.n_cos > 1e-5) ? float(1.44 * max_surface_deviation * sqrt(face.n_sin / face.n_cos)) : FLT_MAX); | ||||
| 
 | ||||
| // Constant stepping along the surface, equivalent to the "surface roughness" metric by Perez and later Pandey et all, see @platch's paper for references.
 | ||||
| //    return float(max_surface_deviation * face.n_sin);
 | ||||
| } | ||||
| 
 | ||||
| void SlicingAdaptive::clear() | ||||
| { | ||||
| 	m_faces.clear(); | ||||
| } | ||||
| 
 | ||||
| void SlicingAdaptive::prepare(const ModelObject &object) | ||||
| { | ||||
|     this->clear(); | ||||
| 
 | ||||
|     TriangleMesh		 mesh			= object.raw_mesh(); | ||||
|     const ModelInstance &first_instance = *object.instances.front(); | ||||
|     mesh.transform(first_instance.get_matrix(), first_instance.is_left_handed()); | ||||
| 
 | ||||
|     // 1) Collect faces from mesh.
 | ||||
|     m_faces.reserve(m_mesh.stl.stats.number_of_facets); | ||||
|     for (stl_facet& face : m_mesh.stl.facet_start) | ||||
|     { | ||||
|         face.normal.normalize(); | ||||
|         m_faces.emplace_back(&face); | ||||
|     m_faces.reserve(mesh.stl.stats.number_of_facets); | ||||
|     for (const stl_facet &face : mesh.stl.facet_start) { | ||||
|     	Vec3f n = face.normal.normalized(); | ||||
| 		m_faces.emplace_back(FaceZ({ face_z_span(face), std::abs(n.z()), std::sqrt(n.x() * n.x() + n.y() * n.y()) })); | ||||
|     } | ||||
| #else | ||||
|     // 1) Collect faces of all meshes.
 | ||||
|     int nfaces_total = 0; | ||||
|     for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh) | ||||
| 		nfaces_total += (*it_mesh)->stl.stats.number_of_facets; | ||||
|     m_faces.reserve(nfaces_total); | ||||
|     for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh) | ||||
|         for (const stl_facet& face : (*it_mesh)->stl.facet_start) | ||||
|             m_faces.emplace_back(&face); | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
| 	// 2) Sort faces lexicographically by their Z span.
 | ||||
| 	std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) { return face_z_span(f1) < face_z_span(f2); }); | ||||
| 
 | ||||
| 	// 3) Generate Z components of the facet normals.
 | ||||
| 	m_face_normal_z.assign(m_faces.size(), 0.0f); | ||||
|     for (size_t iface = 0; iface < m_faces.size(); ++ iface) | ||||
|     	m_face_normal_z[iface] = m_faces[iface]->normal(2); | ||||
| 	std::sort(m_faces.begin(), m_faces.end(), [](const FaceZ &f1, const FaceZ &f2) { return f1.z_span < f2.z_span; }); | ||||
| } | ||||
| 
 | ||||
| float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet) | ||||
| // current_facet is in/out parameter, rememebers the index of the last face of m_faces visited, 
 | ||||
| // where this function will start from.
 | ||||
| // print_z - the top print surface of the previous layer.
 | ||||
| // returns height of the next layer.
 | ||||
| float SlicingAdaptive::next_layer_height(const float print_z, float quality_factor, size_t ¤t_facet) | ||||
| { | ||||
| 	float height = (float)m_slicing_params.max_layer_height; | ||||
| 	bool first_hit = false; | ||||
| 	float  height = (float)m_slicing_params.max_layer_height; | ||||
| 
 | ||||
| 	float  max_surface_deviation; | ||||
| 
 | ||||
| 	{ | ||||
| #if 0 | ||||
| // @platch's formula for quality:
 | ||||
| 	    double delta_min = SURFACE_CONST * m_slicing_params.min_layer_height; | ||||
| 	    double delta_mid = (SURFACE_CONST + 0.5) * m_slicing_params.layer_height; | ||||
| 	    double delta_max = (SURFACE_CONST + 0.5) * m_slicing_params.max_layer_height; | ||||
| #else | ||||
| // Vojtech's formula for triangle area error metric.
 | ||||
| 	    double delta_min = m_slicing_params.min_layer_height; | ||||
| 	    double delta_mid = m_slicing_params.layer_height; | ||||
| 	    double delta_max = m_slicing_params.max_layer_height; | ||||
| #endif | ||||
| 	    max_surface_deviation = (quality_factor < 0.5f) ? | ||||
| 	    	lerp(delta_min, delta_mid, 2. * quality_factor) : | ||||
| 	    	lerp(delta_max, delta_mid, 2. * (1. - quality_factor)); | ||||
| 	} | ||||
| 	 | ||||
| 	// find all facets intersecting the slice-layer
 | ||||
| 	int ordered_id = current_facet; | ||||
| 	for (; ordered_id < int(m_faces.size()); ++ ordered_id) { | ||||
|         std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]); | ||||
|         // facet's minimum is higher than slice_z -> end loop
 | ||||
| 		if (zspan.first >= z) | ||||
| 			break; | ||||
| 		// facet's maximum is higher than slice_z -> store the first event for next cusp_height call to begin at this point
 | ||||
| 		if (zspan.second > z) { | ||||
| 			// first event?
 | ||||
| 			if (! first_hit) { | ||||
| 				first_hit = true; | ||||
| 				current_facet = ordered_id; | ||||
|             } | ||||
| 			// skip touching facets which could otherwise cause small cusp values
 | ||||
| 			if (zspan.second <= z + EPSILON) | ||||
| 				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.0f) ? (float)m_slicing_params.max_layer_height : std::abs(cusp_value / normal_z)); | ||||
|         } | ||||
| 	size_t ordered_id = current_facet; | ||||
| 	{ | ||||
| 		bool first_hit = false; | ||||
| 		for (; ordered_id < m_faces.size(); ++ ordered_id) { | ||||
| 	        const std::pair<float, float> &zspan = m_faces[ordered_id].z_span; | ||||
| 	        // facet's minimum is higher than slice_z -> end loop
 | ||||
| 			if (zspan.first >= print_z) | ||||
| 				break; | ||||
| 			// facet's maximum is higher than slice_z -> store the first event for next cusp_height call to begin at this point
 | ||||
| 			if (zspan.second > print_z) { | ||||
| 				// first event?
 | ||||
| 				if (! first_hit) { | ||||
| 					first_hit = true; | ||||
| 					current_facet = ordered_id; | ||||
| 	            } | ||||
| 				// skip touching facets which could otherwise cause small cusp values
 | ||||
| 				if (zspan.second < print_z + EPSILON) | ||||
| 					continue; | ||||
| 				// compute cusp-height for this facet and store minimum of all heights
 | ||||
| 				height = std::min(height, layer_height_from_slope(m_faces[ordered_id], max_surface_deviation)); | ||||
| 	        } | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// lower height limit due to printer capabilities
 | ||||
| 	height = std::max(height, float(m_slicing_params.min_layer_height)); | ||||
| 
 | ||||
| 	// check for sloped facets inside the determined layer and correct height if necessary
 | ||||
| 	if (height > m_slicing_params.min_layer_height) { | ||||
| 		for (; ordered_id < int(m_faces.size()); ++ ordered_id) { | ||||
|             std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]); | ||||
| 	if (height > float(m_slicing_params.min_layer_height)) { | ||||
| 		for (; ordered_id < m_faces.size(); ++ ordered_id) { | ||||
|             const std::pair<float, float> &zspan = m_faces[ordered_id].z_span; | ||||
|             // facet's minimum is higher than slice_z + height -> end loop
 | ||||
| 			if (zspan.first >= z + height) | ||||
| 			if (zspan.first >= print_z + height) | ||||
| 				break; | ||||
| 
 | ||||
| 			// skip touching facets which could otherwise cause small cusp values
 | ||||
| 			if (zspan.second <= z + EPSILON) | ||||
| 			if (zspan.second < print_z + EPSILON) | ||||
| 				continue; | ||||
| 
 | ||||
| 			// Compute cusp-height for this facet and check against height.
 | ||||
| 			float normal_z = m_face_normal_z[ordered_id]; | ||||
|             float cusp = (normal_z == 0.0f) ? (float)m_slicing_params.max_layer_height : std::abs(cusp_value / normal_z); | ||||
|             float reduced_height = layer_height_from_slope(m_faces[ordered_id], max_surface_deviation); | ||||
| 
 | ||||
| 			float z_diff = zspan.first - z; | ||||
| 
 | ||||
| 			// handle horizontal facets
 | ||||
|             if (normal_z > 0.999f) { | ||||
|                 // Slic3r::debugf "cusp computation, height is reduced from %f", $height;
 | ||||
| 			float z_diff = zspan.first - print_z; | ||||
| 			if (reduced_height < z_diff) { | ||||
| 				assert(z_diff < height + EPSILON); | ||||
| 				// The currently visited triangle's slope limits the next layer height so much, that
 | ||||
| 				// the lowest point of the currently visible triangle is already above the newly proposed layer height.
 | ||||
| 				// This means, that we need to limit the layer height so that the offending newly visited triangle
 | ||||
| 				// is just above of the new layer.
 | ||||
| #ifdef ADAPTIVE_LAYER_HEIGHT_DEBUG | ||||
|                 BOOST_LOG_TRIVIAL(trace) << "cusp computation, height is reduced from " << height << "to " << z_diff << " due to z-diff"; | ||||
| #endif /* ADAPTIVE_LAYER_HEIGHT_DEBUG */ | ||||
| 				height = z_diff; | ||||
| 				// Slic3r::debugf "to %f due to near horizontal facet\n", $height;
 | ||||
| 			} else if (cusp > z_diff) { | ||||
| 				if (cusp < height) { | ||||
| 					// Slic3r::debugf "cusp computation, height is reduced from %f", $height;
 | ||||
| 					height = cusp; | ||||
| 					// Slic3r::debugf "to %f due to new cusp height\n", $height;
 | ||||
| 				} | ||||
| 			} else { | ||||
| 				// Slic3r::debugf "cusp computation, height is reduced from %f", $height;
 | ||||
| 				height = z_diff; | ||||
| 				// Slic3r::debugf "to z-diff: %f\n", $height;
 | ||||
| 			} else if (reduced_height < height) { | ||||
| #ifdef ADAPTIVE_LAYER_HEIGHT_DEBUG | ||||
| 				BOOST_LOG_TRIVIAL(trace) << "adaptive layer computation: height is reduced from " << height << "to " << reduced_height << " due to higher facet"; | ||||
| #endif /* ADAPTIVE_LAYER_HEIGHT_DEBUG */ | ||||
| 				height = reduced_height; | ||||
| 			} | ||||
| 		} | ||||
| 		// 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;	
 | ||||
| #ifdef ADAPTIVE_LAYER_HEIGHT_DEBUG | ||||
|     BOOST_LOG_TRIVIAL(trace) << "adaptive layer computation, layer-bottom at z:" << print_z << ", quality_factor:" << quality_factor << ", resulting layer height:" << height; | ||||
| #endif  /* ADAPTIVE_LAYER_HEIGHT_DEBUG */ | ||||
| 	return height;  | ||||
| } | ||||
| 
 | ||||
| #if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| // Returns the distance to the next horizontal facet in Z-dir 
 | ||||
| // to consider horizontal object features in slice thickness
 | ||||
| float SlicingAdaptive::horizontal_facet_distance(float z) | ||||
| { | ||||
| 	for (size_t i = 0; i < m_faces.size(); ++ i) { | ||||
|         std::pair<float, float> zspan = face_z_span(m_faces[i]); | ||||
|         std::pair<float, float> zspan = m_faces[i].z_span; | ||||
|         // facet's minimum is higher than max forward distance -> end loop
 | ||||
| 		if (zspan.first > z + m_slicing_params.max_layer_height) | ||||
| 			break; | ||||
| 		// min_z == max_z -> horizontal facet
 | ||||
| 		if ((zspan.first > z) && (zspan.first == zspan.second)) | ||||
| 		if (zspan.first > z && zspan.first == zspan.second) | ||||
| 			return zspan.first - z; | ||||
| 	} | ||||
| 	 | ||||
|  | @ -158,6 +213,5 @@ float SlicingAdaptive::horizontal_facet_distance(float z) | |||
| 	return (z + (float)m_slicing_params.max_layer_height > (float)m_slicing_params.object_print_z_height()) ?  | ||||
| 		std::max((float)m_slicing_params.object_print_z_height() - z, 0.f) : (float)m_slicing_params.max_layer_height; | ||||
| } | ||||
| #endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
| }; // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -5,50 +5,36 @@ | |||
| 
 | ||||
| #include "Slicing.hpp" | ||||
| #include "admesh/stl.h" | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| #include "TriangleMesh.hpp" | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
| namespace Slic3r | ||||
| { | ||||
| 
 | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| class ModelVolume; | ||||
| #else | ||||
| class TriangleMesh; | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
| class SlicingAdaptive | ||||
| { | ||||
| public: | ||||
| #if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     void clear(); | ||||
| #endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
|     void set_slicing_parameters(SlicingParameters params) { m_slicing_params = params; } | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     void set_object(const ModelObject& object) { m_object = &object; } | ||||
| #else | ||||
|     void add_mesh(const TriangleMesh* mesh) { m_meshes.push_back(mesh); } | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
|     void prepare(); | ||||
| 	float cusp_height(float z, float cusp_value, int ¤t_facet); | ||||
| #if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     void  clear(); | ||||
|     void  set_slicing_parameters(SlicingParameters params) { m_slicing_params = params; } | ||||
|     void  prepare(const ModelObject &object); | ||||
|     // Return next layer height starting from the last print_z, using a quality measure
 | ||||
|     // (quality in range from 0 to 1, 0 - highest quality at low layer heights, 1 - lowest print quality at high layer heights).
 | ||||
|     // The layer height curve shall be centered roughly around the default profile's layer height for quality 0.5.
 | ||||
| 	float next_layer_height(const float print_z, float quality, size_t ¤t_facet); | ||||
|     float horizontal_facet_distance(float z); | ||||
| #endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
| 	struct FaceZ { | ||||
| 		std::pair<float, float> z_span; | ||||
| 		// Cosine of the normal vector towards the Z axis.
 | ||||
| 		float					n_cos; | ||||
| 		// Sine of the normal vector towards the Z axis.
 | ||||
| 		float					n_sin; | ||||
| 	}; | ||||
| 
 | ||||
| protected: | ||||
| 	SlicingParameters 					m_slicing_params; | ||||
| 	SlicingParameters 		m_slicing_params; | ||||
| 
 | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     const ModelObject*                  m_object; | ||||
|     TriangleMesh                        m_mesh; | ||||
| #else | ||||
|     std::vector<const TriangleMesh*>	m_meshes; | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
|     // Collected faces of all meshes, sorted by raising Z of the bottom most face.
 | ||||
| 	std::vector<const stl_facet*>		m_faces; | ||||
|     // Z component of face normals, normalized.
 | ||||
| 	std::vector<float>					m_face_normal_z; | ||||
| 	std::vector<FaceZ>		m_faces; | ||||
| }; | ||||
| 
 | ||||
| }; // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -53,4 +53,7 @@ | |||
| // Enable selection for missing files in reload from disk command
 | ||||
| #define ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION (1 && ENABLE_2_2_0_ALPHA1) | ||||
| 
 | ||||
| // Enable closing 3Dconnextion imgui settings dialog by clicking on [X] and [Close] buttons
 | ||||
| #define ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG (1 && ENABLE_2_2_0_ALPHA1) | ||||
| 
 | ||||
| #endif // _technologies_h_
 | ||||
|  |  | |||
|  | @ -217,14 +217,6 @@ inline typename CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER | |||
| 	return container[next_idx_modulo(idx, container.size())]; | ||||
| } | ||||
| 
 | ||||
| template<class T, class U = T> | ||||
| inline T exchange(T& obj, U&& new_value) | ||||
| { | ||||
|     T old_value = std::move(obj); | ||||
|     obj = std::forward<U>(new_value); | ||||
|     return old_value; | ||||
| } | ||||
| 
 | ||||
| extern std::string xml_escape(std::string text); | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -116,7 +116,5 @@ | |||
|     <string>NSApplication</string> | ||||
|     <key>NSHighResolutionCapable</key> | ||||
|     <true/> | ||||
|     <key>NSRequiresAquaSystemAppearance</key> | ||||
|     <true/> | ||||
|   </dict> | ||||
| </plist> | ||||
|  |  | |||
|  | @ -191,7 +191,6 @@ Bed3D::Bed3D() | |||
|     : m_type(Custom) | ||||
|     , m_custom_texture("") | ||||
|     , m_custom_model("") | ||||
|     , m_requires_canvas_update(false) | ||||
|     , m_vbo_id(0) | ||||
|     , m_scale_factor(1.0f) | ||||
| { | ||||
|  | @ -205,7 +204,6 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c | |||
|     std::string cst_texture(custom_texture); | ||||
|     if (!cst_texture.empty()) | ||||
|     { | ||||
|         std::replace(cst_texture.begin(), cst_texture.end(), '\\', '/'); | ||||
|         if ((!boost::algorithm::iends_with(custom_texture, ".png") && !boost::algorithm::iends_with(custom_texture, ".svg")) || !boost::filesystem::exists(custom_texture)) | ||||
|             cst_texture = ""; | ||||
|     } | ||||
|  | @ -214,7 +212,6 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c | |||
|     std::string cst_model(custom_model); | ||||
|     if (!cst_model.empty()) | ||||
|     { | ||||
|         std::replace(cst_model.begin(), cst_model.end(), '\\', '/'); | ||||
|         if (!boost::algorithm::iends_with(custom_model, ".stl") || !boost::filesystem::exists(custom_model)) | ||||
|             cst_model = ""; | ||||
|     } | ||||
|  | @ -438,6 +435,7 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& | |||
|                     render_default(bottom); | ||||
|                     return; | ||||
|                 } | ||||
|                 canvas.request_extra_frame(); | ||||
|             } | ||||
| 
 | ||||
|             // starts generating the main texture, compression will run asynchronously
 | ||||
|  | @ -457,6 +455,7 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& | |||
|                     render_default(bottom); | ||||
|                     return; | ||||
|                 } | ||||
|                 canvas.request_extra_frame(); | ||||
|             } | ||||
| 
 | ||||
|             // starts generating the main texture, compression will run asynchronously
 | ||||
|  | @ -481,13 +480,9 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& | |||
|         if (m_temp_texture.get_id() != 0) | ||||
|             m_temp_texture.reset(); | ||||
| 
 | ||||
|         m_requires_canvas_update = true; | ||||
|     } | ||||
|     else if (m_requires_canvas_update && m_texture.all_compressed_data_sent_to_gpu()) | ||||
|         m_requires_canvas_update = false; | ||||
|         canvas.request_extra_frame(); | ||||
| 
 | ||||
|     if (m_texture.all_compressed_data_sent_to_gpu() && canvas.is_keeping_dirty()) | ||||
|         canvas.stop_keeping_dirty(); | ||||
|     } | ||||
| 
 | ||||
|     if (m_triangles.get_vertices_count() > 0) | ||||
|     { | ||||
|  |  | |||
|  | @ -86,8 +86,6 @@ private: | |||
|     mutable GLTexture m_texture; | ||||
|     // temporary texture shown until the main texture has still no levels compressed
 | ||||
|     mutable GLTexture m_temp_texture; | ||||
|     // used to trigger 3D scene update once all compressed textures have been sent to GPU
 | ||||
|     mutable bool m_requires_canvas_update; | ||||
|     mutable Shader m_shader; | ||||
|     mutable unsigned int m_vbo_id; | ||||
|     mutable GLBed m_model; | ||||
|  |  | |||
|  | @ -94,12 +94,13 @@ void BackgroundSlicingProcess::process_fff() | |||
|     m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); | ||||
| #endif // ENABLE_THUMBNAIL_GENERATOR
 | ||||
| 
 | ||||
|     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
 | ||||
|     /* #ys_FIXME_no_exported_codes
 | ||||
|     if (m_fff_print->model().custom_gcode_per_print_z != GUI::wxGetApp().model().custom_gcode_per_print_z) { | ||||
|         GUI::wxGetApp().model().custom_gcode_per_print_z = m_fff_print->model().custom_gcode_per_print_z; | ||||
|         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()) { | ||||
|  |  | |||
|  | @ -61,9 +61,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf | |||
| { | ||||
|     m_shape = default_pt.values; | ||||
|     m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value; | ||||
|     std::replace(m_custom_texture.begin(), m_custom_texture.end(), '\\', '/'); | ||||
|     m_custom_model = custom_model.value.empty() ? NONE : custom_model.value; | ||||
|     std::replace(m_custom_model.begin(), m_custom_model.end(), '\\', '/'); | ||||
| 
 | ||||
|     auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); | ||||
|     sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font()); | ||||
|  | @ -546,8 +544,6 @@ void BedShapePanel::load_texture() | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     std::replace(file_name.begin(), file_name.end(), '\\', '/'); | ||||
| 
 | ||||
|     wxBusyCursor wait; | ||||
| 
 | ||||
|     m_custom_texture = file_name; | ||||
|  | @ -571,8 +567,6 @@ void BedShapePanel::load_model() | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     std::replace(file_name.begin(), file_name.end(), '\\', '/'); | ||||
| 
 | ||||
|     wxBusyCursor wait; | ||||
| 
 | ||||
|     m_custom_model = file_name; | ||||
|  |  | |||
|  | @ -133,7 +133,7 @@ GLCanvas3D::LayersEditing::LayersEditing() | |||
|     , m_slicing_parameters(nullptr) | ||||
|     , m_layer_height_profile_modified(false) | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     , m_adaptive_cusp(0.0f) | ||||
|     , m_adaptive_quality(0.5f) | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
|     , state(Unknown) | ||||
|     , band_width(2.0f) | ||||
|  | @ -268,24 +268,24 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const | |||
|      | ||||
|     ImGui::Separator(); | ||||
|     if (imgui.button(_(L("Adaptive")))) | ||||
|         wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event<float>(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_cusp)); | ||||
|         wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event<float>(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality)); | ||||
| 
 | ||||
|     ImGui::SameLine(); | ||||
|     float text_align = ImGui::GetCursorPosX(); | ||||
|     ImGui::AlignTextToFramePadding(); | ||||
|     imgui.text(_(L("Cusp (mm)"))); | ||||
|     imgui.text(_(L("Quality / Speed"))); | ||||
|     if (ImGui::IsItemHovered()) | ||||
|     { | ||||
|         ImGui::BeginTooltip(); | ||||
|         ImGui::TextUnformatted(_(L("I am a tooltip"))); | ||||
|         ImGui::TextUnformatted(_(L("Higher print quality versus higher print speed."))); | ||||
|         ImGui::EndTooltip(); | ||||
|     } | ||||
| 
 | ||||
|     ImGui::SameLine(); | ||||
|     float widget_align = ImGui::GetCursorPosX(); | ||||
|     ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); | ||||
|     m_adaptive_cusp = clamp(0.0f, 0.5f * (float)m_slicing_parameters->layer_height, m_adaptive_cusp); | ||||
|     ImGui::SliderFloat("", &m_adaptive_cusp, 0.0f, 0.5f * (float)m_slicing_parameters->layer_height, "%.3f"); | ||||
|     m_adaptive_quality = clamp(0.0f, 1.f, m_adaptive_quality); | ||||
|     ImGui::SliderFloat("", &m_adaptive_quality, 0.0f, 1.f, "%.2f"); | ||||
| 
 | ||||
|     ImGui::Separator(); | ||||
|     if (imgui.button(_(L("Smooth")))) | ||||
|  | @ -645,10 +645,10 @@ void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas) | |||
| } | ||||
| 
 | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
| void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float cusp) | ||||
| void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor) | ||||
| { | ||||
|     this->update_slicing_parameters(); | ||||
|     m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, cusp); | ||||
|     m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, quality_factor); | ||||
|     const_cast<ModelObject*>(m_model_object)->layer_height_profile = m_layer_height_profile; | ||||
|     m_layers_texture.valid = false; | ||||
|     canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); | ||||
|  | @ -712,11 +712,6 @@ void GLCanvas3D::LayersEditing::update_slicing_parameters() | |||
| 		m_slicing_parameters = new SlicingParameters(); | ||||
|     	*m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); | ||||
|     } | ||||
| 
 | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     if (m_adaptive_cusp == 0.0f) | ||||
|         m_adaptive_cusp = 0.25f * m_slicing_parameters->layer_height; | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| } | ||||
| 
 | ||||
| float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas) | ||||
|  | @ -1016,24 +1011,25 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items(  const GLCanvas3D | |||
|                                                                 std::vector<float>& colors, | ||||
|                                                                 std::vector<std::string>& cp_legend_items) | ||||
| { | ||||
|     std::vector<Model::CustomGCode> custom_gcode_per_height = wxGetApp().plater()->model().custom_gcode_per_height; | ||||
|     std::vector<Model::CustomGCode> custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z; | ||||
| 
 | ||||
|     const int extruders_cnt = wxGetApp().extruders_edited_cnt(); | ||||
|     if (extruders_cnt == 1)  | ||||
|     { | ||||
|         if (custom_gcode_per_height.empty()) { | ||||
|             cp_legend_items.push_back(I18N::translate_utf8(L("Default print color"))); | ||||
|         if (custom_gcode_per_print_z.empty()) { | ||||
|             cp_legend_items.emplace_back(I18N::translate_utf8(L("Default print color"))); | ||||
|             colors = colors_in; | ||||
|             return; | ||||
|         } | ||||
|         std::vector<std::pair<double, double>> cp_values; | ||||
|         cp_values.reserve(custom_gcode_per_print_z.size()); | ||||
|          | ||||
|         std::vector<double> print_zs = canvas.get_current_print_zs(true); | ||||
|         for (auto custom_code : custom_gcode_per_height) | ||||
|         for (auto custom_code : custom_gcode_per_print_z) | ||||
|         { | ||||
|             if (custom_code.gcode != ColorChangeCode) | ||||
|                 continue; | ||||
|             auto lower_b = std::lower_bound(print_zs.begin(), print_zs.end(), custom_code.height - DoubleSlider::epsilon()); | ||||
|             auto lower_b = std::lower_bound(print_zs.begin(), print_zs.end(), custom_code.print_z - DoubleSlider::epsilon()); | ||||
| 
 | ||||
|             if (lower_b == print_zs.end()) | ||||
|                 continue; | ||||
|  | @ -1044,14 +1040,14 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items(  const GLCanvas3D | |||
|             // 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)); | ||||
|                 cp_values.emplace_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"))); | ||||
|             cp_legend_items.emplace_back(I18N::translate_utf8(L("Default print color"))); | ||||
|             cp_legend_items.emplace_back(I18N::translate_utf8(L("Pause print or custom G-code"))); | ||||
|             colors = colors_in; | ||||
|             return; | ||||
|         } | ||||
|  | @ -1060,7 +1056,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items(  const GLCanvas3D | |||
|         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"))); | ||||
|         cp_legend_items.emplace_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) | ||||
|  | @ -1072,15 +1068,15 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items(  const GLCanvas3D | |||
|             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()); | ||||
|                 cp_legend_items.emplace_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()); | ||||
|                 cp_legend_items.emplace_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()); | ||||
|             cp_legend_items.emplace_back(id_str + (boost::format(I18N::translate_utf8(L("%.2f - %.2f mm"))) % cp_values[i - 1].second % cp_values[i].first).str()); | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|  | @ -1094,20 +1090,20 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items(  const GLCanvas3D | |||
|         size_t color_in_pos = 4 * (color_cnt - 1); | ||||
|          | ||||
|         for (unsigned int i = 0; i < (unsigned int)extruders_cnt; ++i) | ||||
|             cp_legend_items.push_back((boost::format(I18N::translate_utf8(L("Extruder %d"))) % (i + 1)).str()); | ||||
|             cp_legend_items.emplace_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"))); | ||||
|         cp_legend_items.emplace_back(I18N::translate_utf8(L("Pause print or custom G-code"))); | ||||
| 
 | ||||
|         int cnt = custom_gcode_per_height.size(); | ||||
|         int cnt = custom_gcode_per_print_z.size(); | ||||
|         for (int i = cnt-1; i >= 0; --i) | ||||
|             if (custom_gcode_per_height[i].gcode == ColorChangeCode) { | ||||
|             if (custom_gcode_per_print_z[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()); | ||||
|                 cp_legend_items.emplace_back((boost::format(I18N::translate_utf8(L("Color change for Extruder %d at %.2f mm"))) % custom_gcode_per_print_z[i].extruder % custom_gcode_per_print_z[i].print_z).str()); | ||||
|             } | ||||
|     } | ||||
| } | ||||
|  | @ -1398,7 +1394,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar | |||
|     , m_gizmos(*this) | ||||
|     , m_use_clipping_planes(false) | ||||
|     , m_sidebar_field("") | ||||
|     , m_keep_dirty(false) | ||||
|     , m_extra_frame_requested(false) | ||||
|     , m_config(nullptr) | ||||
|     , m_process(nullptr) | ||||
|     , m_model(nullptr) | ||||
|  | @ -1640,8 +1636,6 @@ void GLCanvas3D::bed_shape_changed() | |||
|     refresh_camera_scene_box(); | ||||
|     m_camera.requires_zoom_to_bed = true; | ||||
|     m_dirty = true; | ||||
|     if (m_bed.is_prusa()) | ||||
|         start_keeping_dirty(); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::set_color_by(const std::string& value) | ||||
|  | @ -1694,10 +1688,10 @@ void GLCanvas3D::reset_layer_height_profile() | |||
|     m_dirty = true; | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::adaptive_layer_height_profile(float cusp) | ||||
| void GLCanvas3D::adaptive_layer_height_profile(float quality_factor) | ||||
| { | ||||
|     wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Adaptive"))); | ||||
|     m_layers_editing.adaptive_layer_height_profile(*this, cusp); | ||||
|     m_layers_editing.adaptive_layer_height_profile(*this, quality_factor); | ||||
|     m_layers_editing.state = LayersEditing::Completed; | ||||
|     m_dirty = true; | ||||
| } | ||||
|  | @ -1931,7 +1925,11 @@ void GLCanvas3D::render() | |||
|     m_camera.debug_render(); | ||||
| #endif // ENABLE_CAMERA_STATISTICS
 | ||||
| 
 | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
|     wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); | ||||
| #else | ||||
|     wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
| 
 | ||||
|     wxGetApp().imgui()->render(); | ||||
| 
 | ||||
|  | @ -2636,9 +2634,10 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) | |||
| 
 | ||||
|     _refresh_if_shown_on_screen(); | ||||
| 
 | ||||
|     if (m_keep_dirty || mouse3d_controller_applied) | ||||
|     if (m_extra_frame_requested || mouse3d_controller_applied) | ||||
|     { | ||||
|         m_dirty = true; | ||||
|         m_extra_frame_requested = false; | ||||
|         evt.RequestMore(); | ||||
|     } | ||||
|     else | ||||
|  | @ -5395,7 +5394,7 @@ 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 { | ||||
|             const Model::CustomGCode value(layers[layer_idx]->print_z + EPSILON, "", 0, ""); | ||||
|             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(); | ||||
|         } | ||||
|  | @ -5406,7 +5405,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
| 
 | ||||
|             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; }); | ||||
|                 { return fabs(code.print_z - print_z) < EPSILON; }); | ||||
|             if (it != color_print_values->end()) | ||||
|             { | ||||
|                 const std::string& code = it->gcode; | ||||
|  | @ -5426,7 +5425,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             const Model::CustomGCode value(print_z + EPSILON, "", 0, ""); | ||||
|             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()) | ||||
|             { | ||||
|  |  | |||
|  | @ -185,7 +185,7 @@ private: | |||
|         bool                        m_layer_height_profile_modified; | ||||
| 
 | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|         mutable float               m_adaptive_cusp; | ||||
|         mutable float               m_adaptive_quality; | ||||
|         mutable HeightProfileSmoothingParams m_smooth_params; | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
|  | @ -236,8 +236,8 @@ private: | |||
| 		void accept_changes(GLCanvas3D& canvas); | ||||
|         void reset_layer_height_profile(GLCanvas3D& canvas); | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|         void adaptive_layer_height_profile(GLCanvas3D& canvas, float cusp); | ||||
|         void smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_paramsn); | ||||
|         void adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor); | ||||
|         void smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params); | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
|         static float get_cursor_z_relative(const GLCanvas3D& canvas); | ||||
|  | @ -436,7 +436,9 @@ private: | |||
|     bool m_use_clipping_planes; | ||||
|     mutable SlaCap m_sla_caps[2]; | ||||
|     std::string m_sidebar_field; | ||||
|     bool m_keep_dirty; | ||||
|     // when true renders an extra frame by not resetting m_dirty to false
 | ||||
|     // see request_extra_frame()
 | ||||
|     bool m_extra_frame_requested; | ||||
| 
 | ||||
|     mutable GLVolumeCollection m_volumes; | ||||
|     Selection m_selection; | ||||
|  | @ -540,7 +542,7 @@ public: | |||
| 
 | ||||
| #if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     void reset_layer_height_profile(); | ||||
|     void adaptive_layer_height_profile(float cusp); | ||||
|     void adaptive_layer_height_profile(float quality_factor); | ||||
|     void smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params); | ||||
| #endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
|  | @ -665,9 +667,7 @@ public: | |||
|     void set_cursor(ECursorType type); | ||||
|     void msw_rescale(); | ||||
| 
 | ||||
|     bool is_keeping_dirty() const { return m_keep_dirty; } | ||||
|     void start_keeping_dirty() { m_keep_dirty = true; } | ||||
|     void stop_keeping_dirty() { m_keep_dirty = false; } | ||||
|     void request_extra_frame() { m_extra_frame_requested = true; } | ||||
| 
 | ||||
|     int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); } | ||||
|     void force_main_toolbar_left_action(int item_id) { m_main_toolbar.force_left_action(item_id, *this); } | ||||
|  |  | |||
|  | @ -333,17 +333,12 @@ unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) | |||
| } | ||||
| 
 | ||||
| bool GUI_App::dark_mode() | ||||
| { | ||||
|     const unsigned luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); | ||||
|     return luma < 128; | ||||
| } | ||||
| 
 | ||||
| bool GUI_App::dark_mode_menus() | ||||
| { | ||||
| #if __APPLE__ | ||||
|     return mac_dark_mode(); | ||||
| #else | ||||
|     return dark_mode(); | ||||
|     const unsigned luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); | ||||
|     return luma < 128; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -109,7 +109,6 @@ public: | |||
| 
 | ||||
|     static unsigned get_colour_approx_luma(const wxColour &colour); | ||||
|     static bool     dark_mode(); | ||||
|     static bool     dark_mode_menus(); | ||||
|     void            init_label_colours(); | ||||
|     void            update_label_colours_from_appconfig(); | ||||
|     void            init_fonts(); | ||||
|  |  | |||
|  | @ -1193,7 +1193,13 @@ void ObjectList::get_settings_choice(const wxString& category_name) | |||
| { | ||||
|     wxArrayString names; | ||||
|     wxArrayInt selections; | ||||
|     wxDataViewItem item = GetSelection(); | ||||
| 
 | ||||
|     /* If we try to add settings for object/part from 3Dscene,
 | ||||
|      * for the second try there is selected ItemSettings in ObjectList. | ||||
|      * So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes | ||||
|      */ | ||||
|     const wxDataViewItem selected_item = GetSelection(); | ||||
|     wxDataViewItem item = m_objects_model->GetItemType(selected_item) & itSettings ? m_objects_model->GetParent(selected_item) : selected_item; | ||||
| 
 | ||||
|     const ItemType item_type = m_objects_model->GetItemType(item); | ||||
| 
 | ||||
|  | @ -1319,11 +1325,17 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) | |||
| { | ||||
|     std::vector<std::string> options = get_options_for_bundle(bundle_name); | ||||
|     const Selection& selection = scene_selection(); | ||||
|     wxDataViewItem item = GetSelectedItemsCount() > 1 && selection.is_single_full_object() ?  | ||||
|                           m_objects_model->GetItemById(selection.get_object_idx()) :  | ||||
|                           GetSelection(); | ||||
|     const wxDataViewItem sel_item = // when all instances in object are selected
 | ||||
|                                     GetSelectedItemsCount() > 1 && selection.is_single_full_object() ?  | ||||
|                                     m_objects_model->GetItemById(selection.get_object_idx()) :  | ||||
|                                     GetSelection(); | ||||
| 
 | ||||
|     ItemType item_type = m_objects_model->GetItemType(item); | ||||
|     /* If we try to add settings for object/part from 3Dscene,
 | ||||
|      * for the second try there is selected ItemSettings in ObjectList. | ||||
|      * So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes | ||||
|      */ | ||||
|     wxDataViewItem item = m_objects_model->GetItemType(sel_item) & itSettings ? m_objects_model->GetParent(sel_item) : sel_item; | ||||
|     const ItemType item_type = m_objects_model->GetItemType(item); | ||||
| 
 | ||||
|     /* Because of we couldn't edited layer_height for ItVolume from settings list,
 | ||||
|      * correct options according to the selected item type : | ||||
|  | @ -1547,17 +1559,21 @@ wxMenuItem* ObjectList::append_menu_item_change_type(wxMenu* menu) | |||
| wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent) | ||||
| { | ||||
|     wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "", | ||||
|         [this](wxCommandEvent&) { split_instances(); }, "", menu, [](){return wxGetApp().plater()->can_set_instance_to_object(); }, parent); | ||||
|         [this](wxCommandEvent&) { split_instances(); }, "", menu); | ||||
| 
 | ||||
|     /* New behavior logic:
 | ||||
|      * 1. Split Object to several separated object, if ALL instances are selected | ||||
|      * 2. Separate selected instances from the initial object to the separated object, | ||||
|      *    if some (not all) instances are selected | ||||
|      */ | ||||
|     parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) { | ||||
|         evt.SetText(wxGetApp().plater()->canvas3D()->get_selection().is_single_full_object() ? | ||||
|             _(L("Set as a Separated Objects")) : _(L("Set as a Separated Object"))); | ||||
|         }, menu_item->GetId()); | ||||
|     parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) | ||||
|     { | ||||
|         const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); | ||||
|         evt.SetText(selection.is_single_full_object() ? | ||||
|         _(L("Set as a Separated Objects")) : _(L("Set as a Separated Object"))); | ||||
| 
 | ||||
|         evt.Enable(wxGetApp().plater()->can_set_instance_to_object()); | ||||
|     }, menu_item->GetId()); | ||||
| 
 | ||||
|     return menu_item; | ||||
| } | ||||
|  | @ -1608,7 +1624,8 @@ void ObjectList::append_menu_item_export_stl(wxMenu* menu) const | |||
| void ObjectList::append_menu_item_reload_from_disk(wxMenu* menu) const | ||||
| { | ||||
|     append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")), | ||||
|         [this](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, []() { return wxGetApp().plater()->can_reload_from_disk(); }, wxGetApp().plater()); | ||||
|         [this](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, | ||||
|         []() { return wxGetApp().plater()->can_reload_from_disk(); }, wxGetApp().plater()); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::append_menu_item_change_extruder(wxMenu* menu) const | ||||
|  | @ -1727,13 +1744,22 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) | |||
|     wxMenu *menu = new wxMenu; | ||||
| 
 | ||||
|     settings_menu_hierarchy settings_menu; | ||||
|     const bool is_part = !(m_objects_model->GetItemType(GetSelection()) == itObject || scene_selection().is_single_full_object()); | ||||
| 
 | ||||
|     /* If we try to add settings for object/part from 3Dscene, 
 | ||||
|      * for the second try there is selected ItemSettings in ObjectList. | ||||
|      * So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes | ||||
|      */ | ||||
|     const wxDataViewItem selected_item = GetSelection(); | ||||
|     wxDataViewItem item = m_objects_model->GetItemType(selected_item) & itSettings ? m_objects_model->GetParent(selected_item) : selected_item; | ||||
| 
 | ||||
|     const bool is_part = !(m_objects_model->GetItemType(item) == itObject || scene_selection().is_single_full_object()); | ||||
|     get_options_menu(settings_menu, is_part); | ||||
| 
 | ||||
|     for (auto cat : settings_menu) { | ||||
|         append_menu_item(menu, wxID_ANY, _(cat.first), "", | ||||
|                         [menu, this](wxCommandEvent& event) { get_settings_choice(menu->GetLabel(event.GetId())); },  | ||||
|                         CATEGORY_ICON.find(cat.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(cat.first), parent_menu);  | ||||
|                         CATEGORY_ICON.find(cat.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(cat.first), parent_menu, | ||||
|                         [this]() { return true; }, wxGetApp().plater()); | ||||
|     } | ||||
| 
 | ||||
|     return menu; | ||||
|  | @ -1753,7 +1779,8 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu, const bool is_obje | |||
| 
 | ||||
|         append_menu_item(menu, wxID_ANY, _(it.first), "", | ||||
|                         [menu, this](wxCommandEvent& event) { get_freq_settings_choice(menu->GetLabel(event.GetId())); },  | ||||
|                         CATEGORY_ICON.find(it.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(it.first), menu);  | ||||
|                         CATEGORY_ICON.find(it.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(it.first), menu, | ||||
|                         [this]() { return true; }, wxGetApp().plater()); | ||||
|     } | ||||
| #if 0 | ||||
|     // Add "Quick" settings bundles
 | ||||
|  | @ -1766,7 +1793,8 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu, const bool is_obje | |||
| 
 | ||||
|         append_menu_item(menu, wxID_ANY, wxString::Format(_(L("Quick Add Settings (%s)")), _(it.first)), "", | ||||
|                         [menu, this](wxCommandEvent& event) { get_freq_settings_choice(menu->GetLabel(event.GetId())); },  | ||||
|                         CATEGORY_ICON.find(it.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(it.first), menu);  | ||||
|                         CATEGORY_ICON.find(it.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(it.first), menu, | ||||
|                         [this]() { return true; }, wxGetApp().plater()); | ||||
|     } | ||||
| #endif | ||||
| } | ||||
|  | @ -3006,7 +3034,8 @@ void ObjectList::update_selections() | |||
|     else if (selection.is_single_full_object() || selection.is_multiple_full_object()) | ||||
|     { | ||||
|         const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content(); | ||||
|         if (m_selection_mode & (smSettings | smLayer | smLayerRoot)) | ||||
|         // it's impossible to select Settings, Layer or LayerRoot for several objects
 | ||||
|         if (!selection.is_multiple_full_object() && (m_selection_mode & (smSettings | smLayer | smLayerRoot))) | ||||
|         { | ||||
|             auto obj_idx = objects_content.begin()->first; | ||||
|             wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx); | ||||
|  | @ -3850,8 +3879,8 @@ void ObjectList::show_multi_selection_menu() | |||
|     GetSelections(sels); | ||||
| 
 | ||||
|     for (const wxDataViewItem& item : sels) | ||||
|         if (!(m_objects_model->GetItemType(item) & (itVolume | itObject))) | ||||
|             // show this menu only for Object(s)/Volume(s) selection
 | ||||
|         if (!(m_objects_model->GetItemType(item) & (itVolume | itObject | itInstance))) | ||||
|             // show this menu only for Objects(Instances mixed with Objects)/Volumes selection
 | ||||
|             return; | ||||
| 
 | ||||
|     wxMenu* menu = new wxMenu(); | ||||
|  | @ -3861,7 +3890,12 @@ void ObjectList::show_multi_selection_menu() | |||
|             _(L("Select extruder number for selected objects and/or parts")), | ||||
|             [this](wxCommandEvent&) { extruder_selection(); }, "", menu); | ||||
| 
 | ||||
|     PopupMenu(menu); | ||||
|     append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")), | ||||
|         [this](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, []() { | ||||
|         return wxGetApp().plater()->can_reload_from_disk(); | ||||
|     }, wxGetApp().plater()); | ||||
| 
 | ||||
|     wxGetApp().plater()->PopupMenu(menu); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::extruder_selection() | ||||
|  | @ -3939,8 +3973,15 @@ void ObjectList::update_after_undo_redo() | |||
|     Plater::SuppressSnapshots suppress(wxGetApp().plater()); | ||||
| 
 | ||||
|     // Unselect all objects before deleting them, so that no change of selection is emitted during deletion.
 | ||||
|     unselect_objects();//this->UnselectAll();
 | ||||
| 
 | ||||
|     /* To avoid execution of selection_changed() 
 | ||||
|      * from wxEVT_DATAVIEW_SELECTION_CHANGED emitted from DeleteAll(),  | ||||
|      * wrap this two functions into m_prevent_list_events * | ||||
|      * */ | ||||
|     m_prevent_list_events = true; | ||||
|     this->UnselectAll(); | ||||
|     m_objects_model->DeleteAll(); | ||||
|     m_prevent_list_events = false; | ||||
| 
 | ||||
|     size_t obj_idx = 0; | ||||
|     std::vector<size_t> obj_idxs; | ||||
|  |  | |||
|  | @ -366,6 +366,8 @@ public: | |||
|     void update_printable_state(int obj_idx, int instance_idx); | ||||
|     void toggle_printable_state(wxDataViewItem item); | ||||
| 
 | ||||
|     void show_multi_selection_menu(); | ||||
| 
 | ||||
| private: | ||||
| #ifdef __WXOSX__ | ||||
| //    void OnChar(wxKeyEvent& event);
 | ||||
|  | @ -384,8 +386,6 @@ private: | |||
| 	void OnEditingStarted(wxDataViewEvent &event); | ||||
| #endif /* __WXMSW__ */ | ||||
|     void OnEditingDone(wxDataViewEvent &event); | ||||
| 
 | ||||
|     void show_multi_selection_menu(); | ||||
|     void extruder_selection(); | ||||
|     void set_extruder_for_selected_items(const int extruder) const ; | ||||
| 
 | ||||
|  |  | |||
|  | @ -569,7 +569,7 @@ void Preview::update_view_type(bool slice_completed) | |||
| { | ||||
|     const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config; | ||||
| 
 | ||||
|     const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_height.empty() /*&&
 | ||||
|     const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_print_z.empty() /*&&
 | ||||
|                              (wxGetApp().extruders_edited_cnt()==1 || !slice_completed) */?  | ||||
|                                 _(L("Color Print")) : | ||||
|                                 config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1 ? | ||||
|  | @ -600,7 +600,7 @@ void Preview::create_double_slider() | |||
| 
 | ||||
|     Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { | ||||
|         Model& model = wxGetApp().plater()->model(); | ||||
|         model.custom_gcode_per_height = m_slider->GetTicksValues(); | ||||
|         model.custom_gcode_per_print_z = m_slider->GetTicksValues(); | ||||
|         m_schedule_background_process(); | ||||
| 
 | ||||
|         update_view_type(false); | ||||
|  | @ -646,7 +646,7 @@ void Preview::check_slider_values(std::vector<Model::CustomGCode>& ticks_from_mo | |||
|     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()); | ||||
|             auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val.print_z - DoubleSlider::epsilon()); | ||||
|             return it == layers_z.end(); | ||||
|         }), | ||||
|         ticks_from_model.end()); | ||||
|  | @ -669,7 +669,7 @@ 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<Model::CustomGCode> &ticks_from_model = wxGetApp().plater()->model().custom_gcode_per_height; | ||||
|     std::vector<Model::CustomGCode> &ticks_from_model = wxGetApp().plater()->model().custom_gcode_per_print_z; | ||||
|     check_slider_values(ticks_from_model, layers_z); | ||||
| 
 | ||||
|     m_slider->SetSliderValues(layers_z); | ||||
|  | @ -789,7 +789,7 @@ void Preview::load_print_as_fff(bool keep_z_range) | |||
|         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; | ||||
|             color_print_values = wxGetApp().plater()->model().custom_gcode_per_print_z; | ||||
|     } | ||||
|     else if (gcode_preview_data_valid || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool) ) | ||||
|     { | ||||
|  |  | |||
|  | @ -136,12 +136,25 @@ void GLGizmoCut::on_render_for_picking() const | |||
| 
 | ||||
| void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) | ||||
| { | ||||
|     const float approx_height = m_imgui->scaled(11.0f); | ||||
|     y = std::min(y, bottom_limit - approx_height); | ||||
|     m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); | ||||
|     static float last_y = 0.0f; | ||||
|     static float last_h = 0.0f; | ||||
| 
 | ||||
|     m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); | ||||
| 
 | ||||
|     // adjust window position to avoid overlap the view toolbar
 | ||||
|     float win_h = ImGui::GetWindowHeight(); | ||||
|     y = std::min(y, bottom_limit - win_h); | ||||
|     ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); | ||||
|     if ((last_h != win_h) || (last_y != y)) | ||||
|     { | ||||
|         // ask canvas for another frame to render the window in the correct position
 | ||||
|         m_parent.request_extra_frame(); | ||||
|         if (last_h != win_h) | ||||
|             last_h = win_h; | ||||
|         if (last_y != y) | ||||
|             last_y = y; | ||||
|     } | ||||
| 
 | ||||
|     ImGui::AlignTextToFramePadding(); | ||||
|     m_imgui->text("Z"); | ||||
|     ImGui::SameLine(); | ||||
|  |  | |||
|  | @ -749,17 +749,36 @@ void GLGizmoSlaSupports::make_line_segments() const | |||
| 
 | ||||
| void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit) | ||||
| { | ||||
|     if (!m_c->m_model_object) | ||||
|     static float last_y = 0.0f; | ||||
|     static float last_h = 0.0f; | ||||
| 
 | ||||
|     if (! m_c->m_model_object) | ||||
|         return; | ||||
| 
 | ||||
|     bool first_run = true; // This is a hack to redraw the button when all points are removed,
 | ||||
|                            // so it is not delayed until the background process finishes.
 | ||||
| RENDER_AGAIN: | ||||
|     const float approx_height = m_imgui->scaled(18.0f); | ||||
|     y = std::min(y, bottom_limit - approx_height); | ||||
|     m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); | ||||
|     //m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
 | ||||
|     //const ImVec2 window_size(m_imgui->scaled(18.f, 16.f));
 | ||||
|     //ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) ));
 | ||||
|     //ImGui::SetNextWindowSize(ImVec2(window_size));
 | ||||
| 
 | ||||
|     m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); | ||||
| 
 | ||||
|     // adjust window position to avoid overlap the view toolbar
 | ||||
|     float win_h = ImGui::GetWindowHeight(); | ||||
|     y = std::min(y, bottom_limit - win_h); | ||||
|     ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); | ||||
|     if ((last_h != win_h) || (last_y != y)) | ||||
|     { | ||||
|         // ask canvas for another frame to render the window in the correct position
 | ||||
|         m_parent.request_extra_frame(); | ||||
|         if (last_h != win_h) | ||||
|             last_h = win_h; | ||||
|         if (last_y != y) | ||||
|             last_y = y; | ||||
|     } | ||||
| 
 | ||||
|     // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
 | ||||
| 
 | ||||
|     const float settings_sliders_left = std::max(m_imgui->calc_text_size(m_desc.at("minimal_distance")).x, m_imgui->calc_text_size(m_desc.at("points_density")).x) + m_imgui->scaled(1.f); | ||||
|  |  | |||
|  | @ -20,10 +20,6 @@ GLGizmosManager::GLGizmosManager(GLCanvas3D& parent) | |||
|     , m_enabled(false) | ||||
|     , m_icons_texture_dirty(true) | ||||
|     , m_current(Undefined) | ||||
|     , m_overlay_icons_size(Default_Icons_Size) | ||||
|     , m_overlay_scale(1.0f) | ||||
|     , m_overlay_border(5.0f) | ||||
|     , m_overlay_gap_y(5.0f) | ||||
|     , m_tooltip("") | ||||
|     , m_serializing(false) | ||||
| { | ||||
|  | @ -53,19 +49,18 @@ size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const | |||
|         return Undefined; | ||||
| 
 | ||||
|     float cnv_h = (float)m_parent.get_canvas_size().get_height(); | ||||
|     float height = get_total_overlay_height(); | ||||
|     float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; | ||||
|     float scaled_border = m_overlay_border * m_overlay_scale; | ||||
|     float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; | ||||
|     float scaled_stride_y = scaled_icons_size + scaled_gap_y; | ||||
|     float top_y = 0.5f * (cnv_h - height) + scaled_border; | ||||
|     float height = get_scaled_total_height(); | ||||
|     float icons_size = m_layout.scaled_icons_size(); | ||||
|     float border = m_layout.scaled_border(); | ||||
|     float stride_y = m_layout.scaled_stride_y(); | ||||
|     float top_y = 0.5f * (cnv_h - height) + border; | ||||
| 
 | ||||
|     // is mouse horizontally in the area?
 | ||||
|     if ((scaled_border <= (float)mouse_pos(0) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size))) { | ||||
|     if ((border <= (float)mouse_pos(0) && ((float)mouse_pos(0) <= border + icons_size))) { | ||||
|         // which icon is it on?
 | ||||
|         size_t from_top = (size_t)((float)mouse_pos(1) - top_y)/scaled_stride_y; | ||||
|         size_t from_top = (size_t)((float)mouse_pos(1) - top_y) / stride_y; | ||||
|         // is it really on the icon or already past the border?
 | ||||
|         if ((float)mouse_pos(1) <= top_y + from_top*scaled_stride_y + scaled_icons_size) { | ||||
|         if ((float)mouse_pos(1) <= top_y + from_top * stride_y + icons_size) { | ||||
|             std::vector<size_t> selectable = get_selectable_idxs(); | ||||
|             if (from_top < selectable.size()) | ||||
|                 return selectable[from_top]; | ||||
|  | @ -113,18 +108,18 @@ bool GLGizmosManager::init() | |||
| 
 | ||||
| void GLGizmosManager::set_overlay_icon_size(float size) | ||||
| { | ||||
|     if (m_overlay_icons_size != size) | ||||
|     if (m_layout.icons_size != size) | ||||
|     { | ||||
|         m_overlay_icons_size = size; | ||||
|         m_layout.icons_size = size; | ||||
|         m_icons_texture_dirty = true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLGizmosManager::set_overlay_scale(float scale) | ||||
| { | ||||
|     if (m_overlay_scale != scale) | ||||
|     if (m_layout.scale != scale) | ||||
|     { | ||||
|         m_overlay_scale = scale; | ||||
|         m_layout.scale = scale; | ||||
|         m_icons_texture_dirty = true; | ||||
|     } | ||||
| } | ||||
|  | @ -873,26 +868,27 @@ void GLGizmosManager::do_render_overlay() const | |||
|     float zoom = (float)m_parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| 
 | ||||
|     float height = get_total_overlay_height(); | ||||
|     float width = get_total_overlay_width(); | ||||
|     float scaled_border = m_overlay_border * m_overlay_scale * inv_zoom; | ||||
|     float height = get_scaled_total_height(); | ||||
|     float width = get_scaled_total_width(); | ||||
|     float zoomed_border = m_layout.scaled_border() * inv_zoom; | ||||
| 
 | ||||
|     float top_x = (-0.5f * cnv_w) * inv_zoom; | ||||
|     float top_y = (0.5f * height) * inv_zoom; | ||||
|     float zoomed_top_x = (-0.5f * cnv_w) * inv_zoom; | ||||
|     float zoomed_top_y = (0.5f * height) * inv_zoom; | ||||
| 
 | ||||
|     float left = top_x; | ||||
|     float top = top_y; | ||||
|     float right = left + width * inv_zoom; | ||||
|     float bottom = top - height * inv_zoom; | ||||
|     float zoomed_left = zoomed_top_x; | ||||
|     float zoomed_top = zoomed_top_y; | ||||
|     float zoomed_right = zoomed_left + width * inv_zoom; | ||||
|     float zoomed_bottom = zoomed_top - height * inv_zoom; | ||||
| 
 | ||||
|     render_background(left, top, right, bottom, scaled_border); | ||||
|     render_background(zoomed_left, zoomed_top, zoomed_right, zoomed_bottom, zoomed_border); | ||||
| 
 | ||||
|     top_x += scaled_border; | ||||
|     top_y -= scaled_border; | ||||
|     float scaled_gap_y = m_overlay_gap_y * m_overlay_scale * inv_zoom; | ||||
|     zoomed_top_x += zoomed_border; | ||||
|     zoomed_top_y -= zoomed_border; | ||||
| 
 | ||||
|     float icons_size = m_layout.scaled_icons_size(); | ||||
|     float zoomed_icons_size = icons_size * inv_zoom; | ||||
|     float zoomed_stride_y = m_layout.scaled_stride_y() * inv_zoom; | ||||
| 
 | ||||
|     float scaled_icons_size = m_overlay_icons_size * m_overlay_scale * inv_zoom; | ||||
|     float scaled_stride_y = scaled_icons_size + scaled_gap_y; | ||||
|     unsigned int icons_texture_id = m_icons_texture.get_id(); | ||||
|     int tex_width = m_icons_texture.get_width(); | ||||
|     int tex_height = m_icons_texture.get_height(); | ||||
|  | @ -913,53 +909,36 @@ void GLGizmosManager::do_render_overlay() const | |||
|         int icon_idx = m_current == idx ? 2 : (m_hover == idx ? 1 : 0); | ||||
| #endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
 | ||||
| 
 | ||||
|         float u_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_width; | ||||
|         float v_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_height; | ||||
|         float u_icon_size = icons_size * inv_tex_width; | ||||
|         float v_icon_size = icons_size * inv_tex_height; | ||||
| 
 | ||||
|         float v_top = sprite_id * v_icon_size; | ||||
|         float u_left = icon_idx * u_icon_size; | ||||
|         float v_bottom = v_top + v_icon_size; | ||||
|         float u_right = u_left + u_icon_size; | ||||
| 
 | ||||
|         GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); | ||||
|         GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); | ||||
|         if (idx == m_current) { | ||||
|             float toolbar_top = cnv_h - m_parent.get_view_toolbar_height(); | ||||
|             gizmo->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top); | ||||
|             gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top); | ||||
|         } | ||||
|         top_y -= scaled_stride_y; | ||||
|         zoomed_top_y -= zoomed_stride_y; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| float GLGizmosManager::get_total_overlay_height() const | ||||
| float GLGizmosManager::get_scaled_total_height() const | ||||
| { | ||||
|     float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; | ||||
|     float scaled_border = m_overlay_border * m_overlay_scale; | ||||
|     float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; | ||||
|     float scaled_stride_y = scaled_icons_size + scaled_gap_y; | ||||
|     float height = 2.0f * scaled_border; | ||||
| 
 | ||||
|     /*for (size_t idx=0; idx<m_gizmos.size(); ++idx)
 | ||||
|     { | ||||
|         if ((m_gizmos[idx] == nullptr) || !m_gizmos[idx]->is_selectable()) | ||||
|             continue; | ||||
| 
 | ||||
|         height += scaled_stride_y; | ||||
|     }*/ | ||||
|     height += get_selectable_idxs().size() * scaled_stride_y; | ||||
| 
 | ||||
|     return height - scaled_gap_y; | ||||
|     return m_layout.scale * (2.0f * m_layout.border + (float)get_selectable_idxs().size() * m_layout.stride_y() - m_layout.gap_y); | ||||
| } | ||||
| 
 | ||||
| float GLGizmosManager::get_total_overlay_width() const | ||||
| float GLGizmosManager::get_scaled_total_width() const | ||||
| { | ||||
|     return (2.0f * m_overlay_border + m_overlay_icons_size) * m_overlay_scale; | ||||
|     return 2.0f * m_layout.scaled_border() + m_layout.scaled_icons_size(); | ||||
| } | ||||
| 
 | ||||
| GLGizmoBase* GLGizmosManager::get_current() const | ||||
| { | ||||
|     if (m_current==Undefined || m_gizmos.empty()) | ||||
|         return nullptr; | ||||
|     else | ||||
|         return m_gizmos[m_current].get(); | ||||
|     return ((m_current == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[m_current].get(); | ||||
| } | ||||
| 
 | ||||
| bool GLGizmosManager::generate_icons_texture() const | ||||
|  | @ -984,7 +963,7 @@ bool GLGizmosManager::generate_icons_texture() const | |||
|     states.push_back(std::make_pair(2, false)); // Disabled
 | ||||
| #endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
 | ||||
| 
 | ||||
|     unsigned int sprite_size_px = (unsigned int)(m_overlay_icons_size * m_overlay_scale); | ||||
|     unsigned int sprite_size_px = (unsigned int)m_layout.scaled_icons_size(); | ||||
| //    // force even size
 | ||||
| //    if (sprite_size_px % 2 != 0)
 | ||||
| //        sprite_size_px += 1;
 | ||||
|  |  | |||
|  | @ -65,12 +65,28 @@ public: | |||
|     }; | ||||
| 
 | ||||
| private: | ||||
|     struct Layout | ||||
|     { | ||||
|         float scale{ 1.0f }; | ||||
|         float icons_size{ Default_Icons_Size }; | ||||
|         float border{ 5.0f }; | ||||
|         float gap_y{ 5.0f }; | ||||
| 
 | ||||
|         float stride_y() const { return icons_size + gap_y;} | ||||
| 
 | ||||
|         float scaled_icons_size() const { return scale * icons_size; } | ||||
|         float scaled_border() const { return scale * border; } | ||||
|         float scaled_gap_y() const { return scale * gap_y; } | ||||
|         float scaled_stride_y() const { return scale * stride_y(); } | ||||
|     }; | ||||
| 
 | ||||
|     GLCanvas3D& m_parent; | ||||
|     bool m_enabled; | ||||
|     std::vector<std::unique_ptr<GLGizmoBase>> m_gizmos; | ||||
|     mutable GLTexture m_icons_texture; | ||||
|     mutable bool m_icons_texture_dirty; | ||||
|     BackgroundTexture m_background_texture; | ||||
|     Layout m_layout; | ||||
|     EType m_current; | ||||
|     EType m_hover; | ||||
| 
 | ||||
|  | @ -80,11 +96,6 @@ private: | |||
| 
 | ||||
|     void activate_gizmo(EType type); | ||||
| 
 | ||||
|     float m_overlay_icons_size; | ||||
|     float m_overlay_scale; | ||||
|     float m_overlay_border; | ||||
|     float m_overlay_gap_y; | ||||
| 
 | ||||
|     struct MouseCapture | ||||
|     { | ||||
|         bool left; | ||||
|  | @ -205,8 +216,8 @@ private: | |||
|     void render_background(float left, float top, float right, float bottom, float border) const; | ||||
|     void do_render_overlay() const; | ||||
| 
 | ||||
|     float get_total_overlay_height() const; | ||||
|     float get_total_overlay_width() const; | ||||
|     float get_scaled_total_height() const; | ||||
|     float get_scaled_total_width() const; | ||||
| 
 | ||||
|     bool generate_icons_texture() const; | ||||
| 
 | ||||
|  |  | |||
|  | @ -254,6 +254,16 @@ bool ImGuiWrapper::begin(const wxString &name, int flags) | |||
|     return begin(into_u8(name), flags); | ||||
| } | ||||
| 
 | ||||
| bool ImGuiWrapper::begin(const std::string& name, bool* close, int flags) | ||||
| { | ||||
|     return ImGui::Begin(name.c_str(), close, (ImGuiWindowFlags)flags); | ||||
| } | ||||
| 
 | ||||
| bool ImGuiWrapper::begin(const wxString& name, bool* close, int flags) | ||||
| { | ||||
|     return begin(into_u8(name), close, flags); | ||||
| } | ||||
| 
 | ||||
| void ImGuiWrapper::end() | ||||
| { | ||||
|     ImGui::End(); | ||||
|  |  | |||
|  | @ -56,6 +56,8 @@ public: | |||
| 
 | ||||
|     bool begin(const std::string &name, int flags = 0); | ||||
|     bool begin(const wxString &name, int flags = 0); | ||||
|     bool begin(const std::string& name, bool* close, int flags = 0); | ||||
|     bool begin(const wxString& name, bool* close, int flags = 0); | ||||
|     void end(); | ||||
| 
 | ||||
|     bool button(const wxString &label); | ||||
|  |  | |||
|  | @ -380,16 +380,6 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) | |||
|     this->Maximize(is_maximized); | ||||
| } | ||||
| 
 | ||||
| static std::string menu_icon(const std::string& icon_name) | ||||
| { | ||||
| #ifdef __WXMSW__ | ||||
|     const std::string folder = "white\\"; | ||||
| #else | ||||
|     const std::string folder = "white/"; | ||||
| #endif | ||||
|     return wxGetApp().dark_mode_menus() ? folder+icon_name : icon_name; | ||||
| } | ||||
| 
 | ||||
| void MainFrame::init_menubar() | ||||
| { | ||||
| #ifdef __APPLE__ | ||||
|  | @ -403,7 +393,7 @@ void MainFrame::init_menubar() | |||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->new_project(); }, "", nullptr, | ||||
|             [this](){return m_plater != nullptr && can_start_new_project(); }, this); | ||||
|         append_menu_item(fileMenu, wxID_ANY, _(L("&Open Project")) + dots + "\tCtrl+O", _(L("Open a project file")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, menu_icon("open"), nullptr, | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "open", nullptr, | ||||
|             [this](){return m_plater != nullptr; }, this); | ||||
| 
 | ||||
|         wxMenu* recent_projects_menu = new wxMenu(); | ||||
|  | @ -441,60 +431,65 @@ void MainFrame::init_menubar() | |||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); | ||||
| 
 | ||||
|         append_menu_item(fileMenu, wxID_ANY, _(L("&Save Project")) + "\tCtrl+S", _(L("Save current project file")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, menu_icon("save"), nullptr, | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr, | ||||
|             [this](){return m_plater != nullptr && can_save(); }, this); | ||||
| #ifdef __APPLE__ | ||||
|         append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Shift+S", _(L("Save current project file as")), | ||||
| #else | ||||
|         append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Alt+S", _(L("Save current project file as")), | ||||
| #endif // __APPLE__
 | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, menu_icon("save"), nullptr, | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr, | ||||
|             [this](){return m_plater != nullptr && can_save(); }, this); | ||||
| 
 | ||||
|         fileMenu->AppendSeparator(); | ||||
| 
 | ||||
|         wxMenu* import_menu = new wxMenu(); | ||||
|         append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, menu_icon("import_plater"), nullptr, | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr, | ||||
|             [this](){return m_plater != nullptr; }, this); | ||||
|         import_menu->AppendSeparator(); | ||||
|         append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")), | ||||
|             [this](wxCommandEvent&) { load_config_file(); }, menu_icon("import_config")); | ||||
|             [this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr, | ||||
|             [this]() {return true; }, this); | ||||
|         append_menu_item(import_menu, wxID_ANY, _(L("Import Config from &project")) + dots +"\tCtrl+Alt+L", _(L("Load configuration from project file")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, menu_icon("import_config")); | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, "import_config", nullptr, | ||||
|             [this]() {return true; }, this); | ||||
|         import_menu->AppendSeparator(); | ||||
|         append_menu_item(import_menu, wxID_ANY, _(L("Import Config &Bundle")) + dots, _(L("Load presets from a bundle")), | ||||
|             [this](wxCommandEvent&) { load_configbundle(); }, menu_icon("import_config_bundle")); | ||||
|             [this](wxCommandEvent&) { load_configbundle(); }, "import_config_bundle", nullptr, | ||||
|             [this]() {return true; }, this); | ||||
|         append_submenu(fileMenu, import_menu, wxID_ANY, _(L("&Import")), ""); | ||||
| 
 | ||||
|         wxMenu* export_menu = new wxMenu(); | ||||
|         wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, menu_icon("export_gcode"), nullptr, | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "export_gcode", nullptr, | ||||
|             [this](){return can_export_gcode(); }, this); | ||||
|         m_changeable_menu_items.push_back(item_export_gcode); | ||||
|         wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->send_gcode(); }, menu_icon("export_gcode"), nullptr, | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->send_gcode(); }, "export_gcode", nullptr, | ||||
|             [this](){return can_send_gcode(); }, this); | ||||
|         m_changeable_menu_items.push_back(item_send_gcode); | ||||
|         export_menu->AppendSeparator(); | ||||
|         append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, menu_icon("export_plater"), nullptr, | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "export_plater", nullptr, | ||||
|             [this](){return can_export_model(); }, this); | ||||
|         append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL &including supports")) + dots, _(L("Export current plate as STL including supports")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, menu_icon("export_plater"), nullptr, | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, "export_plater", nullptr, | ||||
|             [this](){return can_export_supports(); }, this); | ||||
|         append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &AMF")) + dots, _(L("Export current plate as AMF")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, menu_icon("export_plater"), nullptr, | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "export_plater", nullptr, | ||||
|             [this](){return can_export_model(); }, this); | ||||
|         export_menu->AppendSeparator(); | ||||
|         append_menu_item(export_menu, wxID_ANY, _(L("Export &toolpaths as OBJ")) + dots, _(L("Export toolpaths as OBJ")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_toolpaths_to_obj(); }, menu_icon("export_plater"), nullptr, | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr, | ||||
|             [this]() {return can_export_toolpaths(); }, this); | ||||
|         export_menu->AppendSeparator(); | ||||
|         append_menu_item(export_menu, wxID_ANY, _(L("Export &Config")) +dots +"\tCtrl+E", _(L("Export current configuration to file")), | ||||
|             [this](wxCommandEvent&) { export_config(); }, menu_icon("export_config")); | ||||
|             [this](wxCommandEvent&) { export_config(); }, "export_config", nullptr, | ||||
|             [this]() {return true; }, this); | ||||
|         append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")), | ||||
|             [this](wxCommandEvent&) { export_configbundle(); }, menu_icon("export_config_bundle")); | ||||
|             [this](wxCommandEvent&) { export_configbundle(); }, "export_config_bundle", nullptr, | ||||
|             [this]() {return true; }, this); | ||||
|         append_submenu(fileMenu, export_menu, wxID_ANY, _(L("&Export")), ""); | ||||
| 
 | ||||
|         fileMenu->AppendSeparator(); | ||||
|  | @ -522,11 +517,12 @@ void MainFrame::init_menubar() | |||
|         fileMenu->AppendSeparator(); | ||||
| #endif | ||||
|         m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _(L("(Re)Slice No&w")) + "\tCtrl+R", _(L("Start new slicing process")), | ||||
|             [this](wxCommandEvent&) { reslice_now(); }, menu_icon("re_slice"), nullptr, | ||||
|             [this](wxCommandEvent&) { reslice_now(); }, "re_slice", nullptr, | ||||
|             [this](){return m_plater != nullptr && can_reslice(); }, this); | ||||
|         fileMenu->AppendSeparator(); | ||||
|         append_menu_item(fileMenu, wxID_ANY, _(L("&Repair STL file")) + dots, _(L("Automatically repair an STL file")), | ||||
|             [this](wxCommandEvent&) { repair_stl(); }, menu_icon("wrench")); | ||||
|             [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, | ||||
|             [this]() {return true; }, this); | ||||
|         fileMenu->AppendSeparator(); | ||||
|         append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), wxString::Format(_(L("Quit %s")), SLIC3R_APP_NAME), | ||||
|             [this](wxCommandEvent&) { Close(false); }); | ||||
|  | @ -562,10 +558,10 @@ void MainFrame::init_menubar() | |||
|         editMenu->AppendSeparator(); | ||||
|         append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + sep + hotkey_delete, | ||||
|             _(L("Deletes the current selection")),[this](wxCommandEvent&) { m_plater->remove_selected(); }, | ||||
|             menu_icon("remove_menu"), nullptr, [this](){return can_delete(); }, this); | ||||
|             "remove_menu", nullptr, [this](){return can_delete(); }, this); | ||||
|         append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete, | ||||
|             _(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); }, | ||||
|             menu_icon("delete_all_menu"), nullptr, [this](){return can_delete_all(); }, this); | ||||
|             "delete_all_menu", nullptr, [this](){return can_delete_all(); }, this); | ||||
| 
 | ||||
|         editMenu->AppendSeparator(); | ||||
|         append_menu_item(editMenu, wxID_ANY, _(L("&Undo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z", | ||||
|  | @ -578,10 +574,10 @@ void MainFrame::init_menubar() | |||
|         editMenu->AppendSeparator(); | ||||
|         append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C", | ||||
|             _(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, | ||||
|             menu_icon("copy_menu"), nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this); | ||||
|             "copy_menu", nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this); | ||||
|         append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V", | ||||
|             _(L("Paste clipboard")), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, | ||||
|             menu_icon("paste_menu"), nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this); | ||||
|             "paste_menu", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this); | ||||
|     } | ||||
| 
 | ||||
|     // Window menu
 | ||||
|  | @ -590,26 +586,30 @@ void MainFrame::init_menubar() | |||
|         size_t tab_offset = 0; | ||||
|         if (m_plater) { | ||||
|             append_menu_item(windowMenu, wxID_HIGHEST + 1, _(L("&Plater Tab")) + "\tCtrl+1", _(L("Show the plater")), | ||||
|                 [this](wxCommandEvent&) { select_tab(0); }, menu_icon("plater")); | ||||
|                 [this](wxCommandEvent&) { select_tab(0); }, "plater", nullptr, | ||||
|                 [this]() {return true; }, this); | ||||
|             tab_offset += 1; | ||||
|         } | ||||
|         if (tab_offset > 0) { | ||||
|             windowMenu->AppendSeparator(); | ||||
|         } | ||||
|         append_menu_item(windowMenu, wxID_HIGHEST + 2, _(L("P&rint Settings Tab")) + "\tCtrl+2", _(L("Show the print settings")), | ||||
|             [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 0); }, menu_icon("cog")); | ||||
|             [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 0); }, "cog", nullptr, | ||||
|             [this]() {return true; }, this); | ||||
|         wxMenuItem* item_material_tab = append_menu_item(windowMenu, wxID_HIGHEST + 3, _(L("&Filament Settings Tab")) + "\tCtrl+3", _(L("Show the filament settings")), | ||||
|             [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 1); }, menu_icon("spool")); | ||||
|             [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 1); }, "spool", nullptr, | ||||
|             [this]() {return true; }, this); | ||||
|         m_changeable_menu_items.push_back(item_material_tab); | ||||
|         append_menu_item(windowMenu, wxID_HIGHEST + 4, _(L("Print&er Settings Tab")) + "\tCtrl+4", _(L("Show the printer settings")), | ||||
|             [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, menu_icon("printer")); | ||||
|             [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, "printer", nullptr, | ||||
|             [this]() {return true; }, this); | ||||
|         if (m_plater) { | ||||
|             windowMenu->AppendSeparator(); | ||||
|             append_menu_item(windowMenu, wxID_HIGHEST + 5, _(L("3&D")) + "\tCtrl+5", _(L("Show the 3D editing view")), | ||||
|                 [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, menu_icon("editor_menu"), nullptr, | ||||
|                 [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "editor_menu", nullptr, | ||||
|                 [this](){return can_change_view(); }, this); | ||||
|             append_menu_item(windowMenu, wxID_HIGHEST + 6, _(L("Pre&view")) + "\tCtrl+6", _(L("Show the 3D slices preview")), | ||||
|                 [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, menu_icon("preview_menu"), nullptr, | ||||
|                 [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "preview_menu", nullptr, | ||||
|                 [this](){return can_change_view(); }, this); | ||||
|         } | ||||
| 
 | ||||
|  | @ -628,7 +628,8 @@ void MainFrame::init_menubar() | |||
| 
 | ||||
|         windowMenu->AppendSeparator(); | ||||
|         append_menu_item(windowMenu, wxID_ANY, _(L("Print &Host Upload Queue")) + "\tCtrl+J", _(L("Display the Print Host Upload Queue window")), | ||||
|             [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, menu_icon("upload_queue")); | ||||
|             [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, | ||||
|             [this]() {return true; }, this); | ||||
|     } | ||||
| 
 | ||||
|     // View menu
 | ||||
|  | @ -727,7 +728,7 @@ void MainFrame::update_menubar() | |||
|     m_changeable_menu_items[miSend]         ->SetItemLabel((is_fff ? _(L("S&end G-code"))           : _(L("S&end to print"))) + dots    + "\tCtrl+Shift+G"); | ||||
| 
 | ||||
|     m_changeable_menu_items[miMaterialTab]  ->SetItemLabel((is_fff ? _(L("&Filament Settings Tab")) : _(L("Mate&rial Settings Tab")))   + "\tCtrl+3"); | ||||
|     m_changeable_menu_items[miMaterialTab]  ->SetBitmap(create_scaled_bitmap(this, menu_icon(is_fff ? "spool": "resin"))); | ||||
|     m_changeable_menu_items[miMaterialTab]  ->SetBitmap(create_scaled_bitmap(this, is_fff ? "spool": "resin")); | ||||
| } | ||||
| 
 | ||||
| // To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
 | ||||
|  |  | |||
|  | @ -5,6 +5,9 @@ | |||
| #include "GUI_App.hpp" | ||||
| #include "PresetBundle.hpp" | ||||
| #include "AppConfig.hpp" | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
| #include "GLCanvas3D.hpp" | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
| 
 | ||||
| #include <wx/glcanvas.h> | ||||
| 
 | ||||
|  | @ -184,7 +187,10 @@ Mouse3DController::Mouse3DController() | |||
|     , m_device(nullptr) | ||||
|     , m_device_str("") | ||||
|     , m_running(false) | ||||
|     , m_settings_dialog(false) | ||||
|     , m_show_settings_dialog(false) | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
|     , m_settings_dialog_closed_by_user(false) | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
| { | ||||
|     m_last_time = std::chrono::high_resolution_clock::now(); | ||||
| } | ||||
|  | @ -229,8 +235,11 @@ bool Mouse3DController::apply(Camera& camera) | |||
|     if (!m_running && is_device_connected()) | ||||
|     { | ||||
|         disconnect_device(); | ||||
|         // hides the settings dialog if the user re-plug the device
 | ||||
|         m_settings_dialog = false; | ||||
|         // hides the settings dialog if the user un-plug the device
 | ||||
|         m_show_settings_dialog = false; | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
|         m_settings_dialog_closed_by_user = false; | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
|     } | ||||
| 
 | ||||
|     // check if the user plugged the device
 | ||||
|  | @ -240,88 +249,144 @@ bool Mouse3DController::apply(Camera& camera) | |||
|     return is_device_connected() ? m_state.apply(camera) : false; | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
| void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const | ||||
| #else | ||||
| void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsigned int canvas_height) const | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
| { | ||||
|     if (!m_running || !m_settings_dialog) | ||||
|     if (!m_running || !m_show_settings_dialog) | ||||
|         return; | ||||
| 
 | ||||
|     ImGuiWrapper& imgui = *wxGetApp().imgui(); | ||||
| 
 | ||||
|     imgui.set_next_window_pos(0.5f * (float)canvas_width, 0.5f * (float)canvas_height, ImGuiCond_Always, 0.5f, 0.5f); | ||||
| 
 | ||||
|     imgui.begin(_(L("3Dconnexion settings")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); | ||||
| 
 | ||||
|     const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator); | ||||
|     ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|     imgui.text(_(L("Device:"))); | ||||
|     ImGui::PopStyleColor(); | ||||
|     ImGui::SameLine(); | ||||
|     imgui.text(m_device_str); | ||||
| 
 | ||||
|     ImGui::Separator(); | ||||
|     ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|     imgui.text(_(L("Speed:"))); | ||||
|     ImGui::PopStyleColor(); | ||||
| 
 | ||||
|     float translation_scale = (float)m_state.get_translation_scale() / State::DefaultTranslationScale; | ||||
|     if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.5f, 2.0f, "%.1f")) | ||||
|         m_state.set_translation_scale(State::DefaultTranslationScale * (double)translation_scale); | ||||
| 
 | ||||
|     float rotation_scale = m_state.get_rotation_scale() / State::DefaultRotationScale; | ||||
|     if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.5f, 2.0f, "%.1f")) | ||||
|         m_state.set_rotation_scale(State::DefaultRotationScale * rotation_scale); | ||||
| 
 | ||||
|     ImGui::Separator(); | ||||
|     ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|     imgui.text(_(L("Deadzone:"))); | ||||
|     ImGui::PopStyleColor(); | ||||
| 
 | ||||
|     float translation_deadzone = (float)m_state.get_translation_deadzone(); | ||||
|     if (imgui.slider_float(_(L("Translation")) + "##2", &translation_deadzone, 0.0f, (float)State::MaxTranslationDeadzone, "%.2f")) | ||||
|         m_state.set_translation_deadzone((double)translation_deadzone); | ||||
| 
 | ||||
|     float rotation_deadzone = m_state.get_rotation_deadzone(); | ||||
|     if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, State::MaxRotationDeadzone, "%.2f")) | ||||
|         m_state.set_rotation_deadzone(rotation_deadzone); | ||||
| 
 | ||||
| #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT | ||||
|     ImGui::Separator(); | ||||
|     ImGui::Separator(); | ||||
|     ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|     imgui.text("DEBUG:"); | ||||
|     imgui.text("Vectors:"); | ||||
|     ImGui::PopStyleColor(); | ||||
|     Vec3f translation = m_state.get_translation().cast<float>(); | ||||
|     Vec3f rotation = m_state.get_rotation(); | ||||
|     ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); | ||||
|     ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); | ||||
| 
 | ||||
|     ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|     imgui.text("Queue size:"); | ||||
|     ImGui::PopStyleColor(); | ||||
| 
 | ||||
|     int translation_size[2] = { (int)m_state.get_translation_queue_size(), (int)m_state.get_translation_queue_max_size() }; | ||||
|     int rotation_size[2] = { (int)m_state.get_rotation_queue_size(), (int)m_state.get_rotation_queue_max_size() }; | ||||
|     int buttons_size[2] = { (int)m_state.get_buttons_queue_size(), (int)m_state.get_buttons_queue_max_size() }; | ||||
| 
 | ||||
|     ImGui::InputInt2("Translation##4", translation_size, ImGuiInputTextFlags_ReadOnly); | ||||
|     ImGui::InputInt2("Rotation##4", rotation_size, ImGuiInputTextFlags_ReadOnly); | ||||
|     ImGui::InputInt2("Buttons", buttons_size, ImGuiInputTextFlags_ReadOnly); | ||||
| 
 | ||||
|     int queue_size = (int)m_state.get_queues_max_size(); | ||||
|     if (ImGui::InputInt("Max size", &queue_size, 1, 1, ImGuiInputTextFlags_ReadOnly)) | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
|     // when the user clicks on [X] or [Close] button we need to trigger
 | ||||
|     // an extra frame to let the dialog disappear
 | ||||
|     if (m_settings_dialog_closed_by_user) | ||||
|     { | ||||
|         if (queue_size > 0) | ||||
|             m_state.set_queues_max_size(queue_size); | ||||
|         m_show_settings_dialog = false; | ||||
|         m_settings_dialog_closed_by_user = false; | ||||
|         canvas.request_extra_frame(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ImGui::Separator(); | ||||
|     ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|     imgui.text("Camera:"); | ||||
|     ImGui::PopStyleColor(); | ||||
|     Vec3f target = wxGetApp().plater()->get_camera().get_target().cast<float>(); | ||||
|     ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); | ||||
|     Size cnv_size = canvas.get_canvas_size(); | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
| 
 | ||||
|     ImGuiWrapper& imgui = *wxGetApp().imgui(); | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
|     imgui.set_next_window_pos(0.5f * (float)cnv_size.get_width(), 0.5f * (float)cnv_size.get_height(), ImGuiCond_Always, 0.5f, 0.5f); | ||||
| #else | ||||
|     imgui.set_next_window_pos(0.5f * (float)canvas_width, 0.5f * (float)canvas_height, ImGuiCond_Always, 0.5f, 0.5f); | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
| 
 | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
|     static ImVec2 last_win_size(0.0f, 0.0f); | ||||
|     bool shown = true; | ||||
|     if (imgui.begin(_(L("3Dconnexion settings")), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) | ||||
|     { | ||||
|         if (shown) | ||||
|         { | ||||
|             ImVec2 win_size = ImGui::GetWindowSize(); | ||||
|             if ((last_win_size.x != win_size.x) || (last_win_size.y != win_size.y)) | ||||
|             { | ||||
|                 // when the user clicks on [X] button, the next time the dialog is shown 
 | ||||
|                 // has a dummy size, so we trigger an extra frame to let it have the correct size
 | ||||
|                 last_win_size = win_size; | ||||
|                 canvas.request_extra_frame(); | ||||
|             } | ||||
| #else | ||||
|     imgui.begin(_(L("3Dconnexion settings")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
| 
 | ||||
|             const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator); | ||||
|             ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|             imgui.text(_(L("Device:"))); | ||||
|             ImGui::PopStyleColor(); | ||||
|             ImGui::SameLine(); | ||||
|             imgui.text(m_device_str); | ||||
| 
 | ||||
|             ImGui::Separator(); | ||||
|             ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|             imgui.text(_(L("Speed:"))); | ||||
|             ImGui::PopStyleColor(); | ||||
| 
 | ||||
|             float translation_scale = (float)m_state.get_translation_scale() / State::DefaultTranslationScale; | ||||
|             if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.5f, 2.0f, "%.1f")) | ||||
|                 m_state.set_translation_scale(State::DefaultTranslationScale * (double)translation_scale); | ||||
| 
 | ||||
|             float rotation_scale = m_state.get_rotation_scale() / State::DefaultRotationScale; | ||||
|             if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.5f, 2.0f, "%.1f")) | ||||
|                 m_state.set_rotation_scale(State::DefaultRotationScale * rotation_scale); | ||||
| 
 | ||||
|             ImGui::Separator(); | ||||
|             ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|             imgui.text(_(L("Deadzone:"))); | ||||
|             ImGui::PopStyleColor(); | ||||
| 
 | ||||
|             float translation_deadzone = (float)m_state.get_translation_deadzone(); | ||||
|             if (imgui.slider_float(_(L("Translation")) + "##2", &translation_deadzone, 0.0f, (float)State::MaxTranslationDeadzone, "%.2f")) | ||||
|                 m_state.set_translation_deadzone((double)translation_deadzone); | ||||
| 
 | ||||
|             float rotation_deadzone = m_state.get_rotation_deadzone(); | ||||
|             if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, State::MaxRotationDeadzone, "%.2f")) | ||||
|                 m_state.set_rotation_deadzone(rotation_deadzone); | ||||
| 
 | ||||
| #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT | ||||
|             ImGui::Separator(); | ||||
|             ImGui::Separator(); | ||||
|             ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|             imgui.text("DEBUG:"); | ||||
|             imgui.text("Vectors:"); | ||||
|             ImGui::PopStyleColor(); | ||||
|             Vec3f translation = m_state.get_translation().cast<float>(); | ||||
|             Vec3f rotation = m_state.get_rotation(); | ||||
|             ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); | ||||
|             ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); | ||||
| 
 | ||||
|             ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|             imgui.text("Queue size:"); | ||||
|             ImGui::PopStyleColor(); | ||||
| 
 | ||||
|             int translation_size[2] = { (int)m_state.get_translation_queue_size(), (int)m_state.get_translation_queue_max_size() }; | ||||
|             int rotation_size[2] = { (int)m_state.get_rotation_queue_size(), (int)m_state.get_rotation_queue_max_size() }; | ||||
|             int buttons_size[2] = { (int)m_state.get_buttons_queue_size(), (int)m_state.get_buttons_queue_max_size() }; | ||||
| 
 | ||||
|             ImGui::InputInt2("Translation##4", translation_size, ImGuiInputTextFlags_ReadOnly); | ||||
|             ImGui::InputInt2("Rotation##4", rotation_size, ImGuiInputTextFlags_ReadOnly); | ||||
|             ImGui::InputInt2("Buttons", buttons_size, ImGuiInputTextFlags_ReadOnly); | ||||
| 
 | ||||
|             int queue_size = (int)m_state.get_queues_max_size(); | ||||
|             if (ImGui::InputInt("Max size", &queue_size, 1, 1, ImGuiInputTextFlags_ReadOnly)) | ||||
|             { | ||||
|                 if (queue_size > 0) | ||||
|                     m_state.set_queues_max_size(queue_size); | ||||
|             } | ||||
| 
 | ||||
|             ImGui::Separator(); | ||||
|             ImGui::PushStyleColor(ImGuiCol_Text, color); | ||||
|             imgui.text("Camera:"); | ||||
|             ImGui::PopStyleColor(); | ||||
|             Vec3f target = wxGetApp().plater()->get_camera().get_target().cast<float>(); | ||||
|             ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
 | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
| 
 | ||||
|             ImGui::Separator(); | ||||
|             if (imgui.button(_(L("Close")))) | ||||
|             { | ||||
|                 // the user clicked on the [Close] button
 | ||||
|                 m_settings_dialog_closed_by_user = true; | ||||
|                 canvas.set_as_dirty(); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // the user clicked on the [X] button
 | ||||
|             m_settings_dialog_closed_by_user = true; | ||||
|             canvas.set_as_dirty(); | ||||
|         } | ||||
|     } | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
| 
 | ||||
|     imgui.end(); | ||||
| } | ||||
|  |  | |||
|  | @ -18,6 +18,9 @@ namespace Slic3r { | |||
| namespace GUI { | ||||
| 
 | ||||
| struct Camera; | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
| class GLCanvas3D; | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
| 
 | ||||
| class Mouse3DController | ||||
| { | ||||
|  | @ -130,7 +133,13 @@ class Mouse3DController | |||
|     hid_device* m_device; | ||||
|     std::string m_device_str; | ||||
|     bool m_running; | ||||
|     bool m_settings_dialog; | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
|     mutable bool m_show_settings_dialog; | ||||
|     // set to true when ther user closes the dialog by clicking on [X] or [Close] buttons
 | ||||
|     mutable bool m_settings_dialog_closed_by_user; | ||||
| #else | ||||
|     bool m_show_settings_dialog; | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
|     std::chrono::time_point<std::chrono::high_resolution_clock> m_last_time; | ||||
| 
 | ||||
| public: | ||||
|  | @ -146,9 +155,13 @@ public: | |||
| 
 | ||||
|     bool apply(Camera& camera); | ||||
| 
 | ||||
|     bool is_settings_dialog_shown() const { return m_settings_dialog; } | ||||
|     void show_settings_dialog(bool show) { m_settings_dialog = show && is_running(); } | ||||
|     bool is_settings_dialog_shown() const { return m_show_settings_dialog; } | ||||
|     void show_settings_dialog(bool show) { m_show_settings_dialog = show && is_running(); } | ||||
| #if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG | ||||
|     void render_settings_dialog(GLCanvas3D& canvas) const; | ||||
| #else | ||||
|     void render_settings_dialog(unsigned int canvas_width, unsigned int canvas_height) const; | ||||
| #endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG
 | ||||
| 
 | ||||
| private: | ||||
|     bool connect_device(); | ||||
|  |  | |||
|  | @ -2252,7 +2252,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ | |||
|                         config += std::move(config_loaded); | ||||
|                     } | ||||
| 
 | ||||
|                     this->model.custom_gcode_per_height = model.custom_gcode_per_height; | ||||
|                     this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; | ||||
|                 } | ||||
| 
 | ||||
|                 if (load_config) | ||||
|  | @ -2671,7 +2671,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); | ||||
| 
 | ||||
|     model.custom_gcode_per_height.clear(); | ||||
|     model.custom_gcode_per_print_z.clear(); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::mirror(Axis axis) | ||||
|  | @ -3219,34 +3219,48 @@ void Plater::priv::reload_from_disk() | |||
|     while (!missing_input_paths.empty()) | ||||
|     { | ||||
|         // ask user to select the missing file
 | ||||
|         std::string search = missing_input_paths.back().string(); | ||||
|         wxFileDialog dialog(q, _(L("Please select the file to reload:")), "", search, file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); | ||||
|         fs::path search = missing_input_paths.back(); | ||||
|         wxString title = _(L("Please select the file to reload")); | ||||
| #if defined(__APPLE__) | ||||
|         title += " (" + from_u8(search.filename().string()) + "):"; | ||||
| #else | ||||
|         title += ":"; | ||||
| #endif // __APPLE__
 | ||||
|         wxFileDialog dialog(q, title, "", from_u8(search.filename().string()), file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); | ||||
|         if (dialog.ShowModal() != wxID_OK) | ||||
|             return; | ||||
| 
 | ||||
|         std::string sel_filename_path = dialog.GetPath().ToUTF8().data(); | ||||
|         std::string sel_filename = fs::path(sel_filename_path).filename().string(); | ||||
|         if (boost::algorithm::iends_with(search, sel_filename)) | ||||
|         if (boost::algorithm::iequals(search.filename().string(), sel_filename)) | ||||
|         { | ||||
|             input_paths.push_back(sel_filename_path); | ||||
|             missing_input_paths.pop_back(); | ||||
| 
 | ||||
|             std::string sel_path = fs::path(sel_filename_path).remove_filename().string(); | ||||
|             fs::path sel_path = fs::path(sel_filename_path).remove_filename().string(); | ||||
| 
 | ||||
|             std::vector<fs::path>::iterator it = missing_input_paths.begin(); | ||||
|             while (it != missing_input_paths.end()) | ||||
|             { | ||||
|                 // try to use the path of the selected file with all remaining missing files
 | ||||
|                 std::string repathed_filename = sel_path + "/" + it->filename().string(); | ||||
|                 fs::path repathed_filename = sel_path; | ||||
|                 repathed_filename /= it->filename(); | ||||
|                 if (fs::exists(repathed_filename)) | ||||
|                 { | ||||
|                     input_paths.push_back(repathed_filename); | ||||
|                     input_paths.push_back(repathed_filename.string()); | ||||
|                     it = missing_input_paths.erase(it); | ||||
|                 } | ||||
|                 else | ||||
|                     ++it; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             wxString message = _(L("It is not allowed to change the file to reload")) + " (" + from_u8(search.filename().string()) + ").\n" + _(L("Do you want to retry")) + " ?"; | ||||
|             wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); | ||||
|             if (dlg.ShowModal() != wxID_YES) | ||||
|                 return; | ||||
|         } | ||||
|     } | ||||
| #endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
 | ||||
| 
 | ||||
|  | @ -3281,7 +3295,8 @@ void Plater::priv::reload_from_disk() | |||
|             int new_volume_idx = old_volume->source.volume_idx; | ||||
|             int new_object_idx = old_volume->source.object_idx; | ||||
| 
 | ||||
|             if (old_volume->source.input_file == path) | ||||
|             if (boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), | ||||
|                 fs::path(path).filename().string())) | ||||
|             { | ||||
|                 assert(new_object_idx < (int)new_model.objects.size()); | ||||
|                 ModelObject* new_model_object = new_model.objects[new_object_idx]; | ||||
|  | @ -3295,6 +3310,7 @@ void Plater::priv::reload_from_disk() | |||
|                     new_volume->set_material_id(old_volume->material_id()); | ||||
|                     new_volume->set_transformation(old_volume->get_transformation()); | ||||
|                     new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); | ||||
|                     new_volume->source.input_file = path; | ||||
|                     std::swap(old_model_object->volumes[old_v.volume_idx], old_model_object->volumes.back()); | ||||
|                     old_model_object->delete_volume(old_model_object->volumes.size() - 1); | ||||
|                 } | ||||
|  | @ -3593,7 +3609,10 @@ void Plater::priv::on_right_click(RBtnEvent& evt) | |||
|         if (evt.data.second) // right button was clicked on empty space
 | ||||
|             menu = &default_menu; | ||||
|         else | ||||
|         { | ||||
|             sidebar->obj_list()->show_multi_selection_menu(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|  | @ -4623,6 +4642,13 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe | |||
| 
 | ||||
|     remove(obj_idx); | ||||
|     p->load_model_objects(new_objects); | ||||
| 
 | ||||
|     Selection& selection = p->get_selection(); | ||||
|     size_t last_id = p->model.objects.size() - 1; | ||||
|     for (size_t i = 0; i < new_objects.size(); ++i) | ||||
|     { | ||||
|         selection.add_object((unsigned int)(last_id - i), i == 0); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Plater::export_gcode() | ||||
|  | @ -5157,6 +5183,7 @@ const DynamicPrintConfig* Plater::get_plater_config() const | |||
|     return p->config; | ||||
| } | ||||
| 
 | ||||
| // Get vector of extruder colors considering filament color, if extruder color is undefined.
 | ||||
| std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const | ||||
| { | ||||
|     const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; | ||||
|  | @ -5176,13 +5203,17 @@ std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const | |||
|     return extruder_colors; | ||||
| } | ||||
| 
 | ||||
| /* Get vector of colors used for rendering of a Preview scene in "Color print" mode
 | ||||
|  * It consists of extruder colors and colors, saved in model.custom_gcode_per_print_z | ||||
|  */ | ||||
| std::vector<std::string> Plater::get_colors_for_color_print() const | ||||
| { | ||||
|     std::vector<std::string> colors = get_extruder_colors_from_plater_config(); | ||||
|     colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.size()); | ||||
| 
 | ||||
|     for (const Model::CustomGCode& code : p->model.custom_gcode_per_height) | ||||
|     for (const Model::CustomGCode& code : p->model.custom_gcode_per_print_z) | ||||
|         if (code.gcode == ColorChangeCode) | ||||
|             colors.push_back(code.color); | ||||
|             colors.emplace_back(code.color); | ||||
| 
 | ||||
|     return colors; | ||||
| } | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ | |||
| 
 | ||||
| #include "libslic3r/libslic3r.h" | ||||
| #include "libslic3r/Utils.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| 
 | ||||
| // Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir.
 | ||||
| // This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions.
 | ||||
|  | @ -868,6 +869,9 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool | |||
|         } | ||||
|         // 4) Load the project config values (the per extruder wipe matrix etc).
 | ||||
|         this->project_config.apply_only(config, s_project_options); | ||||
| 
 | ||||
|         update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z, &this->project_config); | ||||
| 
 | ||||
|         break; | ||||
|     } | ||||
|     case ptSLA: | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ | |||
| #include <wx/colordlg.h> | ||||
| 
 | ||||
| #include <boost/algorithm/string/replace.hpp> | ||||
| #include <boost/nowide/cstdio.hpp> | ||||
| 
 | ||||
| #include "BitmapCache.hpp" | ||||
| #include "GUI.hpp" | ||||
|  | @ -57,7 +58,7 @@ void msw_rescale_menu(wxMenu* menu) | |||
| #endif /* __WXMSW__ */ | ||||
| #endif /* no __WXGTK__ */ | ||||
| 
 | ||||
| void enable_menu_item(wxUpdateUIEvent& evt, std::function<bool()> const cb_condition, wxMenuItem* item) | ||||
| void enable_menu_item(wxUpdateUIEvent& evt, std::function<bool()> const cb_condition, wxMenuItem* item, wxWindow* win) | ||||
| { | ||||
|     const bool enable = cb_condition(); | ||||
|     evt.Enable(enable); | ||||
|  | @ -66,7 +67,7 @@ void enable_menu_item(wxUpdateUIEvent& evt, std::function<bool()> const cb_condi | |||
|     const auto it = msw_menuitem_bitmaps.find(item->GetId()); | ||||
|     if (it != msw_menuitem_bitmaps.end()) | ||||
|     { | ||||
|         const wxBitmap& item_icon = create_scaled_bitmap(nullptr, it->second, 16, false, !enable); | ||||
|         const wxBitmap& item_icon = create_scaled_bitmap(win, it->second, 16, false, !enable); | ||||
|         if (item_icon.IsOk()) | ||||
|             item->SetBitmap(item_icon); | ||||
|     } | ||||
|  | @ -94,8 +95,8 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const | |||
|         menu->Bind(wxEVT_MENU, cb, id); | ||||
| 
 | ||||
|     if (parent) { | ||||
|         parent->Bind(wxEVT_UPDATE_UI, [cb_condition, item](wxUpdateUIEvent& evt) { | ||||
|             enable_menu_item(evt, cb_condition, item); }, id); | ||||
|         parent->Bind(wxEVT_UPDATE_UI, [cb_condition, item, parent](wxUpdateUIEvent& evt) { | ||||
|             enable_menu_item(evt, cb_condition, item, parent); }, id); | ||||
|     } | ||||
| 
 | ||||
|     return item; | ||||
|  | @ -108,7 +109,7 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const | |||
|     if (id == wxID_ANY) | ||||
|         id = wxNewId(); | ||||
| 
 | ||||
|     const wxBitmap& bmp = !icon.empty() ? create_scaled_bitmap(nullptr, icon) : wxNullBitmap;   // FIXME: pass window ptr
 | ||||
|     const wxBitmap& bmp = !icon.empty() ? create_scaled_bitmap(parent, icon) : wxNullBitmap;   // FIXME: pass window ptr
 | ||||
| //#ifdef __WXMSW__
 | ||||
| #ifndef __WXGTK__ | ||||
|     if (bmp.IsOk()) | ||||
|  | @ -126,7 +127,7 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin | |||
| 
 | ||||
|     wxMenuItem* item = new wxMenuItem(menu, id, string, description); | ||||
|     if (!icon.empty()) { | ||||
|         item->SetBitmap(create_scaled_bitmap(nullptr, icon));    // FIXME: pass window ptr
 | ||||
|         item->SetBitmap(create_scaled_bitmap(parent, icon));    // FIXME: pass window ptr
 | ||||
| //#ifdef __WXMSW__
 | ||||
| #ifndef __WXGTK__ | ||||
|         msw_menuitem_bitmaps[id] = icon; | ||||
|  | @ -137,8 +138,8 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin | |||
|     menu->Append(item); | ||||
| 
 | ||||
|     if (parent) { | ||||
|         parent->Bind(wxEVT_UPDATE_UI, [cb_condition, item](wxUpdateUIEvent& evt) { | ||||
|             enable_menu_item(evt, cb_condition, item); }, id); | ||||
|         parent->Bind(wxEVT_UPDATE_UI, [cb_condition, item, parent](wxUpdateUIEvent& evt) { | ||||
|             enable_menu_item(evt, cb_condition, item, parent); }, id); | ||||
|     } | ||||
| 
 | ||||
|     return item; | ||||
|  | @ -424,6 +425,28 @@ static float get_svg_scale_factor(wxWindow *win) | |||
| #endif | ||||
| } | ||||
| 
 | ||||
| // in the Dark mode of any platform, we should draw icons in respect to OS background
 | ||||
| static std::string icon_name_respected_to_mode(const std::string& bmp_name_in) | ||||
| { | ||||
| #ifdef __WXMSW__ | ||||
|     const std::string folder = "white\\"; | ||||
| #else | ||||
|     const std::string folder = "white/"; | ||||
| #endif | ||||
|     std::string bmp_name = Slic3r::GUI::wxGetApp().dark_mode() ? folder + bmp_name_in : bmp_name_in; | ||||
|     boost::replace_last(bmp_name, ".png", ""); | ||||
|     FILE* fp = NULL; | ||||
|     fp = boost::nowide::fopen(Slic3r::var(bmp_name + ".svg").c_str(), "rb"); | ||||
|     if (!fp) | ||||
|     { | ||||
|         bmp_name = bmp_name_in; | ||||
|         boost::replace_last(bmp_name, ".png", ""); | ||||
|         if (fp) fclose(fp); | ||||
|     } | ||||
| 
 | ||||
|     return bmp_name; | ||||
| } | ||||
| 
 | ||||
| // 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*/) | ||||
|  | @ -450,8 +473,10 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, | |||
| 
 | ||||
|     scale_base = (unsigned int)(em_unit(win) * px_cnt * 0.1f + 0.5f); | ||||
| 
 | ||||
|     std::string bmp_name = bmp_name_in; | ||||
|     boost::replace_last(bmp_name, ".png", ""); | ||||
| //    std::string bmp_name = bmp_name_in;
 | ||||
| //    boost::replace_last(bmp_name, ".png", "");
 | ||||
| 
 | ||||
|     std::string bmp_name = icon_name_respected_to_mode(bmp_name_in); | ||||
| 
 | ||||
|     // Try loading an SVG first, then PNG if SVG is not found:
 | ||||
|     wxBitmap *bmp = cache.load_svg(bmp_name, width, height, scale_factor, grayscale); | ||||
|  | @ -2538,7 +2563,7 @@ std::vector<t_custom_code> DoubleSlider::GetTicksValues() const | |||
|         for (const TICK_CODE& tick : m_ticks_) { | ||||
|             if (tick.tick > val_size) | ||||
|                 break; | ||||
|             values.push_back(t_custom_code(m_values[tick.tick], tick.gcode, tick.extruder, tick.color)); | ||||
|             values.emplace_back(t_custom_code{m_values[tick.tick], tick.gcode, tick.extruder, tick.color}); | ||||
|         } | ||||
| 
 | ||||
|     return values; | ||||
|  | @ -2553,12 +2578,12 @@ void DoubleSlider::SetTicksValues(const std::vector<t_custom_code>& heights) | |||
| 
 | ||||
|     m_ticks_.clear(); | ||||
|     for (auto h : heights) { | ||||
|         auto it = std::lower_bound(m_values.begin(), m_values.end(), h.height - epsilon()); | ||||
|         auto it = std::lower_bound(m_values.begin(), m_values.end(), h.print_z - epsilon()); | ||||
| 
 | ||||
|         if (it == m_values.end()) | ||||
|             continue; | ||||
| 
 | ||||
|         m_ticks_.insert(TICK_CODE(it-m_values.begin(), h.gcode, h.extruder, h.color)); | ||||
|         m_ticks_.emplace(TICK_CODE{int(it-m_values.begin()), h.gcode, h.extruder, h.color}); | ||||
|     } | ||||
|      | ||||
|     if (!was_empty && m_ticks_.empty()) | ||||
|  | @ -2642,7 +2667,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_CODE{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; | ||||
|  | @ -3081,7 +3106,7 @@ wxString DoubleSlider::get_tooltip(IconFocus icon_focus) | |||
|     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); | ||||
|         const auto tick_code_it = m_ticks_.find(TICK_CODE{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" + | ||||
|  | @ -3240,13 +3265,13 @@ void DoubleSlider::action_tick(const TicksAction action) | |||
| 
 | ||||
|     const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; | ||||
| 
 | ||||
|     const auto it = m_ticks_.find(tick); | ||||
|     const auto it = m_ticks_.find(TICK_CODE{tick}); | ||||
| 
 | ||||
|     if (it != m_ticks_.end()) // erase this tick
 | ||||
|     { | ||||
|         if (action == taAdd) | ||||
|             return; | ||||
|         m_ticks_.erase(TICK_CODE(tick)); | ||||
|         m_ticks_.erase(TICK_CODE{tick}); | ||||
| 
 | ||||
|         wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
|         Refresh(); | ||||
|  | @ -3350,7 +3375,7 @@ void DoubleSlider::OnRightDown(wxMouseEvent& event) | |||
|     { | ||||
|         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); | ||||
|         auto it = m_ticks_.find(TICK_CODE{ tick }); | ||||
|         if (it == m_ticks_.end()) | ||||
|         { | ||||
|             // show context menu on OnRightUp()
 | ||||
|  | @ -3387,7 +3412,7 @@ int DoubleSlider::get_extruder_for_tick(int tick) | |||
|     if (m_ticks_.empty()) | ||||
|         return 0; | ||||
|      | ||||
|     auto it = m_ticks_.lower_bound(tick); | ||||
|     auto it = m_ticks_.lower_bound(TICK_CODE{tick}); | ||||
|     while (it != m_ticks_.begin()) { | ||||
|         --it; | ||||
|         if(it->gcode == Slic3r::ExtruderChangeCode) | ||||
|  | @ -3454,7 +3479,7 @@ void DoubleSlider::OnRightUp(wxMouseEvent& event) | |||
|     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); | ||||
|         std::set<TICK_CODE>::iterator it = m_ticks_.find(TICK_CODE{ 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")) : | ||||
|  | @ -3526,7 +3551,7 @@ 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); | ||||
|     auto it = m_ticks_.find(TICK_CODE{ tick }); | ||||
|     if (it == m_ticks_.end()) | ||||
|     { | ||||
|         std::string color = ""; | ||||
|  | @ -3535,7 +3560,7 @@ void DoubleSlider::add_code(std::string code, int selected_extruder/* = -1*/) | |||
|             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); | ||||
|                 auto before_tick_it = std::lower_bound(m_ticks_.begin(), m_ticks_.end(), TICK_CODE{ tick }); | ||||
|                 while (before_tick_it != m_ticks_.begin()) { | ||||
|                     --before_tick_it; | ||||
|                     if (before_tick_it->gcode == Slic3r::ColorChangeCode) { | ||||
|  | @ -3580,7 +3605,7 @@ void DoubleSlider::add_code(std::string code, int selected_extruder/* = -1*/) | |||
|                 extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); | ||||
|         } | ||||
| 
 | ||||
|         m_ticks_.insert(TICK_CODE(tick, code, extruder, color)); | ||||
|         m_ticks_.emplace(TICK_CODE{tick, code, extruder, color}); | ||||
| 
 | ||||
|         wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
|         Refresh(); | ||||
|  | @ -3592,7 +3617,7 @@ 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); | ||||
|     std::set<TICK_CODE>::iterator it = m_ticks_.find(TICK_CODE{ tick }); | ||||
|     if (it != m_ticks_.end()) | ||||
|     { | ||||
|         std::string edited_value; | ||||
|  | @ -3619,7 +3644,7 @@ void DoubleSlider::edit_tick() | |||
|         } | ||||
|          | ||||
|         m_ticks_.erase(it); | ||||
|         m_ticks_.insert(changed_tick); | ||||
|         m_ticks_.emplace(changed_tick); | ||||
| 
 | ||||
|         wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
|     } | ||||
|  | @ -3632,9 +3657,9 @@ void DoubleSlider::change_extruder(int extruder) | |||
|     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()) | ||||
|     if (m_ticks_.find(TICK_CODE{tick}) == m_ticks_.end()) | ||||
|     {         | ||||
|         m_ticks_.insert(TICK_CODE(tick, Slic3r::ExtruderChangeCode, extruder, extruder == 0 ? "" : colors[extruder-1])); | ||||
|         m_ticks_.emplace(TICK_CODE{tick, Slic3r::ExtruderChangeCode, extruder, extruder == 0 ? "" : colors[extruder-1]}); | ||||
| 
 | ||||
|         wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); | ||||
|         Refresh(); | ||||
|  | @ -3672,7 +3697,7 @@ void DoubleSlider::edit_extruder_sequence() | |||
|     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])); | ||||
|         m_ticks_.emplace(TICK_CODE{tick, Slic3r::ExtruderChangeCode, cur_extruder + 1, colors[cur_extruder]}); | ||||
| 
 | ||||
|         extruder++; | ||||
|         if (extruder == extr_cnt) | ||||
|  | @ -3680,12 +3705,12 @@ void DoubleSlider::edit_extruder_sequence() | |||
|         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()); | ||||
|             auto val_it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon()); | ||||
| 
 | ||||
|             if (it == m_values.end()) | ||||
|             if (val_it == m_values.end()) | ||||
|                 break; | ||||
| 
 | ||||
|             tick = it - m_values.begin(); | ||||
|             tick = val_it - m_values.begin(); | ||||
|         } | ||||
|         else | ||||
|             tick += m_extruders_sequence.interval_by_layers; | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ | |||
| #include <set> | ||||
| #include <functional> | ||||
| #include "libslic3r/Model.hpp" | ||||
| #include "libslic3r/GCodeWriter.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
|     enum class ModelVolumeType : int; | ||||
|  | @ -958,24 +959,12 @@ private: | |||
| 
 | ||||
|     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; | ||||
|         int         tick = 0; | ||||
|         std::string gcode = Slic3r::ColorChangeCode; | ||||
|         int         extruder = 0; | ||||
|         std::string color; | ||||
|     }; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lukas Matena
						Lukas Matena