mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'master' into lm_drilling_backend_rebased
This commit is contained in:
		
						commit
						a1d4dab999
					
				
					 63 changed files with 3891 additions and 3327 deletions
				
			
		|  | @ -233,11 +233,14 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) | |||
|   ) | ||||
| 
 | ||||
|   if (_is_multi) | ||||
|     list(APPEND OpenVDB_LIB_COMPONENTS ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE} ${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}) | ||||
|     list(APPEND OpenVDB_LIB_COMPONENTS ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}) | ||||
|     if (MSVC OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG) | ||||
|       list(APPEND OpenVDB_LIB_COMPONENTS ${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}) | ||||
|     endif () | ||||
| 
 | ||||
|     list(FIND CMAKE_CONFIGURATION_TYPES "Debug" _has_debug) | ||||
|      | ||||
|     if(OpenVDB_${COMPONENT}_LIBRARY_RELEASE AND (_has_debug LESS 0 OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG)) | ||||
|     if(OpenVDB_${COMPONENT}_LIBRARY_RELEASE AND (NOT MSVC OR _has_debug LESS 0 OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG)) | ||||
|       set(OpenVDB_${COMPONENT}_FOUND TRUE) | ||||
|     else() | ||||
|       set(OpenVDB_${COMPONENT}_FOUND FALSE) | ||||
|  | @ -518,12 +521,19 @@ list(REMOVE_DUPLICATES OpenVDB_LIBRARY_DIRS) | |||
| 
 | ||||
| foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) | ||||
|   if(NOT TARGET OpenVDB::${COMPONENT}) | ||||
|     if (${COMPONENT} STREQUAL openvdb) | ||||
|       include (${CMAKE_CURRENT_LIST_DIR}/CheckAtomic.cmake) | ||||
|       set(_LINK_LIBS ${_OPENVDB_VISIBLE_DEPENDENCIES} ${CMAKE_REQUIRED_LIBRARIES}) | ||||
|     else () | ||||
|       set(_LINK_LIBS _OPENVDB_VISIBLE_DEPENDENCIES) | ||||
|     endif () | ||||
| 
 | ||||
|     add_library(OpenVDB::${COMPONENT} UNKNOWN IMPORTED) | ||||
|     set_target_properties(OpenVDB::${COMPONENT} PROPERTIES | ||||
|       INTERFACE_COMPILE_OPTIONS "${OpenVDB_DEFINITIONS}" | ||||
|       INTERFACE_INCLUDE_DIRECTORIES "${OpenVDB_INCLUDE_DIR}" | ||||
|       IMPORTED_LINK_DEPENDENT_LIBRARIES "${_OPENVDB_HIDDEN_DEPENDENCIES}" # non visible deps | ||||
|       INTERFACE_LINK_LIBRARIES "${_OPENVDB_VISIBLE_DEPENDENCIES}" # visible deps (headers) | ||||
|       INTERFACE_LINK_LIBRARIES "${_LINK_LIBS}" # visible deps (headers) | ||||
|       INTERFACE_COMPILE_FEATURES cxx_std_11 | ||||
|       IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}" | ||||
|    ) | ||||
|  | @ -531,8 +541,13 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) | |||
|    if (_is_multi) | ||||
|      set_target_properties(OpenVDB::${COMPONENT} PROPERTIES  | ||||
|        IMPORTED_LOCATION_RELEASE "${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}" | ||||
|        IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}" | ||||
|      ) | ||||
| 
 | ||||
|      if (MSVC OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG) | ||||
|       set_target_properties(OpenVDB::${COMPONENT} PROPERTIES  | ||||
|         IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}" | ||||
|       )  | ||||
|      endif () | ||||
|    endif () | ||||
| 
 | ||||
|    if (OPENVDB_USE_STATIC_LIBS) | ||||
|  |  | |||
							
								
								
									
										7
									
								
								resources/profiles/Creality.idx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								resources/profiles/Creality.idx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| min_slic3r_version = 2.2.0-alpha3 | ||||
| 0.0.2-alpha0 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer. | ||||
| # The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer, | ||||
| # so they will see the print bed. | ||||
| max_slic3r_version = 2.2.0-alpha2 | ||||
| min_slic3r_version = 2.2.0-alpha0 | ||||
| 0.0.1 Initial version | ||||
|  | @ -5,7 +5,7 @@ | |||
| name = Creality | ||||
| # Configuration version of this file. Config file will only be installed, if the config_version differs. | ||||
| # This means, the server may force the PrusaSlicer configuration to be downgraded. | ||||
| config_version = 0.0.1 | ||||
| config_version = 0.0.2-alpha0 | ||||
| # Where to get the updates from? | ||||
| config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ | ||||
| # changelog_url = http://files.prusa3d.com/?latest=slicer-profiles&lng=%1% | ||||
|  |  | |||
|  | @ -635,13 +635,30 @@ inline bool igl::copyleft::cgal::SelfIntersectMesh< | |||
| { | ||||
|   using namespace std; | ||||
| 
 | ||||
|   auto opposite_vertex = [](const Index a0, const Index a1) { | ||||
|     // get opposite index of A
 | ||||
|     int a2=-1; | ||||
| 	for(int c=0;c<3;++c) | ||||
|       if(c!=a0 && c!=a1) { | ||||
|         a2 = c; | ||||
|         break; | ||||
|       } | ||||
|       assert(a2 != -1); | ||||
|       return a2; | ||||
|   }; | ||||
| 
 | ||||
|   // must be co-planar
 | ||||
|   if( | ||||
|     A.supporting_plane() != B.supporting_plane() && | ||||
|     A.supporting_plane() != B.supporting_plane().opposite()) | ||||
|   { | ||||
|   Index a2 = opposite_vertex(shared[0].first, shared[1].first); | ||||
|   if (! B.supporting_plane().has_on(A.vertex(a2))) | ||||
|     return false; | ||||
|   } | ||||
|    | ||||
|   Index b2 = opposite_vertex(shared[0].second, shared[1].second); | ||||
| 
 | ||||
|   if (int(CGAL::coplanar_orientation(A.vertex(shared[0].first), A.vertex(shared[1].first), A.vertex(a2))) *  | ||||
| 	  int(CGAL::coplanar_orientation(B.vertex(shared[0].second), B.vertex(shared[1].second), B.vertex(b2))) < 0) | ||||
|     // There is certainly no self intersection as the non-shared triangle vertices lie on opposite sides of the shared edge.
 | ||||
|     return false; | ||||
| 
 | ||||
|   // Since A and B are non-degenerate the intersection must be a polygon
 | ||||
|   // (triangle). Either
 | ||||
|   //   - the vertex of A (B) opposite the shared edge of lies on B (A), or
 | ||||
|  | @ -650,22 +667,10 @@ inline bool igl::copyleft::cgal::SelfIntersectMesh< | |||
|   // Determine if the vertex opposite edge (a0,a1) in triangle A lies in
 | ||||
|   // (intersects) triangle B
 | ||||
|   const auto & opposite_point_inside = []( | ||||
|     const Triangle_3 & A, const Index a0, const Index a1, const Triangle_3 & B)  | ||||
|     const Triangle_3 & A, const Index a2, const Triangle_3 & B)  | ||||
|     -> bool | ||||
|   { | ||||
|     // get opposite index
 | ||||
|     Index a2 = -1; | ||||
|     for(int c = 0;c<3;c++) | ||||
|     { | ||||
|       if(c != a0 && c != a1) | ||||
|       { | ||||
|         a2 = c; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     assert(a2 != -1); | ||||
|     bool ret = CGAL::do_intersect(A.vertex(a2),B); | ||||
|     return ret; | ||||
|     return CGAL::do_intersect(A.vertex(a2),B); | ||||
|   }; | ||||
| 
 | ||||
|   // Determine if edge opposite vertex va in triangle A intersects edge
 | ||||
|  | @ -681,8 +686,8 @@ inline bool igl::copyleft::cgal::SelfIntersectMesh< | |||
|   }; | ||||
| 
 | ||||
|   if(  | ||||
|     !opposite_point_inside(A,shared[0].first,shared[1].first,B) && | ||||
|     !opposite_point_inside(B,shared[0].second,shared[1].second,A) && | ||||
|     !opposite_point_inside(A,a2,B) && | ||||
|     !opposite_point_inside(B,b2,A) && | ||||
|     !opposite_edges_intersect(A,shared[0].first,B,shared[1].second) &&  | ||||
|     !opposite_edges_intersect(A,shared[1].first,B,shared[0].second)) | ||||
|   { | ||||
|  | @ -936,4 +941,4 @@ inline void igl::copyleft::cgal::SelfIntersectMesh< | |||
|   //process_chunk(0, candidate_triangle_pairs.size());
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| #endif | ||||
|  | @ -233,15 +233,30 @@ cmake_policy(SET CMP0011 NEW) | |||
| find_package(CGAL REQUIRED) | ||||
| cmake_policy(POP) | ||||
| 
 | ||||
| add_library(libslic3r_cgal OBJECT MeshBoolean.cpp MeshBoolean.hpp) | ||||
| target_include_directories(libslic3r_cgal PRIVATE | ||||
|     ${CMAKE_CURRENT_BINARY_DIR} | ||||
|     $<TARGET_PROPERTY:libigl,INTERFACE_INCLUDE_DIRECTORIES> | ||||
|     $<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_INCLUDE_DIRECTORIES>) | ||||
| target_compile_definitions(libslic3r_cgal PRIVATE  | ||||
|     $<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_COMPILE_DEFINITIONS>) | ||||
| target_compile_options(libslic3r_cgal PRIVATE  | ||||
|     $<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_COMPILE_OPTIONS>) | ||||
| add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp) | ||||
| target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) | ||||
| 
 | ||||
| # Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options | ||||
| # (-frounding-math) still propagate to dependent libs which is not desired. | ||||
| get_target_property(_cgal_tgt CGAL::CGAL ALIASED_TARGET) | ||||
| if (NOT TARGET ${_cgal_tgt}) | ||||
|     set (_cgal_tgt CGAL::CGAL) | ||||
| endif () | ||||
| get_target_property(_opts ${_cgal_tgt} INTERFACE_COMPILE_OPTIONS) | ||||
| if (_opts) | ||||
|     set(_opts_bad "${_opts}") | ||||
|     set(_opts_good "${_opts}") | ||||
|     list(FILTER _opts_bad INCLUDE REGEX frounding-math) | ||||
|     list(FILTER _opts_good EXCLUDE REGEX frounding-math) | ||||
|     set_target_properties(${_cgal_tgt} PROPERTIES INTERFACE_COMPILE_OPTIONS "${_opts_good}") | ||||
|     target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}") | ||||
| endif() | ||||
| 
 | ||||
| target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl) | ||||
| 
 | ||||
| if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround | ||||
|     target_compile_definitions(libslic3r_cgal PRIVATE CGAL_DO_NOT_USE_MPZF) | ||||
| endif () | ||||
| 
 | ||||
| encoding_check(libslic3r) | ||||
| 
 | ||||
|  | @ -263,7 +278,7 @@ target_link_libraries(libslic3r | |||
|     qhull | ||||
|     semver | ||||
|     TBB::tbb | ||||
|     $<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_LINK_LIBRARIES> | ||||
|     libslic3r_cgal | ||||
|     ${CMAKE_DL_LIBS} | ||||
|     ) | ||||
| 
 | ||||
|  | @ -282,5 +297,3 @@ endif() | |||
| if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) | ||||
|     add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE) | ||||
| endif () | ||||
| 
 | ||||
| target_sources(libslic3r PRIVATE $<TARGET_OBJECTS:libslic3r_cgal>) | ||||
|  |  | |||
|  | @ -958,7 +958,7 @@ namespace DoExport { | |||
| 	                skirts.emplace_back(std::move(s)); | ||||
| 	            } | ||||
| 	            ooze_prevention.enable = true; | ||||
| 	            ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.)); | ||||
| 	            ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.))); | ||||
| 	#if 0 | ||||
| 	                require "Slic3r/SVG.pm"; | ||||
| 	                Slic3r::SVG::output( | ||||
|  | @ -1091,7 +1091,7 @@ namespace DoExport { | |||
| static inline std::vector<const PrintInstance*> sort_object_instances_by_max_z(const Print &print) | ||||
| { | ||||
|     std::vector<const PrintObject*> objects(print.objects().begin(), print.objects().end()); | ||||
| 	std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->size(2) < po2->size(2); }); | ||||
| 	std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->size()(2) < po2->size()(2); }); | ||||
| 	std::vector<const PrintInstance*> instances; | ||||
| 	instances.reserve(objects.size()); | ||||
| 	for (const PrintObject *object : objects) | ||||
|  |  | |||
|  | @ -252,46 +252,6 @@ template<class T> struct remove_cvref | |||
| 
 | ||||
| template<class T> using remove_cvref_t = typename remove_cvref<T>::type; | ||||
| 
 | ||||
| template<class T> using DefaultContainer = std::vector<T>; | ||||
| 
 | ||||
| /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
 | ||||
| template<class T, class I, template<class> class Container = DefaultContainer> | ||||
| inline Container<remove_cvref_t<T>> linspace(const T &start, | ||||
|                                              const T &stop, | ||||
|                                              const I &n) | ||||
| { | ||||
|     Container<remove_cvref_t<T>> vals(n, T()); | ||||
| 
 | ||||
|     T      stride = (stop - start) / n; | ||||
|     size_t i      = 0; | ||||
|     std::generate(vals.begin(), vals.end(), [&i, start, stride] { | ||||
|         return start + i++ * stride; | ||||
|     }); | ||||
| 
 | ||||
|     return vals; | ||||
| } | ||||
| 
 | ||||
| /// A set of equidistant values starting from 'start' (inclusive), ending
 | ||||
| /// in the closest multiple of 'stride' less than or equal to 'end' and
 | ||||
| /// leaving 'stride' space between each value. 
 | ||||
| /// Very similar to Matlab [start:stride:end] notation.
 | ||||
| template<class T, template<class> class Container = DefaultContainer> | ||||
| inline Container<remove_cvref_t<T>> grid(const T &start, | ||||
|                                          const T &stop, | ||||
|                                          const T &stride) | ||||
| { | ||||
|     Container<remove_cvref_t<T>> | ||||
|         vals(size_t(std::ceil((stop - start) / stride)), T()); | ||||
|      | ||||
|     int i = 0; | ||||
|     std::generate(vals.begin(), vals.end(), [&i, start, stride] { | ||||
|         return start + i++ * stride;  | ||||
|     }); | ||||
|       | ||||
|     return vals; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // A shorter C++14 style form of the enable_if metafunction
 | ||||
| template<bool B, class T> | ||||
| using enable_if_t = typename std::enable_if<B, T>::type; | ||||
|  | @ -392,6 +352,56 @@ inline IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity) | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
 | ||||
| template<class T, class I> | ||||
| inline std::vector<T> linspace_vector(const ArithmeticOnly<T> &start,  | ||||
|                                       const T &stop,  | ||||
|                                       const IntegerOnly<I> &n) | ||||
| { | ||||
|     std::vector<T> vals(n, T()); | ||||
| 
 | ||||
|     T      stride = (stop - start) / n; | ||||
|     size_t i      = 0; | ||||
|     std::generate(vals.begin(), vals.end(), [&i, start, stride] { | ||||
|         return start + i++ * stride; | ||||
|     }); | ||||
| 
 | ||||
|     return vals; | ||||
| } | ||||
| 
 | ||||
| template<size_t N, class T> | ||||
| inline std::array<ArithmeticOnly<T>, N> linspace_array(const T &start, const T &stop) | ||||
| { | ||||
|     std::array<T, N> vals = {T()}; | ||||
| 
 | ||||
|     T      stride = (stop - start) / N; | ||||
|     size_t i      = 0; | ||||
|     std::generate(vals.begin(), vals.end(), [&i, start, stride] { | ||||
|         return start + i++ * stride; | ||||
|     }); | ||||
| 
 | ||||
|     return vals; | ||||
| } | ||||
| 
 | ||||
| /// A set of equidistant values starting from 'start' (inclusive), ending
 | ||||
| /// in the closest multiple of 'stride' less than or equal to 'end' and
 | ||||
| /// leaving 'stride' space between each value. 
 | ||||
| /// Very similar to Matlab [start:stride:end] notation.
 | ||||
| template<class T> | ||||
| inline std::vector<ArithmeticOnly<T>> grid(const T &start,  | ||||
|                                            const T &stop,  | ||||
|                                            const T &stride) | ||||
| { | ||||
|     std::vector<T> vals(size_t(std::ceil((stop - start) / stride)), T()); | ||||
|      | ||||
|     int i = 0; | ||||
|     std::generate(vals.begin(), vals.end(), [&i, start, stride] { | ||||
|         return start + i++ * stride;  | ||||
|     }); | ||||
|       | ||||
|     return vals; | ||||
| } | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #endif // MTUTILS_HPP
 | ||||
|  |  | |||
|  | @ -111,7 +111,7 @@ static TriangleMesh cgal_to_triangle_mesh(const _CGALMesh &cgalmesh) | |||
|         auto    vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face)); | ||||
|         int     i   = 0; | ||||
|         Vec3crd trface; | ||||
|         for (auto v : vtc) trface(i++) = int(v.idx()); | ||||
|         for (auto v : vtc) trface(i++) = static_cast<unsigned>(v); | ||||
|         facets.emplace_back(trface); | ||||
|     } | ||||
|      | ||||
|  |  | |||
|  | @ -907,10 +907,8 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const | |||
| 
 | ||||
|         const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); | ||||
|         for (const ModelVolume *v : this->volumes) | ||||
|         { | ||||
|             if (v->is_model_part()) | ||||
|                 m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); | ||||
|         } | ||||
|     } | ||||
| 	return m_raw_bounding_box; | ||||
| } | ||||
|  | @ -1115,7 +1113,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b | |||
|     if (keep_upper) { | ||||
|         upper->set_model(nullptr); | ||||
|         upper->sla_support_points.clear(); | ||||
|         lower->sla_drain_holes.clear(); | ||||
|         upper->sla_drain_holes.clear(); | ||||
|         upper->sla_points_status = sla::PointsStatus::NoPoints; | ||||
|         upper->clear_volumes(); | ||||
|         upper->input_file = ""; | ||||
|  |  | |||
|  | @ -674,6 +674,7 @@ public: | |||
|         set_rotation(Z, rotation); | ||||
|         set_offset(X, unscale<double>(offs(X))); | ||||
|         set_offset(Y, unscale<double>(offs(Y))); | ||||
|         this->object->invalidate_bounding_box(); | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|  |  | |||
|  | @ -836,7 +836,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ | |||
|         // Update the ModelObject instance, possibly invalidate the linked PrintObjects.
 | ||||
|         assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); | ||||
|         // Check whether a model part volume was added or removed, their transformations or order changed.
 | ||||
|         // Only volume IDs, volume types and their order are checked, configuration and other parameters are NOT checked.
 | ||||
|         // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked.
 | ||||
|         bool model_parts_differ         = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); | ||||
|         bool modifiers_differ           = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); | ||||
|         bool support_blockers_differ    = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER); | ||||
|  | @ -899,10 +899,14 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ | |||
| 	                model_object.instances.emplace_back(new ModelInstance(*model_instance)); | ||||
| 	                model_object.instances.back()->set_model_object(&model_object); | ||||
| 	            } | ||||
| 	        } else { | ||||
| 	        	// Just synchronize the content of the instances. This avoids memory allocation and it does not invalidate ModelInstance pointers,
 | ||||
| 	        	// which may be accessed by G-code export in the meanwhile to deduce sequential print order.
 | ||||
|                 auto new_instance = model_object_new.instances.begin(); | ||||
| 	        } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(),  | ||||
| 	        		[](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable &&  | ||||
| 	        						           l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) { | ||||
| 	        	// If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid.
 | ||||
| 	        	// This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only.
 | ||||
| 	 			model_object.invalidate_bounding_box(); | ||||
| 	        	// Synchronize the content of instances.
 | ||||
| 	        	auto new_instance = model_object_new.instances.begin(); | ||||
| 				for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) { | ||||
| 					(*old_instance)->set_transformation((*new_instance)->get_transformation()); | ||||
|                     (*old_instance)->print_volume_state = (*new_instance)->print_volume_state; | ||||
|  | @ -1197,7 +1201,7 @@ std::string Print::validate() const | |||
|         { | ||||
|             std::vector<coord_t> object_height; | ||||
|             for (const PrintObject *object : m_objects) | ||||
|                 object_height.insert(object_height.end(), object->instances().size(), object->size(2)); | ||||
|                 object_height.insert(object_height.end(), object->instances().size(), object->size()(2)); | ||||
|             std::sort(object_height.begin(), object_height.end()); | ||||
|             // Ignore the tallest *copy* (this is why we repeat height for all of them):
 | ||||
|             // it will be printed as last one so its height doesn't matter.
 | ||||
|  | @ -1429,7 +1433,7 @@ BoundingBox Print::bounding_box() const | |||
|     for (const PrintObject *object : m_objects) | ||||
|         for (const PrintInstance &instance : object->instances()) { | ||||
|             bb.merge(instance.shift); | ||||
|             bb.merge(instance.shift + to_2d(object->size)); | ||||
|             bb.merge(instance.shift + to_2d(object->size())); | ||||
|         } | ||||
|     return bb; | ||||
| } | ||||
|  |  | |||
|  | @ -120,17 +120,17 @@ public: | |||
|     // so that next call to make_perimeters() performs a union() before computing loops
 | ||||
|     bool                    typed_slices; | ||||
| 
 | ||||
|     Vec3crd                 size;           // XYZ in scaled coordinates
 | ||||
| 
 | ||||
|     // XYZ in scaled coordinates
 | ||||
|     const Vec3crd&           size() const			{ return m_size; } | ||||
|     const PrintObjectConfig& config() const         { return m_config; }     | ||||
|     const LayerPtrs&        layers() const          { return m_layers; } | ||||
|     const SupportLayerPtrs& support_layers() const  { return m_support_layers; } | ||||
|     const Transform3d&      trafo() const           { return m_trafo; } | ||||
|     const PrintInstances&   instances() const       { return m_instances; } | ||||
|     const Point 			instance_center(size_t idx) const { return m_instances[idx].shift + m_copies_shift + Point(this->size.x() / 2, this->size.y() / 2); } | ||||
|     const Point 			instance_center(size_t idx) const { return m_instances[idx].shift + m_copies_shift + Point(this->size().x() / 2, this->size().y() / 2); } | ||||
| 
 | ||||
|     // since the object is aligned to origin, bounding box coincides with size
 | ||||
|     BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } | ||||
|     BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size())); } | ||||
| 
 | ||||
|     // adds region_id, too, if necessary
 | ||||
|     void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) { | ||||
|  | @ -235,6 +235,8 @@ private: | |||
|     void combine_infill(); | ||||
|     void _generate_support_material(); | ||||
| 
 | ||||
|     // XYZ in scaled coordinates
 | ||||
|     Vec3crd									m_size; | ||||
|     PrintObjectConfig                       m_config; | ||||
|     // Translation in Z + Rotation + Scaling / Mirroring.
 | ||||
|     Transform3d                             m_trafo = Transform3d::Identity(); | ||||
|  |  | |||
|  | @ -43,7 +43,7 @@ namespace Slic3r { | |||
| PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_instances) : | ||||
|     PrintObjectBaseWithState(print, model_object), | ||||
|     typed_slices(false), | ||||
|     size(Vec3crd::Zero()) | ||||
|     m_size(Vec3crd::Zero()) | ||||
| { | ||||
|     // Compute the translation to be applied to our meshes so that we work with smaller coordinates
 | ||||
|     { | ||||
|  | @ -56,7 +56,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta | |||
|         const BoundingBoxf3 modobj_bbox = model_object->raw_bounding_box(); | ||||
|         m_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1)); | ||||
|         // Scale the object size and store it
 | ||||
|         this->size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast<coord_t>(); | ||||
|         this->m_size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast<coord_t>(); | ||||
|     } | ||||
|      | ||||
|     if (add_instances) { | ||||
|  | @ -73,6 +73,8 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta | |||
| 
 | ||||
| PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances) | ||||
| { | ||||
|     for (PrintInstance &i : instances) | ||||
|     	i.shift += m_copies_shift; | ||||
|     // Invalidate and set copies.
 | ||||
|     PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED; | ||||
|     bool equal_length = instances.size() == m_instances.size(); | ||||
|  | @ -83,7 +85,7 @@ PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances) | |||
|         if (m_print->invalidate_steps({ psSkirt, psBrim, psGCodeExport }) || | ||||
|             (! equal_length && m_print->invalidate_step(psWipeTower))) | ||||
|             status = PrintBase::APPLY_STATUS_INVALIDATED; | ||||
|         m_instances = instances; | ||||
|         m_instances = std::move(instances); | ||||
| 	    for (PrintInstance &i : m_instances) | ||||
| 	    	i.print_object = this; | ||||
|     } | ||||
|  | @ -1448,7 +1450,7 @@ void PrintObject::update_slicing_parameters() | |||
| { | ||||
|     if (! m_slicing_params.valid) | ||||
|         m_slicing_params = SlicingParameters::create_from_config( | ||||
|             this->print()->config(), m_config, unscale<double>(this->size(2)), this->object_extruders()); | ||||
|             this->print()->config(), m_config, unscale<double>(this->size()(2)), this->object_extruders()); | ||||
| } | ||||
| 
 | ||||
| SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ | |||
| #include <igl/ray_mesh_intersect.h> | ||||
| #include <igl/point_mesh_squared_distance.h> | ||||
| #include <igl/remove_duplicate_vertices.h> | ||||
| #include <igl/collapse_small_triangles.h> | ||||
| #include <igl/signed_distance.h> | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(pop) | ||||
|  | @ -194,17 +195,12 @@ public: | |||
| #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ | ||||
| }; | ||||
| 
 | ||||
| EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { | ||||
|     static const double dEPS = 1e-6; | ||||
|      | ||||
| static const constexpr double MESH_EPS = 1e-6; | ||||
| 
 | ||||
| void to_eigen_mesh(const TriangleMesh &tmesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F) | ||||
| { | ||||
|     const stl_file& stl = tmesh.stl; | ||||
|      | ||||
|     auto&& bb = tmesh.bounding_box(); | ||||
|     m_ground_level += bb.min(Z); | ||||
|      | ||||
|     Eigen::MatrixXd V; | ||||
|     Eigen::MatrixXi F; | ||||
|      | ||||
|     V.resize(3*stl.stats.number_of_facets, 3); | ||||
|     F.resize(stl.stats.number_of_facets, 3); | ||||
|     for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { | ||||
|  | @ -217,9 +213,37 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { | |||
|         F(i, 2) = int(3*i+2); | ||||
|     } | ||||
|      | ||||
|     // We will convert this to a proper 3d mesh with no duplicate points.
 | ||||
|     Eigen::VectorXi SVI, SVJ; | ||||
|     igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F); | ||||
|     if (!tmesh.has_shared_vertices()) | ||||
|     { | ||||
|         Eigen::MatrixXd rV; | ||||
|         Eigen::MatrixXi rF; | ||||
|         // We will convert this to a proper 3d mesh with no duplicate points.
 | ||||
|         Eigen::VectorXi SVI, SVJ; | ||||
|         igl::remove_duplicate_vertices(V, F, MESH_EPS, rV, SVI, SVJ, rF); | ||||
|         V = std::move(rV); | ||||
|         F = std::move(rF); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &out) | ||||
| { | ||||
|     Pointf3s points(size_t(V.rows()));  | ||||
|     std::vector<Vec3crd> facets(size_t(F.rows())); | ||||
|      | ||||
|     for (Eigen::Index i = 0; i < V.rows(); ++i) | ||||
|         points[size_t(i)] = V.row(i); | ||||
|      | ||||
|     for (Eigen::Index i = 0; i < F.rows(); ++i) | ||||
|         facets[size_t(i)] = F.row(i); | ||||
|      | ||||
|     out = {points, facets}; | ||||
| } | ||||
| 
 | ||||
| EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { | ||||
|     auto&& bb = tmesh.bounding_box(); | ||||
|     m_ground_level += bb.min(Z); | ||||
|      | ||||
|     to_eigen_mesh(tmesh, m_V, m_F); | ||||
|      | ||||
|     // Build the AABB accelaration tree
 | ||||
|     m_aabb->init(m_V, m_F); | ||||
|  | @ -262,6 +286,10 @@ EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other) | |||
|     m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this; | ||||
| } | ||||
| 
 | ||||
| EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default; | ||||
| 
 | ||||
| EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default; | ||||
| 
 | ||||
| EigenMesh3D::hit_result | ||||
| EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const | ||||
| { | ||||
|  |  | |||
|  | @ -12,6 +12,9 @@ namespace sla { | |||
| 
 | ||||
| struct Contour3D; | ||||
| 
 | ||||
| void to_eigen_mesh(const TriangleMesh &mesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F); | ||||
| void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &); | ||||
| 
 | ||||
| /// An index-triangle structure for libIGL functions. Also serves as an
 | ||||
| /// alternative (raw) input format for the SLASupportTree.
 | ||||
| //  Implemented in libslic3r/SLA/Common.cpp
 | ||||
|  | @ -30,11 +33,15 @@ class EigenMesh3D { | |||
| 
 | ||||
| public: | ||||
|      | ||||
|     EigenMesh3D(const TriangleMesh&); | ||||
|     explicit EigenMesh3D(const TriangleMesh&); | ||||
|     explicit EigenMesh3D(const Contour3D &other); | ||||
|      | ||||
|     EigenMesh3D(const EigenMesh3D& other); | ||||
|     EigenMesh3D(const Contour3D &other); | ||||
|     EigenMesh3D& operator=(const EigenMesh3D&); | ||||
|      | ||||
|     EigenMesh3D(EigenMesh3D &&other); | ||||
|     EigenMesh3D& operator=(EigenMesh3D &&other); | ||||
|      | ||||
|     ~EigenMesh3D(); | ||||
|      | ||||
|     inline double ground_level() const { return m_ground_level + m_gnd_offset; } | ||||
|  | @ -70,9 +77,6 @@ public: | |||
|         inline bool is_valid() const { return m_mesh != nullptr; } | ||||
|         inline bool is_hit() const { return !std::isinf(m_t); } | ||||
| 
 | ||||
|         // Hit_result can decay into a double as the hit distance.
 | ||||
|         inline operator double() const { return distance(); } | ||||
| 
 | ||||
|         inline const Vec3d& normal() const { | ||||
|             assert(is_valid()); | ||||
|             return m_normal; | ||||
|  |  | |||
|  | @ -1,3 +1,5 @@ | |||
| #include <string_view> | ||||
| 
 | ||||
| #include <libslic3r/SLA/RasterWriter.hpp> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
|  | @ -12,14 +14,16 @@ | |||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| std::string RasterWriter::createIniContent(const std::string& projectname) const  | ||||
| void RasterWriter::write_ini(const std::map<std::string, std::string> &m, std::string &ini) | ||||
| { | ||||
|     for (auto ¶m : m) ini += param.first + " = " + param.second + "\n";     | ||||
| } | ||||
| 
 | ||||
| std::string RasterWriter::create_ini_content(const std::string& projectname) const  | ||||
| { | ||||
|     std::string out("action = print\njobDir = "); | ||||
|     out += projectname + "\n"; | ||||
|      | ||||
|     for (auto ¶m : m_config) | ||||
|         out += param.first + " = " + param.second + "\n";     | ||||
|      | ||||
|     write_ini(m_config, out); | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
|  | @ -53,7 +57,12 @@ void RasterWriter::save(Zipper &zipper, const std::string &prjname) | |||
| 
 | ||||
|         zipper.add_entry("config.ini"); | ||||
| 
 | ||||
|         zipper << createIniContent(project); | ||||
|         zipper << create_ini_content(project); | ||||
|          | ||||
|         zipper.add_entry("prusaslicer.ini"); | ||||
|         std::string prusaslicer_ini; | ||||
|         write_ini(m_slicer_config, prusaslicer_ini); | ||||
|         zipper << prusaslicer_ini; | ||||
| 
 | ||||
|         for(unsigned i = 0; i < m_layers_rst.size(); i++) | ||||
|         { | ||||
|  | @ -89,6 +98,29 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) | |||
|     return ret;     | ||||
| } | ||||
| 
 | ||||
| void append_full_config(const DynamicPrintConfig &cfg, std::map<std::string, std::string> &keys) | ||||
| { | ||||
|     using namespace std::literals::string_view_literals; | ||||
|      | ||||
|     // Sorted list of config keys, which shall not be stored into the ini.
 | ||||
|     static constexpr auto banned_keys = {  | ||||
| 		"compatible_printers"sv, | ||||
|         "compatible_prints"sv, | ||||
|         "print_host"sv, | ||||
|         "printhost_apikey"sv, | ||||
|         "printhost_cafile"sv | ||||
|     }; | ||||
|      | ||||
|     assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); | ||||
|     auto is_banned = [](const std::string &key) { | ||||
|         return std::binary_search(banned_keys.begin(), banned_keys.end(), key); | ||||
|     }; | ||||
|      | ||||
|     for (const std::string &key : cfg.keys()) | ||||
|         if (! is_banned(key) && ! cfg.option(key)->is_nil()) | ||||
|             keys[key] = cfg.opt_serialize(key); | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| void RasterWriter::set_config(const DynamicPrintConfig &cfg) | ||||
|  | @ -101,9 +133,9 @@ void RasterWriter::set_config(const DynamicPrintConfig &cfg) | |||
|     m_config["printerVariant"] = get_cfg_value(cfg, "printer_variant"); | ||||
|     m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id"); | ||||
|     m_config["printProfile"]   = get_cfg_value(cfg, "sla_print_settings_id"); | ||||
| 
 | ||||
|     m_config["fileCreationTimestamp"] = Utils::utc_timestamp(); | ||||
|     m_config["prusaSlicerVersion"]    = SLIC3R_BUILD_ID; | ||||
|     append_full_config(cfg, m_slicer_config); | ||||
| } | ||||
| 
 | ||||
| void RasterWriter::set_statistics(const PrintStatistics &stats) | ||||
|  |  | |||
|  | @ -66,8 +66,10 @@ private: | |||
|     double             m_gamma; | ||||
| 
 | ||||
|     std::map<std::string, std::string> m_config; | ||||
|     std::map<std::string, std::string> m_slicer_config; | ||||
|      | ||||
|     std::string createIniContent(const std::string& projectname) const; | ||||
|     static void write_ini(const std::map<std::string, std::string> &m, std::string &ini); | ||||
|     std::string create_ini_content(const std::string& projectname) const; | ||||
| 
 | ||||
| public: | ||||
|      | ||||
|  |  | |||
|  | @ -166,190 +166,182 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder &   builder, | |||
|     return pc == ABORT; | ||||
| } | ||||
| 
 | ||||
| // Give points on a 3D ring with given center, radius and orientation
 | ||||
| // method based on:
 | ||||
| // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
 | ||||
| template<size_t N> | ||||
| class PointRing { | ||||
|     std::array<double, N> m_phis; | ||||
|      | ||||
|     // Two vectors that will be perpendicular to each other and to the
 | ||||
|     // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
 | ||||
|     // placeholder.
 | ||||
|     // a and b vectors are perpendicular to the ring direction and to each other.
 | ||||
|     // Together they define the plane where we have to iterate with the
 | ||||
|     // given angles in the 'm_phis' vector
 | ||||
|     Vec3d a = {0, 1, 0}, b; | ||||
|     double m_radius = 0.; | ||||
|      | ||||
|     static inline bool constexpr is_one(double val)  | ||||
|     {  | ||||
|         return std::abs(std::abs(val) - 1) < 1e-20; | ||||
|     } | ||||
|      | ||||
| public: | ||||
|      | ||||
|     PointRing(const Vec3d &n) | ||||
|     { | ||||
|         m_phis = linspace_array<N>(0., 2 * PI); | ||||
|      | ||||
|         // We have to address the case when the direction vector v (same as
 | ||||
|         // dir) is coincident with one of the world axes. In this case two of
 | ||||
|         // its components will be completely zero and one is 1.0. Our method
 | ||||
|         // becomes dangerous here due to division with zero. Instead, vector
 | ||||
|         // 'a' can be an element-wise rotated version of 'v'
 | ||||
|         if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { | ||||
|             a = {n(Z), n(X), n(Y)}; | ||||
|             b = {n(Y), n(Z), n(X)}; | ||||
|         } | ||||
|         else { | ||||
|             a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); | ||||
|             b = a.cross(n); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     Vec3d get(size_t idx, const Vec3d src, double r) const | ||||
|     { | ||||
|         double phi = m_phis[idx]; | ||||
|         double sinphi = std::sin(phi); | ||||
|         double cosphi = std::cos(phi); | ||||
|       | ||||
|         double rpscos = r * cosphi; | ||||
|         double rpssin = r * sinphi; | ||||
|       | ||||
|         // Point on the sphere
 | ||||
|         return {src(X) + rpscos * a(X) + rpssin * b(X), | ||||
|                 src(Y) + rpscos * a(Y) + rpssin * b(Y), | ||||
|                 src(Z) + rpscos * a(Z) + rpssin * b(Z)}; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template<class C, class Hit = EigenMesh3D::hit_result>  | ||||
| static Hit min_hit(const C &hits) | ||||
| { | ||||
|     auto mit = std::min_element(hits.begin(), hits.end(),  | ||||
|                                 [](const Hit &h1, const Hit &h2) {  | ||||
|         return h1.distance() < h2.distance();  | ||||
|     }); | ||||
|      | ||||
|     return *mit; | ||||
| } | ||||
| 
 | ||||
| EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( | ||||
|     const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width) | ||||
| { | ||||
|     static const size_t SAMPLES = 8; | ||||
|      | ||||
|     // method based on:
 | ||||
|     // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
 | ||||
|     // Move away slightly from the touching point to avoid raycasting on the
 | ||||
|     // inner surface of the mesh.
 | ||||
|      | ||||
|     const double& sd = m_cfg.safety_distance_mm; | ||||
|      | ||||
|     auto& m = m_mesh; | ||||
|     using HitResult = EigenMesh3D::hit_result; | ||||
|      | ||||
|     // Hit results
 | ||||
|     std::array<HitResult, SAMPLES> hits; | ||||
|      | ||||
|     struct Rings { | ||||
|         double rpin; | ||||
|         double rback; | ||||
|         Vec3d  spin; | ||||
|         Vec3d  sback; | ||||
|         PointRing<SAMPLES> ring; | ||||
|          | ||||
|         Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } | ||||
|         Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } | ||||
|     } rings {r_pin + sd, r_back + sd, s, s + width * dir, dir}; | ||||
|      | ||||
|     // We will shoot multiple rays from the head pinpoint in the direction
 | ||||
|     // of the pinhead robe (side) surface. The result will be the smallest
 | ||||
|     // hit distance.
 | ||||
|      | ||||
|     // Move away slightly from the touching point to avoid raycasting on the
 | ||||
|     // inner surface of the mesh.
 | ||||
|     Vec3d v = dir;     // Our direction (axis)
 | ||||
|     Vec3d c = s + width * dir; | ||||
|     const double& sd = m_cfg.safety_distance_mm; | ||||
|     ccr::enumerate(hits.begin(), hits.end(),  | ||||
|                    [&m, &rings, sd](HitResult &hit, size_t i) { | ||||
|      | ||||
|     // Two vectors that will be perpendicular to each other and to the
 | ||||
|     // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
 | ||||
|     // placeholder.
 | ||||
|     Vec3d a(0, 1, 0), b; | ||||
|        // Point on the circle on the pin sphere
 | ||||
|        Vec3d ps = rings.pinring(i); | ||||
|        // This is the point on the circle on the back sphere
 | ||||
|        Vec3d p = rings.backring(i); | ||||
|         | ||||
|        // Point ps is not on mesh but can be inside or
 | ||||
|        // outside as well. This would cause many problems
 | ||||
|        // with ray-casting. To detect the position we will
 | ||||
|        // use the ray-casting result (which has an is_inside
 | ||||
|        // predicate).       
 | ||||
|      | ||||
|     // The portions of the circle (the head-back circle) for which we will
 | ||||
|     // shoot rays.
 | ||||
|     std::array<double, SAMPLES> phis; | ||||
|     for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); | ||||
|        Vec3d n = (p - ps).normalized(); | ||||
|        auto  q = m.query_ray_hit(ps + sd * n, n); | ||||
|      | ||||
|     auto& m = m_mesh; | ||||
|     using HitResult = EigenMesh3D::hit_result; | ||||
|        if (q.is_inside()) { // the hit is inside the model
 | ||||
|            if (q.distance() > rings.rpin) { | ||||
|                // If we are inside the model and the hit
 | ||||
|                // distance is bigger than our pin circle
 | ||||
|                // diameter, it probably indicates that the
 | ||||
|                // support point was already inside the
 | ||||
|                // model, or there is really no space
 | ||||
|                // around the point. We will assign a zero
 | ||||
|                // hit distance to these cases which will
 | ||||
|                // enforce the function return value to be
 | ||||
|                // an invalid ray with zero hit distance.
 | ||||
|                // (see min_element at the end)
 | ||||
|                hit = HitResult(0.0); | ||||
|            } else { | ||||
|                // re-cast the ray from the outside of the
 | ||||
|                // object. The starting point has an offset
 | ||||
|                // of 2*safety_distance because the
 | ||||
|                // original ray has also had an offset
 | ||||
|                auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); | ||||
|                hit = q2; | ||||
|            } | ||||
|        } else | ||||
|            hit = q; | ||||
|     }); | ||||
|      | ||||
|     // Hit results
 | ||||
|     std::array<HitResult, SAMPLES> hits; | ||||
|      | ||||
|     // We have to address the case when the direction vector v (same as
 | ||||
|     // dir) is coincident with one of the world axes. In this case two of
 | ||||
|     // its components will be completely zero and one is 1.0. Our method
 | ||||
|     // becomes dangerous here due to division with zero. Instead, vector
 | ||||
|     // 'a' can be an element-wise rotated version of 'v'
 | ||||
|     auto chk1 = [] (double val) { | ||||
|         return std::abs(std::abs(val) - 1) < 1e-20; | ||||
|     }; | ||||
|      | ||||
|     if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) { | ||||
|         a = {v(Z), v(X), v(Y)}; | ||||
|         b = {v(Y), v(Z), v(X)}; | ||||
|     } | ||||
|     else { | ||||
|         a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize(); | ||||
|         b = a.cross(v); | ||||
|     } | ||||
| 
 | ||||
|     // Now a and b vectors are perpendicular to v and to each other.
 | ||||
|     // Together they define the plane where we have to iterate with the
 | ||||
|     // given angles in the 'phis' vector
 | ||||
|     ccr::enumerate( | ||||
|         phis.begin(), phis.end(), | ||||
|         [&hits, &m, sd, r_pin, r_back, s, a, b, c](double phi, size_t i) { | ||||
|            double sinphi = std::sin(phi); | ||||
|            double cosphi = std::cos(phi); | ||||
|          | ||||
|            // Let's have a safety coefficient for the radiuses.
 | ||||
|            double rpscos = (sd + r_pin) * cosphi; | ||||
|            double rpssin = (sd + r_pin) * sinphi; | ||||
|            double rpbcos = (sd + r_back) * cosphi; | ||||
|            double rpbsin = (sd + r_back) * sinphi; | ||||
|          | ||||
|            // Point on the circle on the pin sphere
 | ||||
|            Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X), | ||||
|                     s(Y) + rpscos * a(Y) + rpssin * b(Y), | ||||
|                     s(Z) + rpscos * a(Z) + rpssin * b(Z)); | ||||
|          | ||||
|            // Point ps is not on mesh but can be inside or
 | ||||
|            // outside as well. This would cause many problems
 | ||||
|            // with ray-casting. To detect the position we will
 | ||||
|            // use the ray-casting result (which has an is_inside
 | ||||
|            // predicate).
 | ||||
|          | ||||
|            // This is the point on the circle on the back sphere
 | ||||
|            Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X), | ||||
|                    c(Y) + rpbcos * a(Y) + rpbsin * b(Y), | ||||
|                    c(Z) + rpbcos * a(Z) + rpbsin * b(Z)); | ||||
|          | ||||
|            Vec3d n = (p - ps).normalized(); | ||||
|            auto  q = m.query_ray_hit(ps + sd * n, n); | ||||
|          | ||||
|            if (q.is_inside()) { // the hit is inside the model
 | ||||
|                if (q.distance() > r_pin + sd) { | ||||
|                    // If we are inside the model and the hit
 | ||||
|                    // distance is bigger than our pin circle
 | ||||
|                    // diameter, it probably indicates that the
 | ||||
|                    // support point was already inside the
 | ||||
|                    // model, or there is really no space
 | ||||
|                    // around the point. We will assign a zero
 | ||||
|                    // hit distance to these cases which will
 | ||||
|                    // enforce the function return value to be
 | ||||
|                    // an invalid ray with zero hit distance.
 | ||||
|                    // (see min_element at the end)
 | ||||
|                    hits[i] = HitResult(0.0); | ||||
|                } else { | ||||
|                    // re-cast the ray from the outside of the
 | ||||
|                    // object. The starting point has an offset
 | ||||
|                    // of 2*safety_distance because the
 | ||||
|                    // original ray has also had an offset
 | ||||
|                    auto q2 = m.query_ray_hit( | ||||
|                        ps + (q.distance() + 2 * sd) * n, n); | ||||
|                    hits[i] = q2; | ||||
|                } | ||||
|            } else | ||||
|                hits[i] = q; | ||||
|         }); | ||||
| 
 | ||||
|     auto mit = std::min_element(hits.begin(), hits.end()); | ||||
|      | ||||
|     return *mit; | ||||
|     return min_hit(hits); | ||||
| } | ||||
| 
 | ||||
| EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( | ||||
|     const Vec3d &s, const Vec3d &dir, double r, bool ins_check) | ||||
|     const Vec3d &src, const Vec3d &dir, double r, bool ins_check) | ||||
| { | ||||
|     static const size_t SAMPLES = 8; | ||||
|     PointRing<SAMPLES> ring{dir}; | ||||
|      | ||||
|     // helper vector calculations
 | ||||
|     Vec3d a(0, 1, 0), b; | ||||
|     const double& sd = m_cfg.safety_distance_mm; | ||||
|      | ||||
|     // INFO: for explanation of the method used here, see the previous
 | ||||
|     // method's comments.
 | ||||
|      | ||||
|     auto chk1 = [] (double val) { | ||||
|         return std::abs(std::abs(val) - 1) < 1e-20; | ||||
|     }; | ||||
|      | ||||
|     if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) { | ||||
|         a = {dir(Z), dir(X), dir(Y)}; | ||||
|         b = {dir(Y), dir(Z), dir(X)}; | ||||
|     } | ||||
|     else { | ||||
|         a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize(); | ||||
|         b = a.cross(dir); | ||||
|     } | ||||
|      | ||||
|     // circle portions
 | ||||
|     std::array<double, SAMPLES> phis; | ||||
|     for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); | ||||
|      | ||||
|     auto& m = m_mesh; | ||||
|     using HitResult = EigenMesh3D::hit_result; | ||||
|     using Hit = EigenMesh3D::hit_result; | ||||
|      | ||||
|     // Hit results
 | ||||
|     std::array<HitResult, SAMPLES> hits; | ||||
|     std::array<Hit, SAMPLES> hits; | ||||
|      | ||||
|     ccr::enumerate( | ||||
|         phis.begin(), phis.end(), | ||||
|         [&m, a, b, sd, dir, r, s, ins_check, &hits] (double phi, size_t i) { | ||||
|             double sinphi = std::sin(phi); | ||||
|             double cosphi = std::cos(phi); | ||||
|              | ||||
|             // Let's have a safety coefficient for the radiuses.
 | ||||
|             double rcos = (sd + r) * cosphi; | ||||
|             double rsin = (sd + r) * sinphi; | ||||
|              | ||||
|             // Point on the circle on the pin sphere
 | ||||
|             Vec3d p (s(X) + rcos * a(X) + rsin * b(X), | ||||
|                     s(Y) + rcos * a(Y) + rsin * b(Y), | ||||
|                     s(Z) + rcos * a(Z) + rsin * b(Z)); | ||||
|              | ||||
|             auto hr = m.query_ray_hit(p + sd*dir, dir); | ||||
|              | ||||
|             if(ins_check && hr.is_inside()) { | ||||
|                 if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0); | ||||
|                 else { | ||||
|                     // re-cast the ray from the outside of the object
 | ||||
|                     auto hr2 = | ||||
|                         m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); | ||||
|                      | ||||
|                     hits[i] = hr2; | ||||
|                 } | ||||
|             } else hits[i] = hr; | ||||
|         }); | ||||
|     ccr::enumerate(hits.begin(), hits.end(),  | ||||
|                    [this, r, src, ins_check, &ring, dir] (Hit &hit, size_t i) { | ||||
|          | ||||
|         const double sd = m_cfg.safety_distance_mm; | ||||
|          | ||||
|         // Point on the circle on the pin sphere
 | ||||
|         Vec3d p = ring.get(i, src, r + sd); | ||||
|          | ||||
|         auto hr = m_mesh.query_ray_hit(p + sd * dir, dir); | ||||
|          | ||||
|         if(ins_check && hr.is_inside()) { | ||||
|             if(hr.distance() > 2 * r + sd) hit = Hit(0.0); | ||||
|             else { | ||||
|                 // re-cast the ray from the outside of the object
 | ||||
|                 hit = m_mesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); | ||||
|             } | ||||
|         } else hit = hr; | ||||
|     }); | ||||
|      | ||||
|     auto mit = std::min_element(hits.begin(), hits.end()); | ||||
|      | ||||
|     return *mit; | ||||
|     return min_hit(hits); | ||||
| } | ||||
| 
 | ||||
| bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, | ||||
|  | @ -419,7 +411,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, | |||
|      | ||||
|     // TODO: This is a workaround to not have a faulty last bridge
 | ||||
|     while(ej(Z) >= eupper(Z) /*endz*/) { | ||||
|         if(bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r) >= bridge_distance) | ||||
|         if(bridge_mesh_distance(sj, dirv(sj, ej), pillar.r) >= bridge_distance) | ||||
|         { | ||||
|             m_builder.add_crossbridge(sj, ej, pillar.r); | ||||
|             was_connected = true; | ||||
|  | @ -430,7 +422,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, | |||
|             Vec3d sjback(ej(X), ej(Y), sj(Z)); | ||||
|             Vec3d ejback(sj(X), sj(Y), ej(Z)); | ||||
|             if (sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) && | ||||
|                 bridge_mesh_intersect(sjback, dirv(sjback, ejback), | ||||
|                 bridge_mesh_distance(sjback, dirv(sjback, ejback), | ||||
|                                       pillar.r) >= bridge_distance) { | ||||
|                 // need to check collision for the cross stick
 | ||||
|                 m_builder.add_crossbridge(sjback, ejback, pillar.r); | ||||
|  | @ -487,7 +479,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, | |||
|             bridgestart(Z) -= zdiff; | ||||
|             touchjp(Z) = Zdown; | ||||
|              | ||||
|             double t = bridge_mesh_intersect(headjp, {0,0,-1}, r); | ||||
|             double t = bridge_mesh_distance(headjp, DOWN, r); | ||||
|              | ||||
|             // We can't insert a pillar under the source head to connect
 | ||||
|             // with the nearby pillar's starting junction
 | ||||
|  | @ -505,8 +497,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, | |||
|     double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm; | ||||
|     if(bridgeend(Z) < minz) return false; | ||||
|      | ||||
|     double t = bridge_mesh_intersect(bridgestart, | ||||
|                                      dirv(bridgestart, bridgeend), r); | ||||
|     double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r); | ||||
|      | ||||
|     // Cannot insert the bridge. (further search might not worth the hassle)
 | ||||
|     if(t < distance(bridgestart, bridgeend)) return false; | ||||
|  | @ -633,7 +624,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, | |||
|         }; | ||||
|          | ||||
|         // We have to check if the bridge is feasible.
 | ||||
|         if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) | ||||
|         if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) | ||||
|             abort_in_shame(); | ||||
|         else { | ||||
|             // If the new endpoint is below ground, do not make a pillar
 | ||||
|  | @ -764,7 +755,7 @@ void SupportTreeBuildsteps::filter() | |||
|                     { | ||||
|                         auto dir = spheric_to_dir(plr, azm).normalized(); | ||||
|                          | ||||
|                         double score = pinhead_mesh_intersect( | ||||
|                         double score = pinhead_mesh_distance( | ||||
|                             hp, dir, pin_r, m_cfg.head_back_radius_mm, w); | ||||
|                          | ||||
|                         return score; | ||||
|  | @ -950,11 +941,11 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir) | |||
| { | ||||
|     auto hjp = head.junction_point(); | ||||
|     double r = head.r_back_mm; | ||||
|     double t = bridge_mesh_intersect(hjp, dir, head.r_back_mm); | ||||
|     double t = bridge_mesh_distance(hjp, dir, head.r_back_mm); | ||||
|     double d = 0, tdown = 0; | ||||
|     t = std::min(t, m_cfg.max_bridge_length_mm); | ||||
| 
 | ||||
|     while (d < t && !std::isinf(tdown = bridge_mesh_intersect(hjp + d * dir, DOWN, r))) | ||||
|     while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r))) | ||||
|         d += r; | ||||
|      | ||||
|     if(!std::isinf(tdown)) return false; | ||||
|  | @ -989,7 +980,7 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head) | |||
|     auto oresult = solver.optimize_max( | ||||
|         [this, hjp, r_back](double plr, double azm) { | ||||
|             Vec3d n = spheric_to_dir(plr, azm).normalized(); | ||||
|             return bridge_mesh_intersect(hjp, n, r_back); | ||||
|             return bridge_mesh_distance(hjp, n, r_back); | ||||
|         }, | ||||
|         initvals(polar, azimuth),  // let's start with what we have
 | ||||
|         bound(3*PI/4, PI),  // Must not exceed the slope limit
 | ||||
|  | @ -1259,9 +1250,8 @@ void SupportTreeBuildsteps::interconnect_pillars() | |||
|                     m_pillar_index.insert(pp.endpoint(), unsigned(pp.id)); | ||||
| 
 | ||||
|                     m_builder.add_junction(s, pillar().r); | ||||
|                     double t = bridge_mesh_intersect(pillarsp, | ||||
|                                                      dirv(pillarsp, s), | ||||
|                                                      pillar().r); | ||||
|                     double t = bridge_mesh_distance(pillarsp, dirv(pillarsp, s), | ||||
|                                                     pillar().r); | ||||
|                     if (distance(pillarsp, s) < t) | ||||
|                         m_builder.add_bridge(pillarsp, s, pillar().r); | ||||
| 
 | ||||
|  | @ -1312,8 +1302,8 @@ void SupportTreeBuildsteps::routing_headless() | |||
|         Vec3d sj = sp + R * n;              // stick start point
 | ||||
|          | ||||
|         // This is only for checking
 | ||||
|         double idist = bridge_mesh_intersect(sph, DOWN, R, true); | ||||
|         double realdist = ray_mesh_intersect(sj, DOWN); | ||||
|         double idist = bridge_mesh_distance(sph, DOWN, R, true); | ||||
|         double realdist = ray_mesh_intersect(sj, DOWN).distance(); | ||||
|         double dist = realdist; | ||||
|          | ||||
|         if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level; | ||||
|  |  | |||
|  | @ -206,10 +206,10 @@ class SupportTreeBuildsteps { | |||
|     // When bridging heads to pillars... TODO: find a cleaner solution
 | ||||
|     ccr::BlockingMutex m_bridge_mutex; | ||||
| 
 | ||||
|     inline double ray_mesh_intersect(const Vec3d& s, | ||||
|                                      const Vec3d& dir) | ||||
|     inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s,  | ||||
|                                                       const Vec3d& dir) | ||||
|     { | ||||
|         return m_mesh.query_ray_hit(s, dir).distance(); | ||||
|         return m_mesh.query_ray_hit(s, dir); | ||||
|     } | ||||
| 
 | ||||
|     // This function will test if a future pinhead would not collide with the
 | ||||
|  | @ -229,6 +229,11 @@ class SupportTreeBuildsteps { | |||
|         double r_pin, | ||||
|         double r_back, | ||||
|         double width); | ||||
|      | ||||
|     template<class...Args> | ||||
|     inline double pinhead_mesh_distance(Args&&...args) { | ||||
|         return pinhead_mesh_intersect(std::forward<Args>(args)...).distance(); | ||||
|     } | ||||
| 
 | ||||
|     // Checking bridge (pillar and stick as well) intersection with the model.
 | ||||
|     // If the function is used for headless sticks, the ins_check parameter
 | ||||
|  | @ -243,6 +248,11 @@ class SupportTreeBuildsteps { | |||
|         const Vec3d& dir, | ||||
|         double r, | ||||
|         bool ins_check = false); | ||||
|      | ||||
|     template<class...Args> | ||||
|     inline double bridge_mesh_distance(Args&&...args) { | ||||
|         return bridge_mesh_intersect(std::forward<Args>(args)...).distance(); | ||||
|     } | ||||
| 
 | ||||
|     // Helper function for interconnecting two pillars with zig-zag bridges.
 | ||||
|     bool interconnect(const Pillar& pillar, const Pillar& nextpillar); | ||||
|  |  | |||
|  | @ -678,7 +678,7 @@ void SLAPrint::process() | |||
| 
 | ||||
|     // We want to first process all objects...
 | ||||
|     std::vector<SLAPrintObjectStep> level1_obj_steps = { | ||||
|         slaposHollowing, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad | ||||
|         slaposHollowing, slaposDrillHoles, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad | ||||
|     }; | ||||
| 
 | ||||
|     // and then slice all supports to allow preview to be displayed ASAP
 | ||||
|  | @ -984,10 +984,10 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step) | |||
|     // propagate to dependent steps
 | ||||
|     if (step == slaposHollowing) { | ||||
|         invalidated |= this->invalidate_all_steps(); | ||||
|     } else if (step == slaposObjectSlice) { | ||||
|         invalidated |= this->invalidate_steps({ slaposDrillHolesIfHollowed, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports }); | ||||
|     } else if (step == slaposDrillHoles) { | ||||
|         invalidated |= this->invalidate_steps({ slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports }); | ||||
|         invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); | ||||
|     } else if (step == slaposDrillHolesIfHollowed) { | ||||
|     } else if (step == slaposObjectSlice) { | ||||
|         invalidated |= this->invalidate_steps({ slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports }); | ||||
|         invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); | ||||
|     } else if (step == slaposSupportPoints) { | ||||
|  | @ -1095,8 +1095,6 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const | |||
|     const std::vector<ExPolygons>& v = o == soModel? m_po->get_model_slices() : | ||||
|                                                      m_po->get_support_slices(); | ||||
| 
 | ||||
|     if(idx >= v.size()) return EMPTY_SLICE; | ||||
| 
 | ||||
|     return idx >= v.size() ? EMPTY_SLICE : v[idx]; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -20,8 +20,8 @@ enum SLAPrintStep : unsigned int { | |||
| 
 | ||||
| enum SLAPrintObjectStep : unsigned int { | ||||
|     slaposHollowing, | ||||
|     slaposDrillHoles, | ||||
| 	slaposObjectSlice, | ||||
|     slaposDrillHolesIfHollowed, | ||||
| 	slaposSupportPoints, | ||||
| 	slaposSupportTree, | ||||
| 	slaposPad, | ||||
|  | @ -138,9 +138,9 @@ public: | |||
|         // Returns the current layer height
 | ||||
|         float layer_height() const { return m_height; } | ||||
| 
 | ||||
|         bool is_valid() const { return ! std::isnan(m_slice_z); } | ||||
|         bool is_valid() const { return m_po && ! std::isnan(m_slice_z); } | ||||
| 
 | ||||
|         const SLAPrintObject* print_obj() const { assert(m_po); return m_po; } | ||||
|         const SLAPrintObject* print_obj() const { return m_po; } | ||||
| 
 | ||||
|         // Methods for setting the indices into the slice vectors.
 | ||||
|         void set_model_slice_idx(const SLAPrintObject &po, size_t id) { | ||||
|  |  | |||
|  | @ -26,9 +26,9 @@ namespace Slic3r { | |||
| namespace { | ||||
| 
 | ||||
| const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = { | ||||
|     5,  // slaposHollowing,
 | ||||
|     20, // slaposObjectSlice,
 | ||||
|     5,  // slaposDrillHolesIfHollowed
 | ||||
|     10, // slaposHollowing,
 | ||||
|     10, // slaposDrillHolesIfHollowed
 | ||||
|     10, // slaposObjectSlice,
 | ||||
|     20, // slaposSupportPoints,
 | ||||
|     10, // slaposSupportTree,
 | ||||
|     10, // slaposPad,
 | ||||
|  | @ -38,9 +38,9 @@ const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = { | |||
| std::string OBJ_STEP_LABELS(size_t idx) | ||||
| { | ||||
|     switch (idx) { | ||||
|     case slaposHollowing:            return L("Hollowing and drilling holes"); | ||||
|     case slaposHollowing:            return L("Hollowing model"); | ||||
|     case slaposDrillHoles:           return L("Drilling holes into hollowed model."); | ||||
|     case slaposObjectSlice:          return L("Slicing model"); | ||||
|     case slaposDrillHolesIfHollowed: return L("Drilling holes into hollowed model."); | ||||
|     case slaposSupportPoints:        return L("Generating support points"); | ||||
|     case slaposSupportTree:          return L("Generating support tree"); | ||||
|     case slaposPad:                  return L("Generating pad"); | ||||
|  | @ -80,70 +80,70 @@ SLAPrint::Steps::Steps(SLAPrint *print) | |||
| void SLAPrint::Steps::hollow_model(SLAPrintObject &po) | ||||
| { | ||||
|     po.m_hollowing_data.reset(); | ||||
|     bool drilling_needed = ! po.m_model_object->sla_drain_holes.empty(); | ||||
| 
 | ||||
|     // If the mesh is broken, stop immediately, even before hollowing.
 | ||||
|     //if (drilling_needed && po.transformed_mesh().needed_repair())
 | ||||
|     //    throw std::runtime_error(L("The mesh appears to be too broken "
 | ||||
|     //                               "to drill holes into it reliably."));
 | ||||
| 
 | ||||
|     if (! po.m_config.hollowing_enable.getBool()) | ||||
|         BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";     | ||||
|     else { | ||||
|         BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; | ||||
| 
 | ||||
|         double thickness = po.m_config.hollowing_min_thickness.getFloat(); | ||||
|         double quality  = po.m_config.hollowing_quality.getFloat(); | ||||
|         double closing_d = po.m_config.hollowing_closing_distance.getFloat(); | ||||
|         sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; | ||||
|         auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg); | ||||
| 
 | ||||
|         if (meshptr->empty()) | ||||
|             BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; | ||||
|         else { | ||||
|             po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); | ||||
|             po.m_hollowing_data->interior = *meshptr; | ||||
|             auto &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; | ||||
|             hollowed_mesh = po.transformed_mesh(); | ||||
|             hollowed_mesh.merge(po.m_hollowing_data->interior); | ||||
|             hollowed_mesh.require_shared_vertices(); | ||||
|         } | ||||
|     if (! po.m_config.hollowing_enable.getBool()) { | ||||
|         BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; | ||||
| 
 | ||||
|     // Drill holes into the hollowed/original mesh.
 | ||||
|     if (! drilling_needed) | ||||
|         BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; | ||||
|     double thickness = po.m_config.hollowing_min_thickness.getFloat(); | ||||
|     double quality  = po.m_config.hollowing_quality.getFloat(); | ||||
|     double closing_d = po.m_config.hollowing_closing_distance.getFloat(); | ||||
|     sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; | ||||
|     auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg); | ||||
| 
 | ||||
|     if (meshptr->empty()) | ||||
|         BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; | ||||
|     else { | ||||
|         BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; | ||||
|         sla::DrainHoles drainholes = po.transformed_drainhole_points(); | ||||
| 
 | ||||
|         TriangleMesh holes_mesh; | ||||
| 
 | ||||
|         for (const sla::DrainHole &holept : drainholes) | ||||
|             holes_mesh.merge(sla::to_triangle_mesh(holept.to_mesh())); | ||||
| 
 | ||||
|         holes_mesh.require_shared_vertices(); | ||||
|         MeshBoolean::self_union(holes_mesh); | ||||
| 
 | ||||
|         // If there is no hollowed mesh yet, copy the original mesh.
 | ||||
|         if (! po.m_hollowing_data) { | ||||
|             po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); | ||||
|             po.m_hollowing_data->hollow_mesh_with_holes = po.transformed_mesh(); | ||||
|         } | ||||
| 
 | ||||
|         TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; | ||||
|         hollowed_mesh = po.get_mesh_to_print(); | ||||
|         try { | ||||
|             MeshBoolean::cgal::minus(hollowed_mesh, holes_mesh); | ||||
|         } | ||||
|         catch (const std::runtime_error& ex) { | ||||
|             throw std::runtime_error(L("Drilling holes into the mesh failed. " | ||||
|                 "This is usually caused by broken model. Try to fix it first.")); | ||||
|         } | ||||
|         po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); | ||||
|         po.m_hollowing_data->interior = *meshptr; | ||||
|         auto &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; | ||||
|         hollowed_mesh = po.transformed_mesh(); | ||||
|         hollowed_mesh.merge(po.m_hollowing_data->interior); | ||||
|         hollowed_mesh.require_shared_vertices(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | ||||
| { | ||||
|     // Drill holes into the hollowed/original mesh.
 | ||||
|     if (po.m_model_object->sla_drain_holes.empty()) { | ||||
|         BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; | ||||
|     sla::DrainHoles drainholes = po.transformed_drainhole_points(); | ||||
|      | ||||
|     TriangleMesh holes_mesh; | ||||
|      | ||||
|     for (const sla::DrainHole &holept : drainholes) | ||||
|         holes_mesh.merge(sla::to_triangle_mesh(holept.to_mesh())); | ||||
|      | ||||
|     holes_mesh.require_shared_vertices(); | ||||
|     MeshBoolean::cgal::self_union(holes_mesh); //FIXME-fix and use the cgal version
 | ||||
|      | ||||
|     // If there is no hollowed mesh yet, copy the original mesh.
 | ||||
|     if (! po.m_hollowing_data) { | ||||
|         po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); | ||||
|         po.m_hollowing_data->hollow_mesh_with_holes = po.transformed_mesh(); | ||||
|     } | ||||
|      | ||||
|     TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; | ||||
|      | ||||
|     try { | ||||
|         MeshBoolean::cgal::minus(hollowed_mesh, holes_mesh); | ||||
|     } catch (const std::runtime_error &ex) { | ||||
|         throw std::runtime_error(L( | ||||
|             "Drilling holes into the mesh failed. " | ||||
|             "This is usually caused by broken model. Try to fix it first.")); | ||||
|     } | ||||
| 
 | ||||
|     hollowed_mesh.require_shared_vertices(); | ||||
| } | ||||
| 
 | ||||
| // The slicing will be performed on an imaginary 1D grid which starts from
 | ||||
| // the bottom of the bounding box created around the supported model. So
 | ||||
| // the first layer which is usually thicker will be part of the supports
 | ||||
|  | @ -478,14 +478,16 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo | |||
| } | ||||
| 
 | ||||
| // get polygons for all instances in the object
 | ||||
| static ClipperPolygons get_all_polygons( | ||||
|     const ExPolygons &                           input_polygons, | ||||
|     const std::vector<SLAPrintObject::Instance> &instances, | ||||
|     bool                                         is_lefthanded) | ||||
| static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o) | ||||
| { | ||||
|     namespace sl = libnest2d::sl; | ||||
|      | ||||
|     if (!record.print_obj()) return {}; | ||||
|      | ||||
|     ClipperPolygons polygons; | ||||
|     auto &input_polygons = record.get_slice(o); | ||||
|     auto &instances = record.print_obj()->instances(); | ||||
|     bool is_lefthanded = record.print_obj()->is_left_handed(); | ||||
|     polygons.reserve(input_polygons.size() * instances.size()); | ||||
|      | ||||
|     for (const ExPolygon& polygon : input_polygons) { | ||||
|  | @ -558,6 +560,12 @@ void SLAPrint::Steps::initialize_printer_input() | |||
|         coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs; | ||||
|          | ||||
|         for(const SliceRecord& slicerecord : o->get_slice_index()) { | ||||
|             if (!slicerecord.is_valid()) | ||||
|                 throw std::runtime_error( | ||||
|                     L("There are unprintable objects. Try to " | ||||
|                       "adjust support settings to make the " | ||||
|                       "objects printable.")); | ||||
| 
 | ||||
|             coord_t lvlid = slicerecord.print_level() - gndlvl; | ||||
|              | ||||
|             // Neat trick to round the layer levels to the grid.
 | ||||
|  | @ -660,22 +668,13 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { | |||
|         supports_polygons.reserve(c); | ||||
|          | ||||
|         for(const SliceRecord& record : layer.slices()) { | ||||
|             const SLAPrintObject *po = record.print_obj(); | ||||
|              | ||||
|             const ExPolygons &modelslices = record.get_slice(soModel); | ||||
|              | ||||
|             bool is_lefth = record.print_obj()->is_left_handed(); | ||||
|             if (!modelslices.empty()) { | ||||
|                 ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth); | ||||
|                 for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp)); | ||||
|             } | ||||
|              | ||||
|             const ExPolygons &supportslices = record.get_slice(soSupport); | ||||
|              | ||||
|             if (!supportslices.empty()) { | ||||
|                 ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth); | ||||
|                 for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp)); | ||||
|             } | ||||
|             ClipperPolygons modelslices = get_all_polygons(record, soModel); | ||||
|             for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp)); | ||||
|          | ||||
|             ClipperPolygons supportslices = get_all_polygons(record, soSupport); | ||||
|             for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp)); | ||||
|          | ||||
|         } | ||||
|          | ||||
|         model_polygons = polyunion(model_polygons); | ||||
|  | @ -864,8 +863,8 @@ void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj) | |||
| { | ||||
|     switch(step) { | ||||
|     case slaposHollowing: hollow_model(obj); break; | ||||
|     case slaposDrillHoles: drill_holes(obj); break; | ||||
|     case slaposObjectSlice: slice_model(obj); break; | ||||
|     case slaposDrillHolesIfHollowed: break; | ||||
|     case slaposSupportPoints:  support_points(obj); break; | ||||
|     case slaposSupportTree: support_tree(obj); break; | ||||
|     case slaposPad: generate_pad(obj); break; | ||||
|  |  | |||
|  | @ -44,6 +44,7 @@ public: | |||
|     Steps(SLAPrint *print); | ||||
|      | ||||
|     void hollow_model(SLAPrintObject &po); | ||||
|     void drill_holes (SLAPrintObject &po); | ||||
|     void slice_model(SLAPrintObject& po); | ||||
|     void support_points(SLAPrintObject& po); | ||||
|     void support_tree(SLAPrintObject& po); | ||||
|  |  | |||
|  | @ -21,8 +21,10 @@ | |||
| #include <array> | ||||
| #include <type_traits> | ||||
| #include <algorithm> | ||||
| #include <cmath> | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| #include <ostream> | ||||
| #include <iostream> | ||||
| #endif | ||||
| 
 | ||||
|  | @ -63,7 +65,7 @@ namespace implementation { | |||
| template<bool B, class T> | ||||
| using enable_if_t = typename std::enable_if<B, T>::type; | ||||
| 
 | ||||
| // Meta predicates for floating, 'scaled coord' and generic arithmetic types
 | ||||
| // Meta predicates for floating, integer and generic arithmetic types
 | ||||
| template<class T, class O = T> | ||||
| using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>; | ||||
| 
 | ||||
|  | @ -82,41 +84,15 @@ struct remove_cvref { | |||
| template< class T > | ||||
| using remove_cvref_t = typename remove_cvref<T>::type; | ||||
| 
 | ||||
| struct DOut { | ||||
| #ifndef NDEBUG | ||||
|     std::ostream& out = std::cout; | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| template<class T> | ||||
| inline DOut&& operator<<( DOut&& out, T&& d) { | ||||
| #ifndef NDEBUG | ||||
|     out.out << d; | ||||
| #endif | ||||
|     return std::move(out); | ||||
| } | ||||
| 
 | ||||
| inline DOut dout() { return DOut(); } | ||||
| 
 | ||||
| template<class T> FloatingOnly<T, bool> is_approx(T val, T ref) { return std::abs(val - ref) < 1e-8; } | ||||
| template<class T> IntegerOnly <T, bool> is_approx(T val, T ref) { val == ref; } | ||||
| 
 | ||||
| template<class T, size_t N = 10> class SymetricMatrix { | ||||
| template<class T> class SymetricMatrix { | ||||
|     static const constexpr size_t N = 10; | ||||
| public: | ||||
|      | ||||
|     explicit SymetricMatrix(ArithmeticOnly<T> c = T()) { std::fill(m, m + N, c); } | ||||
|      | ||||
|     SymetricMatrix(T m11, T m12, T m13, T m14, | ||||
|                    T m22, T m23, T m24, | ||||
|                    T m33, T m34, | ||||
|                    T m44) | ||||
|     { | ||||
|         m[0] = m11;  m[1] = m12;  m[2] = m13;  m[3] = m14; | ||||
|         m[4] = m22;  m[5] = m23;  m[6] = m24; | ||||
|         m[7] = m33;  m[8] = m34; | ||||
|         m[9] = m44; | ||||
|     } | ||||
|      | ||||
|     // Make plane
 | ||||
|     SymetricMatrix(T a, T b, T c, T d) | ||||
|     { | ||||
|  | @ -140,21 +116,16 @@ public: | |||
|         return det; | ||||
|     } | ||||
|      | ||||
|     const SymetricMatrix operator+(const SymetricMatrix& n) const | ||||
|     const SymetricMatrix& operator+=(const SymetricMatrix& n) | ||||
|     { | ||||
|         return SymetricMatrix(m[0] + n[0], m[1] + n[1], m[2] + n[2], m[3]+n[3], | ||||
|                               m[4] + n[4], m[5] + n[5], m[6] + n[6],  | ||||
|                               m[7] + n[7], m[8] + n[8], | ||||
|                               m[9] + n[9]); | ||||
|         for (size_t i = 0; i < N; ++i) m[i] += n[i]; | ||||
|         return *this; | ||||
|     } | ||||
|      | ||||
|     SymetricMatrix& operator+=(const SymetricMatrix& n) | ||||
|     SymetricMatrix operator+(const SymetricMatrix& n) | ||||
|     { | ||||
|         m[0]+=n[0]; m[1]+=n[1]; m[2]+=n[2]; m[3]+=n[3]; | ||||
|         m[4]+=n[4]; m[5]+=n[5]; m[6]+=n[6]; m[7]+=n[7]; | ||||
|         m[8]+=n[8]; m[9]+=n[9]; | ||||
|          | ||||
|         return *this; | ||||
|         SymetricMatrix self = *this; | ||||
|         return self += n; | ||||
|     } | ||||
|      | ||||
|     T m[N]; | ||||
|  | @ -349,10 +320,10 @@ public: | |||
|          | ||||
|     } | ||||
|      | ||||
|     void simplify_mesh_lossless(); | ||||
|     template<class ProgressFn> void simplify_mesh_lossless(ProgressFn &&fn); | ||||
|     void simplify_mesh_lossless() { simplify_mesh_lossless([](int){}); } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| template<class Mesh> void SimplifiableMesh<Mesh>::compact_faces() | ||||
| { | ||||
|     auto it = std::remove_if(m_faceinfo.begin(), m_faceinfo.end(), | ||||
|  | @ -604,7 +575,7 @@ bool SimplifiableMesh<Mesh>::flipped(const Vertex &    p, | |||
| } | ||||
| 
 | ||||
| template<class Mesh> | ||||
| void SimplifiableMesh<Mesh>::simplify_mesh_lossless() | ||||
| template<class Fn> void SimplifiableMesh<Mesh>::simplify_mesh_lossless(Fn &&fn) | ||||
| { | ||||
|     // init
 | ||||
|     for (FaceInfo &fi : m_faceinfo) fi.deleted = false; | ||||
|  | @ -628,7 +599,7 @@ void SimplifiableMesh<Mesh>::simplify_mesh_lossless() | |||
|         //
 | ||||
|         double threshold = std::numeric_limits<double>::epsilon(); //1.0E-3 EPS; // Really? (tm)
 | ||||
|          | ||||
|         dout() << "lossless iteration " << iteration << "\n"; | ||||
|         fn(iteration); | ||||
|          | ||||
|         for (FaceInfo &fi : m_faceinfo) { | ||||
|             if (fi.err[3] > threshold || fi.deleted || fi.dirty) continue; | ||||
|  |  | |||
|  | @ -93,8 +93,6 @@ namespace PerlUtils { | |||
|     extern std::string path_to_parent_path(const char *src); | ||||
| }; | ||||
| 
 | ||||
| std::string string_printf(const char *format, ...); | ||||
| 
 | ||||
| // Standard "generated by Slic3r version xxx timestamp xxx" header string, 
 | ||||
| // to be placed at the top of Slic3r generated files.
 | ||||
| std::string header_slic3r_generated(); | ||||
|  |  | |||
|  | @ -231,16 +231,17 @@ static inline bool is_approx(Number value, Number test_value) | |||
| } | ||||
| 
 | ||||
| template<class...Args> | ||||
| std::string string_printf(const char *const fmt, Args &&...args) | ||||
| std::string string_printf(const char *const _fmt, Args &&...args) | ||||
| { | ||||
|     static const size_t INITIAL_LEN = 1024; | ||||
|     std::vector<char> buffer(INITIAL_LEN, '\0'); | ||||
|      | ||||
|     int bufflen = snprintf(buffer.data(), INITIAL_LEN - 1, fmt, std::forward<Args>(args)...); | ||||
|     auto fmt = std::string("%s") + _fmt; | ||||
|     int bufflen = snprintf(buffer.data(), INITIAL_LEN - 1, fmt.c_str(), "", std::forward<Args>(args)...); | ||||
|      | ||||
|     if (bufflen >= int(INITIAL_LEN)) { | ||||
|         buffer.resize(size_t(bufflen) + 1); | ||||
|         snprintf(buffer.data(), buffer.size(), fmt, std::forward<Args>(args)...); | ||||
|         snprintf(buffer.data(), buffer.size(), fmt.c_str(), "", std::forward<Args>(args)...); | ||||
|     } | ||||
|      | ||||
|     return std::string(buffer.begin(), buffer.begin() + bufflen); | ||||
|  |  | |||
|  | @ -577,24 +577,6 @@ namespace PerlUtils { | |||
|     std::string path_to_parent_path(const char *src)    { return boost::filesystem::path(src).parent_path().string(); } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| std::string string_printf(const char *format, ...) | ||||
| { | ||||
|     va_list args1; | ||||
|     va_start(args1, format); | ||||
|     va_list args2; | ||||
|     va_copy(args2, args1); | ||||
| 
 | ||||
|     size_t needed_size = ::vsnprintf(nullptr, 0, format, args1) + 1; | ||||
|     va_end(args1); | ||||
| 
 | ||||
|     std::string res(needed_size, '\0'); | ||||
|     ::vsnprintf(&res.front(), res.size(), format, args2); | ||||
|     va_end(args2); | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| std::string header_slic3r_generated() | ||||
| { | ||||
|     return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp(); | ||||
|  |  | |||
|  | @ -147,6 +147,8 @@ set(SLIC3R_GUI_SOURCES | |||
|     GUI/Mouse3DController.hpp | ||||
|     GUI/DoubleSlider.cpp | ||||
|     GUI/DoubleSlider.hpp | ||||
|     GUI/ObjectDataViewModel.cpp | ||||
|     GUI/ObjectDataViewModel.hpp | ||||
|     Utils/Http.cpp | ||||
|     Utils/Http.hpp | ||||
|     Utils/FixModelByWin10.cpp | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ | |||
| #include "libslic3r/SLAPrint.hpp" | ||||
| #include "libslic3r/Slicing.hpp" | ||||
| #include "libslic3r/GCode/Analyzer.hpp" | ||||
| #include "slic3r/GUI/PresetBundle.hpp" | ||||
| #include "slic3r/GUI/BitmapCache.hpp" | ||||
| #include "libslic3r/Format/STL.hpp" | ||||
| #include "libslic3r/Utils.hpp" | ||||
| 
 | ||||
|  | @ -792,14 +792,14 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con | |||
|     for (unsigned int i = 0; i < colors_count; ++i) | ||||
|     { | ||||
|         const std::string& txt_color = config->opt_string("extruder_colour", i); | ||||
|         if (PresetBundle::parse_color(txt_color, rgb)) | ||||
|         if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) | ||||
|         { | ||||
|             colors[i].set(txt_color, rgb); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             const std::string& txt_color = config->opt_string("filament_colour", i); | ||||
|             if (PresetBundle::parse_color(txt_color, rgb)) | ||||
|             if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) | ||||
|                 colors[i].set(txt_color, rgb); | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -1,6 +1,9 @@ | |||
| #include "BitmapCache.hpp" | ||||
| 
 | ||||
| #include "libslic3r/Utils.hpp" | ||||
| #include "../Utils/MacDarkMode.hpp" | ||||
| #include "GUI.hpp" | ||||
| 
 | ||||
| #include <boost/filesystem.hpp> | ||||
| 
 | ||||
| #if ! defined(WIN32) && ! defined(__APPLE__) | ||||
|  | @ -20,6 +23,16 @@ | |||
| 
 | ||||
| namespace Slic3r { namespace GUI { | ||||
| 
 | ||||
| BitmapCache::BitmapCache() | ||||
| { | ||||
| #ifdef __APPLE__ | ||||
|     // Note: win->GetContentScaleFactor() is not used anymore here because it tends to
 | ||||
|     // return bogus results quite often (such as 1.0 on Retina or even 0.0).
 | ||||
|     // We're using the max scaling factor across all screens because it's very likely to be good enough.
 | ||||
|     m_scale = mac_max_scaling_factor(); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void BitmapCache::clear() | ||||
| { | ||||
|     for (std::pair<const std::string, wxBitmap*> &bitmap : m_map) | ||||
|  | @ -55,6 +68,14 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_ | |||
|     auto      it     = m_map.find(bitmap_key); | ||||
|     if (it == m_map.end()) { | ||||
|         bitmap = new wxBitmap(width, height); | ||||
| #ifdef __APPLE__ | ||||
|         // Contrary to intuition, the `scale` argument isn't "please scale this to such and such"
 | ||||
|         // but rather "the wxImage is sized for backing scale such and such".
 | ||||
|         // So, We need to let the Mac OS wxBitmap implementation
 | ||||
|         // know that the image may already be scaled appropriately for Retina,
 | ||||
|         // and thereby that it's not supposed to upscale it.
 | ||||
|         bitmap->CreateScaled(width, height, -1, m_scale); | ||||
| #endif | ||||
|         m_map[bitmap_key] = bitmap; | ||||
|     } else { | ||||
|         bitmap = it->second; | ||||
|  | @ -100,8 +121,13 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg | |||
|     size_t width  = 0; | ||||
|     size_t height = 0; | ||||
|     for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { | ||||
| #ifdef __APPLE__ | ||||
|         width += bmp->GetScaledWidth(); | ||||
|         height = std::max<size_t>(height, bmp->GetScaledHeight()); | ||||
| #else | ||||
|         width += bmp->GetWidth(); | ||||
|         height = std::max<size_t>(height, bmp->GetHeight()); | ||||
| #endif | ||||
|     } | ||||
| 
 | ||||
| #ifdef BROKEN_ALPHA | ||||
|  | @ -167,7 +193,12 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg | |||
|     for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { | ||||
|         if (bmp->GetWidth() > 0) | ||||
|             memDC.DrawBitmap(*bmp, x, 0, true); | ||||
| #ifdef __APPLE__ | ||||
|         // we should "move" with step equal to non-scaled width
 | ||||
|         x += bmp->GetScaledWidth(); | ||||
| #else | ||||
|         x += bmp->GetWidth(); | ||||
| #endif  | ||||
|     } | ||||
|     memDC.SelectObject(wxNullBitmap); | ||||
|     return bitmap; | ||||
|  | @ -175,7 +206,7 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg | |||
| #endif | ||||
| } | ||||
| 
 | ||||
| wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, float scale /* = 1.0f */, const bool grayscale/* = false*/) | ||||
| wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale/* = false*/) | ||||
| { | ||||
|     wxImage image(width, height); | ||||
|     image.InitAlpha(); | ||||
|  | @ -192,7 +223,7 @@ wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned w | |||
|     if (grayscale) | ||||
|         image = image.ConvertToGreyscale(m_gs, m_gs, m_gs); | ||||
| 
 | ||||
|     return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image), scale)); | ||||
|     return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image), m_scale)); | ||||
| } | ||||
| 
 | ||||
| wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width, unsigned height, | ||||
|  | @ -227,12 +258,12 @@ wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width, | |||
| } | ||||
| 
 | ||||
| wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height,  | ||||
|     float scale /* = 1.0f */, const bool grayscale/* = false*/, const bool dark_mode/* = false*/) | ||||
|     const bool grayscale/* = false*/, const bool dark_mode/* = false*/) | ||||
| { | ||||
|     std::string bitmap_key = bitmap_name + ( target_height !=0 ?  | ||||
|                                            "-h" + std::to_string(target_height) :  | ||||
|                                            "-w" + std::to_string(target_width)) | ||||
|                                          + (scale != 1.0f ? "-s" + std::to_string(scale) : "") | ||||
|                                          + (m_scale != 1.0f ? "-s" + std::to_string(m_scale) : "") | ||||
|                                          + (grayscale ? "-gs" : ""); | ||||
| 
 | ||||
|     /* For the Dark mode of any platform, we should draw icons in respect to OS background
 | ||||
|  | @ -272,7 +303,7 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_ | |||
|     if (image == nullptr) | ||||
|         return nullptr; | ||||
| 
 | ||||
|     target_height != 0 ? target_height *= scale : target_width *= scale; | ||||
|     target_height != 0 ? target_height *= m_scale : target_width *= m_scale; | ||||
| 
 | ||||
|     float svg_scale = target_height != 0 ?  | ||||
|                   (float)target_height / image->height  : target_width != 0 ? | ||||
|  | @ -297,11 +328,16 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_ | |||
|     ::nsvgDeleteRasterizer(rast); | ||||
|     ::nsvgDelete(image); | ||||
| 
 | ||||
|     return this->insert_raw_rgba(bitmap_key, width, height, data.data(), scale, grayscale); | ||||
|     return this->insert_raw_rgba(bitmap_key, width, height, data.data(), grayscale); | ||||
| } | ||||
| 
 | ||||
| wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency) | ||||
| //we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap
 | ||||
| wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling/* = false*/) | ||||
| { | ||||
|     double scale = suppress_scaling ? 1.0f : m_scale; | ||||
|     width  *= scale; | ||||
|     height *= scale; | ||||
| 
 | ||||
|     wxImage image(width, height); | ||||
|     image.InitAlpha(); | ||||
|     unsigned char* imgdata = image.GetData(); | ||||
|  | @ -312,7 +348,32 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi | |||
|         *imgdata ++ = b; | ||||
|         *imgalpha ++ = transparency; | ||||
|     } | ||||
|     return wxImage_to_wxBitmap_with_alpha(std::move(image)); | ||||
|     return wxImage_to_wxBitmap_with_alpha(std::move(image), scale); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static inline int hex_digit_to_int(const char c) | ||||
| { | ||||
|     return | ||||
|         (c >= '0' && c <= '9') ? int(c - '0') : | ||||
|         (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : | ||||
|         (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; | ||||
| } | ||||
| 
 | ||||
| bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out) | ||||
| { | ||||
|     rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; | ||||
|     if (scolor.size() != 7 || scolor.front() != '#') | ||||
|         return false; | ||||
|     const char* c = scolor.data() + 1; | ||||
|     for (size_t i = 0; i < 3; ++i) { | ||||
|         int digit1 = hex_digit_to_int(*c++); | ||||
|         int digit2 = hex_digit_to_int(*c++); | ||||
|         if (digit1 == -1 || digit2 == -1) | ||||
|             return false; | ||||
|         rgb_out[i] = (unsigned char)(digit1 * 16 + digit2); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
|  |  | |||
|  | @ -1,24 +1,22 @@ | |||
| #ifndef SLIC3R_GUI_BITMAP_CACHE_HPP | ||||
| #define SLIC3R_GUI_BITMAP_CACHE_HPP | ||||
| 
 | ||||
| #include <map> | ||||
| 
 | ||||
| #include <wx/wxprec.h> | ||||
| #ifndef WX_PRECOMP | ||||
|     #include <wx/wx.h> | ||||
| #endif | ||||
| 
 | ||||
| #include "libslic3r/libslic3r.h" | ||||
| #include "libslic3r/Config.hpp" | ||||
| 
 | ||||
| #include "GUI.hpp" | ||||
| 
 | ||||
| namespace Slic3r { namespace GUI { | ||||
| 
 | ||||
| class BitmapCache | ||||
| { | ||||
| public: | ||||
| 	BitmapCache() {} | ||||
| 	BitmapCache(); | ||||
| 	~BitmapCache() { clear(); } | ||||
| 	void 			clear(); | ||||
| 	double			scale() { return m_scale; } | ||||
| 
 | ||||
| 	wxBitmap* 		find(const std::string &name) 		{ auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; } | ||||
| 	const wxBitmap* find(const std::string &name) const { return const_cast<BitmapCache*>(this)->find(name); } | ||||
|  | @ -29,20 +27,23 @@ public: | |||
| 	wxBitmap* 		insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3); | ||||
| 	wxBitmap* 		insert(const std::string &name, const std::vector<wxBitmap> &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); } | ||||
| 	wxBitmap* 		insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end); | ||||
| 	wxBitmap* 		insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, float scale = 1.0f, const bool grayscale = false); | ||||
| 	wxBitmap* 		insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale = false); | ||||
| 
 | ||||
| 	// Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero.
 | ||||
|     wxBitmap* 		load_png(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false); | ||||
| 	// Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width.
 | ||||
|     wxBitmap* 		load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, float scale = 1.0f, const bool grayscale = false, const bool dark_mode = false); | ||||
|     wxBitmap* 		load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false); | ||||
| 
 | ||||
| 	static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency); | ||||
| 	static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } | ||||
| 	static wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } | ||||
| 	/*static */wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false); | ||||
| 	/*static */wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3], bool suppress_scaling = false) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } | ||||
| 	/*static */wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } | ||||
| 
 | ||||
| 	static bool                 parse_color(const std::string& scolor, unsigned char* rgb_out); | ||||
| 
 | ||||
| private: | ||||
|     std::map<std::string, wxBitmap*>	m_map; | ||||
|     double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs)
 | ||||
|     double	m_gs	= 0.2;	// value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs)
 | ||||
| 	double	m_scale = 1.0;	// value, used for correct scaling of SVG icons on Retina display
 | ||||
| }; | ||||
| 
 | ||||
| } // GUI
 | ||||
|  |  | |||
|  | @ -14,7 +14,9 @@ | |||
| 
 | ||||
| #include <GL/glew.h> | ||||
| 
 | ||||
| #if !ENABLE_6DOF_CAMERA | ||||
| static const float GIMBALL_LOCK_THETA_MAX = 180.0f; | ||||
| #endif // !ENABLE_6DOF_CAMERA
 | ||||
| 
 | ||||
| // phi / theta angles to orient the camera.
 | ||||
| static const float VIEW_DEFAULT[2] = { 45.0f, 45.0f }; | ||||
|  | @ -66,13 +68,10 @@ std::string Camera::get_type_as_string() const | |||
| { | ||||
|     switch (m_type) | ||||
|     { | ||||
|     case Unknown: | ||||
|         return "unknown"; | ||||
|     case Perspective: | ||||
|         return "perspective"; | ||||
|     case Unknown:     return "unknown"; | ||||
|     case Perspective: return "perspective"; | ||||
|     default: | ||||
|     case Ortho: | ||||
|         return "orthographic"; | ||||
|     case Ortho:       return "orthographic"; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
|  | @ -88,10 +87,7 @@ void Camera::set_type(EType type) | |||
| 
 | ||||
| void Camera::set_type(const std::string& type) | ||||
| { | ||||
|     if (type == "1") | ||||
|         set_type(Perspective); | ||||
|     else | ||||
|         set_type(Ortho); | ||||
|     set_type((type == "1") ? Perspective : Ortho); | ||||
| } | ||||
| 
 | ||||
| void Camera::select_next_type() | ||||
|  | @ -157,17 +153,17 @@ void Camera::select_view(const std::string& direction) | |||
|     if (direction == "iso") | ||||
|         set_default_orientation(); | ||||
|     else if (direction == "left") | ||||
|         m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ()); | ||||
|         look_at(m_target - m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ()); | ||||
|     else if (direction == "right") | ||||
|         m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ()); | ||||
|         look_at(m_target + m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ()); | ||||
|     else if (direction == "top") | ||||
|         m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY()); | ||||
|         look_at(m_target + m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY()); | ||||
|     else if (direction == "bottom") | ||||
|         m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitZ(), m_target, -Vec3d::UnitY()); | ||||
|         look_at(m_target - m_distance * Vec3d::UnitZ(), m_target, -Vec3d::UnitY()); | ||||
|     else if (direction == "front") | ||||
|         m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ()); | ||||
|         look_at(m_target - m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ()); | ||||
|     else if (direction == "rear") | ||||
|         m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ()); | ||||
|         look_at(m_target + m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ()); | ||||
| } | ||||
| #else | ||||
| bool Camera::select_view(const std::string& direction) | ||||
|  | @ -244,17 +240,27 @@ void Camera::apply_view_matrix() const | |||
| 
 | ||||
| void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z) const | ||||
| { | ||||
| #if !ENABLE_6DOF_CAMERA | ||||
|     set_distance(DefaultDistance); | ||||
| #endif // !ENABLE_6DOF_CAMERA
 | ||||
| 
 | ||||
|     double w = 0.0; | ||||
|     double h = 0.0; | ||||
| 
 | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     double old_distance = m_distance; | ||||
|     m_frustrum_zs = calc_tight_frustrum_zs_around(box); | ||||
|     if (m_distance != old_distance) | ||||
|         // the camera has been moved re-apply view matrix
 | ||||
|         apply_view_matrix(); | ||||
| #else | ||||
|     while (true) | ||||
|     { | ||||
|         m_frustrum_zs = calc_tight_frustrum_zs_around(box); | ||||
| #endif // !ENABLE_6DOF_CAMERA
 | ||||
| 
 | ||||
|         if (near_z > 0.0) | ||||
|             m_frustrum_zs.first = std::min(m_frustrum_zs.first, near_z); | ||||
|             m_frustrum_zs.first = std::max(std::min(m_frustrum_zs.first, near_z), FrustrumMinNearZ); | ||||
| 
 | ||||
|         if (far_z > 0.0) | ||||
|             m_frustrum_zs.second = std::max(m_frustrum_zs.second, far_z); | ||||
|  | @ -262,12 +268,9 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa | |||
|         w = 0.5 * (double)m_viewport[2]; | ||||
|         h = 0.5 * (double)m_viewport[3]; | ||||
| 
 | ||||
|         if (m_zoom != 0.0) | ||||
|         { | ||||
|             double inv_zoom = 1.0 / m_zoom; | ||||
|             w *= inv_zoom; | ||||
|             h *= inv_zoom; | ||||
|         } | ||||
|         double inv_zoom = get_inv_zoom(); | ||||
|         w *= inv_zoom; | ||||
|         h *= inv_zoom; | ||||
| 
 | ||||
|         switch (m_type) | ||||
|         { | ||||
|  | @ -288,6 +291,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa | |||
|         } | ||||
|         } | ||||
| 
 | ||||
| #if !ENABLE_6DOF_CAMERA | ||||
|         if (m_type == Perspective) | ||||
|         { | ||||
|             double fov_deg = Geometry::rad2deg(2.0 * std::atan(h / m_frustrum_zs.first)); | ||||
|  | @ -307,6 +311,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa | |||
|         else | ||||
|             break; | ||||
|     } | ||||
| #endif // !ENABLE_6DOF_CAMERA
 | ||||
| 
 | ||||
|     glsafe(::glMatrixMode(GL_PROJECTION)); | ||||
|     glsafe(::glLoadIdentity()); | ||||
|  | @ -331,14 +336,22 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa | |||
| } | ||||
| 
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
| #if ENABLE_6DOF_CAMERA | ||||
| void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor) | ||||
| #else | ||||
| void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| #else | ||||
| void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) | ||||
| #endif // ENABLE_THUMBNAIL_GENERATOR
 | ||||
| { | ||||
|     // Calculate the zoom factor needed to adjust the view around the given box.
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor); | ||||
| #else | ||||
|     double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h, margin_factor); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| #else | ||||
|     double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h); | ||||
| #endif // ENABLE_THUMBNAIL_GENERATOR
 | ||||
|  | @ -355,10 +368,18 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) | |||
| } | ||||
| 
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
| #if ENABLE_6DOF_CAMERA | ||||
| void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor) | ||||
| #else | ||||
| void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor) | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| { | ||||
|     Vec3d center; | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     double zoom = calc_zoom_to_volumes_factor(volumes, center, margin_factor); | ||||
| #else | ||||
|     double zoom = calc_zoom_to_volumes_factor(volumes, canvas_w, canvas_h, center, margin_factor); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
|     if (zoom > 0.0) | ||||
|     { | ||||
|         m_zoom = zoom; | ||||
|  | @ -396,6 +417,7 @@ void Camera::debug_render() const | |||
|     float deltaZ = farZ - nearZ; | ||||
|     float zoom = (float)m_zoom; | ||||
|     float fov = (float)get_fov(); | ||||
|     std::array<int, 4>viewport = get_viewport(); | ||||
|     float gui_scale = (float)get_gui_scale(); | ||||
| 
 | ||||
|     ImGui::InputText("Type", type.data(), type.length(), ImGuiInputTextFlags_ReadOnly); | ||||
|  | @ -415,6 +437,8 @@ void Camera::debug_render() const | |||
|     ImGui::InputFloat("Zoom", &zoom, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); | ||||
|     ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); | ||||
|     ImGui::Separator(); | ||||
|     ImGui::InputInt4("Viewport", viewport.data(), ImGuiInputTextFlags_ReadOnly); | ||||
|     ImGui::Separator(); | ||||
|     ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); | ||||
|     imgui.end(); | ||||
| } | ||||
|  | @ -434,10 +458,31 @@ void Camera::translate_world(const Vec3d& displacement) | |||
| 
 | ||||
| void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad) | ||||
| { | ||||
|     // FIXME -> The following is a HACK !!!
 | ||||
|     // When the value of the zenit rotation is large enough, the following call to rotate() shows
 | ||||
|     // numerical instability introducing some scaling into m_view_matrix (verified by checking
 | ||||
|     // that the camera space unit vectors are no more unit).
 | ||||
|     // See also https://dev.prusa3d.com/browse/SPE-1082
 | ||||
|     // We split the zenit rotation into a set of smaller rotations which are then applied.
 | ||||
|     static const double MAX_ALLOWED = Geometry::deg2rad(0.1); | ||||
|     unsigned int zenit_steps_count = 1 + (unsigned int)(std::abs(delta_zenit_rad) / MAX_ALLOWED); | ||||
|     double zenit_step = delta_zenit_rad / (double)zenit_steps_count; | ||||
| 
 | ||||
|     Vec3d target = m_target; | ||||
|     translate_world(-target); | ||||
|     m_view_matrix.rotate(Eigen::AngleAxisd(delta_zenit_rad, get_dir_right())); | ||||
|     m_view_matrix.rotate(Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ())); | ||||
| 
 | ||||
|     if (zenit_step != 0.0) | ||||
|     { | ||||
|         Vec3d right = get_dir_right(); | ||||
|         for (unsigned int i = 0; i < zenit_steps_count; ++i) | ||||
|         { | ||||
|             m_view_matrix.rotate(Eigen::AngleAxisd(zenit_step, right)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (delta_azimut_rad != 0.0) | ||||
|         m_view_matrix.rotate(Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ())); | ||||
| 
 | ||||
|     translate_world(target); | ||||
| } | ||||
| 
 | ||||
|  | @ -457,49 +502,77 @@ void Camera::rotate_local_around_pivot(const Vec3d& rotation_rad, const Vec3d& p | |||
|     m_view_matrix.rotate(Eigen::AngleAxisd(rotation_rad(2), get_dir_forward())); | ||||
|     translate_world(center); | ||||
| } | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| 
 | ||||
| double Camera::min_zoom() const | ||||
| { | ||||
|     return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box, (int)m_viewport[2], (int)m_viewport[3]); | ||||
| } | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box); | ||||
| #else | ||||
|     return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box, m_viewport[2], m_viewport[3]); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| } | ||||
| 
 | ||||
| std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const | ||||
| { | ||||
|     std::pair<double, double> ret; | ||||
|     auto& [near_z, far_z] = ret; | ||||
| 
 | ||||
| #if !ENABLE_6DOF_CAMERA | ||||
|     while (true) | ||||
|     { | ||||
| #endif // !ENABLE_6DOF_CAMERA
 | ||||
|         // box in eye space
 | ||||
|         BoundingBoxf3 eye_box = box.transformed(m_view_matrix); | ||||
|         ret.first = -eye_box.max(2); | ||||
|         ret.second = -eye_box.min(2); | ||||
|         near_z = -eye_box.max(2); | ||||
|         far_z = -eye_box.min(2); | ||||
| 
 | ||||
|         // apply margin
 | ||||
|         ret.first -= FrustrumZMargin; | ||||
|         ret.second += FrustrumZMargin; | ||||
|         near_z -= FrustrumZMargin; | ||||
|         far_z += FrustrumZMargin; | ||||
| 
 | ||||
|         // ensure min size
 | ||||
|         if (ret.second - ret.first < FrustrumMinZRange) | ||||
|         if (far_z - near_z < FrustrumMinZRange) | ||||
|         { | ||||
|             double mid_z = 0.5 * (ret.first + ret.second); | ||||
|             double mid_z = 0.5 * (near_z + far_z); | ||||
|             double half_size = 0.5 * FrustrumMinZRange; | ||||
|             ret.first = mid_z - half_size; | ||||
|             ret.second = mid_z + half_size; | ||||
|             near_z = mid_z - half_size; | ||||
|             far_z = mid_z + half_size; | ||||
|         } | ||||
| 
 | ||||
|         if (ret.first >= FrustrumMinNearZ) | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|         if (near_z < FrustrumMinNearZ) | ||||
|         { | ||||
|             float delta = FrustrumMinNearZ - near_z; | ||||
|             set_distance(m_distance + delta); | ||||
|             near_z += delta; | ||||
|             far_z += delta; | ||||
|         } | ||||
|         else if ((near_z > 2.0 * FrustrumMinNearZ) && (m_distance > DefaultDistance)) | ||||
|         { | ||||
|             float delta = m_distance - DefaultDistance; | ||||
|             set_distance(DefaultDistance); | ||||
|             near_z -= delta; | ||||
|             far_z -= delta; | ||||
|         } | ||||
| #else | ||||
|         if (near_z >= FrustrumMinNearZ) | ||||
|             break; | ||||
| 
 | ||||
|         // ensure min Near Z
 | ||||
|         set_distance(m_distance + FrustrumMinNearZ - ret.first); | ||||
|         // ensure min near z
 | ||||
|         set_distance(m_distance + FrustrumMinNearZ - near_z); | ||||
|     } | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
| #if ENABLE_6DOF_CAMERA | ||||
| double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor) const | ||||
| #else | ||||
| double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) const | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| #else | ||||
| double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const | ||||
| #endif // ENABLE_THUMBNAIL_GENERATOR
 | ||||
|  | @ -511,8 +584,10 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca | |||
|     // project the box vertices on a plane perpendicular to the camera forward axis
 | ||||
|     // then calculates the vertices coordinate on this plane along the camera xy axes
 | ||||
| 
 | ||||
| #if !ENABLE_6DOF_CAMERA | ||||
|     // ensure that the view matrix is updated
 | ||||
|     apply_view_matrix(); | ||||
| #endif // !ENABLE_6DOF_CAMERA
 | ||||
| 
 | ||||
|     Vec3d right = get_dir_right(); | ||||
|     Vec3d up = get_dir_up(); | ||||
|  | @ -569,11 +644,19 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca | |||
|     dx *= margin_factor; | ||||
|     dy *= margin_factor; | ||||
| 
 | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy); | ||||
| #else | ||||
|     return std::min((double)canvas_w / dx, (double)canvas_h / dy); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
| #if ENABLE_6DOF_CAMERA | ||||
| double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor) const | ||||
| #else | ||||
| double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor) const | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| { | ||||
|     if (volumes.empty()) | ||||
|         return -1.0; | ||||
|  | @ -581,8 +664,10 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canv | |||
|     // project the volumes vertices on a plane perpendicular to the camera forward axis
 | ||||
|     // then calculates the vertices coordinate on this plane along the camera xy axes
 | ||||
| 
 | ||||
| #if !ENABLE_6DOF_CAMERA | ||||
|     // ensure that the view matrix is updated
 | ||||
|     apply_view_matrix(); | ||||
| #endif // !ENABLE_6DOF_CAMERA
 | ||||
| 
 | ||||
|     Vec3d right = get_dir_right(); | ||||
|     Vec3d up = get_dir_up(); | ||||
|  | @ -634,46 +719,57 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canv | |||
|     if ((dx <= 0.0) || (dy <= 0.0)) | ||||
|         return -1.0f; | ||||
| 
 | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy); | ||||
| #else | ||||
|     return std::min((double)canvas_w / dx, (double)canvas_h / dy); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| } | ||||
| #endif // ENABLE_THUMBNAIL_GENERATOR
 | ||||
| 
 | ||||
| void Camera::set_distance(double distance) const | ||||
| { | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     if (m_distance != distance) | ||||
|     { | ||||
|         m_view_matrix.translate((distance - m_distance) * get_dir_forward()); | ||||
|         m_distance = distance; | ||||
|     } | ||||
| #else | ||||
|     m_distance = distance; | ||||
|     apply_view_matrix(); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_6DOF_CAMERA | ||||
| Transform3d Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) const | ||||
| void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) | ||||
| { | ||||
|     Vec3d unit_z = (position - target).normalized(); | ||||
|     Vec3d unit_x = up.cross(unit_z).normalized(); | ||||
|     Vec3d unit_y = unit_z.cross(unit_x).normalized(); | ||||
| 
 | ||||
|     Transform3d matrix; | ||||
|     m_target = target; | ||||
|     Vec3d new_position = m_target + m_distance * unit_z; | ||||
| 
 | ||||
|     matrix(0, 0) = unit_x(0); | ||||
|     matrix(0, 1) = unit_x(1); | ||||
|     matrix(0, 2) = unit_x(2); | ||||
|     matrix(0, 3) = -unit_x.dot(position); | ||||
|     m_view_matrix(0, 0) = unit_x(0); | ||||
|     m_view_matrix(0, 1) = unit_x(1); | ||||
|     m_view_matrix(0, 2) = unit_x(2); | ||||
|     m_view_matrix(0, 3) = -unit_x.dot(new_position); | ||||
| 
 | ||||
|     matrix(1, 0) = unit_y(0); | ||||
|     matrix(1, 1) = unit_y(1); | ||||
|     matrix(1, 2) = unit_y(2); | ||||
|     matrix(1, 3) = -unit_y.dot(position); | ||||
|     m_view_matrix(1, 0) = unit_y(0); | ||||
|     m_view_matrix(1, 1) = unit_y(1); | ||||
|     m_view_matrix(1, 2) = unit_y(2); | ||||
|     m_view_matrix(1, 3) = -unit_y.dot(new_position); | ||||
| 
 | ||||
|     matrix(2, 0) = unit_z(0); | ||||
|     matrix(2, 1) = unit_z(1); | ||||
|     matrix(2, 2) = unit_z(2); | ||||
|     matrix(2, 3) = -unit_z.dot(position); | ||||
|     m_view_matrix(2, 0) = unit_z(0); | ||||
|     m_view_matrix(2, 1) = unit_z(1); | ||||
|     m_view_matrix(2, 2) = unit_z(2); | ||||
|     m_view_matrix(2, 3) = -unit_z.dot(new_position); | ||||
| 
 | ||||
|     matrix(3, 0) = 0.0; | ||||
|     matrix(3, 1) = 0.0; | ||||
|     matrix(3, 2) = 0.0; | ||||
|     matrix(3, 3) = 1.0; | ||||
| 
 | ||||
|     return matrix; | ||||
|     m_view_matrix(3, 0) = 0.0; | ||||
|     m_view_matrix(3, 1) = 0.0; | ||||
|     m_view_matrix(3, 2) = 0.0; | ||||
|     m_view_matrix(3, 3) = 1.0; | ||||
| } | ||||
| 
 | ||||
| void Camera::set_default_orientation() | ||||
|  |  | |||
|  | @ -48,11 +48,7 @@ private: | |||
|     mutable double m_gui_scale; | ||||
| 
 | ||||
|     mutable std::array<int, 4> m_viewport; | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     Transform3d m_view_matrix; | ||||
| #else | ||||
|     mutable Transform3d m_view_matrix; | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
|     mutable Transform3d m_projection_matrix; | ||||
|     mutable std::pair<double, double> m_frustrum_zs; | ||||
| 
 | ||||
|  | @ -71,7 +67,11 @@ public: | |||
|     const Vec3d& get_target() const { return m_target; } | ||||
|     void set_target(const Vec3d& target); | ||||
| 
 | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     double get_distance() const { return (get_position() - m_target).norm(); } | ||||
| #else | ||||
|     double get_distance() const { return m_distance; } | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
|     double get_gui_scale() const { return m_gui_scale; } | ||||
| 
 | ||||
| #if !ENABLE_6DOF_CAMERA | ||||
|  | @ -115,8 +115,13 @@ public: | |||
|     void apply_projection(const BoundingBoxf3& box, double near_z = -1.0, double far_z = -1.0) const; | ||||
| 
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     void zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor); | ||||
|     void zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor = DefaultZoomToVolumesMarginFactor); | ||||
| #else | ||||
|     void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor); | ||||
|     void zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToVolumesMarginFactor); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| #else | ||||
|     void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h); | ||||
| #endif // ENABLE_THUMBNAIL_GENERATOR
 | ||||
|  | @ -141,25 +146,29 @@ public: | |||
| 
 | ||||
|     // returns true if the camera z axis (forward) is pointing in the negative direction of the world z axis
 | ||||
|     bool is_looking_downward() const { return get_dir_forward().dot(Vec3d::UnitZ()) < 0.0; } | ||||
| 
 | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
|     double max_zoom() const { return 100.0; } | ||||
|     double min_zoom() const; | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| 
 | ||||
| private: | ||||
|     // returns tight values for nearZ and farZ plane around the given bounding box
 | ||||
|     // the camera MUST be outside of the bounding box in eye coordinate of the given box
 | ||||
|     std::pair<double, double> calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const; | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor) const; | ||||
|     double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const; | ||||
| #else | ||||
|     double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor) const; | ||||
|     double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const; | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| #else | ||||
|     double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const; | ||||
| #endif // ENABLE_THUMBNAIL_GENERATOR
 | ||||
|     void set_distance(double distance) const; | ||||
| 
 | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     Transform3d look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) const; | ||||
|     void look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up); | ||||
|     void set_default_orientation(); | ||||
|     Vec3d validate_target(const Vec3d& target) const; | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
|  |  | |||
|  | @ -52,28 +52,26 @@ Control::Control( wxWindow *parent, | |||
|     if (!is_osx) | ||||
|         SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
 | ||||
| 
 | ||||
|     const float scale_factor = get_svg_scale_factor(this); | ||||
| 
 | ||||
|     m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "thumb_up")); | ||||
|     m_bmp_thumb_lower  = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "thumb_down")); | ||||
|     m_thumb_size = m_bmp_thumb_lower.bmp().GetSize()*(1.0/scale_factor); | ||||
|     m_thumb_size = m_bmp_thumb_lower.GetBmpSize(); | ||||
| 
 | ||||
|     m_bmp_add_tick_on  = ScalableBitmap(this, "colorchange_add"); | ||||
|     m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_f"); | ||||
|     m_bmp_del_tick_on  = ScalableBitmap(this, "colorchange_del"); | ||||
|     m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_del_f"); | ||||
|     m_tick_icon_dim = int((float)m_bmp_add_tick_on.bmp().GetSize().x / scale_factor); | ||||
|     m_tick_icon_dim = m_bmp_add_tick_on.GetBmpWidth(); | ||||
| 
 | ||||
|     m_bmp_one_layer_lock_on    = ScalableBitmap(this, "lock_closed"); | ||||
|     m_bmp_one_layer_lock_off   = ScalableBitmap(this, "lock_closed_f"); | ||||
|     m_bmp_one_layer_unlock_on  = ScalableBitmap(this, "lock_open"); | ||||
|     m_bmp_one_layer_unlock_off = ScalableBitmap(this, "lock_open_f"); | ||||
|     m_lock_icon_dim   = int((float)m_bmp_one_layer_lock_on.bmp().GetSize().x / scale_factor); | ||||
|     m_lock_icon_dim   = m_bmp_one_layer_lock_on.GetBmpWidth(); | ||||
| 
 | ||||
|     m_bmp_revert               = ScalableBitmap(this, "undo"); | ||||
|     m_revert_icon_dim = int((float)m_bmp_revert.bmp().GetSize().x / scale_factor); | ||||
|     m_revert_icon_dim = m_bmp_revert.GetBmpWidth(); | ||||
|     m_bmp_cog                  = ScalableBitmap(this, "cog"); | ||||
|     m_cog_icon_dim    = int((float)m_bmp_cog.bmp().GetSize().x / scale_factor); | ||||
|     m_cog_icon_dim    = m_bmp_cog.GetBmpWidth(); | ||||
| 
 | ||||
|     m_selection = ssUndef; | ||||
|     m_ticks.set_pause_print_msg(_utf8(L("Place bearings in slots and resume"))); | ||||
|  | @ -554,16 +552,9 @@ void Control::draw_ticks(wxDC& dc) | |||
| 
 | ||||
|         // Draw icon for "Pause print" or "Custom Gcode"
 | ||||
|         if (tick.gcode != ColorChangeCode && tick.gcode != ToolChangeCode) | ||||
|             icon = create_scaled_bitmap(this, tick.gcode == PausePrintCode ? "pause_print" : "edit_gcode"); | ||||
|         else | ||||
|         { | ||||
|             if ((tick.gcode == ColorChangeCode && ( | ||||
|                 (m_ticks.mode == t_mode::SingleExtruder && m_mode == t_mode::MultiExtruder ) || | ||||
|                 (m_ticks.mode == t_mode::MultiExtruder  && m_mode == t_mode::SingleExtruder)   )) || | ||||
|                 (tick.gcode == ToolChangeCode  &&  | ||||
|                 (m_ticks.mode == t_mode::MultiAsSingle  && m_mode != t_mode::MultiAsSingle )   )) | ||||
|             icon = create_scaled_bitmap(this, "error_tick"); | ||||
|         } | ||||
|             icon = create_scaled_bitmap(tick.gcode == PausePrintCode ? "pause_print" : "edit_gcode"); | ||||
|         else if (m_ticks.is_conflict_tick(tick, m_mode, m_only_extruder, m_values[tick.tick])) | ||||
|             icon = create_scaled_bitmap("error_tick"); | ||||
| 
 | ||||
|         if (!icon.IsNull()) | ||||
|         { | ||||
|  | @ -753,7 +744,7 @@ bool Control::is_point_in_rect(const wxPoint& pt, const wxRect& rect) | |||
|             rect.GetTop()  <= pt.y && pt.y <= rect.GetBottom(); | ||||
| } | ||||
| 
 | ||||
| int Control::is_point_near_tick(const wxPoint& pt) | ||||
| int Control::get_tick_near_point(const wxPoint& pt) | ||||
| { | ||||
|     for (auto tick : m_ticks.ticks) { | ||||
|         const wxCoord pos = get_position_from_value(tick.tick); | ||||
|  | @ -833,7 +824,7 @@ void Control::OnLeftDown(wxMouseEvent& event) | |||
|         detect_selected_slider(pos); | ||||
| 
 | ||||
|     if (!m_selection) { | ||||
|         const int tick_val  = is_point_near_tick(pos); | ||||
|         const int tick_val  = get_tick_near_point(pos); | ||||
|         /* Set current thumb position to the nearest tick (if it is)
 | ||||
|          * OR to a value corresponding to the mouse click | ||||
|          * */  | ||||
|  | @ -896,20 +887,70 @@ wxString Control::get_tooltip(IconFocus icon_focus) | |||
|     { | ||||
|         const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; | ||||
|         const auto tick_code_it = m_ticks.ticks.find(TickCode{tick}); | ||||
|         tooltip = tick_code_it == m_ticks.ticks.end()               ? (m_mode == t_mode::MultiAsSingle ? | ||||
|                       _(L("For add change extruder use left mouse button click")) : | ||||
|                       _(L("For add color change use left mouse button click"))  ) + "\n" + | ||||
|                       _(L("For add another code use right mouse button click"))   : | ||||
|                   tick_code_it->gcode == ColorChangeCode    ? ( m_mode == t_mode::SingleExtruder ? | ||||
|                       _(L("For Delete color change use left mouse button click\n" | ||||
|                           "For Edit color use right mouse button click")) : | ||||
|                       from_u8((boost::format(_utf8(L("Delete color change for Extruder %1%"))) % tick_code_it->extruder).str()) ): | ||||
|                   tick_code_it->gcode == PausePrintCode     ?  | ||||
|                       _(L("Delete pause")) : | ||||
|                   tick_code_it->gcode == ToolChangeCode ? | ||||
|                       from_u8((boost::format(_utf8(L("Delete extruder change to \"%1%\""))) % tick_code_it->extruder).str()) : | ||||
|                       from_u8((boost::format(_utf8(L("For Delete \"%1%\" code use left mouse button click\n" | ||||
|                                                      "For Edit \"%1%\" code use right mouse button click"))) % tick_code_it->gcode ).str()); | ||||
| 
 | ||||
|         /* Note: just on OSX!!!
 | ||||
|          * Right click event causes a little scrolling.  | ||||
|          * So, as a workaround we use Ctrl+LeftMouseClick instead of RightMouseClick | ||||
|          * Show this information in tooltip | ||||
|          * */ | ||||
| 
 | ||||
|         if (tick_code_it == m_ticks.ticks.end())    // tick doesn't exist
 | ||||
|         { | ||||
|             // Show mode as a first string of tooltop
 | ||||
|             tooltip = "    " + _(L("Slider(print) mode")) + ": "; | ||||
|             tooltip += (m_mode == t_mode::SingleExtruder ? CustomGCode::SingleExtruderMode : | ||||
|                         m_mode == t_mode::MultiAsSingle  ? CustomGCode::MultiAsSingleMode  : | ||||
|                         CustomGCode::MultiExtruderMode ); | ||||
|             tooltip += "\n\n"; | ||||
| 
 | ||||
|             // Show list of actions with new tick
 | ||||
|             tooltip += ( m_mode == t_mode::MultiAsSingle                            ? | ||||
|                       _(L("For add change extruder use left mouse button click"))   : | ||||
|                       _(L("For add color change use left mouse button click"))  ) + " " + | ||||
|                       _(L("OR pres \"+\" key")) + "\n" + ( | ||||
|                           is_osx ?  | ||||
|                       _(L("For add another code use Ctrl + Left mouse button click")) : | ||||
|                       _(L("For add another code use right mouse button click")) ); | ||||
|         } | ||||
|         else                                        // tick exists
 | ||||
|         { | ||||
|             // Show custom Gcode as a first string of tooltop
 | ||||
|             tooltip = "    "; | ||||
|             tooltip +=  tick_code_it->gcode == ColorChangeCode ?    ( | ||||
|                             m_mode == t_mode::SingleExtruder ?  | ||||
|                             from_u8((boost::format(_utf8(L("Color change (\"%1%\")"))) % tick_code_it->gcode ).str()) : | ||||
|                             from_u8((boost::format(_utf8(L("Color change (\"%1%\") for Extruder %2%"))) %  | ||||
|                                                    tick_code_it->gcode % tick_code_it->extruder).str()) ) : | ||||
|                         tick_code_it->gcode == PausePrintCode ? | ||||
|                             from_u8((boost::format(_utf8(L("Pause print (\"%1%\")"))) % tick_code_it->gcode ).str()) : | ||||
|                         tick_code_it->gcode == ToolChangeCode ? | ||||
|                             from_u8((boost::format(_utf8(L("Extruder(tool) is changed to Extruder \"%1%\""))) % tick_code_it->extruder ).str()) : | ||||
|                             from_u8((boost::format(_utf8(L("\"%1%\""))) % tick_code_it->gcode ).str()) ; | ||||
| 
 | ||||
|             // If tick is marked as a conflict (exclamation icon),
 | ||||
|             // we should to explain why
 | ||||
|             ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_only_extruder, m_values[tick]); | ||||
|             if (conflict != ctNone) | ||||
|                 tooltip += "\n\n" + _(L("Note")) + "! "; | ||||
|             if (conflict == ctModeConflict) | ||||
|                 tooltip +=  _(L("G-code of this tick has a conflict with slider(print) mode.\n" | ||||
|                                 "Any its editing will cause a changes of DoubleSlider data.")); | ||||
|             else if (conflict == ctMeaninglessColorChange) | ||||
|                 tooltip +=  _(L("There is a color change for extruder that wouldn't be used till the end of printing.\n" | ||||
|                                 "This code wouldn't be processed during GCode generation.")); | ||||
|             else if (conflict == ctMeaninglessToolChange) | ||||
|                 tooltip +=  _(L("There is a extruder change to the same extruder.\n" | ||||
|                                 "This code wouldn't be processed during GCode generation.")); | ||||
|             else if (conflict == ctRedundant) | ||||
|                 tooltip +=  _(L("There is a color change for extruder that has not been used before.\n" | ||||
|                                 "Check your choice to avoid redundant color changes.")); | ||||
| 
 | ||||
|             // Show list of actions with existing tick
 | ||||
|             tooltip += "\n\n" + _(L("For Delete tick use left mouse button click OR pres \"-\" key")) + "\n" + ( | ||||
|                           is_osx ?  | ||||
|                        _(L("For Edit tick use Ctrl + Left mouse button click")) : | ||||
|                        _(L("For Edit tick use right mouse button click")) ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return tooltip; | ||||
|  | @ -988,7 +1029,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current | |||
|                                                    _(L("Change extruder (N/A)")); | ||||
| 
 | ||||
|         wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _(L("Use another extruder"))); | ||||
|         change_extruder_menu_item->SetBitmap(create_scaled_bitmap(this, active_extruders[1] > 0 ? "edit_uni" : "change_extruder")); | ||||
|         change_extruder_menu_item->SetBitmap(create_scaled_bitmap(active_extruders[1] > 0 ? "edit_uni" : "change_extruder")); | ||||
| 
 | ||||
|         GUI::wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this, change_extruder_menu_item](wxUpdateUIEvent& evt) { | ||||
|             enable_menu_item(evt, [this]() {return m_mode == t_mode::MultiAsSingle; }, change_extruder_menu_item, this); }, | ||||
|  | @ -1001,7 +1042,8 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren | |||
|     const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt(); | ||||
|     if (extruders_cnt > 1) | ||||
|     { | ||||
|         std::set<int> used_extruders_for_tick = get_used_extruders_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); | ||||
|         int tick = m_selection == ssLower ? m_lower_value : m_higher_value;  | ||||
|         std::set<int> used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_only_extruder, m_values[tick]); | ||||
| 
 | ||||
|         wxMenu* add_color_change_menu = new wxMenu(); | ||||
| 
 | ||||
|  | @ -1014,14 +1056,14 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren | |||
| 
 | ||||
|             append_menu_item(add_color_change_menu, wxID_ANY, item_name, "", | ||||
|                 [this, i](wxCommandEvent&) { add_code_as_tick(ColorChangeCode, i); }, "", menu, | ||||
|                 [is_used_extruder]() { return is_used_extruder; }, GUI::wxGetApp().plater()); | ||||
|                 []() { return true; }, GUI::wxGetApp().plater()); | ||||
|         } | ||||
| 
 | ||||
|         const wxString menu_name = switch_current_code ?  | ||||
|                                    from_u8((boost::format(_utf8(L("Switch code to Color change (%1%) for:"))) % ColorChangeCode).str()) :  | ||||
|                                    from_u8((boost::format(_utf8(L("Add color change (%1%) for:"))) % ColorChangeCode).str()); | ||||
|         wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, ""); | ||||
|         add_color_change_menu_item->SetBitmap(create_scaled_bitmap(this, "colorchange_add_m")); | ||||
|         add_color_change_menu_item->SetBitmap(create_scaled_bitmap("colorchange_add_m")); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -1220,7 +1262,7 @@ std::array<int, 2> Control::get_active_extruders_for_tick(int tick) const | |||
| 
 | ||||
|     auto it = m_ticks.ticks.lower_bound(TickCode{tick}); | ||||
| 
 | ||||
|     if (it->tick == tick) // current tick exists
 | ||||
|     if (it != m_ticks.ticks.end() && it->tick == tick) // current tick exists
 | ||||
|         extruders[1] = it->extruder; | ||||
| 
 | ||||
|     while (it != m_ticks.ticks.begin()) { | ||||
|  | @ -1235,10 +1277,10 @@ std::array<int, 2> Control::get_active_extruders_for_tick(int tick) const | |||
| } | ||||
| 
 | ||||
| // Get used extruders for tick. 
 | ||||
| // Means all extruders(toools) will be used during printing from current tick to the end
 | ||||
| std::set<int> Control::get_used_extruders_for_tick(int tick) const | ||||
| // Means all extruders(tools) which will be used during printing from current tick to the end
 | ||||
| std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extruder, double print_z) const | ||||
| { | ||||
|     if (m_mode == t_mode::MultiExtruder) | ||||
|     if (mode == t_mode::MultiExtruder) | ||||
|     { | ||||
|         // #ys_FIXME: get tool ordering from _correct_ place
 | ||||
|         const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_print().get_tool_ordering(); | ||||
|  | @ -1248,7 +1290,7 @@ std::set<int> Control::get_used_extruders_for_tick(int tick) const | |||
| 
 | ||||
|         std::set<int> used_extruders; | ||||
| 
 | ||||
|         auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(m_values[tick])); | ||||
|         auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(print_z)); | ||||
|         for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools) | ||||
|         { | ||||
|             const std::vector<unsigned>& extruders = it_layer_tools->extruders; | ||||
|  | @ -1259,12 +1301,11 @@ std::set<int> Control::get_used_extruders_for_tick(int tick) const | |||
|         return used_extruders; | ||||
|     } | ||||
| 
 | ||||
|     const int default_initial_extruder = m_mode == t_mode::MultiAsSingle ? std::max(m_only_extruder, 1) : 1; | ||||
|     if (m_ticks.empty()) | ||||
|     const int default_initial_extruder = mode == t_mode::MultiAsSingle ? std::max(only_extruder, 1) : 1; | ||||
|     if (ticks.empty()) | ||||
|         return {default_initial_extruder}; | ||||
| 
 | ||||
|     std::set<int> used_extruders; | ||||
|     const std::set<TickCode>& ticks = m_ticks.ticks; | ||||
| 
 | ||||
|     auto it_start = ticks.lower_bound(TickCode{tick}); | ||||
|     auto it = it_start; | ||||
|  | @ -1341,7 +1382,7 @@ void Control::OnRightUp(wxMouseEvent& event) | |||
|             append_menu_item(&menu, wxID_ANY, it->gcode == ColorChangeCode ? _(L("Edit color")) : | ||||
|                                               it->gcode == PausePrintCode  ? _(L("Edit pause print message")) : | ||||
|                                               _(L("Edit custom G-code")), "", | ||||
|                 [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu); | ||||
|                 [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu, []() {return true; }, this); | ||||
| 
 | ||||
|         if (it->gcode == ColorChangeCode && m_mode == t_mode::MultiAsSingle) | ||||
|             append_change_extruder_menu_item(&menu, true); | ||||
|  | @ -1350,7 +1391,7 @@ void Control::OnRightUp(wxMouseEvent& event) | |||
|                                           it->gcode == ToolChangeCode  ? _(L("Delete tool change")) : | ||||
|                                           it->gcode == PausePrintCode  ? _(L("Delete pause print")) : | ||||
|                                           _(L("Delete custom G-code")), "", | ||||
|             [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu); | ||||
|             [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu, []() {return true; }, this); | ||||
| 
 | ||||
|         GUI::wxGetApp().plater()->PopupMenu(&menu); | ||||
| 
 | ||||
|  | @ -1379,6 +1420,28 @@ static std::string get_new_color(const std::string& color) | |||
|     return ""; | ||||
| } | ||||
| 
 | ||||
| // To avoid get an empty string from wxTextEntryDialog 
 | ||||
| // Let disable OK button, if TextCtrl is empty
 | ||||
| static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg) | ||||
| { | ||||
|     // detect TextCtrl and OK button
 | ||||
|     wxTextCtrl* textctrl {nullptr}; | ||||
|     wxWindowList& dlg_items = dlg->GetChildren(); | ||||
|     for (auto item : dlg_items) { | ||||
|         textctrl = dynamic_cast<wxTextCtrl*>(item); | ||||
|         if (textctrl) | ||||
|             break; | ||||
|     } | ||||
| 
 | ||||
|     if (!textctrl) | ||||
|         return; | ||||
| 
 | ||||
|     wxButton* btn_OK = static_cast<wxButton*>(dlg->FindWindowById(wxID_OK)); | ||||
|     btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl](wxUpdateUIEvent& evt) { | ||||
|         evt.Enable(!textctrl->IsEmpty()); | ||||
|     }, btn_OK->GetId()); | ||||
| } | ||||
| 
 | ||||
| static std::string get_custom_code(const std::string& code_in, double height) | ||||
| { | ||||
|     wxString msg_text = from_u8(_utf8(L("Enter custom G-code used on current layer"))) + ":"; | ||||
|  | @ -1387,7 +1450,9 @@ static std::string get_custom_code(const std::string& code_in, double height) | |||
|     // get custom gcode
 | ||||
|     wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in, | ||||
|         wxTextEntryDialogStyle | wxTE_MULTILINE); | ||||
|     if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) | ||||
|     upgrade_text_entry_dialog(&dlg); | ||||
| 
 | ||||
|     if (dlg.ShowModal() != wxID_OK) | ||||
|         return ""; | ||||
| 
 | ||||
|     return dlg.GetValue().ToStdString(); | ||||
|  | @ -1401,6 +1466,8 @@ static std::string get_pause_print_msg(const std::string& msg_in, double height) | |||
|     // get custom gcode
 | ||||
|     wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in), | ||||
|         wxTextEntryDialogStyle); | ||||
|     upgrade_text_entry_dialog(&dlg); | ||||
| 
 | ||||
|     if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) | ||||
|         return ""; | ||||
| 
 | ||||
|  | @ -1759,6 +1826,61 @@ bool TickCodeInfo::has_tick_with_code(const std::string& gcode) | |||
|     return false; | ||||
| } | ||||
| 
 | ||||
| ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mode, int only_extruder, double print_z) | ||||
| { | ||||
|     if ((tick.gcode == ColorChangeCode && ( | ||||
|             (mode == t_mode::SingleExtruder && out_mode == t_mode::MultiExtruder ) || | ||||
|             (mode == t_mode::MultiExtruder  && out_mode == t_mode::SingleExtruder)    )) || | ||||
|         (tick.gcode == ToolChangeCode && | ||||
|             (mode == t_mode::MultiAsSingle && out_mode != t_mode::MultiAsSingle)) ) | ||||
|         return ctModeConflict; | ||||
| 
 | ||||
|     // check ColorChange tick
 | ||||
|     if (tick.gcode == ColorChangeCode) | ||||
|     { | ||||
|         // We should mark a tick as a "MeaninglessColorChange", 
 | ||||
|         // if it has a ColorChange for unused extruder from current print to end of the print
 | ||||
|         std::set<int> used_extruders_for_tick = get_used_extruders_for_tick(tick.tick, only_extruder, print_z); | ||||
| 
 | ||||
|         if (used_extruders_for_tick.find(tick.extruder) == used_extruders_for_tick.end()) | ||||
|             return ctMeaninglessColorChange; | ||||
| 
 | ||||
|         // We should mark a tick as a "Redundant", 
 | ||||
|         // if it has a ColorChange for extruder that has not been used before
 | ||||
|         if (mode == t_mode::MultiAsSingle && tick.extruder != std::max<int>(only_extruder, 1) ) | ||||
|         { | ||||
|             auto it = ticks.lower_bound( tick ); | ||||
|             if (it == ticks.begin() && it->gcode == ToolChangeCode && tick.extruder == it->extruder) | ||||
|                 return ctNone; | ||||
| 
 | ||||
|             while (it != ticks.begin()) { | ||||
|                 --it; | ||||
|                 if (it->gcode == ToolChangeCode && tick.extruder == it->extruder) | ||||
|                     return ctNone; | ||||
|             } | ||||
| 
 | ||||
|             return ctRedundant; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // check ToolChange tick
 | ||||
|     if (mode == t_mode::MultiAsSingle && tick.gcode == ToolChangeCode) | ||||
|     { | ||||
|         // We should mark a tick as a "MeaninglessToolChange", 
 | ||||
|         // if it has a ToolChange to the same extruder
 | ||||
| 
 | ||||
|         auto it = ticks.find(tick); | ||||
|         if (it == ticks.begin()) | ||||
|             return tick.extruder == std::max<int>(only_extruder, 1) ? ctMeaninglessToolChange : ctNone; | ||||
| 
 | ||||
|         --it; | ||||
|         if (it->gcode == ToolChangeCode && tick.extruder == it->extruder) | ||||
|             return ctMeaninglessToolChange; | ||||
|     } | ||||
| 
 | ||||
|     return ctNone; | ||||
| } | ||||
| 
 | ||||
| } // DoubleSlider
 | ||||
| 
 | ||||
| } // Slic3r
 | ||||
|  |  | |||
|  | @ -39,6 +39,15 @@ enum IconFocus { | |||
|     ifCog | ||||
| }; | ||||
| 
 | ||||
| enum ConflictType | ||||
| { | ||||
|     ctNone, | ||||
|     ctModeConflict, | ||||
|     ctMeaninglessColorChange, | ||||
|     ctMeaninglessToolChange, | ||||
|     ctRedundant | ||||
| }; | ||||
| 
 | ||||
| using t_mode = CustomGCode::Mode; | ||||
| 
 | ||||
| struct TickCode | ||||
|  | @ -73,7 +82,13 @@ public: | |||
|     void switch_code(const std::string& code_from, const std::string& code_to); | ||||
|     bool switch_code_for_tick(std::set<TickCode>::iterator it, const std::string& code_to, const int extruder); | ||||
|     void erase_all_ticks_with_code(const std::string& gcode); | ||||
|     bool has_tick_with_code(const std::string& gcode); | ||||
| 
 | ||||
|     bool            has_tick_with_code(const std::string& gcode); | ||||
|     ConflictType    is_conflict_tick(const TickCode& tick, t_mode out_mode, int only_extruder, double print_z); | ||||
| 
 | ||||
|     // Get used extruders for tick.
 | ||||
|     // Means all extruders(tools) which will be used during printing from current tick to the end
 | ||||
|     std::set<int>   get_used_extruders_for_tick(int tick, int only_extruder, double print_z) const; | ||||
| 
 | ||||
|     void suppress_plus (bool suppress) { m_suppress_plus = suppress; } | ||||
|     void suppress_minus(bool suppress) { m_suppress_minus = suppress; } | ||||
|  | @ -230,7 +245,7 @@ protected: | |||
| private: | ||||
| 
 | ||||
|     bool    is_point_in_rect(const wxPoint& pt, const wxRect& rect); | ||||
|     int     is_point_near_tick(const wxPoint& pt); | ||||
|     int     get_tick_near_point(const wxPoint& pt); | ||||
| 
 | ||||
|     double      get_scroll_step(); | ||||
|     wxString    get_label(const SelectedSlider& selection) const; | ||||
|  | @ -251,10 +266,6 @@ private: | |||
|     // Use those values to disable selection of active extruders
 | ||||
|     std::array<int, 2> get_active_extruders_for_tick(int tick) const; | ||||
| 
 | ||||
|     // Get used extruders for tick. 
 | ||||
|     // Means all extruders(toools) will be used during printing from current tick to the end
 | ||||
|     std::set<int>   get_used_extruders_for_tick(int tick) const; | ||||
| 
 | ||||
|     void    post_ticks_changed_event(const std::string& gcode = ""); | ||||
|     bool    check_ticks_changed_event(const std::string& gcode); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1256,6 +1256,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); | |||
| wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent); | ||||
| 
 | ||||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
| const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; | ||||
|  | @ -1710,6 +1711,13 @@ void GLCanvas3D::render() | |||
|     } | ||||
| 
 | ||||
|     const Size& cnv_size = get_canvas_size(); | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene
 | ||||
|     // to preview, this was called before canvas had its final size. It reported zero width
 | ||||
|     // and the viewport was set incorrectly, leading to tripping glAsserts further down
 | ||||
|     // the road (in apply_projection). That's why the minimum size is forced to 10.
 | ||||
|     m_camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height())); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| 
 | ||||
|     if (m_camera.requires_zoom_to_bed) | ||||
|     { | ||||
|  | @ -2647,6 +2655,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) | |||
|                   post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); | ||||
|                   break; | ||||
|         case WXK_ESCAPE: { deselect_all(); break; } | ||||
|         case WXK_F5: { post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK)); break; } | ||||
|         case '0': { select_view("iso"); break; } | ||||
|         case '1': { select_view("top"); break; } | ||||
|         case '2': { select_view("bottom"); break; } | ||||
|  | @ -3839,8 +3848,13 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool | |||
| #if ENABLE_6DOF_CAMERA | ||||
|     camera.set_scene_box(scene_bounding_box()); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); | ||||
|     camera.zoom_to_volumes(visible_volumes); | ||||
| #else | ||||
|     camera.zoom_to_volumes(visible_volumes, thumbnail_data.width, thumbnail_data.height); | ||||
|     camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
|     camera.apply_view_matrix(); | ||||
| 
 | ||||
|     double near_z = -1.0; | ||||
|  | @ -4431,8 +4445,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) | |||
|     // ensures that this canvas is current
 | ||||
|     _set_current(); | ||||
| 
 | ||||
| #if !ENABLE_6DOF_CAMERA | ||||
|     // updates camera
 | ||||
|     m_camera.apply_viewport(0, 0, w, h); | ||||
| #endif // !ENABLE_6DOF_CAMERA
 | ||||
| } | ||||
| 
 | ||||
| BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const | ||||
|  | @ -4456,8 +4472,12 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be | |||
| #if ENABLE_THUMBNAIL_GENERATOR | ||||
| void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor) | ||||
| { | ||||
| #if ENABLE_6DOF_CAMERA | ||||
|     m_camera.zoom_to_box(box, margin_factor); | ||||
| #else | ||||
|     const Size& cnv_size = get_canvas_size(); | ||||
|     m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height(), margin_factor); | ||||
| #endif // ENABLE_6DOF_CAMERA
 | ||||
|     m_dirty = true; | ||||
| } | ||||
| #else | ||||
|  |  | |||
|  | @ -108,6 +108,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); | |||
| wxDECLARE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent); | ||||
| 
 | ||||
| class GLCanvas3D | ||||
| { | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| 
 | ||||
| #include <wx/glcanvas.h> | ||||
| #include <wx/timer.h> | ||||
| #include <wx/msgdlg.h> | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <string> | ||||
|  |  | |||
|  | @ -960,8 +960,11 @@ void GUI_App::load_current_presets() | |||
| 	this->plater()->set_printer_technology(printer_technology); | ||||
|     for (Tab *tab : tabs_list) | ||||
| 		if (tab->supports_printer_technology(printer_technology)) { | ||||
| 			if (tab->type() == Preset::TYPE_PRINTER) | ||||
| 			if (tab->type() == Preset::TYPE_PRINTER) { | ||||
| 				static_cast<TabPrinter*>(tab)->update_pages(); | ||||
| 				// Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change().
 | ||||
| 				this->plater()->force_print_bed_update(); | ||||
| 			} | ||||
| 			tab->load_current_preset(); | ||||
| 		} | ||||
| } | ||||
|  |  | |||
|  | @ -90,20 +90,20 @@ ObjectList::ObjectList(wxWindow* parent) : | |||
|         // see note in PresetBundle::load_compatible_bitmaps()
 | ||||
| 
 | ||||
|         // ptFFF
 | ||||
|         CATEGORY_ICON[L("Layers and Perimeters")]    = create_scaled_bitmap(nullptr, "layers"); | ||||
|         CATEGORY_ICON[L("Infill")]                   = create_scaled_bitmap(nullptr, "infill"); | ||||
|         CATEGORY_ICON[L("Support material")]         = create_scaled_bitmap(nullptr, "support"); | ||||
|         CATEGORY_ICON[L("Speed")]                    = create_scaled_bitmap(nullptr, "time"); | ||||
|         CATEGORY_ICON[L("Extruders")]                = create_scaled_bitmap(nullptr, "funnel"); | ||||
|         CATEGORY_ICON[L("Extrusion Width")]          = create_scaled_bitmap(nullptr, "funnel"); | ||||
|         CATEGORY_ICON[L("Wipe options")]             = create_scaled_bitmap(nullptr, "funnel"); | ||||
| //         CATEGORY_ICON[L("Skirt and brim")]          = create_scaled_bitmap(nullptr, "skirt+brim"); 
 | ||||
| //         CATEGORY_ICON[L("Speed > Acceleration")]    = create_scaled_bitmap(nullptr, "time");
 | ||||
|         CATEGORY_ICON[L("Advanced")]                 = create_scaled_bitmap(nullptr, "wrench"); | ||||
|         CATEGORY_ICON[L("Layers and Perimeters")]    = create_scaled_bitmap("layers"); | ||||
|         CATEGORY_ICON[L("Infill")]                   = create_scaled_bitmap("infill"); | ||||
|         CATEGORY_ICON[L("Support material")]         = create_scaled_bitmap("support"); | ||||
|         CATEGORY_ICON[L("Speed")]                    = create_scaled_bitmap("time"); | ||||
|         CATEGORY_ICON[L("Extruders")]                = create_scaled_bitmap("funnel"); | ||||
|         CATEGORY_ICON[L("Extrusion Width")]          = create_scaled_bitmap("funnel"); | ||||
|         CATEGORY_ICON[L("Wipe options")]             = create_scaled_bitmap("funnel"); | ||||
| //         CATEGORY_ICON[L("Skirt and brim")]          = create_scaled_bitmap("skirt+brim"); 
 | ||||
| //         CATEGORY_ICON[L("Speed > Acceleration")]    = create_scaled_bitmap("time");
 | ||||
|         CATEGORY_ICON[L("Advanced")]                 = create_scaled_bitmap("wrench"); | ||||
|         // ptSLA
 | ||||
|         CATEGORY_ICON[L("Supports")]                 = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); | ||||
|         CATEGORY_ICON[L("Pad")]                      = create_scaled_bitmap(nullptr, "pad"); | ||||
|         CATEGORY_ICON[L("Hollowing")]                = create_scaled_bitmap(nullptr, "hollowing"); | ||||
|         CATEGORY_ICON[L("Supports")]                 = create_scaled_bitmap("support"/*"sla_supports"*/); | ||||
|         CATEGORY_ICON[L("Pad")]                      = create_scaled_bitmap("pad"); | ||||
|         CATEGORY_ICON[L("Hollowing")]                = create_scaled_bitmap("hollowing"); | ||||
|     } | ||||
| 
 | ||||
|     // create control
 | ||||
|  | @ -230,9 +230,9 @@ ObjectList::ObjectList(wxWindow* parent) : | |||
| 	// So the postponed EnsureVisible() call is planned for an item, which may not exist at the Idle processing time, if this wxEVT_SIZE
 | ||||
| 	// event is succeeded by a delete of the currently active item. We are trying our luck by postponing the wxEVT_SIZE triggered EnsureVisible(),
 | ||||
| 	// which seems to be working as of now.
 | ||||
| 	this->CallAfter([this](){ this->EnsureVisible(this->GetCurrentItem()); }); | ||||
|     this->CallAfter([this](){ ensure_current_item_visible(); }); | ||||
| #else | ||||
| 	this->EnsureVisible(this->GetCurrentItem()); | ||||
|     ensure_current_item_visible(); | ||||
| #endif | ||||
| 	e.Skip(); | ||||
| 	})); | ||||
|  | @ -265,7 +265,7 @@ void ObjectList::create_objects_ctrl() | |||
| 
 | ||||
|     // column ItemName(Icon+Text) of the view control: 
 | ||||
|     // And Icon can be consisting of several bitmaps
 | ||||
|     AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(), | ||||
|     AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(this), | ||||
|         colName, 20*em, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); | ||||
| 
 | ||||
|     // column PrintableProperty (Icon) of the view control:
 | ||||
|  | @ -559,10 +559,10 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const | |||
| 
 | ||||
| void ObjectList::init_icons() | ||||
| { | ||||
|     m_bmp_solidmesh         = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)        ].second); | ||||
|     m_bmp_modifiermesh      = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::PARAMETER_MODIFIER)].second); | ||||
|     m_bmp_support_enforcer  = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)  ].second); | ||||
|     m_bmp_support_blocker   = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)   ].second);  | ||||
|     m_bmp_solidmesh         = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)        ].second); | ||||
|     m_bmp_modifiermesh      = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::PARAMETER_MODIFIER)].second); | ||||
|     m_bmp_support_enforcer  = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)  ].second); | ||||
|     m_bmp_support_blocker   = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)   ].second);  | ||||
| 
 | ||||
|     m_bmp_vector.reserve(4); // bitmaps for different types of parts 
 | ||||
|     m_bmp_vector.push_back(&m_bmp_solidmesh.bmp());          | ||||
|  | @ -575,12 +575,12 @@ void ObjectList::init_icons() | |||
|     m_objects_model->SetVolumeBitmaps(m_bmp_vector); | ||||
| 
 | ||||
|     // init icon for manifold warning
 | ||||
|     m_bmp_manifold_warning  = ScalableBitmap(nullptr, "exclamation"); | ||||
|     m_bmp_manifold_warning  = ScalableBitmap(this, "exclamation"); | ||||
|     // Set warning bitmap for the model
 | ||||
|     m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); | ||||
| 
 | ||||
|     // init bitmap for "Add Settings" context menu
 | ||||
|     m_bmp_cog               = ScalableBitmap(nullptr, "cog"); | ||||
|     m_bmp_cog               = ScalableBitmap(this, "cog"); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::msw_rescale_icons() | ||||
|  | @ -607,23 +607,20 @@ void ObjectList::msw_rescale_icons() | |||
| 
 | ||||
|     // Update CATEGORY_ICON according to new scale
 | ||||
|     { | ||||
|         // Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget,
 | ||||
|         // see note in PresetBundle::load_compatible_bitmaps()
 | ||||
| 
 | ||||
|         // ptFFF
 | ||||
|         CATEGORY_ICON[L("Layers and Perimeters")]    = create_scaled_bitmap(nullptr, "layers"); | ||||
|         CATEGORY_ICON[L("Infill")]                   = create_scaled_bitmap(nullptr, "infill"); | ||||
|         CATEGORY_ICON[L("Support material")]         = create_scaled_bitmap(nullptr, "support"); | ||||
|         CATEGORY_ICON[L("Speed")]                    = create_scaled_bitmap(nullptr, "time"); | ||||
|         CATEGORY_ICON[L("Extruders")]                = create_scaled_bitmap(nullptr, "funnel"); | ||||
|         CATEGORY_ICON[L("Extrusion Width")]          = create_scaled_bitmap(nullptr, "funnel"); | ||||
|         CATEGORY_ICON[L("Wipe options")]             = create_scaled_bitmap(nullptr, "funnel"); | ||||
| //         CATEGORY_ICON[L("Skirt and brim")]          = create_scaled_bitmap(nullptr, "skirt+brim"); 
 | ||||
| //         CATEGORY_ICON[L("Speed > Acceleration")]    = create_scaled_bitmap(nullptr, "time");
 | ||||
|         CATEGORY_ICON[L("Advanced")]                 = create_scaled_bitmap(nullptr, "wrench"); | ||||
|         CATEGORY_ICON[L("Layers and Perimeters")]    = create_scaled_bitmap("layers"); | ||||
|         CATEGORY_ICON[L("Infill")]                   = create_scaled_bitmap("infill"); | ||||
|         CATEGORY_ICON[L("Support material")]         = create_scaled_bitmap("support"); | ||||
|         CATEGORY_ICON[L("Speed")]                    = create_scaled_bitmap("time"); | ||||
|         CATEGORY_ICON[L("Extruders")]                = create_scaled_bitmap("funnel"); | ||||
|         CATEGORY_ICON[L("Extrusion Width")]          = create_scaled_bitmap("funnel"); | ||||
|         CATEGORY_ICON[L("Wipe options")]             = create_scaled_bitmap("funnel"); | ||||
| //         CATEGORY_ICON[L("Skirt and brim")]          = create_scaled_bitmap("skirt+brim"); 
 | ||||
| //         CATEGORY_ICON[L("Speed > Acceleration")]    = create_scaled_bitmap("time");
 | ||||
|         CATEGORY_ICON[L("Advanced")]                 = create_scaled_bitmap("wrench"); | ||||
|         // ptSLA
 | ||||
|         CATEGORY_ICON[L("Supports")]                 = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); | ||||
|         CATEGORY_ICON[L("Pad")]                      = create_scaled_bitmap(nullptr, "pad"); | ||||
|         CATEGORY_ICON[L("Supports")]                 = create_scaled_bitmap("support"/*"sla_supports"*/); | ||||
|         CATEGORY_ICON[L("Pad")]                      = create_scaled_bitmap("pad"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -1003,14 +1000,13 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) | |||
|     const bool mult_sel = multiple_selection(); | ||||
| 
 | ||||
|     if ((mult_sel && !selected_instances_of_same_object()) || | ||||
|         (!mult_sel && (GetSelection() != item)) || | ||||
|         m_objects_model->GetParent(item) == wxDataViewItem(nullptr) ) { | ||||
|         (!mult_sel && (GetSelection() != item)) ) { | ||||
|         event.Veto(); | ||||
|         return; | ||||
|     } | ||||
|     | ||||
|     const ItemType& type = m_objects_model->GetItemType(item); | ||||
|     if (!(type & (itVolume | itInstance))) { | ||||
|     if (!(type & (itVolume | itObject | itInstance))) { | ||||
|         event.Veto(); | ||||
|         return; | ||||
|     } | ||||
|  | @ -1024,11 +1020,13 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) | |||
|         for (auto sel : sels ) | ||||
|             sub_obj_idxs.insert(m_objects_model->GetInstanceIdByItem(sel)); | ||||
|     } | ||||
|     else  | ||||
|     else if (type & itObject) | ||||
|         m_dragged_data.init(m_objects_model->GetIdByItem(item), type); | ||||
|     else | ||||
|         m_dragged_data.init(m_objects_model->GetObjectIdByItem(item),  | ||||
|                         type&itVolume ? m_objects_model->GetVolumeIdByItem(item) : | ||||
|                             type&itVolume ? m_objects_model->GetVolumeIdByItem(item) : | ||||
|                                         m_objects_model->GetInstanceIdByItem(item),  | ||||
|                         type); | ||||
|                             type); | ||||
| 
 | ||||
|     /* Under MSW or OSX, DnD moves an item to the place of another selected item
 | ||||
|     * But under GTK, DnD moves an item between another two items. | ||||
|  | @ -1049,10 +1047,20 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) | |||
| 
 | ||||
| bool ObjectList::can_drop(const wxDataViewItem& item) const  | ||||
| { | ||||
|     return (m_dragged_data.type() == itInstance && !item.IsOk())     || | ||||
|            (m_dragged_data.type() == itVolume && item.IsOk() && | ||||
|             m_objects_model->GetItemType(item) == itVolume && | ||||
|             m_dragged_data.obj_idx() == m_objects_model->GetObjectIdByItem(item)); | ||||
|     // move instance(s) or object on "empty place" of ObjectList
 | ||||
|     if ( (m_dragged_data.type() & (itInstance | itObject)) && !item.IsOk() ) | ||||
|         return true; | ||||
| 
 | ||||
|     // type of moved item should be the same as a "destination" item
 | ||||
|     if (!item.IsOk() || !(m_dragged_data.type() & (itVolume|itObject)) ||  | ||||
|         m_objects_model->GetItemType(item) != m_dragged_data.type() ) | ||||
|         return false; | ||||
| 
 | ||||
|     // move volumes inside one object only
 | ||||
|     if (m_dragged_data.type() & itVolume) | ||||
|         return m_dragged_data.obj_idx() == m_objects_model->GetObjectIdByItem(item); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void ObjectList::OnDropPossible(wxDataViewEvent &event) | ||||
|  | @ -1082,9 +1090,6 @@ void ObjectList::OnDrop(wxDataViewEvent &event) | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const int from_volume_id = m_dragged_data.sub_obj_idx(); | ||||
|     int to_volume_id = m_objects_model->GetVolumeIdByItem(item); | ||||
| 
 | ||||
| // It looks like a fixed in current version of the wxWidgets
 | ||||
| // #ifdef __WXGTK__
 | ||||
| //     /* Under GTK, DnD moves an item between another two items.
 | ||||
|  | @ -1096,14 +1101,33 @@ void ObjectList::OnDrop(wxDataViewEvent &event) | |||
| 
 | ||||
|     take_snapshot(_((m_dragged_data.type() == itVolume) ? L("Volumes in Object reordered") : L("Object reordered"))); | ||||
| 
 | ||||
|     auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; | ||||
|     auto delta = to_volume_id < from_volume_id ? -1 : 1; | ||||
|     int cnt = 0; | ||||
|     for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) | ||||
|         std::swap(volumes[id], volumes[id + delta]); | ||||
|     if (m_dragged_data.type() & itVolume) | ||||
|     { | ||||
|         int from_volume_id = m_dragged_data.sub_obj_idx(); | ||||
|         int to_volume_id   = m_objects_model->GetVolumeIdByItem(item); | ||||
|         int delta = to_volume_id < from_volume_id ? -1 : 1; | ||||
| 
 | ||||
|     select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, | ||||
|                                                     m_objects_model->GetParent(item))); | ||||
|         auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; | ||||
| 
 | ||||
|         int cnt = 0; | ||||
|         for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) | ||||
|             std::swap(volumes[id], volumes[id + delta]); | ||||
| 
 | ||||
|         select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, m_objects_model->GetParent(item))); | ||||
| 
 | ||||
|     } | ||||
|     else if (m_dragged_data.type() & itObject) | ||||
|     { | ||||
|         int from_obj_id = m_dragged_data.obj_idx(); | ||||
|         int to_obj_id   = item.IsOk() ? m_objects_model->GetIdByItem(item) : ((int)m_objects->size()-1); | ||||
|         int delta = to_obj_id < from_obj_id ? -1 : 1; | ||||
| 
 | ||||
|         int cnt = 0; | ||||
|         for (int id = from_obj_id; cnt < abs(from_obj_id - to_obj_id); id += delta, cnt++) | ||||
|             std::swap((*m_objects)[id], (*m_objects)[id + delta]); | ||||
| 
 | ||||
|         select_item(m_objects_model->ReorganizeObjects(from_obj_id, to_obj_id)); | ||||
|     } | ||||
| 
 | ||||
|     changed_object(m_dragged_data.obj_idx()); | ||||
| 
 | ||||
|  | @ -1741,7 +1765,8 @@ void ObjectList::create_instance_popupmenu(wxMenu*menu) | |||
| void ObjectList::create_default_popupmenu(wxMenu*menu) | ||||
| { | ||||
|     wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID); | ||||
|     append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part"); | ||||
|     append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part",  | ||||
|         [](){return true;}, this); | ||||
| } | ||||
| 
 | ||||
| wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) | ||||
|  | @ -3162,7 +3187,7 @@ void ObjectList::update_selections() | |||
|     select_items(sels); | ||||
| 
 | ||||
|     // Scroll selected Item in the middle of an object list
 | ||||
|     this->EnsureVisible(this->GetCurrentItem()); | ||||
|     ensure_current_item_visible(); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::update_selections_on_canvas() | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| 
 | ||||
| #include <map> | ||||
| #include <vector> | ||||
| #include <set> | ||||
| 
 | ||||
| #include <wx/bitmap.h> | ||||
| #include <wx/dataview.h> | ||||
|  | @ -10,6 +11,7 @@ | |||
| 
 | ||||
| #include "Event.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| #include "ObjectDataViewModel.hpp" | ||||
| 
 | ||||
| class wxBoxSizer; | ||||
| class wxBitmapComboBox; | ||||
|  | @ -171,6 +173,12 @@ private: | |||
|     SettingsBundle m_freq_settings_sla; | ||||
| #endif | ||||
| 
 | ||||
|     inline void ensure_current_item_visible() | ||||
|     { | ||||
|         if (const auto &item = this->GetCurrentItem()) | ||||
|             this->EnsureVisible(item); | ||||
|     } | ||||
| 
 | ||||
| public: | ||||
|     ObjectList(wxWindow* parent); | ||||
|     ~ObjectList(); | ||||
|  |  | |||
|  | @ -9,6 +9,8 @@ | |||
| #include <wx/stattext.h> | ||||
| #include <wx/sizer.h> | ||||
| 
 | ||||
| #include <algorithm> | ||||
| 
 | ||||
| #include "slic3r/GUI/GUI_App.hpp" | ||||
| 
 | ||||
| 
 | ||||
|  | @ -189,7 +191,7 @@ void GLGizmoCut::update_max_z(const Selection& selection) const | |||
| void GLGizmoCut::set_cut_z(double cut_z) const | ||||
| { | ||||
|     // Clamp the plane to the object's bounding box
 | ||||
|     m_cut_z = std::max(0.0, std::min(m_max_z, cut_z)); | ||||
|     m_cut_z = std::clamp(cut_z, 0.0, m_max_z); | ||||
| } | ||||
| 
 | ||||
| void GLGizmoCut::perform_cut(const Selection& selection) | ||||
|  |  | |||
|  | @ -25,6 +25,9 @@ class GLGizmoCut : public GLGizmoBase | |||
| public: | ||||
|     GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
| 
 | ||||
|     double get_cut_z() const { return m_cut_z; } | ||||
|     void set_cut_z(double cut_z) const; | ||||
| 
 | ||||
| protected: | ||||
|     virtual bool on_init(); | ||||
|     virtual void on_load(cereal::BinaryInputArchive& ar) { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } | ||||
|  | @ -40,7 +43,6 @@ protected: | |||
| 
 | ||||
| private: | ||||
|     void update_max_z(const Selection& selection) const; | ||||
|     void set_cut_z(double cut_z) const; | ||||
|     void perform_cut(const Selection& selection); | ||||
|     double calc_projection(const Linef3& mouse_ray) const; | ||||
| }; | ||||
|  |  | |||
|  | @ -500,7 +500,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) | |||
|             processed = true; | ||||
|         } | ||||
|         else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow)) | ||||
|                         // don't allow dragging objects with the Sla gizmo on
 | ||||
|             // don't allow dragging objects with the Sla gizmo on
 | ||||
|             processed = true; | ||||
|         else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) | ||||
|         { | ||||
|  | @ -557,12 +557,9 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) | |||
|         else if (evt.LeftUp() && is_dragging()) | ||||
|         { | ||||
|             switch (m_current) { | ||||
|             case Move : m_parent.do_move(L("Gizmo-Move")); | ||||
|                         break; | ||||
|             case Scale : m_parent.do_scale(L("Gizmo-Scale")); | ||||
|                          break; | ||||
|             case Rotate : m_parent.do_rotate(L("Gizmo-Rotate")); | ||||
|                           break; | ||||
|             case Move : m_parent.do_move(L("Gizmo-Move")); break; | ||||
|             case Scale : m_parent.do_scale(L("Gizmo-Scale")); break; | ||||
|             case Rotate : m_parent.do_rotate(L("Gizmo-Rotate")); break; | ||||
|             default : break; | ||||
|             } | ||||
| 
 | ||||
|  | @ -779,6 +776,64 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) | |||
|                     processed = true; | ||||
|             } | ||||
|         } | ||||
|         else if (m_current == Move) | ||||
|         { | ||||
|             auto do_move = [this, &processed](const Vec3d& displacement) { | ||||
|                 Selection& selection = m_parent.get_selection(); | ||||
|                 selection.start_dragging(); | ||||
|                 selection.translate(displacement); | ||||
|                 wxGetApp().obj_manipul()->set_dirty(); | ||||
|                 m_parent.do_move(L("Gizmo-Move")); | ||||
|                 m_parent.set_as_dirty(); | ||||
|                 processed = true; | ||||
|             }; | ||||
| 
 | ||||
|             switch (keyCode) | ||||
|             { | ||||
|             case WXK_NUMPAD_LEFT:  case WXK_LEFT:  { do_move(-Vec3d::UnitX()); break; } | ||||
|             case WXK_NUMPAD_RIGHT: case WXK_RIGHT: { do_move(Vec3d::UnitX()); break; } | ||||
|             case WXK_NUMPAD_UP:    case WXK_UP:    { do_move(Vec3d::UnitY()); break; } | ||||
|             case WXK_NUMPAD_DOWN:  case WXK_DOWN:  { do_move(-Vec3d::UnitY()); break; } | ||||
|             default: { break; } | ||||
|             } | ||||
|         } | ||||
|         else if (m_current == Rotate) | ||||
|         { | ||||
|             auto do_rotate = [this, &processed](const Vec3d& rotation) { | ||||
|                 Selection& selection = m_parent.get_selection(); | ||||
|                 selection.start_dragging(); | ||||
|                 selection.rotate(rotation, TransformationType(TransformationType::World_Relative_Joint)); | ||||
|                 wxGetApp().obj_manipul()->set_dirty(); | ||||
|                 m_parent.do_rotate(L("Gizmo-Rotate")); | ||||
|                 m_parent.set_as_dirty(); | ||||
|                 processed = true; | ||||
|             }; | ||||
| 
 | ||||
|             switch (keyCode) | ||||
|             { | ||||
|             case WXK_NUMPAD_LEFT:  case WXK_LEFT:  { do_rotate(Vec3d(0.0, 0.0, 0.5 * M_PI)); break; } | ||||
|             case WXK_NUMPAD_RIGHT: case WXK_RIGHT: { do_rotate(-Vec3d(0.0, 0.0, 0.5 * M_PI)); break; } | ||||
|             case WXK_NUMPAD_UP:    case WXK_UP:    { do_rotate(Vec3d(0.0, 0.0, 0.25 * M_PI)); break; } | ||||
|             case WXK_NUMPAD_DOWN:  case WXK_DOWN:  { do_rotate(-Vec3d(0.0, 0.0, 0.25 * M_PI)); break; } | ||||
|             default: { break; } | ||||
|             } | ||||
|         } | ||||
|         else if (m_current == Cut) | ||||
|         { | ||||
|             auto do_move = [this, &processed](double delta_z) { | ||||
|                 GLGizmoCut* cut = dynamic_cast<GLGizmoCut*>(get_current()); | ||||
|                 cut->set_cut_z(delta_z + cut->get_cut_z()); | ||||
|                 m_parent.set_as_dirty(); | ||||
|                 processed = true; | ||||
|             }; | ||||
| 
 | ||||
|             switch (keyCode) | ||||
|             { | ||||
|             case WXK_NUMPAD_UP:   case WXK_UP:   { do_move(1.0); break; } | ||||
|             case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; } | ||||
|             default: { break; } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| //        if (processed)
 | ||||
| //            m_parent.set_cursor(GLCanvas3D::Standard);
 | ||||
|  |  | |||
|  | @ -68,7 +68,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S | |||
|     /* Load default preset bitmaps before a tabpanel initialization,
 | ||||
|      * but after filling of an em_unit value  | ||||
|      */ | ||||
|     wxGetApp().preset_bundle->load_default_preset_bitmaps(this); | ||||
|     wxGetApp().preset_bundle->load_default_preset_bitmaps(); | ||||
| 
 | ||||
|     // initialize tabpanel and menubar
 | ||||
|     init_tabpanel(); | ||||
|  | @ -345,7 +345,7 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) | |||
|     /* Load default preset bitmaps before a tabpanel initialization,
 | ||||
|      * but after filling of an em_unit value | ||||
|      */ | ||||
|     wxGetApp().preset_bundle->load_default_preset_bitmaps(this); | ||||
|     wxGetApp().preset_bundle->load_default_preset_bitmaps(); | ||||
| 
 | ||||
|     // update Plater
 | ||||
|     wxGetApp().plater()->msw_rescale(); | ||||
|  | @ -578,6 +578,11 @@ void MainFrame::init_menubar() | |||
|         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(); }, | ||||
|             "paste_menu", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this); | ||||
|          | ||||
|         editMenu->AppendSeparator(); | ||||
|         append_menu_item(editMenu, wxID_ANY, _(L("Re&load from disk")) + sep + "F5", | ||||
|             _(L("Reload the plater from disk")), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); }, | ||||
|             "", nullptr, [this]() {return !m_plater->model().objects.empty(); }, this); | ||||
|     } | ||||
| 
 | ||||
|     // Window menu
 | ||||
|  | @ -728,7 +733,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, is_fff ? "spool": "resin")); | ||||
|     m_changeable_menu_items[miMaterialTab]  ->SetBitmap(create_scaled_bitmap(is_fff ? "spool": "resin")); | ||||
| } | ||||
| 
 | ||||
| // To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
 | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he | |||
| 	rightsizer->Add(btn_sizer, 0, wxALIGN_RIGHT); | ||||
| 
 | ||||
| 	if (! bitmap.IsOk()) { | ||||
| 		bitmap = create_scaled_bitmap(this, "PrusaSlicer_192px.png", 192); | ||||
| 		bitmap = create_scaled_bitmap("PrusaSlicer_192px.png", this, 192); | ||||
| 	} | ||||
| 
 | ||||
| 	logo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap); | ||||
|  | @ -99,7 +99,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) | |||
| 	btn_ok->SetFocus(); | ||||
| 	btn_sizer->Add(btn_ok, 0, wxRIGHT, HORIZ_SPACING); | ||||
| 
 | ||||
| 	logo->SetBitmap(create_scaled_bitmap(this, "PrusaSlicer_192px_grayscale.png", 192)); | ||||
| 	logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 192)); | ||||
| 
 | ||||
|     SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit())); | ||||
| 	Fit(); | ||||
|  |  | |||
							
								
								
									
										1764
									
								
								src/slic3r/GUI/ObjectDataViewModel.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1764
									
								
								src/slic3r/GUI/ObjectDataViewModel.cpp
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										516
									
								
								src/slic3r/GUI/ObjectDataViewModel.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										516
									
								
								src/slic3r/GUI/ObjectDataViewModel.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,516 @@ | |||
| #ifndef slic3r_GUI_ObjectDataViewModel_hpp_ | ||||
| #define slic3r_GUI_ObjectDataViewModel_hpp_ | ||||
| 
 | ||||
| #include <wx/dataview.h> | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| enum class ModelVolumeType : int; | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| typedef double                          coordf_t; | ||||
| typedef std::pair<coordf_t, coordf_t>   t_layer_height_range; | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // DataViewBitmapText: helper class used by BitmapTextRenderer
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| class DataViewBitmapText : public wxObject | ||||
| { | ||||
| public: | ||||
|     DataViewBitmapText( const wxString &text = wxEmptyString, | ||||
|                         const wxBitmap& bmp = wxNullBitmap) : | ||||
|         m_text(text), | ||||
|         m_bmp(bmp) | ||||
|     { } | ||||
| 
 | ||||
|     DataViewBitmapText(const DataViewBitmapText &other) | ||||
|         : wxObject(), | ||||
|         m_text(other.m_text), | ||||
|         m_bmp(other.m_bmp) | ||||
|     { } | ||||
| 
 | ||||
|     void SetText(const wxString &text)      { m_text = text; } | ||||
|     wxString GetText() const                { return m_text; } | ||||
|     void SetBitmap(const wxBitmap &bmp)     { m_bmp = bmp; } | ||||
|     const wxBitmap &GetBitmap() const       { return m_bmp; } | ||||
| 
 | ||||
|     bool IsSameAs(const DataViewBitmapText& other) const { | ||||
|         return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp); | ||||
|     } | ||||
| 
 | ||||
|     bool operator==(const DataViewBitmapText& other) const { | ||||
|         return IsSameAs(other); | ||||
|     } | ||||
| 
 | ||||
|     bool operator!=(const DataViewBitmapText& other) const { | ||||
|         return !IsSameAs(other); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     wxString    m_text; | ||||
|     wxBitmap    m_bmp; | ||||
| 
 | ||||
|     wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText); | ||||
| }; | ||||
| DECLARE_VARIANT_OBJECT(DataViewBitmapText) | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // BitmapTextRenderer
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING | ||||
| class BitmapTextRenderer : public wxDataViewRenderer | ||||
| #else | ||||
| class BitmapTextRenderer : public wxDataViewCustomRenderer | ||||
| #endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
 | ||||
| { | ||||
| public: | ||||
|     BitmapTextRenderer(wxWindow* parent, | ||||
|         wxDataViewCellMode mode = | ||||
| #ifdef __WXOSX__ | ||||
|         wxDATAVIEW_CELL_INERT | ||||
| #else | ||||
|         wxDATAVIEW_CELL_EDITABLE | ||||
| #endif | ||||
| 
 | ||||
|         , int align = wxDVR_DEFAULT_ALIGNMENT | ||||
| #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING | ||||
|     ); | ||||
| #else | ||||
|         ) : | ||||
|     wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align), | ||||
|         m_parent(parent) | ||||
|     {} | ||||
| #endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
 | ||||
| 
 | ||||
|     bool SetValue(const wxVariant& value); | ||||
|     bool GetValue(wxVariant& value) const; | ||||
| #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY | ||||
|     virtual wxString GetAccessibleDescription() const override; | ||||
| #endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
 | ||||
| 
 | ||||
|     virtual bool Render(wxRect cell, wxDC* dc, int state) override; | ||||
|     virtual wxSize GetSize() const override; | ||||
| 
 | ||||
|     bool        HasEditorCtrl() const override | ||||
|     { | ||||
| #ifdef __WXOSX__ | ||||
|         return false; | ||||
| #else | ||||
|         return true; | ||||
| #endif | ||||
|     } | ||||
|     wxWindow* CreateEditorCtrl(wxWindow* parent, | ||||
|         wxRect labelRect, | ||||
|         const wxVariant& value) override; | ||||
|     bool        GetValueFromEditorCtrl(wxWindow* ctrl, | ||||
|         wxVariant& value) override; | ||||
|     bool        WasCanceled() const { return m_was_unusable_symbol; } | ||||
| 
 | ||||
| private: | ||||
|     DataViewBitmapText  m_value; | ||||
|     bool                m_was_unusable_symbol{ false }; | ||||
|     wxWindow* m_parent{ nullptr }; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // BitmapChoiceRenderer
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| class BitmapChoiceRenderer : public wxDataViewCustomRenderer | ||||
| { | ||||
| public: | ||||
|     BitmapChoiceRenderer(wxDataViewCellMode mode = | ||||
| #ifdef __WXOSX__ | ||||
|         wxDATAVIEW_CELL_INERT | ||||
| #else | ||||
|         wxDATAVIEW_CELL_EDITABLE | ||||
| #endif | ||||
|         , int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | ||||
|     ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} | ||||
| 
 | ||||
|     bool SetValue(const wxVariant& value); | ||||
|     bool GetValue(wxVariant& value) const; | ||||
| 
 | ||||
|     virtual bool Render(wxRect cell, wxDC* dc, int state) override; | ||||
|     virtual wxSize GetSize() const override; | ||||
| 
 | ||||
|     bool        HasEditorCtrl() const override { return true; } | ||||
|     wxWindow* CreateEditorCtrl(wxWindow* parent, | ||||
|         wxRect labelRect, | ||||
|         const wxVariant& value) override; | ||||
|     bool        GetValueFromEditorCtrl(wxWindow* ctrl, | ||||
|         wxVariant& value) override; | ||||
| 
 | ||||
| private: | ||||
|     DataViewBitmapText  m_value; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // ObjectDataViewModelNode: a node inside ObjectDataViewModel
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| enum ItemType { | ||||
|     itUndef         = 0, | ||||
|     itObject        = 1, | ||||
|     itVolume        = 2, | ||||
|     itInstanceRoot  = 4, | ||||
|     itInstance      = 8, | ||||
|     itSettings      = 16, | ||||
|     itLayerRoot     = 32, | ||||
|     itLayer         = 64, | ||||
| }; | ||||
| 
 | ||||
| enum ColumnNumber | ||||
| { | ||||
|     colName         = 0,    // item name
 | ||||
|     colPrint           ,    // printable property
 | ||||
|     colExtruder        ,    // extruder selection
 | ||||
|     colEditing         ,    // item editing
 | ||||
| }; | ||||
| 
 | ||||
| enum PrintIndicator | ||||
| { | ||||
|     piUndef         = 0,    // no print indicator
 | ||||
|     piPrintable        ,    // printable
 | ||||
|     piUnprintable      ,    // unprintable
 | ||||
| }; | ||||
| 
 | ||||
| class ObjectDataViewModelNode; | ||||
| WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); | ||||
| 
 | ||||
| class ObjectDataViewModelNode | ||||
| { | ||||
|     ObjectDataViewModelNode*	    m_parent; | ||||
|     MyObjectTreeModelNodePtrArray   m_children; | ||||
|     wxBitmap                        m_empty_bmp; | ||||
|     size_t                          m_volumes_cnt = 0; | ||||
|     std::vector< std::string >      m_opt_categories; | ||||
|     t_layer_height_range            m_layer_range = { 0.0f, 0.0f }; | ||||
| 
 | ||||
|     wxString				        m_name; | ||||
|     wxBitmap&                       m_bmp = m_empty_bmp; | ||||
|     ItemType				        m_type; | ||||
|     int                             m_idx = -1; | ||||
|     bool					        m_container = false; | ||||
|     wxString				        m_extruder = "default"; | ||||
|     wxBitmap                        m_extruder_bmp; | ||||
|     wxBitmap				        m_action_icon; | ||||
|     PrintIndicator                  m_printable {piUndef}; | ||||
|     wxBitmap				        m_printable_icon; | ||||
| 
 | ||||
|     std::string                     m_action_icon_name = ""; | ||||
|     ModelVolumeType                 m_volume_type; | ||||
| 
 | ||||
| public: | ||||
|     ObjectDataViewModelNode(const wxString& name, | ||||
|                             const wxString& extruder): | ||||
|         m_parent(NULL), | ||||
|         m_name(name), | ||||
|         m_type(itObject), | ||||
|         m_extruder(extruder) | ||||
|     { | ||||
|         set_action_and_extruder_icons(); | ||||
|         init_container(); | ||||
| 	} | ||||
| 
 | ||||
|     ObjectDataViewModelNode(ObjectDataViewModelNode* parent, | ||||
|                             const wxString& sub_obj_name, | ||||
|                             const wxBitmap& bmp, | ||||
|                             const wxString& extruder, | ||||
|                             const int idx = -1 ) : | ||||
|         m_parent	(parent), | ||||
|         m_name		(sub_obj_name), | ||||
|         m_type		(itVolume), | ||||
|         m_idx       (idx), | ||||
|         m_extruder  (extruder) | ||||
|     { | ||||
|         m_bmp = bmp; | ||||
|         set_action_and_extruder_icons(); | ||||
|         init_container(); | ||||
|     } | ||||
| 
 | ||||
|     ObjectDataViewModelNode(ObjectDataViewModelNode* parent, | ||||
|                             const t_layer_height_range& layer_range, | ||||
|                             const int idx = -1, | ||||
|                             const wxString& extruder = wxEmptyString ); | ||||
| 
 | ||||
|     ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); | ||||
| 
 | ||||
|     ~ObjectDataViewModelNode() | ||||
|     { | ||||
|         // free all our children nodes
 | ||||
|         size_t count = m_children.GetCount(); | ||||
|         for (size_t i = 0; i < count; i++) | ||||
|         { | ||||
|             ObjectDataViewModelNode *child = m_children[i]; | ||||
|             delete child; | ||||
|         } | ||||
| #ifndef NDEBUG | ||||
|         // Indicate that the object was deleted.
 | ||||
|         m_idx = -2; | ||||
| #endif /* NDEBUG */ | ||||
|     } | ||||
| 
 | ||||
| 	void init_container(); | ||||
| 	bool IsContainer() const | ||||
| 	{ | ||||
| 		return m_container; | ||||
| 	} | ||||
| 
 | ||||
|     ObjectDataViewModelNode* GetParent() | ||||
|     { | ||||
|         assert(m_parent == nullptr || m_parent->valid()); | ||||
|         return m_parent; | ||||
|     } | ||||
|     MyObjectTreeModelNodePtrArray& GetChildren() | ||||
|     { | ||||
|         return m_children; | ||||
|     } | ||||
|     ObjectDataViewModelNode* GetNthChild(unsigned int n) | ||||
|     { | ||||
|         return m_children.Item(n); | ||||
|     } | ||||
|     void Insert(ObjectDataViewModelNode* child, unsigned int n) | ||||
|     { | ||||
|         if (!m_container) | ||||
|             m_container = true; | ||||
|         m_children.Insert(child, n); | ||||
|     } | ||||
|     void Append(ObjectDataViewModelNode* child) | ||||
|     { | ||||
|         if (!m_container) | ||||
|             m_container = true; | ||||
|         m_children.Add(child); | ||||
|     } | ||||
|     void RemoveAllChildren() | ||||
|     { | ||||
|         if (GetChildCount() == 0) | ||||
|             return; | ||||
|         for (int id = int(GetChildCount()) - 1; id >= 0; --id) | ||||
|         { | ||||
|             if (m_children.Item(id)->GetChildCount() > 0) | ||||
|                 m_children[id]->RemoveAllChildren(); | ||||
|             auto node = m_children[id]; | ||||
|             m_children.RemoveAt(id); | ||||
|             delete node; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     size_t GetChildCount() const | ||||
|     { | ||||
|         return m_children.GetCount(); | ||||
|     } | ||||
| 
 | ||||
|     bool            SetValue(const wxVariant &variant, unsigned int col); | ||||
| 
 | ||||
|     void            SetBitmap(const wxBitmap &icon) { m_bmp = icon; } | ||||
|     const wxBitmap& GetBitmap() const               { return m_bmp; } | ||||
|     const wxString& GetName() const                 { return m_name; } | ||||
|     ItemType        GetType() const                 { return m_type; } | ||||
| 	void			SetIdx(const int& idx); | ||||
| 	int             GetIdx() const                  { return m_idx; } | ||||
| 	t_layer_height_range    GetLayerRange() const   { return m_layer_range; } | ||||
|     PrintIndicator  IsPrintable() const             { return m_printable; } | ||||
| 
 | ||||
|     // use this function only for childrens
 | ||||
|     void AssignAllVal(ObjectDataViewModelNode& from_node) | ||||
|     { | ||||
|         // ! Don't overwrite other values because of equality of this values for all children --
 | ||||
|         m_name = from_node.m_name; | ||||
|         m_bmp = from_node.m_bmp; | ||||
|         m_idx = from_node.m_idx; | ||||
|         m_extruder = from_node.m_extruder; | ||||
|         m_type = from_node.m_type; | ||||
|     } | ||||
| 
 | ||||
|     bool SwapChildrens(int frst_id, int scnd_id) { | ||||
|         if (GetChildCount() < 2 || | ||||
|             frst_id < 0 || (size_t)frst_id >= GetChildCount() || | ||||
|             scnd_id < 0 || (size_t)scnd_id >= GetChildCount()) | ||||
|             return false; | ||||
| 
 | ||||
|         ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); | ||||
|         ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); | ||||
| 
 | ||||
|         new_scnd.m_idx = m_children.Item(scnd_id)->m_idx; | ||||
|         new_frst.m_idx = m_children.Item(frst_id)->m_idx; | ||||
| 
 | ||||
|         m_children.Item(frst_id)->AssignAllVal(new_frst); | ||||
|         m_children.Item(scnd_id)->AssignAllVal(new_scnd); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // Set action icons for node
 | ||||
|     void        set_action_and_extruder_icons(); | ||||
| 	// Set printable icon for node
 | ||||
|     void        set_printable_icon(PrintIndicator printable); | ||||
| 
 | ||||
|     void        update_settings_digest_bitmaps(); | ||||
|     bool        update_settings_digest(const std::vector<std::string>& categories); | ||||
|     int         volume_type() const { return int(m_volume_type); } | ||||
|     void        msw_rescale(); | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
|     bool 		valid(); | ||||
| #endif /* NDEBUG */ | ||||
|     bool        invalid() const { return m_idx < -1; } | ||||
| 
 | ||||
| private: | ||||
|     friend class ObjectDataViewModel; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // ObjectDataViewModel
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| // custom message the model sends to associated control to notify a last volume deleted from the object:
 | ||||
| wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); | ||||
| 
 | ||||
| class ObjectDataViewModel :public wxDataViewModel | ||||
| { | ||||
|     std::vector<ObjectDataViewModelNode*>       m_objects; | ||||
|     std::vector<wxBitmap*>                      m_volume_bmps; | ||||
|     wxBitmap*                                   m_warning_bmp { nullptr }; | ||||
| 
 | ||||
|     wxDataViewCtrl*                             m_ctrl { nullptr }; | ||||
| 
 | ||||
| public: | ||||
|     ObjectDataViewModel(); | ||||
|     ~ObjectDataViewModel(); | ||||
| 
 | ||||
|     wxDataViewItem Add( const wxString &name, | ||||
|                         const int extruder, | ||||
|                         const bool has_errors = false); | ||||
|     wxDataViewItem AddVolumeChild(  const wxDataViewItem &parent_item, | ||||
|                                     const wxString &name, | ||||
|                                     const Slic3r::ModelVolumeType volume_type, | ||||
|                                     const bool has_errors = false, | ||||
|                                     const int extruder = 0, | ||||
|                                     const bool create_frst_child = true); | ||||
|     wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); | ||||
|     wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); | ||||
|     wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector<bool>& print_indicator); | ||||
|     wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); | ||||
|     wxDataViewItem AddLayersChild(  const wxDataViewItem &parent_item, | ||||
|                                     const t_layer_height_range& layer_range, | ||||
|                                     const int extruder = 0, | ||||
|                                     const int index = -1); | ||||
|     wxDataViewItem Delete(const wxDataViewItem &item); | ||||
|     wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); | ||||
|     void DeleteAll(); | ||||
|     void DeleteChildren(wxDataViewItem& parent); | ||||
|     void DeleteVolumeChildren(wxDataViewItem& parent); | ||||
|     void DeleteSettings(const wxDataViewItem& parent); | ||||
|     wxDataViewItem GetItemById(int obj_idx); | ||||
|     wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type); | ||||
|     wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); | ||||
|     wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); | ||||
|     wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx); | ||||
|     wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); | ||||
|     int  GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); | ||||
|     int  GetIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; | ||||
|     int  GetObjectIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetVolumeIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetInstanceIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetLayerIdByItem(const wxDataViewItem& item) const; | ||||
|     void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); | ||||
|     int  GetRowByItem(const wxDataViewItem& item) const; | ||||
|     bool IsEmpty() { return m_objects.empty(); } | ||||
|     bool InvalidItem(const wxDataViewItem& item); | ||||
| 
 | ||||
|     // helper method for wxLog
 | ||||
| 
 | ||||
|     wxString    GetName(const wxDataViewItem &item) const; | ||||
|     wxBitmap&   GetBitmap(const wxDataViewItem &item) const; | ||||
|     wxString    GetExtruder(const wxDataViewItem &item) const; | ||||
|     int         GetExtruderNumber(const wxDataViewItem &item) const; | ||||
| 
 | ||||
|     // helper methods to change the model
 | ||||
| 
 | ||||
|     virtual unsigned int    GetColumnCount() const override { return 3;} | ||||
|     virtual wxString        GetColumnType(unsigned int col) const override{ return wxT("string"); } | ||||
| 
 | ||||
|     virtual void GetValue(  wxVariant &variant, | ||||
|                             const wxDataViewItem &item, | ||||
|                             unsigned int col) const override; | ||||
|     virtual bool SetValue(  const wxVariant &variant, | ||||
|                             const wxDataViewItem &item, | ||||
|                             unsigned int col) override; | ||||
|     bool SetValue(  const wxVariant &variant, | ||||
|                     const int item_idx, | ||||
|                     unsigned int col); | ||||
| 
 | ||||
|     void SetExtruder(const wxString& extruder, wxDataViewItem item); | ||||
| 
 | ||||
|     // For parent move child from cur_volume_id place to new_volume_id
 | ||||
|     // Remaining items will moved up/down accordingly
 | ||||
|     wxDataViewItem  ReorganizeChildren( const int cur_volume_id, | ||||
|                                         const int new_volume_id, | ||||
|                                         const wxDataViewItem &parent); | ||||
|     wxDataViewItem  ReorganizeObjects( int current_id, int new_id); | ||||
| 
 | ||||
|     virtual bool    IsEnabled(const wxDataViewItem &item, unsigned int col) const override; | ||||
| 
 | ||||
|     virtual wxDataViewItem  GetParent(const wxDataViewItem &item) const override; | ||||
|     // get object item
 | ||||
|     wxDataViewItem          GetTopParent(const wxDataViewItem &item) const; | ||||
|     virtual bool            IsContainer(const wxDataViewItem &item) const override; | ||||
|     virtual unsigned int    GetChildren(const wxDataViewItem &parent, | ||||
|                                         wxDataViewItemArray &array) const override; | ||||
|     void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const; | ||||
|     // Is the container just a header or an item with all columns
 | ||||
|     // In our case it is an item with all columns
 | ||||
|     virtual bool    HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override {	return true; } | ||||
| 
 | ||||
|     ItemType        GetItemType(const wxDataViewItem &item) const ; | ||||
|     wxDataViewItem  GetItemByType(  const wxDataViewItem &parent_item, | ||||
|                                     ItemType type) const; | ||||
|     wxDataViewItem  GetSettingsItem(const wxDataViewItem &item) const; | ||||
|     wxDataViewItem  GetInstanceRootItem(const wxDataViewItem &item) const; | ||||
|     wxDataViewItem  GetLayerRootItem(const wxDataViewItem &item) const; | ||||
|     bool    IsSettingsItem(const wxDataViewItem &item) const; | ||||
|     void    UpdateSettingsDigest(   const wxDataViewItem &item, | ||||
|                                     const std::vector<std::string>& categories); | ||||
| 
 | ||||
|     bool    IsPrintable(const wxDataViewItem &item) const; | ||||
|     void    UpdateObjectPrintable(wxDataViewItem parent_item); | ||||
|     void    UpdateInstancesPrintable(wxDataViewItem parent_item); | ||||
| 
 | ||||
|     void    SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; } | ||||
|     void    SetWarningBitmap(wxBitmap* bitmap)                          { m_warning_bmp = bitmap; } | ||||
|     void    SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); | ||||
|     wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, | ||||
|                                       int subobj_idx = -1,  | ||||
|                                       ItemType subobj_type = itInstance); | ||||
|     wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item); | ||||
| 
 | ||||
|     void    SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } | ||||
|     // Rescale bitmaps for existing Items
 | ||||
|     void    Rescale(); | ||||
| 
 | ||||
|     wxBitmap    GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, | ||||
|                               const bool is_marked = false); | ||||
|     void        DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); | ||||
|     t_layer_height_range    GetLayerRangeByItem(const wxDataViewItem& item) const; | ||||
| 
 | ||||
|     bool        UpdateColumValues(unsigned col); | ||||
|     void        UpdateExtruderBitmap(wxDataViewItem item); | ||||
| 
 | ||||
| private: | ||||
|     wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type); | ||||
|     wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #endif // slic3r_GUI_ObjectDataViewModel_hpp_
 | ||||
|  | @ -169,7 +169,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : | |||
|     info_manifold_text->SetFont(wxGetApp().small_font()); | ||||
|     info_manifold = new wxStaticText(parent, wxID_ANY, ""); | ||||
|     info_manifold->SetFont(wxGetApp().small_font()); | ||||
|     manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(parent, "exclamation")); | ||||
|     manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap("exclamation")); | ||||
|     auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); | ||||
|     sizer_manifold->Add(info_manifold_text, 0); | ||||
|     sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); | ||||
|  | @ -188,7 +188,7 @@ void ObjectInfo::show_sizer(bool show) | |||
| 
 | ||||
| void ObjectInfo::msw_rescale() | ||||
| { | ||||
|     manifold_warning_icon->SetBitmap(create_scaled_bitmap(nullptr, "exclamation")); | ||||
|     manifold_warning_icon->SetBitmap(create_scaled_bitmap("exclamation")); | ||||
| } | ||||
| 
 | ||||
| enum SlicedInfoIdx | ||||
|  | @ -258,7 +258,7 @@ void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const w | |||
| } | ||||
| 
 | ||||
| PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : | ||||
| wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), 0, nullptr, wxCB_READONLY), | ||||
| PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)), | ||||
|     preset_type(preset_type), | ||||
|     last_selected(wxNOT_FOUND), | ||||
|     m_em_unit(wxGetApp().em_unit()) | ||||
|  | @ -1875,6 +1875,7 @@ struct Plater::priv | |||
| 	} | ||||
|     void export_gcode(fs::path output_path, PrintHostJob upload_job); | ||||
|     void reload_from_disk(); | ||||
|     void reload_all_from_disk(); | ||||
|     void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); | ||||
| 
 | ||||
|     void set_current_panel(wxPanel* panel); | ||||
|  | @ -2075,6 +2076,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event<float>& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_RELOAD_FROM_DISK, [this](SimpleEvent&) { if (!this->model.objects.empty()) this->reload_all_from_disk(); }); | ||||
| 
 | ||||
|     // 3DScene/Toolbar:
 | ||||
|     view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); | ||||
|  | @ -3447,6 +3449,24 @@ void Plater::priv::reload_from_disk() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::reload_all_from_disk() | ||||
| { | ||||
|     Plater::TakeSnapshot snapshot(q, _(L("Reload all from disk"))); | ||||
|     Plater::SuppressSnapshots suppress(q); | ||||
| 
 | ||||
|     Selection& selection = get_selection(); | ||||
|     Selection::IndicesList curr_idxs = selection.get_volume_idxs(); | ||||
|     // reload from disk uses selection
 | ||||
|     select_all(); | ||||
|     reload_from_disk(); | ||||
|     // restore previous selection
 | ||||
|     selection.clear(); | ||||
|     for (unsigned int idx : curr_idxs) | ||||
|     { | ||||
|         selection.add(idx, false); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) | ||||
| { | ||||
|     if (obj_idx < 0) | ||||
|  | @ -5034,6 +5054,11 @@ void Plater::reload_from_disk() | |||
|     p->reload_from_disk(); | ||||
| } | ||||
| 
 | ||||
| void Plater::reload_all_from_disk() | ||||
| { | ||||
|     p->reload_all_from_disk(); | ||||
| } | ||||
| 
 | ||||
| bool Plater::has_toolpaths_to_export() const | ||||
| { | ||||
|     return  p->preview->get_canvas3d()->has_toolpaths_to_export(); | ||||
|  | @ -5376,6 +5401,13 @@ void Plater::force_filament_colors_update() | |||
|         this->p->schedule_background_process(); | ||||
| } | ||||
| 
 | ||||
| void Plater::force_print_bed_update() | ||||
| { | ||||
| 	// Fill in the printer model key with something which cannot possibly be valid, so that Plater::on_config_change() will update the print bed
 | ||||
| 	// once a new Printer profile config is loaded.
 | ||||
| 	p->config->opt_string("printer_model", true) = "\x01\x00\x01"; | ||||
| } | ||||
| 
 | ||||
| void Plater::on_activate() | ||||
| { | ||||
| #ifdef __linux__ | ||||
|  | @ -5392,11 +5424,6 @@ void Plater::on_activate() | |||
| 	this->p->show_delayed_error_message(); | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
| { | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| 
 | ||||
| #include "3DScene.hpp" | ||||
| #include "GLTexture.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| 
 | ||||
| class wxButton; | ||||
| class ScalableButton; | ||||
|  | @ -49,7 +50,7 @@ using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>; | |||
| class Plater; | ||||
| enum class ActionButtonType : int; | ||||
| 
 | ||||
| class PresetComboBox : public wxBitmapComboBox | ||||
| class PresetComboBox : public PresetBitmapComboBox | ||||
| { | ||||
| public: | ||||
|     PresetComboBox(wxWindow *parent, Preset::Type preset_type); | ||||
|  | @ -193,6 +194,7 @@ public: | |||
|     void export_amf(); | ||||
|     void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); | ||||
|     void reload_from_disk(); | ||||
|     void reload_all_from_disk(); | ||||
|     bool has_toolpaths_to_export() const; | ||||
|     void export_toolpaths_to_obj() const; | ||||
|     void hollow(); | ||||
|  | @ -227,9 +229,9 @@ public: | |||
|     void on_extruders_change(size_t extruders_count); | ||||
|     void on_config_change(const DynamicPrintConfig &config); | ||||
|     void force_filament_colors_update(); | ||||
|     void force_print_bed_update(); | ||||
|     // On activating the parent window.
 | ||||
|     void on_activate(); | ||||
|     const DynamicPrintConfig* get_plater_config() const; | ||||
|     std::vector<std::string> get_extruder_colors_from_plater_config() const; | ||||
|     std::vector<std::string> get_colors_for_color_print() const; | ||||
| 
 | ||||
|  |  | |||
|  | @ -550,7 +550,6 @@ const std::vector<std::string>& Preset::sla_printer_options() | |||
|         s_opts = { | ||||
|             "printer_technology", | ||||
|             "bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height", | ||||
|             "bed_shape", "max_print_height", | ||||
|             "display_width", "display_height", "display_pixels_x", "display_pixels_y", | ||||
|             "display_mirror_x", "display_mirror_y", | ||||
|             "display_orientation", | ||||
|  | @ -874,18 +873,14 @@ bool PresetCollection::delete_preset(const std::string& name) | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void PresetCollection::load_bitmap_default(wxWindow *window, const std::string &file_name) | ||||
| void PresetCollection::load_bitmap_default(const std::string &file_name) | ||||
| { | ||||
|     // XXX: See note in PresetBundle::load_compatible_bitmaps()
 | ||||
|     (void)window; | ||||
|     *m_bitmap_main_frame = create_scaled_bitmap(nullptr, file_name); | ||||
|     *m_bitmap_main_frame = create_scaled_bitmap(file_name); | ||||
| } | ||||
| 
 | ||||
| void PresetCollection::load_bitmap_add(wxWindow *window, const std::string &file_name) | ||||
| void PresetCollection::load_bitmap_add(const std::string &file_name) | ||||
| { | ||||
|     // XXX: See note in PresetBundle::load_compatible_bitmaps()
 | ||||
|     (void)window; | ||||
|     *m_bitmap_add = create_scaled_bitmap(nullptr, file_name); | ||||
|     *m_bitmap_add = create_scaled_bitmap(file_name); | ||||
| } | ||||
| 
 | ||||
| const Preset* PresetCollection::get_selected_preset_parent() const | ||||
|  |  | |||
|  | @ -313,10 +313,10 @@ public: | |||
|     bool            delete_preset(const std::string& name); | ||||
| 
 | ||||
|     // Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame.
 | ||||
|     void            load_bitmap_default(wxWindow *window, const std::string &file_name); | ||||
|     void            load_bitmap_default(const std::string &file_name); | ||||
| 
 | ||||
|     // Load "add new printer" bitmap to be placed at the wxBitmapComboBox of a MainFrame.
 | ||||
|     void            load_bitmap_add(wxWindow *window, const std::string &file_name); | ||||
|     void            load_bitmap_add(const std::string &file_name); | ||||
| 
 | ||||
|     // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items.
 | ||||
|     void            set_bitmap_compatible  (const wxBitmap *bmp) { m_bitmap_compatible   = bmp; } | ||||
|  |  | |||
|  | @ -480,19 +480,12 @@ void PresetBundle::export_selections(AppConfig &config) | |||
|     config.set("presets", "printer",      printers.get_selected_preset_name()); | ||||
| } | ||||
| 
 | ||||
| void PresetBundle::load_compatible_bitmaps(wxWindow *window) | ||||
| void PresetBundle::load_compatible_bitmaps() | ||||
| { | ||||
|     // We don't actually pass the window pointer here and instead generate
 | ||||
|     // a low DPI bitmap, because the wxBitmapComboBox and wxDataViewCtrl don't support
 | ||||
|     // high DPI bitmaps very well, they compute their dimensions wrong.
 | ||||
|     // TODO: Update this when fixed in wxWidgets
 | ||||
|     // See also PresetCollection::load_bitmap_default() and PresetCollection::load_bitmap_add()
 | ||||
| 
 | ||||
|     (void)window; | ||||
|     *m_bitmapCompatible     = create_scaled_bitmap(nullptr, "flag_green"); | ||||
|     *m_bitmapIncompatible   = create_scaled_bitmap(nullptr, "flag_red"); | ||||
|     *m_bitmapLock           = create_scaled_bitmap(nullptr, "lock_closed"); | ||||
|     *m_bitmapLockOpen       = create_scaled_bitmap(nullptr, "lock_open"); | ||||
|     *m_bitmapCompatible     = create_scaled_bitmap("flag_green"); | ||||
|     *m_bitmapIncompatible   = create_scaled_bitmap("flag_red"); | ||||
|     *m_bitmapLock           = create_scaled_bitmap("lock_closed"); | ||||
|     *m_bitmapLockOpen       = create_scaled_bitmap("lock_open"); | ||||
| 
 | ||||
|     prints       .set_bitmap_compatible(m_bitmapCompatible); | ||||
|     filaments    .set_bitmap_compatible(m_bitmapCompatible); | ||||
|  | @ -1536,31 +1529,7 @@ void PresetBundle::set_filament_preset(size_t idx, const std::string &name) | |||
|     filament_presets[idx] = Preset::remove_suffix_modified(name); | ||||
| } | ||||
| 
 | ||||
| static inline int hex_digit_to_int(const char c) | ||||
| { | ||||
|     return  | ||||
|         (c >= '0' && c <= '9') ? int(c - '0') :  | ||||
|         (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : | ||||
|         (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; | ||||
| } | ||||
| 
 | ||||
| bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out) | ||||
| { | ||||
|     rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; | ||||
|     if (scolor.size() != 7 || scolor.front() != '#') | ||||
|         return false; | ||||
|     const char *c = scolor.data() + 1; | ||||
|     for (size_t i = 0; i < 3; ++ i) { | ||||
|         int digit1 = hex_digit_to_int(*c ++); | ||||
|         int digit2 = hex_digit_to_int(*c ++); | ||||
|         if (digit1 == -1 || digit2 == -1) | ||||
|             return false; | ||||
|         rgb_out[i] = (unsigned char)(digit1 * 16 + digit2); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void PresetBundle::load_default_preset_bitmaps(wxWindow *window) | ||||
| void PresetBundle::load_default_preset_bitmaps() | ||||
| { | ||||
|     // Clear bitmap cache, before load new scaled default preset bitmaps 
 | ||||
|     m_bitmapCache->clear(); | ||||
|  | @ -1570,13 +1539,13 @@ void PresetBundle::load_default_preset_bitmaps(wxWindow *window) | |||
|     this->sla_materials.clear_bitmap_cache(); | ||||
|     this->printers.clear_bitmap_cache(); | ||||
| 
 | ||||
|     this->prints.load_bitmap_default(window, "cog"); | ||||
|     this->sla_prints.load_bitmap_default(window, "cog"); | ||||
|     this->filaments.load_bitmap_default(window, "spool.png"); | ||||
|     this->sla_materials.load_bitmap_default(window, "resin"); | ||||
|     this->printers.load_bitmap_default(window, "printer"); | ||||
|     this->printers.load_bitmap_add(window, "add.png"); | ||||
|     this->load_compatible_bitmaps(window); | ||||
|     this->prints.load_bitmap_default("cog"); | ||||
|     this->sla_prints.load_bitmap_default("cog"); | ||||
|     this->filaments.load_bitmap_default("spool.png"); | ||||
|     this->sla_materials.load_bitmap_default("resin"); | ||||
|     this->printers.load_bitmap_default("printer"); | ||||
|     this->printers.load_bitmap_add("add.png"); | ||||
|     this->load_compatible_bitmaps(); | ||||
| } | ||||
| 
 | ||||
| void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui) | ||||
|  | @ -1587,7 +1556,7 @@ void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::Pre | |||
| 
 | ||||
|     unsigned char rgb[3]; | ||||
|     std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder); | ||||
|     if (! parse_color(extruder_color, rgb)) | ||||
|     if (!m_bitmapCache->parse_color(extruder_color, rgb)) | ||||
|         // Extruder color is not defined.
 | ||||
|         extruder_color.clear(); | ||||
| 
 | ||||
|  | @ -1623,7 +1592,12 @@ void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::Pre | |||
| 
 | ||||
|     // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so
 | ||||
|     // set a bitmap height to m_bitmapLock->GetHeight()
 | ||||
|     const int icon_height       = m_bitmapLock->GetHeight();//2 * icon_unit;    //16 * scale_f + 0.5f;
 | ||||
|     // Note, under OSX we should use a ScaledHeight because of Retina scale
 | ||||
| #ifdef __APPLE__ | ||||
|     const int icon_height       = m_bitmapLock->GetScaledHeight(); | ||||
| #else | ||||
|     const int icon_height       = m_bitmapLock->GetHeight(); | ||||
| #endif | ||||
| 
 | ||||
|     wxString tooltip = ""; | ||||
| 
 | ||||
|  | @ -1652,10 +1626,10 @@ void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::Pre | |||
|                 // Paint a red flag for incompatible presets.
 | ||||
|                 bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(normal_icon_width, icon_height) : *m_bitmapIncompatible); | ||||
|             // Paint the color bars.
 | ||||
|             parse_color(filament_rgb, rgb); | ||||
|             m_bitmapCache->parse_color(filament_rgb, rgb); | ||||
|             bmps.emplace_back(m_bitmapCache->mksolid(single_bar ? wide_icon_width : normal_icon_width, icon_height, rgb)); | ||||
|             if (! single_bar) { | ||||
|                 parse_color(extruder_rgb, rgb); | ||||
|                 m_bitmapCache->parse_color(extruder_rgb, rgb); | ||||
|                 bmps.emplace_back(m_bitmapCache->mksolid(thin_icon_width, icon_height, rgb)); | ||||
|             } | ||||
|             // Paint a lock at the system presets.
 | ||||
|  |  | |||
|  | @ -54,8 +54,7 @@ public: | |||
| 
 | ||||
|     // There will be an entry for each system profile loaded, 
 | ||||
|     // and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors.
 | ||||
|     // std::set<VendorProfile>     vendors;
 | ||||
|     VendorMap                      vendors; | ||||
|     VendorMap                   vendors; | ||||
| 
 | ||||
|     struct ObsoletePresets { | ||||
|         std::vector<std::string> prints; | ||||
|  | @ -130,9 +129,7 @@ public: | |||
|     // preset if the current print or filament preset is not compatible.
 | ||||
|     void                        update_compatible(bool select_other_if_incompatible); | ||||
| 
 | ||||
|     static bool                 parse_color(const std::string &scolor, unsigned char *rgb_out); | ||||
| 
 | ||||
|     void                        load_default_preset_bitmaps(wxWindow *window); | ||||
|     void                        load_default_preset_bitmaps(); | ||||
| 
 | ||||
|     // Set the is_visible flag for printer vendors, printer models and printer variants
 | ||||
|     // based on the user configuration.
 | ||||
|  | @ -161,7 +158,7 @@ private: | |||
|     // If it is not an external config, then the config will be stored into the user profile directory.
 | ||||
|     void                        load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config); | ||||
|     void                        load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); | ||||
|     void                        load_compatible_bitmaps(wxWindow *window); | ||||
|     void                        load_compatible_bitmaps(); | ||||
| 
 | ||||
|     DynamicPrintConfig          full_fff_config() const; | ||||
|     DynamicPrintConfig          full_sla_config() const; | ||||
|  |  | |||
|  | @ -114,7 +114,7 @@ void Tab::create_preset_tab() | |||
| #endif //__WXOSX__
 | ||||
| 
 | ||||
|     // preset chooser
 | ||||
|     m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(35 * m_em_unit, -1), 0, 0, wxCB_READONLY); | ||||
|     m_presets_choice = new PresetBitmapComboBox(panel, wxSize(35 * m_em_unit, -1)); | ||||
| 
 | ||||
|     auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); | ||||
| 
 | ||||
|  | @ -1690,7 +1690,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) | |||
|         auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { | ||||
|             auto btn = new wxButton(parent, wxID_ANY, " " + _(L("Browse"))+" " +dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); | ||||
|             btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); | ||||
|             btn->SetBitmap(create_scaled_bitmap(this, "browse")); | ||||
|             btn->SetBitmap(create_scaled_bitmap("browse")); | ||||
|             auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|             sizer->Add(btn); | ||||
| 
 | ||||
|  |  | |||
|  | @ -120,7 +120,7 @@ protected: | |||
|     Preset::Type        m_type; | ||||
| 	std::string			m_name; | ||||
| 	const wxString		m_title; | ||||
| 	wxBitmapComboBox*	m_presets_choice; | ||||
| 	PresetBitmapComboBox*	m_presets_choice; | ||||
| 	ScalableButton*		m_btn_save_preset; | ||||
| 	ScalableButton*		m_btn_delete_preset; | ||||
| 	ScalableButton*		m_btn_hide_incompatible_presets; | ||||
|  |  | |||
|  | @ -149,7 +149,7 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, w | |||
|     MsgDialog(nullptr, wxString::Format(_(L("%s incompatibility")), SLIC3R_APP_NAME),  | ||||
|                        wxString::Format(_(L("%s configuration is incompatible")), SLIC3R_APP_NAME), wxID_NONE) | ||||
| { | ||||
| 	logo->SetBitmap(create_scaled_bitmap(this, "PrusaSlicer_192px_grayscale.png", 192)); | ||||
| 	logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 192)); | ||||
| 
 | ||||
| 	auto *text = new wxStaticText(this, wxID_ANY, wxString::Format(_(L( | ||||
| 		"This version of %s is not compatible with currently installed configuration bundles.\n" | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| #include <algorithm> | ||||
| #include <sstream> | ||||
| #include "WipeTowerDialog.hpp" | ||||
| #include "PresetBundle.hpp" | ||||
| #include "BitmapCache.hpp" | ||||
| #include "GUI.hpp" | ||||
| #include "I18N.hpp" | ||||
| #include "GUI_App.hpp" | ||||
|  | @ -191,7 +191,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, con | |||
| 
 | ||||
|     for (const std::string& color : extruder_colours) { | ||||
|         unsigned char rgb[3]; | ||||
|         Slic3r::PresetBundle::parse_color(color, rgb); | ||||
|         Slic3r::GUI::BitmapCache::parse_color(color, rgb); | ||||
|         m_colours.push_back(wxColor(rgb[0], rgb[1], rgb[2])); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -4,23 +4,14 @@ | |||
| #include <wx/checklst.h> | ||||
| #include <wx/combo.h> | ||||
| #include <wx/dataview.h> | ||||
| #include <wx/dc.h> | ||||
| #include <wx/wupdlock.h> | ||||
| #include <wx/button.h> | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/menu.h> | ||||
| #include <wx/wx.h> | ||||
| #include <wx/bmpcbox.h> | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <set> | ||||
| #include <functional> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
|     enum class ModelVolumeType : int; | ||||
| }; | ||||
| 
 | ||||
| typedef double                          coordf_t; | ||||
| typedef std::pair<coordf_t, coordf_t>   t_layer_height_range; | ||||
| 
 | ||||
| #ifdef __WXMSW__ | ||||
| void                msw_rescale_menu(wxMenu* menu); | ||||
|  | @ -48,15 +39,13 @@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, | |||
| void enable_menu_item(wxUpdateUIEvent& evt, std::function<bool()> const cb_condition, wxMenuItem* item, wxWindow* win); | ||||
| 
 | ||||
| class wxDialog; | ||||
| class wxBitmapComboBox; | ||||
| 
 | ||||
| void    edit_tooltip(wxString& tooltip); | ||||
| void    msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector<int>& btn_ids); | ||||
| int     em_unit(wxWindow* win); | ||||
| float   get_svg_scale_factor(wxWindow* win); | ||||
| 
 | ||||
| wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name, | ||||
|     const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false); | ||||
| wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr,  | ||||
|     const int px_cnt = 16, const bool grayscale = false); | ||||
| 
 | ||||
| std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon = false); | ||||
| void apply_extruder_selector(wxBitmapComboBox** ctrl, | ||||
|  | @ -102,6 +91,37 @@ public: | |||
|     void OnListBoxSelection(wxCommandEvent& evt); | ||||
| }; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| // ***  PresetBitmapComboBox  ***
 | ||||
| 
 | ||||
| // BitmapComboBox used to presets list on Sidebar and Tabs
 | ||||
| class PresetBitmapComboBox: public wxBitmapComboBox | ||||
| { | ||||
| public: | ||||
|     PresetBitmapComboBox(wxWindow* parent, const wxSize& size = wxDefaultSize); | ||||
|     ~PresetBitmapComboBox() {} | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
| protected: | ||||
|     /* For PresetBitmapComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina
 | ||||
|      * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean | ||||
|      * "please scale this to such and such" but rather | ||||
|      * "the wxImage is already sized for backing scale such and such". ) | ||||
|      * Unfortunately, the constructor changes the size of wxBitmap too. | ||||
|      * Thus We need to use unscaled size value for bitmaps that we use | ||||
|      * to avoid scaled size of control items. | ||||
|      * For this purpose control drawing methods and | ||||
|      * control size calculation methods (virtual) are overridden. | ||||
|      **/ | ||||
|     virtual bool OnAddBitmap(const wxBitmap& bitmap) override; | ||||
|     virtual void OnDrawItem(wxDC& dc, const wxRect& rect, int item, int flags) const override; | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // ***  wxDataViewTreeCtrlComboBox  ***
 | ||||
| 
 | ||||
|  | @ -127,587 +147,6 @@ public: | |||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // DataViewBitmapText: helper class used by PrusaBitmapTextRenderer
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| class DataViewBitmapText : public wxObject | ||||
| { | ||||
| public: | ||||
|     DataViewBitmapText( const wxString &text = wxEmptyString, | ||||
|                         const wxBitmap& bmp = wxNullBitmap) : | ||||
|         m_text(text), | ||||
|         m_bmp(bmp) | ||||
|     { } | ||||
| 
 | ||||
|     DataViewBitmapText(const DataViewBitmapText &other) | ||||
|         : wxObject(), | ||||
|         m_text(other.m_text), | ||||
|         m_bmp(other.m_bmp) | ||||
|     { } | ||||
| 
 | ||||
|     void SetText(const wxString &text)      { m_text = text; } | ||||
|     wxString GetText() const                { return m_text; } | ||||
|     void SetBitmap(const wxBitmap &bmp)     { m_bmp = bmp; } | ||||
|     const wxBitmap &GetBitmap() const       { return m_bmp; } | ||||
| 
 | ||||
|     bool IsSameAs(const DataViewBitmapText& other) const { | ||||
|         return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp); | ||||
|     } | ||||
| 
 | ||||
|     bool operator==(const DataViewBitmapText& other) const { | ||||
|         return IsSameAs(other); | ||||
|     } | ||||
| 
 | ||||
|     bool operator!=(const DataViewBitmapText& other) const { | ||||
|         return !IsSameAs(other); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     wxString    m_text; | ||||
|     wxBitmap    m_bmp; | ||||
| 
 | ||||
|     wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText); | ||||
| }; | ||||
| DECLARE_VARIANT_OBJECT(DataViewBitmapText) | ||||
| 
 | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // ObjectDataViewModelNode: a node inside ObjectDataViewModel
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| enum ItemType { | ||||
|     itUndef         = 0, | ||||
|     itObject        = 1, | ||||
|     itVolume        = 2, | ||||
|     itInstanceRoot  = 4, | ||||
|     itInstance      = 8, | ||||
|     itSettings      = 16, | ||||
|     itLayerRoot     = 32, | ||||
|     itLayer         = 64, | ||||
| }; | ||||
| 
 | ||||
| enum ColumnNumber | ||||
| { | ||||
|     colName         = 0,    // item name
 | ||||
|     colPrint           ,    // printable property
 | ||||
|     colExtruder        ,    // extruder selection
 | ||||
|     colEditing         ,    // item editing
 | ||||
| }; | ||||
| 
 | ||||
| enum PrintIndicator | ||||
| { | ||||
|     piUndef         = 0,    // no print indicator
 | ||||
|     piPrintable        ,    // printable
 | ||||
|     piUnprintable      ,    // unprintable
 | ||||
| }; | ||||
| 
 | ||||
| class ObjectDataViewModelNode; | ||||
| WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); | ||||
| 
 | ||||
| class ObjectDataViewModelNode | ||||
| { | ||||
|     ObjectDataViewModelNode*	    m_parent; | ||||
|     MyObjectTreeModelNodePtrArray   m_children; | ||||
|     wxBitmap                        m_empty_bmp; | ||||
|     size_t                          m_volumes_cnt = 0; | ||||
|     std::vector< std::string >      m_opt_categories; | ||||
|     t_layer_height_range            m_layer_range = { 0.0f, 0.0f }; | ||||
| 
 | ||||
|     wxString				        m_name; | ||||
|     wxBitmap&                       m_bmp = m_empty_bmp; | ||||
|     ItemType				        m_type; | ||||
|     int                             m_idx = -1; | ||||
|     bool					        m_container = false; | ||||
|     wxString				        m_extruder = "default"; | ||||
|     wxBitmap                        m_extruder_bmp; | ||||
|     wxBitmap				        m_action_icon; | ||||
|     PrintIndicator                  m_printable {piUndef}; | ||||
|     wxBitmap				        m_printable_icon; | ||||
| 
 | ||||
|     std::string                     m_action_icon_name = ""; | ||||
|     Slic3r::ModelVolumeType         m_volume_type; | ||||
| 
 | ||||
| public: | ||||
|     ObjectDataViewModelNode(const wxString &name, | ||||
|                             const wxString& extruder): | ||||
|         m_parent(NULL), | ||||
|         m_name(name), | ||||
|         m_type(itObject), | ||||
|         m_extruder(extruder) | ||||
|     { | ||||
|         set_action_and_extruder_icons(); | ||||
|         init_container(); | ||||
| 	} | ||||
| 
 | ||||
|     ObjectDataViewModelNode(ObjectDataViewModelNode* parent, | ||||
|                             const wxString& sub_obj_name, | ||||
|                             const wxBitmap& bmp, | ||||
|                             const wxString& extruder, | ||||
|                             const int idx = -1 ) : | ||||
|         m_parent	(parent), | ||||
|         m_name		(sub_obj_name), | ||||
|         m_type		(itVolume), | ||||
|         m_idx       (idx), | ||||
|         m_extruder  (extruder) | ||||
|     { | ||||
|         m_bmp = bmp; | ||||
|         set_action_and_extruder_icons(); | ||||
|         init_container(); | ||||
|     } | ||||
| 
 | ||||
|     ObjectDataViewModelNode(ObjectDataViewModelNode* parent, | ||||
|                             const t_layer_height_range& layer_range, | ||||
|                             const int idx = -1, | ||||
|                             const wxString& extruder = wxEmptyString ); | ||||
| 
 | ||||
|     ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); | ||||
| 
 | ||||
|     ~ObjectDataViewModelNode() | ||||
|     { | ||||
|         // free all our children nodes
 | ||||
|         size_t count = m_children.GetCount(); | ||||
|         for (size_t i = 0; i < count; i++) | ||||
|         { | ||||
|             ObjectDataViewModelNode *child = m_children[i]; | ||||
|             delete child; | ||||
|         } | ||||
| #ifndef NDEBUG | ||||
|         // Indicate that the object was deleted.
 | ||||
|         m_idx = -2; | ||||
| #endif /* NDEBUG */ | ||||
|     } | ||||
| 
 | ||||
| 	void init_container(); | ||||
| 	bool IsContainer() const | ||||
| 	{ | ||||
| 		return m_container; | ||||
| 	} | ||||
| 
 | ||||
|     ObjectDataViewModelNode* GetParent() | ||||
|     { | ||||
|         assert(m_parent == nullptr || m_parent->valid()); | ||||
|         return m_parent; | ||||
|     } | ||||
|     MyObjectTreeModelNodePtrArray& GetChildren() | ||||
|     { | ||||
|         return m_children; | ||||
|     } | ||||
|     ObjectDataViewModelNode* GetNthChild(unsigned int n) | ||||
|     { | ||||
|         return m_children.Item(n); | ||||
|     } | ||||
|     void Insert(ObjectDataViewModelNode* child, unsigned int n) | ||||
|     { | ||||
|         if (!m_container) | ||||
|             m_container = true; | ||||
|         m_children.Insert(child, n); | ||||
|     } | ||||
|     void Append(ObjectDataViewModelNode* child) | ||||
|     { | ||||
|         if (!m_container) | ||||
|             m_container = true; | ||||
|         m_children.Add(child); | ||||
|     } | ||||
|     void RemoveAllChildren() | ||||
|     { | ||||
|         if (GetChildCount() == 0) | ||||
|             return; | ||||
|         for (int id = int(GetChildCount()) - 1; id >= 0; --id) | ||||
|         { | ||||
|             if (m_children.Item(id)->GetChildCount() > 0) | ||||
|                 m_children[id]->RemoveAllChildren(); | ||||
|             auto node = m_children[id]; | ||||
|             m_children.RemoveAt(id); | ||||
|             delete node; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     size_t GetChildCount() const | ||||
|     { | ||||
|         return m_children.GetCount(); | ||||
|     } | ||||
| 
 | ||||
|     bool            SetValue(const wxVariant &variant, unsigned int col); | ||||
| 
 | ||||
|     void            SetBitmap(const wxBitmap &icon) { m_bmp = icon; } | ||||
|     const wxBitmap& GetBitmap() const               { return m_bmp; } | ||||
|     const wxString& GetName() const                 { return m_name; } | ||||
|     ItemType        GetType() const                 { return m_type; } | ||||
| 	void			SetIdx(const int& idx); | ||||
| 	int             GetIdx() const                  { return m_idx; } | ||||
| 	t_layer_height_range    GetLayerRange() const   { return m_layer_range; } | ||||
|     PrintIndicator  IsPrintable() const             { return m_printable; } | ||||
| 
 | ||||
|     // use this function only for childrens
 | ||||
|     void AssignAllVal(ObjectDataViewModelNode& from_node) | ||||
|     { | ||||
|         // ! Don't overwrite other values because of equality of this values for all children --
 | ||||
|         m_name = from_node.m_name; | ||||
|         m_bmp = from_node.m_bmp; | ||||
|         m_idx = from_node.m_idx; | ||||
|         m_extruder = from_node.m_extruder; | ||||
|         m_type = from_node.m_type; | ||||
|     } | ||||
| 
 | ||||
|     bool SwapChildrens(int frst_id, int scnd_id) { | ||||
|         if (GetChildCount() < 2 || | ||||
|             frst_id < 0 || (size_t)frst_id >= GetChildCount() || | ||||
|             scnd_id < 0 || (size_t)scnd_id >= GetChildCount()) | ||||
|             return false; | ||||
| 
 | ||||
|         ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); | ||||
|         ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); | ||||
| 
 | ||||
|         new_scnd.m_idx = m_children.Item(scnd_id)->m_idx; | ||||
|         new_frst.m_idx = m_children.Item(frst_id)->m_idx; | ||||
| 
 | ||||
|         m_children.Item(frst_id)->AssignAllVal(new_frst); | ||||
|         m_children.Item(scnd_id)->AssignAllVal(new_scnd); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     // Set action icons for node
 | ||||
|     void        set_action_and_extruder_icons(); | ||||
| 	// Set printable icon for node
 | ||||
|     void        set_printable_icon(PrintIndicator printable); | ||||
| 
 | ||||
|     void        update_settings_digest_bitmaps(); | ||||
|     bool        update_settings_digest(const std::vector<std::string>& categories); | ||||
|     int         volume_type() const { return int(m_volume_type); } | ||||
|     void        msw_rescale(); | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
|     bool 		valid(); | ||||
| #endif /* NDEBUG */ | ||||
|     bool        invalid() const { return m_idx < -1; } | ||||
| 
 | ||||
| private: | ||||
|     friend class ObjectDataViewModel; | ||||
| }; | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // ObjectDataViewModel
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| // custom message the model sends to associated control to notify a last volume deleted from the object:
 | ||||
| wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); | ||||
| 
 | ||||
| class ObjectDataViewModel :public wxDataViewModel | ||||
| { | ||||
|     std::vector<ObjectDataViewModelNode*>       m_objects; | ||||
|     std::vector<wxBitmap*>                      m_volume_bmps; | ||||
|     wxBitmap*                                   m_warning_bmp { nullptr }; | ||||
| 
 | ||||
|     wxDataViewCtrl*                             m_ctrl { nullptr }; | ||||
| 
 | ||||
| public: | ||||
|     ObjectDataViewModel(); | ||||
|     ~ObjectDataViewModel(); | ||||
| 
 | ||||
|     wxDataViewItem Add( const wxString &name, | ||||
|                         const int extruder, | ||||
|                         const bool has_errors = false); | ||||
|     wxDataViewItem AddVolumeChild(  const wxDataViewItem &parent_item, | ||||
|                                     const wxString &name, | ||||
|                                     const Slic3r::ModelVolumeType volume_type, | ||||
|                                     const bool has_errors = false, | ||||
|                                     const int extruder = 0, | ||||
|                                     const bool create_frst_child = true); | ||||
|     wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); | ||||
|     wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); | ||||
|     wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector<bool>& print_indicator); | ||||
|     wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); | ||||
|     wxDataViewItem AddLayersChild(  const wxDataViewItem &parent_item, | ||||
|                                     const t_layer_height_range& layer_range, | ||||
|                                     const int extruder = 0, | ||||
|                                     const int index = -1); | ||||
|     wxDataViewItem Delete(const wxDataViewItem &item); | ||||
|     wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); | ||||
|     void DeleteAll(); | ||||
|     void DeleteChildren(wxDataViewItem& parent); | ||||
|     void DeleteVolumeChildren(wxDataViewItem& parent); | ||||
|     void DeleteSettings(const wxDataViewItem& parent); | ||||
|     wxDataViewItem GetItemById(int obj_idx); | ||||
|     wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type); | ||||
|     wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); | ||||
|     wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); | ||||
|     wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx); | ||||
|     wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); | ||||
|     int  GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); | ||||
|     int  GetIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; | ||||
|     int  GetObjectIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetVolumeIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetInstanceIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetLayerIdByItem(const wxDataViewItem& item) const; | ||||
|     void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); | ||||
|     int  GetRowByItem(const wxDataViewItem& item) const; | ||||
|     bool IsEmpty() { return m_objects.empty(); } | ||||
|     bool InvalidItem(const wxDataViewItem& item); | ||||
| 
 | ||||
|     // helper method for wxLog
 | ||||
| 
 | ||||
|     wxString    GetName(const wxDataViewItem &item) const; | ||||
|     wxBitmap&   GetBitmap(const wxDataViewItem &item) const; | ||||
|     wxString    GetExtruder(const wxDataViewItem &item) const; | ||||
|     int         GetExtruderNumber(const wxDataViewItem &item) const; | ||||
| 
 | ||||
|     // helper methods to change the model
 | ||||
| 
 | ||||
|     virtual unsigned int    GetColumnCount() const override { return 3;} | ||||
|     virtual wxString        GetColumnType(unsigned int col) const override{ return wxT("string"); } | ||||
| 
 | ||||
|     virtual void GetValue(  wxVariant &variant, | ||||
|                             const wxDataViewItem &item, | ||||
|                             unsigned int col) const override; | ||||
|     virtual bool SetValue(  const wxVariant &variant, | ||||
|                             const wxDataViewItem &item, | ||||
|                             unsigned int col) override; | ||||
|     bool SetValue(  const wxVariant &variant, | ||||
|                     const int item_idx, | ||||
|                     unsigned int col); | ||||
| 
 | ||||
|     void SetExtruder(const wxString& extruder, wxDataViewItem item); | ||||
| 
 | ||||
|     // For parent move child from cur_volume_id place to new_volume_id
 | ||||
|     // Remaining items will moved up/down accordingly
 | ||||
|     wxDataViewItem  ReorganizeChildren( const int cur_volume_id, | ||||
|                                         const int new_volume_id, | ||||
|                                         const wxDataViewItem &parent); | ||||
| 
 | ||||
|     virtual bool    IsEnabled(const wxDataViewItem &item, unsigned int col) const override; | ||||
| 
 | ||||
|     virtual wxDataViewItem  GetParent(const wxDataViewItem &item) const override; | ||||
|     // get object item
 | ||||
|     wxDataViewItem          GetTopParent(const wxDataViewItem &item) const; | ||||
|     virtual bool            IsContainer(const wxDataViewItem &item) const override; | ||||
|     virtual unsigned int    GetChildren(const wxDataViewItem &parent, | ||||
|                                         wxDataViewItemArray &array) const override; | ||||
|     void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const; | ||||
|     // Is the container just a header or an item with all columns
 | ||||
|     // In our case it is an item with all columns
 | ||||
|     virtual bool    HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override {	return true; } | ||||
| 
 | ||||
|     ItemType        GetItemType(const wxDataViewItem &item) const ; | ||||
|     wxDataViewItem  GetItemByType(  const wxDataViewItem &parent_item, | ||||
|                                     ItemType type) const; | ||||
|     wxDataViewItem  GetSettingsItem(const wxDataViewItem &item) const; | ||||
|     wxDataViewItem  GetInstanceRootItem(const wxDataViewItem &item) const; | ||||
|     wxDataViewItem  GetLayerRootItem(const wxDataViewItem &item) const; | ||||
|     bool    IsSettingsItem(const wxDataViewItem &item) const; | ||||
|     void    UpdateSettingsDigest(   const wxDataViewItem &item, | ||||
|                                     const std::vector<std::string>& categories); | ||||
| 
 | ||||
|     bool    IsPrintable(const wxDataViewItem &item) const; | ||||
|     void    UpdateObjectPrintable(wxDataViewItem parent_item); | ||||
|     void    UpdateInstancesPrintable(wxDataViewItem parent_item); | ||||
| 
 | ||||
|     void    SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; } | ||||
|     void    SetWarningBitmap(wxBitmap* bitmap)                          { m_warning_bmp = bitmap; } | ||||
|     void    SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); | ||||
|     wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, | ||||
|                                       int subobj_idx = -1,  | ||||
|                                       ItemType subobj_type = itInstance); | ||||
|     wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item); | ||||
| 
 | ||||
|     void    SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } | ||||
|     // Rescale bitmaps for existing Items
 | ||||
|     void    Rescale(); | ||||
| 
 | ||||
|     wxBitmap    GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, | ||||
|                               const bool is_marked = false); | ||||
|     void        DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); | ||||
|     t_layer_height_range    GetLayerRangeByItem(const wxDataViewItem& item) const; | ||||
| 
 | ||||
|     bool        UpdateColumValues(unsigned col); | ||||
|     void        UpdateExtruderBitmap(wxDataViewItem item); | ||||
| 
 | ||||
| private: | ||||
|     wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type); | ||||
|     wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); | ||||
| }; | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // BitmapTextRenderer
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING | ||||
| class BitmapTextRenderer : public wxDataViewRenderer | ||||
| #else | ||||
| class BitmapTextRenderer : public wxDataViewCustomRenderer | ||||
| #endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
 | ||||
| { | ||||
| public: | ||||
|     BitmapTextRenderer(wxDataViewCellMode mode = | ||||
| #ifdef __WXOSX__ | ||||
|                                                         wxDATAVIEW_CELL_INERT | ||||
| #else | ||||
|                                                         wxDATAVIEW_CELL_EDITABLE | ||||
| #endif | ||||
| 
 | ||||
|                             ,int align = wxDVR_DEFAULT_ALIGNMENT | ||||
| #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING | ||||
|                             ); | ||||
| #else | ||||
|                             ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} | ||||
| #endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
 | ||||
| 
 | ||||
|     bool SetValue(const wxVariant &value); | ||||
|     bool GetValue(wxVariant &value) const; | ||||
| #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY | ||||
|     virtual wxString GetAccessibleDescription() const override; | ||||
| #endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
 | ||||
| 
 | ||||
|     virtual bool Render(wxRect cell, wxDC *dc, int state); | ||||
|     virtual wxSize GetSize() const; | ||||
| 
 | ||||
|     bool        HasEditorCtrl() const override | ||||
|     { | ||||
| #ifdef __WXOSX__ | ||||
|         return false; | ||||
| #else | ||||
|         return true; | ||||
| #endif | ||||
|     } | ||||
|     wxWindow*   CreateEditorCtrl(wxWindow* parent, | ||||
|                                  wxRect labelRect, | ||||
|                                  const wxVariant& value) override; | ||||
|     bool        GetValueFromEditorCtrl( wxWindow* ctrl, | ||||
|                                         wxVariant& value) override; | ||||
|     bool        WasCanceled() const { return m_was_unusable_symbol; } | ||||
| 
 | ||||
| private: | ||||
|     DataViewBitmapText m_value; | ||||
|     bool                    m_was_unusable_symbol {false}; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // BitmapChoiceRenderer
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| class BitmapChoiceRenderer : public wxDataViewCustomRenderer | ||||
| { | ||||
| public: | ||||
|     BitmapChoiceRenderer(wxDataViewCellMode mode = | ||||
| #ifdef __WXOSX__ | ||||
|                                                     wxDATAVIEW_CELL_INERT | ||||
| #else | ||||
|                                                     wxDATAVIEW_CELL_EDITABLE | ||||
| #endif | ||||
|                          ,int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | ||||
|         ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} | ||||
| 
 | ||||
|     bool SetValue(const wxVariant& value); | ||||
|     bool GetValue(wxVariant& value) const; | ||||
| 
 | ||||
|     virtual bool Render(wxRect cell, wxDC* dc, int state); | ||||
|     virtual wxSize GetSize() const; | ||||
| 
 | ||||
|     bool        HasEditorCtrl() const override { return true; } | ||||
|     wxWindow*   CreateEditorCtrl(wxWindow* parent, | ||||
|                                  wxRect labelRect, | ||||
|                                  const wxVariant& value) override; | ||||
|     bool        GetValueFromEditorCtrl( wxWindow* ctrl, | ||||
|                                         wxVariant& value) override; | ||||
| 
 | ||||
| private: | ||||
|     DataViewBitmapText  m_value; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // MyCustomRenderer
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| class MyCustomRenderer : public wxDataViewCustomRenderer | ||||
| { | ||||
| public: | ||||
|     // This renderer can be either activatable or editable, for demonstration
 | ||||
|     // purposes. In real programs, you should select whether the user should be
 | ||||
|     // able to activate or edit the cell and it doesn't make sense to switch
 | ||||
|     // between the two -- but this is just an example, so it doesn't stop us.
 | ||||
|     explicit MyCustomRenderer(wxDataViewCellMode mode) | ||||
|         : wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER) | ||||
|     { } | ||||
| 
 | ||||
|     virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/ | ||||
|     { | ||||
|         dc->SetBrush(*wxLIGHT_GREY_BRUSH); | ||||
|         dc->SetPen(*wxTRANSPARENT_PEN); | ||||
| 
 | ||||
|         rect.Deflate(2); | ||||
|         dc->DrawRoundedRectangle(rect, 5); | ||||
| 
 | ||||
|         RenderText(m_value, | ||||
|             0, // no offset
 | ||||
|             wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), | ||||
|             dc, | ||||
|             state); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|         virtual bool ActivateCell(const wxRect& WXUNUSED(cell), | ||||
|         wxDataViewModel *WXUNUSED(model), | ||||
|         const wxDataViewItem &WXUNUSED(item), | ||||
|         unsigned int WXUNUSED(col), | ||||
|         const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/ | ||||
|     { | ||||
|         wxString position; | ||||
|         if (mouseEvent) | ||||
|             position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y); | ||||
|         else | ||||
|             position = "from keyboard"; | ||||
| //		wxLogMessage("MyCustomRenderer ActivateCell() %s", position);
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|         virtual wxSize GetSize() const override/*wxOVERRIDE*/ | ||||
|     { | ||||
|         return wxSize(60, 20); | ||||
|     } | ||||
| 
 | ||||
|         virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/ | ||||
|     { | ||||
|         m_value = value.GetString(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|         virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; } | ||||
| 
 | ||||
|         virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; } | ||||
| 
 | ||||
|         virtual wxWindow* | ||||
|         CreateEditorCtrl(wxWindow* parent, | ||||
|         wxRect labelRect, | ||||
|         const wxVariant& value) override/*wxOVERRIDE*/ | ||||
|     { | ||||
|         wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value, | ||||
|         labelRect.GetPosition(), | ||||
|         labelRect.GetSize(), | ||||
|         wxTE_PROCESS_ENTER); | ||||
|         text->SetInsertionPointEnd(); | ||||
| 
 | ||||
|         return text; | ||||
|     } | ||||
| 
 | ||||
|         virtual bool | ||||
|             GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/ | ||||
|     { | ||||
|         wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl); | ||||
|         if (!text) | ||||
|             return false; | ||||
| 
 | ||||
|         value = text->GetValue(); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     wxString m_value; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| // ScalableBitmap
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
|  | @ -718,11 +157,14 @@ public: | |||
|     ScalableBitmap() {}; | ||||
|     ScalableBitmap( wxWindow *parent, | ||||
|                     const std::string& icon_name = "", | ||||
|                     const int px_cnt = 16, | ||||
|                     const bool is_horizontal = false); | ||||
|                     const int px_cnt = 16); | ||||
| 
 | ||||
|     ~ScalableBitmap() {} | ||||
| 
 | ||||
|     wxSize  GetBmpSize() const; | ||||
|     int     GetBmpWidth() const; | ||||
|     int     GetBmpHeight() const; | ||||
| 
 | ||||
|     void                msw_rescale(); | ||||
| 
 | ||||
|     const wxBitmap&     bmp() const { return m_bmp; } | ||||
|  | @ -730,14 +172,12 @@ public: | |||
|     const std::string&  name() const{ return m_icon_name; } | ||||
| 
 | ||||
|     int                 px_cnt()const           {return m_px_cnt;} | ||||
|     bool                is_horizontal()const    {return m_is_horizontal;} | ||||
| 
 | ||||
| private: | ||||
|     wxWindow*       m_parent{ nullptr }; | ||||
|     wxBitmap        m_bmp = wxBitmap(); | ||||
|     std::string     m_icon_name = ""; | ||||
|     int             m_px_cnt {16}; | ||||
|     bool            m_is_horizontal {false}; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -821,7 +261,6 @@ private: | |||
| 
 | ||||
|     // bitmap dimensions 
 | ||||
|     int             m_px_cnt{ 16 }; | ||||
|     bool            m_is_horizontal{ false }; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,58 +26,6 @@ const char *const SUPPORT_TEST_MODELS[] = { | |||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| // Test pair hash for 'nums' random number pairs.
 | ||||
| template <class I, class II> void test_pairhash() | ||||
| { | ||||
|     const constexpr size_t nums = 1000; | ||||
|     I A[nums] = {0}, B[nums] = {0}; | ||||
|     std::unordered_set<I> CH; | ||||
|     std::unordered_map<II, std::pair<I, I>> ints; | ||||
|      | ||||
|     std::random_device rd; | ||||
|     std::mt19937 gen(rd()); | ||||
|      | ||||
|     const I Ibits = int(sizeof(I) * CHAR_BIT); | ||||
|     const II IIbits = int(sizeof(II) * CHAR_BIT); | ||||
|      | ||||
|     int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits; | ||||
|     if (std::is_signed<I>::value) bits -= 1; | ||||
|     const I Imin = 0; | ||||
|     const I Imax = I(std::pow(2., bits) - 1); | ||||
|      | ||||
|     std::uniform_int_distribution<I> dis(Imin, Imax); | ||||
|      | ||||
|     for (size_t i = 0; i < nums;) { | ||||
|         I a = dis(gen); | ||||
|         if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; } | ||||
|     } | ||||
|      | ||||
|     for (size_t i = 0; i < nums;) { | ||||
|         I b = dis(gen); | ||||
|         if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; } | ||||
|     } | ||||
|      | ||||
|     for (size_t i = 0; i < nums; ++i) { | ||||
|         I a = A[i], b = B[i]; | ||||
|          | ||||
|         REQUIRE(a != b); | ||||
|          | ||||
|         II hash_ab = sla::pairhash<I, II>(a, b); | ||||
|         II hash_ba = sla::pairhash<I, II>(b, a); | ||||
|         REQUIRE(hash_ab == hash_ba); | ||||
|          | ||||
|         auto it = ints.find(hash_ab); | ||||
|          | ||||
|         if (it != ints.end()) { | ||||
|             REQUIRE(( | ||||
|                 (it->second.first == a && it->second.second == b) || | ||||
|                 (it->second.first == b && it->second.second == a) | ||||
|                 )); | ||||
|         } else | ||||
|             ints[hash_ab] = std::make_pair(a, b); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") { | ||||
|     test_pairhash<int, int>(); | ||||
|     test_pairhash<int, long>(); | ||||
|  | @ -225,69 +173,6 @@ TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { | |||
|     REQUIRE(raster.pixel_dimensions().h_mm == Approx(pixdim.h_mm)); | ||||
| } | ||||
| 
 | ||||
| using TPixel = uint8_t; | ||||
| static constexpr const TPixel FullWhite = 255; | ||||
| static constexpr const TPixel FullBlack = 0; | ||||
| 
 | ||||
| template <class A, int N> constexpr int arraysize(const A (&)[N]) { return N; } | ||||
| 
 | ||||
| static void check_raster_transformations(sla::Raster::Orientation o, | ||||
|                                          sla::Raster::TMirroring  mirroring) | ||||
| { | ||||
|     double disp_w = 120., disp_h = 68.; | ||||
|     sla::Raster::Resolution res{2560, 1440}; | ||||
|     sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; | ||||
|      | ||||
|     auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); | ||||
|     sla::Raster::Trafo trafo{o, mirroring}; | ||||
|     trafo.origin_x = bb.center().x(); | ||||
|     trafo.origin_y = bb.center().y(); | ||||
|      | ||||
|     sla::Raster raster{res, pixdim, trafo}; | ||||
|      | ||||
|     // create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors)
 | ||||
|     coord_t pw = 32 * coord_t(std::ceil(scaled<double>(pixdim.w_mm))); | ||||
|     coord_t ph = 32 * coord_t(std::ceil(scaled<double>(pixdim.h_mm))); | ||||
|     ExPolygon box; | ||||
|     box.contour.points = {{-pw, -ph}, {pw, -ph}, {pw, ph}, {-pw, ph}}; | ||||
|      | ||||
|     double tr_x = scaled<double>(20.), tr_y = tr_x; | ||||
|      | ||||
|     box.translate(tr_x, tr_y); | ||||
|     ExPolygon expected_box = box; | ||||
|      | ||||
|     // Now calculate the position of the translated box according to output
 | ||||
|     // trafo.
 | ||||
|     if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.); | ||||
|      | ||||
|     if (mirroring[X]) | ||||
|         for (auto &p : expected_box.contour.points) p.x() = -p.x(); | ||||
|      | ||||
|     if (mirroring[Y]) | ||||
|         for (auto &p : expected_box.contour.points) p.y() = -p.y(); | ||||
|      | ||||
|     raster.draw(box); | ||||
|      | ||||
|     Point expected_coords = expected_box.contour.bounding_box().center(); | ||||
|     double rx = unscaled(expected_coords.x() + bb.center().x()) / pixdim.w_mm; | ||||
|     double ry = unscaled(expected_coords.y() + bb.center().y()) / pixdim.h_mm; | ||||
|     auto w = size_t(std::floor(rx)); | ||||
|     auto h = res.height_px - size_t(std::floor(ry)); | ||||
|      | ||||
|     REQUIRE((w < res.width_px && h < res.height_px)); | ||||
|      | ||||
|     auto px = raster.read_pixel(w, h); | ||||
|      | ||||
|     if (px != FullWhite) { | ||||
|         sla::PNGImage img; | ||||
|         std::fstream outf("out.png", std::ios::out); | ||||
|          | ||||
|         outf << img.serialize(raster); | ||||
|     } | ||||
|      | ||||
|     REQUIRE(px == FullWhite); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { | ||||
|     sla::Raster::TMirroring mirrorings[] = {sla::Raster::NoMirror, | ||||
|                                             sla::Raster::MirrorX, | ||||
|  | @ -301,54 +186,6 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { | |||
|             check_raster_transformations(orientation, mirror); | ||||
| } | ||||
| 
 | ||||
| static ExPolygon square_with_hole(double v) | ||||
| { | ||||
|     ExPolygon poly; | ||||
|     coord_t V = scaled(v / 2.); | ||||
|      | ||||
|     poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}}; | ||||
|     poly.holes.emplace_back(); | ||||
|     V = V / 2; | ||||
|     poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}}; | ||||
|     return poly; | ||||
| } | ||||
| 
 | ||||
| static double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim) | ||||
| { | ||||
|     return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack); | ||||
| } | ||||
| 
 | ||||
| static double raster_white_area(const sla::Raster &raster) | ||||
| { | ||||
|     if (raster.empty()) return std::nan(""); | ||||
|      | ||||
|     auto res = raster.resolution(); | ||||
|     double a = 0; | ||||
|      | ||||
|     for (size_t x = 0; x < res.width_px; ++x) | ||||
|         for (size_t y = 0; y < res.height_px; ++y) { | ||||
|             auto px = raster.read_pixel(x, y); | ||||
|             a += pixel_area(px, raster.pixel_dimensions()); | ||||
|         } | ||||
|      | ||||
|     return a; | ||||
| } | ||||
| 
 | ||||
| static double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd) | ||||
| { | ||||
|     auto lines = p.lines(); | ||||
|     double pix_err = pixel_area(FullWhite, pd)  / 2.; | ||||
|      | ||||
|     // Worst case is when a line is parallel to the shorter axis of one pixel,
 | ||||
|     // when the line will be composed of the max number of pixels
 | ||||
|     double pix_l = std::min(pd.h_mm, pd.w_mm); | ||||
|      | ||||
|     double error = 0.; | ||||
|     for (auto &l : lines) | ||||
|         error += (unscaled(l.length()) / pix_l) * pix_err; | ||||
|      | ||||
|     return error; | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { | ||||
|     double disp_w = 120., disp_h = 68.; | ||||
|  | @ -388,8 +225,4 @@ TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]") | |||
|         std::fstream infile{"extruder_idler_quads.obj", std::ios::in}; | ||||
|         cntr.from_obj(infile); | ||||
|     } | ||||
|      | ||||
|      | ||||
|      | ||||
|      | ||||
| } | ||||
|  |  | |||
|  | @ -292,3 +292,103 @@ void check_validity(const TriangleMesh &input_mesh, int flags) | |||
|         REQUIRE(mesh.is_manifold()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirroring mirroring) | ||||
| { | ||||
|     double disp_w = 120., disp_h = 68.; | ||||
|     sla::Raster::Resolution res{2560, 1440}; | ||||
|     sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; | ||||
|      | ||||
|     auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); | ||||
|     sla::Raster::Trafo trafo{o, mirroring}; | ||||
|     trafo.origin_x = bb.center().x(); | ||||
|     trafo.origin_y = bb.center().y(); | ||||
|      | ||||
|     sla::Raster raster{res, pixdim, trafo}; | ||||
|      | ||||
|     // create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors)
 | ||||
|     coord_t pw = 32 * coord_t(std::ceil(scaled<double>(pixdim.w_mm))); | ||||
|     coord_t ph = 32 * coord_t(std::ceil(scaled<double>(pixdim.h_mm))); | ||||
|     ExPolygon box; | ||||
|     box.contour.points = {{-pw, -ph}, {pw, -ph}, {pw, ph}, {-pw, ph}}; | ||||
|      | ||||
|     double tr_x = scaled<double>(20.), tr_y = tr_x; | ||||
|      | ||||
|     box.translate(tr_x, tr_y); | ||||
|     ExPolygon expected_box = box; | ||||
|      | ||||
|     // Now calculate the position of the translated box according to output
 | ||||
|     // trafo.
 | ||||
|     if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.); | ||||
|      | ||||
|     if (mirroring[X]) | ||||
|         for (auto &p : expected_box.contour.points) p.x() = -p.x(); | ||||
|      | ||||
|     if (mirroring[Y]) | ||||
|         for (auto &p : expected_box.contour.points) p.y() = -p.y(); | ||||
|      | ||||
|     raster.draw(box); | ||||
|      | ||||
|     Point expected_coords = expected_box.contour.bounding_box().center(); | ||||
|     double rx = unscaled(expected_coords.x() + bb.center().x()) / pixdim.w_mm; | ||||
|     double ry = unscaled(expected_coords.y() + bb.center().y()) / pixdim.h_mm; | ||||
|     auto w = size_t(std::floor(rx)); | ||||
|     auto h = res.height_px - size_t(std::floor(ry)); | ||||
|      | ||||
|     REQUIRE((w < res.width_px && h < res.height_px)); | ||||
|      | ||||
|     auto px = raster.read_pixel(w, h); | ||||
|      | ||||
|     if (px != FullWhite) { | ||||
|         sla::PNGImage img; | ||||
|         std::fstream outf("out.png", std::ios::out); | ||||
|          | ||||
|         outf << img.serialize(raster); | ||||
|     } | ||||
|      | ||||
|     REQUIRE(px == FullWhite); | ||||
| } | ||||
| 
 | ||||
| ExPolygon square_with_hole(double v) | ||||
| { | ||||
|     ExPolygon poly; | ||||
|     coord_t V = scaled(v / 2.); | ||||
|      | ||||
|     poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}}; | ||||
|     poly.holes.emplace_back(); | ||||
|     V = V / 2; | ||||
|     poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}}; | ||||
|     return poly; | ||||
| } | ||||
| 
 | ||||
| double raster_white_area(const sla::Raster &raster) | ||||
| { | ||||
|     if (raster.empty()) return std::nan(""); | ||||
|      | ||||
|     auto res = raster.resolution(); | ||||
|     double a = 0; | ||||
|      | ||||
|     for (size_t x = 0; x < res.width_px; ++x) | ||||
|         for (size_t y = 0; y < res.height_px; ++y) { | ||||
|             auto px = raster.read_pixel(x, y); | ||||
|             a += pixel_area(px, raster.pixel_dimensions()); | ||||
|         } | ||||
|      | ||||
|     return a; | ||||
| } | ||||
| 
 | ||||
| double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd) | ||||
| { | ||||
|     auto lines = p.lines(); | ||||
|     double pix_err = pixel_area(FullWhite, pd)  / 2.; | ||||
|      | ||||
|     // Worst case is when a line is parallel to the shorter axis of one pixel,
 | ||||
|     // when the line will be composed of the max number of pixels
 | ||||
|     double pix_l = std::min(pd.h_mm, pd.w_mm); | ||||
|      | ||||
|     double error = 0.; | ||||
|     for (auto &l : lines) | ||||
|         error += (unscaled(l.length()) / pix_l) * pix_err; | ||||
|      | ||||
|     return error; | ||||
| } | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| 
 | ||||
| // Debug
 | ||||
| #include <fstream> | ||||
| #include <unordered_set> | ||||
| 
 | ||||
| #include "libslic3r/libslic3r.h" | ||||
| #include "libslic3r/Format/OBJ.hpp" | ||||
|  | @ -109,4 +110,78 @@ inline void test_support_model_collision( | |||
|     test_support_model_collision(obj_filename, input_supportcfg, hcfg, {}); | ||||
| } | ||||
| 
 | ||||
| // Test pair hash for 'nums' random number pairs.
 | ||||
| template <class I, class II> void test_pairhash() | ||||
| { | ||||
|     const constexpr size_t nums = 1000; | ||||
|     I A[nums] = {0}, B[nums] = {0}; | ||||
|     std::unordered_set<I> CH; | ||||
|     std::unordered_map<II, std::pair<I, I>> ints; | ||||
|      | ||||
|     std::random_device rd; | ||||
|     std::mt19937 gen(rd()); | ||||
|      | ||||
|     const I Ibits = int(sizeof(I) * CHAR_BIT); | ||||
|     const II IIbits = int(sizeof(II) * CHAR_BIT); | ||||
|      | ||||
|     int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits; | ||||
|     if (std::is_signed<I>::value) bits -= 1; | ||||
|     const I Imin = 0; | ||||
|     const I Imax = I(std::pow(2., bits) - 1); | ||||
|      | ||||
|     std::uniform_int_distribution<I> dis(Imin, Imax); | ||||
|      | ||||
|     for (size_t i = 0; i < nums;) { | ||||
|         I a = dis(gen); | ||||
|         if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; } | ||||
|     } | ||||
|      | ||||
|     for (size_t i = 0; i < nums;) { | ||||
|         I b = dis(gen); | ||||
|         if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; } | ||||
|     } | ||||
|      | ||||
|     for (size_t i = 0; i < nums; ++i) { | ||||
|         I a = A[i], b = B[i]; | ||||
|          | ||||
|         REQUIRE(a != b); | ||||
|          | ||||
|         II hash_ab = sla::pairhash<I, II>(a, b); | ||||
|         II hash_ba = sla::pairhash<I, II>(b, a); | ||||
|         REQUIRE(hash_ab == hash_ba); | ||||
|          | ||||
|         auto it = ints.find(hash_ab); | ||||
|          | ||||
|         if (it != ints.end()) { | ||||
|             REQUIRE(( | ||||
|                 (it->second.first == a && it->second.second == b) || | ||||
|                 (it->second.first == b && it->second.second == a) | ||||
|                 )); | ||||
|         } else | ||||
|             ints[hash_ab] = std::make_pair(a, b); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // SLA Raster test utils:
 | ||||
| 
 | ||||
| using TPixel = uint8_t; | ||||
| static constexpr const TPixel FullWhite = 255; | ||||
| static constexpr const TPixel FullBlack = 0; | ||||
| 
 | ||||
| template <class A, int N> constexpr int arraysize(const A (&)[N]) { return N; } | ||||
| 
 | ||||
| void check_raster_transformations(sla::Raster::Orientation o, | ||||
|                                          sla::Raster::TMirroring  mirroring); | ||||
| 
 | ||||
| ExPolygon square_with_hole(double v); | ||||
| 
 | ||||
| inline double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim) | ||||
| { | ||||
|     return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack); | ||||
| } | ||||
| 
 | ||||
| double raster_white_area(const sla::Raster &raster); | ||||
| 
 | ||||
| double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd); | ||||
| 
 | ||||
| #endif // SLA_TEST_UTILS_HPP
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lukas Matena
						Lukas Matena