mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	Merge branch lm_seam_painter_frontend
This commit is contained in:
		
						commit
						6646198a9b
					
				
					 24 changed files with 1233 additions and 861 deletions
				
			
		
							
								
								
									
										42
									
								
								resources/icons/seam.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								resources/icons/seam.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
| <svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||
| 	 viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> | ||||
| <g id="SLA_supports"> | ||||
| 	<g> | ||||
| 		<path fill="#FFFFFF" d="M117.02,121.54H11.12c-1.93,0-3.5-1.57-3.5-3.5v-3.99c0-1.93,1.57-3.5,3.5-3.5h105.9 | ||||
| 			c1.93,0,3.5,1.57,3.5,3.5v3.99C120.52,119.97,118.95,121.54,117.02,121.54z M11.12,113.55c-0.27,0-0.5,0.23-0.5,0.5v3.99 | ||||
| 			c0,0.27,0.23,0.5,0.5,0.5h105.9c0.27,0,0.5-0.23,0.5-0.5v-3.99c0-0.27-0.23-0.5-0.5-0.5H11.12z"/> | ||||
| 	</g> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M32.08,108.06c-0.83,0-1.5-0.67-1.5-1.5v-18.8c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5v18.8 | ||||
| 			C33.58,107.39,32.9,108.06,32.08,108.06z"/> | ||||
| 	</g> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M48.02,108.06c-0.83,0-1.5-0.67-1.5-1.5v-8.14c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5v8.14 | ||||
| 			C49.52,107.39,48.85,108.06,48.02,108.06z"/> | ||||
| 	</g> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M88.04,108.06c-0.83,0-1.5-0.67-1.5-1.5V93.09c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5v13.47 | ||||
| 			C89.54,107.39,88.87,108.06,88.04,108.06z"/> | ||||
| 	</g> | ||||
| 	<path fill="#FFFFFF" d="M70.36,95.12l-6.29,4.2l-14.45-9.63l-2.5,2.6l15.96,10.64c0.3,0.2,0.64,0.3,0.99,0.3s0.69-0.1,0.99-0.3 | ||||
| 		l7.9-5.27L70.36,95.12z"/> | ||||
| 	<polygon fill="#FFFFFF" points="88.97,86.99 86.35,84.46 77.91,90.09 80.5,92.63 	"/> | ||||
| 	<path fill="#FFFFFF" d="M103.99,35.1L65.05,9.14c-0.6-0.4-1.37-0.4-1.97,0L24.14,35.1c-0.49,0.33-0.79,0.88-0.79,1.48v38.91 | ||||
| 		c0,0.59,0.3,1.15,0.79,1.48l15.47,10.32l2.5-2.6L26.9,74.54V37.53l37.16-24.78l37.16,24.78v37.01l-7.32,4.88l2.61,2.53l7.46-4.98 | ||||
| 		c0.49-0.33,0.79-0.88,0.79-1.48V36.58C104.78,35.99,104.49,35.43,103.99,35.1z"/> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M96.16,108.06c-0.83,0-1.5-0.67-1.5-1.5V87.93L79,73.22c-0.6-0.57-0.63-1.52-0.07-2.12 | ||||
| 			c0.57-0.6,1.52-0.63,2.12-0.07l16.13,15.15c0.3,0.28,0.47,0.68,0.47,1.09v19.27C97.66,107.39,96.99,108.06,96.16,108.06z"/> | ||||
| 	</g> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M40.08,108.06c-0.83,0-1.5-0.67-1.5-1.5V93.29c0-0.39,0.15-0.76,0.41-1.04l16.13-16.91 | ||||
| 			c0.57-0.6,1.52-0.62,2.12-0.05c0.6,0.57,0.62,1.52,0.05,2.12L41.58,93.89v12.67C41.58,107.39,40.91,108.06,40.08,108.06z"/> | ||||
| 	</g> | ||||
| 	<g> | ||||
| 		<path fill="#ED6B21" d="M80.13,108.06c-0.83,0-1.5-0.67-1.5-1.5v-7.65L62.94,83.19c-0.59-0.59-0.58-1.54,0-2.12 | ||||
| 			c0.59-0.58,1.54-0.59,2.12,0l16.13,16.15c0.28,0.28,0.44,0.66,0.44,1.06v8.27C81.63,107.39,80.96,108.06,80.13,108.06z"/> | ||||
| 	</g> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 2.5 KiB | 
|  | @ -122,8 +122,8 @@ public: | |||
|     // Height of the extrusion, used for visualization purposes.
 | ||||
|     float height; | ||||
| 
 | ||||
|     ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {}; | ||||
|     ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {}; | ||||
|     ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {} | ||||
|     ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {} | ||||
|     ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} | ||||
|     ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} | ||||
|     ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} | ||||
|  |  | |||
|  | @ -87,6 +87,7 @@ const char* TRANSFORM_ATTR = "transform"; | |||
| const char* PRINTABLE_ATTR = "printable"; | ||||
| const char* INSTANCESCOUNT_ATTR = "instances_count"; | ||||
| const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; | ||||
| const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; | ||||
| 
 | ||||
| const char* KEY_ATTR = "key"; | ||||
| const char* VALUE_ATTR = "value"; | ||||
|  | @ -285,6 +286,7 @@ namespace Slic3r { | |||
|             std::vector<float> vertices; | ||||
|             std::vector<unsigned int> triangles; | ||||
|             std::vector<std::string> custom_supports; | ||||
|             std::vector<std::string> custom_seam; | ||||
| 
 | ||||
|             bool empty() | ||||
|             { | ||||
|  | @ -296,6 +298,7 @@ namespace Slic3r { | |||
|                 vertices.clear(); | ||||
|                 triangles.clear(); | ||||
|                 custom_supports.clear(); | ||||
|                 custom_seam.clear(); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|  | @ -1553,6 +1556,7 @@ namespace Slic3r { | |||
|         m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR)); | ||||
| 
 | ||||
|         m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); | ||||
|         m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|  | @ -1886,14 +1890,18 @@ namespace Slic3r { | |||
|                 volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); | ||||
|             volume->calculate_convex_hull(); | ||||
| 
 | ||||
|             // recreate custom supports from previously loaded attribute
 | ||||
|             // recreate custom supports and seam from previously loaded attribute
 | ||||
|             for (unsigned i=0; i<triangles_count; ++i) { | ||||
|                 size_t index = src_start_id/3 + i; | ||||
|                 assert(index < geometry.custom_supports.size()); | ||||
|                 assert(index < geometry.custom_seam.size()); | ||||
|                 if (! geometry.custom_supports[index].empty()) | ||||
|                     volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); | ||||
|                 if (! geometry.custom_seam[index].empty()) | ||||
|                     volume->m_seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             // apply the remaining volume's metadata
 | ||||
|             for (const Metadata& metadata : volume_data.metadata) | ||||
|             { | ||||
|  | @ -2410,6 +2418,10 @@ namespace Slic3r { | |||
|                 if (! custom_supports_data_string.empty()) | ||||
|                     stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" "; | ||||
| 
 | ||||
|                 std::string custom_seam_data_string = volume->m_seam_facets.get_triangle_as_string(i); | ||||
|                 if (! custom_seam_data_string.empty()) | ||||
|                     stream << CUSTOM_SEAM_ATTR << "=\"" << custom_seam_data_string << "\" "; | ||||
| 
 | ||||
|                 stream << "/>\n"; | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -2590,7 +2590,7 @@ plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| static Points::iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) | ||||
| static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) | ||||
| { | ||||
|     assert(polygon.points.size() >= 2); | ||||
|     if (polygon.points.size() <= 1) | ||||
|  | @ -2778,7 +2778,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou | |||
|         // Insert a projection of last_pos into the polygon.
 | ||||
|         size_t last_pos_proj_idx; | ||||
|         { | ||||
|             Points::iterator it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); | ||||
|             auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); | ||||
|             last_pos_proj_idx = it - polygon.points.begin(); | ||||
|         } | ||||
| 
 | ||||
|  | @ -2798,11 +2798,9 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou | |||
|             if (was_clockwise) | ||||
|                 ccwAngle = - ccwAngle; | ||||
|             float penalty = 0; | ||||
| //            if (ccwAngle <- float(PI/3.))
 | ||||
|             if (ccwAngle <- float(0.6 * PI)) | ||||
|                 // Sharp reflex vertex. We love that, it hides the seam perfectly.
 | ||||
|                 penalty = 0.f; | ||||
| //            else if (ccwAngle > float(PI/3.))
 | ||||
|             else if (ccwAngle > float(0.6 * PI)) | ||||
|                 // Seams on sharp convex vertices are more visible than on reflex vertices.
 | ||||
|                 penalty = penaltyConvexVertex; | ||||
|  | @ -2815,7 +2813,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou | |||
|                 penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); | ||||
|             } | ||||
|             // Give a negative penalty for points close to the last point or the prefered seam location.
 | ||||
|             //float dist_to_last_pos_proj = last_pos_proj.distance_to(polygon.points[i]);
 | ||||
|             float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?  | ||||
|                 std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :  | ||||
|                 std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); | ||||
|  | @ -2835,14 +2832,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou | |||
|                 // Signed distance is positive outside the object, negative inside the object.
 | ||||
|                 // The point is considered at an overhang, if it is more than nozzle radius
 | ||||
|                 // outside of the lower layer contour.
 | ||||
|                 #ifdef NDEBUG // to suppress unused variable warning in release mode
 | ||||
|                     (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); | ||||
|                 #else | ||||
|                     bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); | ||||
|                 #endif | ||||
|                 [[maybe_unused]] bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); | ||||
|                 // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
 | ||||
|                 // then the signed distnace shall always be known.
 | ||||
|                 assert(found);  | ||||
|                 assert(found); | ||||
|                 penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); | ||||
|             } | ||||
|         } | ||||
|  | @ -2850,7 +2843,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou | |||
|         // Find a point with a minimum penalty.
 | ||||
|         size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); | ||||
| 
 | ||||
|         // if (seam_position == spAligned)
 | ||||
|         // For all (aligned, nearest, rear) seams:
 | ||||
|         { | ||||
|             // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
 | ||||
|  |  | |||
|  | @ -1009,6 +1009,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial | |||
|     for (ModelVolume* volume : volumes) | ||||
|     { | ||||
|         volume->m_supported_facets.clear(); | ||||
|         volume->m_seam_facets.clear(); | ||||
|         if (!volume->mesh().empty()) { | ||||
|             TriangleMesh mesh(volume->mesh()); | ||||
|             mesh.require_shared_vertices(); | ||||
|  | @ -1114,6 +1115,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b | |||
|         const auto volume_matrix = volume->get_matrix(); | ||||
| 
 | ||||
|         volume->m_supported_facets.clear(); | ||||
|         volume->m_seam_facets.clear(); | ||||
| 
 | ||||
|         if (! volume->is_model_part()) { | ||||
|             // Modifiers are not cut, but we still need to add the instance transformation
 | ||||
|  | @ -1833,7 +1835,7 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSupportType type) const | ||||
| indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const | ||||
| { | ||||
|     TriangleSelector selector(mv.mesh()); | ||||
|     selector.deserialize(m_data); | ||||
|  | @ -1995,6 +1997,16 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject | |||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new) { | ||||
|     assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART)); | ||||
|     assert(mo.volumes.size() == mo_new.volumes.size()); | ||||
|     for (size_t i=0; i<mo.volumes.size(); ++i) { | ||||
|         if (! mo_new.volumes[i]->m_seam_facets.is_same_as(mo.volumes[i]->m_seam_facets)) | ||||
|             return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| extern bool model_has_multi_part_objects(const Model &model) | ||||
| { | ||||
|     for (const ModelObject *model_object : model.objects) | ||||
|  |  | |||
|  | @ -394,7 +394,7 @@ enum class ModelVolumeType : int { | |||
|     SUPPORT_BLOCKER, | ||||
| }; | ||||
| 
 | ||||
| enum class FacetSupportType : int8_t { | ||||
| enum class EnforcerBlockerType : int8_t { | ||||
|     // Maximum is 3. The value is serialized in TriangleSelector into 2 bits!
 | ||||
|     NONE      = 0, | ||||
|     ENFORCER  = 1, | ||||
|  | @ -407,7 +407,7 @@ public: | |||
| 
 | ||||
|     const std::map<int, std::vector<bool>>& get_data() const { return m_data; } | ||||
|     bool set(const TriangleSelector& selector); | ||||
|     indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; | ||||
|     indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; | ||||
|     void clear(); | ||||
|     std::string get_triangle_as_string(int i) const; | ||||
|     void set_triangle_from_string(int triangle_id, const std::string& str); | ||||
|  | @ -464,6 +464,9 @@ public: | |||
|     // List of mesh facets to be supported/unsupported.
 | ||||
|     FacetsAnnotation    m_supported_facets; | ||||
| 
 | ||||
|     // List of seam enforcers/blockers.
 | ||||
|     FacetsAnnotation   m_seam_facets; | ||||
| 
 | ||||
|     // A parent object owning this modifier volume.
 | ||||
|     ModelObject*        get_object() const { return this->object; } | ||||
|     ModelVolumeType     type() const { return m_type; } | ||||
|  | @ -593,7 +596,7 @@ private: | |||
|         ObjectBase(other), | ||||
|         name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), | ||||
|         config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), | ||||
|         m_supported_facets(other.m_supported_facets) | ||||
|         m_supported_facets(other.m_supported_facets), m_seam_facets(other.m_seam_facets) | ||||
|     { | ||||
| 		assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); | ||||
| 		assert(this->id() == other.id() && this->config.id() == other.config.id()); | ||||
|  | @ -612,6 +615,7 @@ private: | |||
| 		assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id()); | ||||
| 
 | ||||
|         m_supported_facets.clear(); | ||||
|         m_seam_facets.clear(); | ||||
|     } | ||||
| 
 | ||||
|     ModelVolume& operator=(ModelVolume &rhs) = delete; | ||||
|  | @ -625,7 +629,7 @@ private: | |||
| 	template<class Archive> void load(Archive &ar) { | ||||
| 		bool has_convex_hull; | ||||
|         ar(name, source, m_mesh, m_type, m_material_id, m_transformation, | ||||
|            m_is_splittable, has_convex_hull, m_supported_facets); | ||||
|            m_is_splittable, has_convex_hull, m_supported_facets, m_seam_facets); | ||||
|         cereal::load_by_value(ar, config); | ||||
| 		assert(m_mesh); | ||||
| 		if (has_convex_hull) { | ||||
|  | @ -639,7 +643,7 @@ private: | |||
| 	template<class Archive> void save(Archive &ar) const { | ||||
| 		bool has_convex_hull = m_convex_hull.get() != nullptr; | ||||
|         ar(name, source, m_mesh, m_type, m_material_id, m_transformation, | ||||
|            m_is_splittable, has_convex_hull, m_supported_facets); | ||||
|            m_is_splittable, has_convex_hull, m_supported_facets, m_seam_facets); | ||||
|         cereal::save_by_value(ar, config); | ||||
| 		if (has_convex_hull) | ||||
| 			cereal::save_optional(ar, m_convex_hull); | ||||
|  | @ -904,6 +908,10 @@ extern bool model_volume_list_changed(const ModelObject &model_object_old, const | |||
| // The function assumes that volumes list is synchronized.
 | ||||
| extern bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new); | ||||
| 
 | ||||
| // Test whether the now ModelObject has newer custom seam data than the old one.
 | ||||
| // The function assumes that volumes list is synchronized.
 | ||||
| extern bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new); | ||||
| 
 | ||||
| // If the model has multi-part objects, then it is currently not supported by the SLA mode.
 | ||||
| // Either the model cannot be loaded, or a SLA printer has to be activated.
 | ||||
| extern bool model_has_multi_part_objects(const Model &model); | ||||
|  |  | |||
|  | @ -404,6 +404,7 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, | |||
|         mv_dst.name   = mv_src.name; | ||||
| 		static_cast<DynamicPrintConfig&>(mv_dst.config) = static_cast<const DynamicPrintConfig&>(mv_src.config); | ||||
|         mv_dst.m_supported_facets = mv_src.m_supported_facets; | ||||
|         mv_dst.m_seam_facets = mv_src.m_seam_facets; | ||||
|         //FIXME what to do with the materials?
 | ||||
|         // mv_dst.m_material_id = mv_src.m_material_id;
 | ||||
|         ++ i_src; | ||||
|  | @ -867,6 +868,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ | |||
|                 model_volume_list_update_supports(model_object, model_object_new); | ||||
|             } | ||||
|         } | ||||
|         if (model_custom_seam_data_changed(model_object, model_object_new)) { | ||||
|             update_apply_status(this->invalidate_step(psGCodeExport)); | ||||
|         } | ||||
|         if (! model_parts_differ && ! modifiers_differ) { | ||||
|             // Synchronize Object's config.
 | ||||
|             bool object_config_changed = model_object.config != model_object_new.config; | ||||
|  |  | |||
|  | @ -191,10 +191,8 @@ public: | |||
|     std::vector<ExPolygons>     slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); } | ||||
|     std::vector<ExPolygons>     slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } | ||||
| 
 | ||||
|     // Helpers to project custom supports on slices
 | ||||
|     void project_and_append_custom_supports(FacetSupportType type, std::vector<ExPolygons>& expolys) const; | ||||
|     void project_and_append_custom_enforcers(std::vector<ExPolygons>& enforcers) const { project_and_append_custom_supports(FacetSupportType::ENFORCER, enforcers); } | ||||
|     void project_and_append_custom_blockers(std::vector<ExPolygons>& blockers) const { project_and_append_custom_supports(FacetSupportType::BLOCKER, blockers); } | ||||
|     // Helpers to project custom facets on slices
 | ||||
|     void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector<ExPolygons>& expolys) const; | ||||
| 
 | ||||
| private: | ||||
|     // to be called from Print only.
 | ||||
|  |  | |||
|  | @ -2669,12 +2669,14 @@ void PrintObject::_generate_support_material() | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void PrintObject::project_and_append_custom_supports( | ||||
|         FacetSupportType type, std::vector<ExPolygons>& expolys) const | ||||
| void PrintObject::project_and_append_custom_facets( | ||||
|         bool seam, EnforcerBlockerType type, std::vector<ExPolygons>& expolys) const | ||||
| { | ||||
|     for (const ModelVolume* mv : this->model_object()->volumes) { | ||||
|         const indexed_triangle_set custom_facets = mv->m_supported_facets.get_facets(*mv, type); | ||||
|         if (custom_facets.indices.empty()) | ||||
|         const indexed_triangle_set custom_facets = seam | ||||
|                 ? mv->m_seam_facets.get_facets(*mv, type) | ||||
|                 : mv->m_supported_facets.get_facets(*mv, type); | ||||
|         if (! mv->is_model_part() || custom_facets.indices.empty()) | ||||
|             continue; | ||||
| 
 | ||||
|         const Transform3f& tr1 = mv->get_matrix().cast<float>(); | ||||
|  | @ -2721,7 +2723,7 @@ void PrintObject::project_and_append_custom_supports( | |||
| 
 | ||||
|             // Ignore triangles with upward-pointing normal. Don't forget about mirroring.
 | ||||
|             float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z(); | ||||
|             if (tr_det_sign * z_comp > 0.) | ||||
|             if (! seam && tr_det_sign * z_comp > 0.) | ||||
|                 continue; | ||||
| 
 | ||||
|             // Sort the three vertices according to z-coordinate.
 | ||||
|  |  | |||
|  | @ -972,8 +972,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ | |||
|     std::vector<ExPolygons> blockers  = object.slice_support_blockers(); | ||||
| 
 | ||||
|     // Append custom supports.
 | ||||
|     object.project_and_append_custom_enforcers(enforcers); | ||||
|     object.project_and_append_custom_blockers(blockers); | ||||
|     object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers); | ||||
|     object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); | ||||
| 
 | ||||
|     // Output layers, sorted by top Z.
 | ||||
|     MyLayersPtr contact_out; | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si | |||
| 
 | ||||
| void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, | ||||
|                                     const Vec3f& source, const Vec3f& dir, | ||||
|                                     float radius, FacetSupportType new_state) | ||||
|                                     float radius, EnforcerBlockerType new_state) | ||||
| { | ||||
|     assert(facet_start < m_orig_size_indices); | ||||
|     assert(is_approx(dir.norm(), 1.f)); | ||||
|  | @ -77,7 +77,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, | |||
| // the triangle recursively, selecting just subtriangles truly inside the circle.
 | ||||
| // This is done by an actual recursive call. Returns false if the triangle is
 | ||||
| // outside the cursor.
 | ||||
| bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) | ||||
| bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool recursive_call) | ||||
| { | ||||
|     assert(facet_idx < int(m_triangles.size())); | ||||
| 
 | ||||
|  | @ -140,7 +140,7 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void TriangleSelector::set_facet(int facet_idx, FacetSupportType state) | ||||
| void TriangleSelector::set_facet(int facet_idx, EnforcerBlockerType state) | ||||
| { | ||||
|     assert(facet_idx < m_orig_size_indices); | ||||
|     undivide_triangle(facet_idx); | ||||
|  | @ -157,7 +157,7 @@ void TriangleSelector::split_triangle(int facet_idx) | |||
| 
 | ||||
|     Triangle* tr = &m_triangles[facet_idx]; | ||||
| 
 | ||||
|     FacetSupportType old_type = tr->get_state(); | ||||
|     EnforcerBlockerType old_type = tr->get_state(); | ||||
| 
 | ||||
|     if (tr->was_split_before() != 0) { | ||||
|         // This triangle is not split at the moment, but was at one point
 | ||||
|  | @ -323,7 +323,7 @@ void TriangleSelector::remove_useless_children(int facet_idx) | |||
| 
 | ||||
| 
 | ||||
|     // Return if a child is not leaf or two children differ in type.
 | ||||
|     FacetSupportType first_child_type = FacetSupportType::NONE; | ||||
|     EnforcerBlockerType first_child_type = EnforcerBlockerType::NONE; | ||||
|     for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { | ||||
|         if (m_triangles[tr.children[child_idx]].is_split()) | ||||
|             return; | ||||
|  | @ -456,7 +456,7 @@ void TriangleSelector::push_triangle(int a, int b, int c) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) | ||||
| void TriangleSelector::perform_split(int facet_idx, EnforcerBlockerType old_state) | ||||
| { | ||||
|     Triangle* tr = &m_triangles[facet_idx]; | ||||
| 
 | ||||
|  | @ -520,7 +520,7 @@ void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| indexed_triangle_set TriangleSelector::get_facets(FacetSupportType state) const | ||||
| indexed_triangle_set TriangleSelector::get_facets(EnforcerBlockerType state) const | ||||
| { | ||||
|     indexed_triangle_set out; | ||||
|     for (const Triangle& tr : m_triangles) { | ||||
|  | @ -542,7 +542,7 @@ std::map<int, std::vector<bool>> TriangleSelector::serialize() const | |||
| { | ||||
|     // Each original triangle of the mesh is assigned a number encoding its state
 | ||||
|     // or how it is split. Each triangle is encoded by 4 bits (xxyy):
 | ||||
|     // leaf triangle: xx = FacetSupportType, yy = 0
 | ||||
|     // leaf triangle: xx = EnforcerBlockerType, yy = 0
 | ||||
|     // non-leaf:      xx = special side, yy = number of split sides
 | ||||
|     // These are bitwise appended and formed into one 64-bit integer.
 | ||||
| 
 | ||||
|  | @ -553,7 +553,7 @@ std::map<int, std::vector<bool>> TriangleSelector::serialize() const | |||
|     for (int i=0; i<m_orig_size_indices; ++i) { | ||||
|         const Triangle& tr = m_triangles[i]; | ||||
| 
 | ||||
|         if (! tr.is_split() && tr.get_state() == FacetSupportType::NONE) | ||||
|         if (! tr.is_split() && tr.get_state() == EnforcerBlockerType::NONE) | ||||
|             continue; // no need to save anything, unsplit and unselected is default
 | ||||
| 
 | ||||
|         std::vector<bool> data; // complete encoding of this mesh triangle
 | ||||
|  | @ -627,7 +627,7 @@ void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data) | |||
|             int num_of_split_sides = (next_code & 0b11); | ||||
|             int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; | ||||
|             bool is_split = num_of_children != 0; | ||||
|             FacetSupportType state = FacetSupportType(next_code >> 2); | ||||
|             EnforcerBlockerType state = EnforcerBlockerType(next_code >> 2); | ||||
|             int special_side = (next_code >> 2); | ||||
| 
 | ||||
|             // Take care of the first iteration separately, so handling of the others is simpler.
 | ||||
|  | @ -641,7 +641,7 @@ void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data) | |||
|                     // then go to the next.
 | ||||
|                     parents.push_back({triangle_id, 0, num_of_children}); | ||||
|                     m_triangles[triangle_id].set_division(num_of_children-1, special_side); | ||||
|                     perform_split(triangle_id, FacetSupportType::NONE); | ||||
|                     perform_split(triangle_id, EnforcerBlockerType::NONE); | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
|  | @ -655,7 +655,7 @@ void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data) | |||
|                 const ProcessingInfo& last = parents.back(); | ||||
|                 int this_idx = m_triangles[last.facet_id].children[last.processed_children]; | ||||
|                 m_triangles[this_idx].set_division(num_of_children-1, special_side); | ||||
|                 perform_split(this_idx, FacetSupportType::NONE); | ||||
|                 perform_split(this_idx, EnforcerBlockerType::NONE); | ||||
|                 parents.push_back({this_idx, 0, num_of_children}); | ||||
|             } else { | ||||
|                 // this triangle belongs to last split one
 | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| enum class FacetSupportType : int8_t; | ||||
| enum class EnforcerBlockerType : int8_t; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | @ -29,13 +29,13 @@ public: | |||
|                       const Vec3f& source, // camera position (mesh coords)
 | ||||
|                       const Vec3f& dir,    // direction of the ray (mesh coords)
 | ||||
|                       float radius,        // radius of the cursor
 | ||||
|                       FacetSupportType new_state);   // enforcer or blocker?
 | ||||
|                       EnforcerBlockerType new_state);   // enforcer or blocker?
 | ||||
| 
 | ||||
|     // Get facets currently in the given state.
 | ||||
|     indexed_triangle_set get_facets(FacetSupportType state) const; | ||||
|     indexed_triangle_set get_facets(EnforcerBlockerType state) const; | ||||
| 
 | ||||
|     // Set facet of the mesh to a given state. Only works for original triangles.
 | ||||
|     void set_facet(int facet_idx, FacetSupportType state); | ||||
|     void set_facet(int facet_idx, EnforcerBlockerType state); | ||||
| 
 | ||||
|     // Clear everything and make the tree empty.
 | ||||
|     void reset(); | ||||
|  | @ -59,7 +59,7 @@ protected: | |||
|         // It increments/decrements reference counter on vertices.
 | ||||
|         Triangle(int a, int b, int c) | ||||
|             : verts_idxs{a, b, c}, | ||||
|               state{FacetSupportType(0)}, | ||||
|               state{EnforcerBlockerType(0)}, | ||||
|               number_of_splits{0}, | ||||
|               special_side_idx{0}, | ||||
|               old_number_of_splits{0} | ||||
|  | @ -77,8 +77,8 @@ protected: | |||
|         void set_division(int sides_to_split, int special_side_idx = -1); | ||||
| 
 | ||||
|         // Get/set current state.
 | ||||
|         void set_state(FacetSupportType type) { assert(! is_split()); state = type; } | ||||
|         FacetSupportType get_state() const { assert(! is_split()); return state; } | ||||
|         void set_state(EnforcerBlockerType type) { assert(! is_split()); state = type; } | ||||
|         EnforcerBlockerType get_state() const { assert(! is_split()); return state; } | ||||
| 
 | ||||
|         // Get info on how it's split.
 | ||||
|         bool is_split() const { return number_of_split_sides() != 0; } | ||||
|  | @ -90,7 +90,7 @@ protected: | |||
|     private: | ||||
|         int number_of_splits; | ||||
|         int special_side_idx; | ||||
|         FacetSupportType state; | ||||
|         EnforcerBlockerType state; | ||||
| 
 | ||||
|         // How many children were spawned during last split?
 | ||||
|         // Is not reset on remerging the triangle.
 | ||||
|  | @ -133,7 +133,7 @@ protected: | |||
|     float m_old_cursor_radius; | ||||
| 
 | ||||
|     // Private functions:
 | ||||
|     bool select_triangle(int facet_idx, FacetSupportType type, | ||||
|     bool select_triangle(int facet_idx, EnforcerBlockerType type, | ||||
|                          bool recursive_call = false); | ||||
|     bool is_point_inside_cursor(const Vec3f& point) const; | ||||
|     int  vertices_inside(int facet_idx) const; | ||||
|  | @ -144,7 +144,7 @@ protected: | |||
|     bool is_pointer_in_triangle(int facet_idx) const; | ||||
|     bool is_edge_inside_cursor(int facet_idx) const; | ||||
|     void push_triangle(int a, int b, int c); | ||||
|     void perform_split(int facet_idx, FacetSupportType old_state); | ||||
|     void perform_split(int facet_idx, EnforcerBlockerType old_state); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -53,6 +53,10 @@ set(SLIC3R_GUI_SOURCES | |||
|     GUI/Gizmos/GLGizmoCut.hpp | ||||
|     GUI/Gizmos/GLGizmoHollow.cpp | ||||
|     GUI/Gizmos/GLGizmoHollow.hpp | ||||
|     GUI/Gizmos/GLGizmoPainterBase.cpp | ||||
|     GUI/Gizmos/GLGizmoPainterBase.hpp | ||||
|     GUI/Gizmos/GLGizmoSeam.cpp | ||||
|     GUI/Gizmos/GLGizmoSeam.hpp | ||||
|     GUI/GLSelectionRectangle.cpp | ||||
|     GUI/GLSelectionRectangle.hpp | ||||
|     GUI/GLModel.hpp | ||||
|  |  | |||
|  | @ -3674,7 +3674,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|         else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) | ||||
|         { | ||||
|             if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports | ||||
|              && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports) | ||||
|              && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports | ||||
|              && m_gizmos.get_current_type() != GLGizmosManager::Seam) | ||||
|             { | ||||
|                 m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); | ||||
|                 m_dirty = true; | ||||
|  | @ -3749,14 +3750,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|         if (!m_mouse.drag.move_requires_threshold) | ||||
|         { | ||||
|             m_mouse.dragging = true; | ||||
| 
 | ||||
|             // Translation of objects is forbidden when SLA supports/hollowing/fdm
 | ||||
|             // supports gizmo is active.
 | ||||
|             if (m_gizmos.get_current_type() == GLGizmosManager::SlaSupports | ||||
|              || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports | ||||
|              || m_gizmos.get_current_type() == GLGizmosManager::Hollow) | ||||
|                 return; | ||||
| 
 | ||||
|             Vec3d cur_pos = m_mouse.drag.start_position_3D; | ||||
|             // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag
 | ||||
|             if (m_selection.contains_volume(get_first_hover_volume_idx())) | ||||
|  | @ -5474,7 +5467,8 @@ void GLCanvas3D::_render_bed(bool bottom, bool show_axes) const | |||
| 
 | ||||
|     bool show_texture = ! bottom || | ||||
|             (m_gizmos.get_current_type() != GLGizmosManager::FdmSupports | ||||
|           && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports); | ||||
|           && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports | ||||
|           && m_gizmos.get_current_type() != GLGizmosManager::Seam); | ||||
| 
 | ||||
|     wxGetApp().plater()->get_bed().render(const_cast<GLCanvas3D&>(*this), bottom, scale_factor, show_axes, show_texture); | ||||
| } | ||||
|  |  | |||
|  | @ -41,6 +41,7 @@ | |||
| #include "3DScene.hpp" | ||||
| #include "MainFrame.hpp" | ||||
| #include "Plater.hpp" | ||||
| #include "GLCanvas3D.hpp" | ||||
| 
 | ||||
| #include "../Utils/PresetUpdater.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
|  | @ -1026,6 +1027,7 @@ void GUI_App::update_mode() | |||
|         tab->update_mode(); | ||||
| 
 | ||||
|     plater()->update_object_menu(); | ||||
|     plater()->canvas3D()->update_gizmos_on_off_state(); | ||||
| } | ||||
| 
 | ||||
| void GUI_App::add_config_menu(wxMenuBar *menu) | ||||
|  |  | |||
|  | @ -124,7 +124,6 @@ public: | |||
|     void set_state(EState state) { m_state = state; on_set_state(); } | ||||
| 
 | ||||
|     int get_shortcut_key() const { return m_shortcut_key; } | ||||
|     void set_shortcut_key(int key) { m_shortcut_key = key; } | ||||
| 
 | ||||
|     const std::string& get_icon_filename() const { return m_icon_filename; } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,40 +1,40 @@ | |||
| // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
 | ||||
| #include "GLGizmoFdmSupports.hpp" | ||||
| 
 | ||||
| #include "libslic3r/Model.hpp" | ||||
| 
 | ||||
| //#include "slic3r/GUI/3DScene.hpp"
 | ||||
| #include "slic3r/GUI/GLCanvas3D.hpp" | ||||
| #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" | ||||
| #include "slic3r/GUI/GUI_App.hpp" | ||||
| #include "slic3r/GUI/ImGuiWrapper.hpp" | ||||
| #include "slic3r/GUI/Plater.hpp" | ||||
| 
 | ||||
| 
 | ||||
| #include <GL/glew.h> | ||||
| 
 | ||||
| #include "slic3r/GUI/GUI_App.hpp" | ||||
| #include "slic3r/GUI/Camera.hpp" | ||||
| #include "slic3r/GUI/Plater.hpp" | ||||
| #include "libslic3r/PresetBundle.hpp" | ||||
| #include "libslic3r/Model.hpp" | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| 
 | ||||
| GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, icon_filename, sprite_id) | ||||
|     , m_quadric(nullptr) | ||||
| 
 | ||||
| void GLGizmoFdmSupports::on_shutdown() | ||||
| { | ||||
|     m_clipping_plane.reset(new ClippingPlane()); | ||||
|     m_quadric = ::gluNewQuadric(); | ||||
|     if (m_quadric != nullptr) | ||||
|         // using GLU_FILL does not work when the instance's transformation
 | ||||
|         // contains mirroring (normals are reverted)
 | ||||
|         ::gluQuadricDrawStyle(m_quadric, GLU_FILL); | ||||
|     if (m_setting_angle) { | ||||
|         m_setting_angle = false; | ||||
|         m_parent.use_slope(false); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| GLGizmoFdmSupports::~GLGizmoFdmSupports() | ||||
| 
 | ||||
| 
 | ||||
| std::string GLGizmoFdmSupports::on_get_name() const | ||||
| { | ||||
|     if (m_quadric != nullptr) | ||||
|         ::gluDeleteQuadric(m_quadric); | ||||
|     return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| bool GLGizmoFdmSupports::on_init() | ||||
| { | ||||
|     m_shortcut_key = WXK_CONTROL_L; | ||||
|  | @ -54,38 +54,6 @@ bool GLGizmoFdmSupports::on_init() | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::activate_internal_undo_redo_stack(bool activate) | ||||
| { | ||||
|     if (activate && ! m_internal_stack_active) { | ||||
|         Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on")); | ||||
|         wxGetApp().plater()->enter_gizmos_stack(); | ||||
|         m_internal_stack_active = true; | ||||
|     } | ||||
|     if (! activate && m_internal_stack_active) { | ||||
|         wxGetApp().plater()->leave_gizmos_stack(); | ||||
|         Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off")); | ||||
|         m_internal_stack_active = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const Selection& selection) | ||||
| { | ||||
|     if (m_state != On) | ||||
|         return; | ||||
| 
 | ||||
|     const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; | ||||
| 
 | ||||
|     if (mo && selection.is_from_single_instance() | ||||
|      && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) | ||||
|     { | ||||
|         update_from_model_object(); | ||||
|         m_old_mo_id = mo->id(); | ||||
|         m_old_volumes_size = mo->volumes.size(); | ||||
|         m_schedule_update = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::on_render() const | ||||
| { | ||||
|  | @ -94,7 +62,8 @@ void GLGizmoFdmSupports::on_render() const | |||
|     glsafe(::glEnable(GL_BLEND)); | ||||
|     glsafe(::glEnable(GL_DEPTH_TEST)); | ||||
| 
 | ||||
|     render_triangles(selection); | ||||
|     if (! m_setting_angle) | ||||
|         render_triangles(selection); | ||||
| 
 | ||||
|     m_c->object_clipper()->render_cut(); | ||||
|     render_cursor_circle(); | ||||
|  | @ -102,405 +71,6 @@ void GLGizmoFdmSupports::on_render() const | |||
|     glsafe(::glDisable(GL_BLEND)); | ||||
| } | ||||
| 
 | ||||
| void GLGizmoFdmSupports::render_triangles(const Selection& selection) const | ||||
| { | ||||
|     if (m_setting_angle) | ||||
|         return; | ||||
| 
 | ||||
|     const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
| 
 | ||||
|     glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); | ||||
|     ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); } ); | ||||
|     glsafe(::glPolygonOffset(-1.0, 1.0)); | ||||
| 
 | ||||
|     // Take care of the clipping plane. The normal of the clipping plane is
 | ||||
|     // saved with opposite sign than we need to pass to OpenGL (FIXME)
 | ||||
|     bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; | ||||
|     if (clipping_plane_active) { | ||||
|         const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); | ||||
|         double clp_data[4]; | ||||
|         memcpy(clp_data, clp->get_data(), 4 * sizeof(double)); | ||||
|         for (int i=0; i<3; ++i) | ||||
|             clp_data[i] = -1. * clp_data[i]; | ||||
| 
 | ||||
|         glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)clp_data)); | ||||
|         glsafe(::glEnable(GL_CLIP_PLANE0)); | ||||
|     } | ||||
| 
 | ||||
|     int mesh_id = -1; | ||||
|     for (const ModelVolume* mv : mo->volumes) { | ||||
|         if (! mv->is_model_part()) | ||||
|             continue; | ||||
| 
 | ||||
|         ++mesh_id; | ||||
| 
 | ||||
|         const Transform3d trafo_matrix = | ||||
|             mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * | ||||
|             mv->get_matrix(); | ||||
| 
 | ||||
|         bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; | ||||
|         if (is_left_handed) | ||||
|             glsafe(::glFrontFace(GL_CW)); | ||||
| 
 | ||||
|         glsafe(::glPushMatrix()); | ||||
|         glsafe(::glMultMatrixd(trafo_matrix.data())); | ||||
| 
 | ||||
|         if (! m_setting_angle) | ||||
|             m_triangle_selectors[mesh_id]->render(m_imgui); | ||||
| 
 | ||||
|         glsafe(::glPopMatrix()); | ||||
|         if (is_left_handed) | ||||
|             glsafe(::glFrontFace(GL_CCW)); | ||||
|     } | ||||
|     if (clipping_plane_active) | ||||
|         glsafe(::glDisable(GL_CLIP_PLANE0)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::render_cursor_circle() const | ||||
| { | ||||
|     const Camera& camera = wxGetApp().plater()->get_camera(); | ||||
|     float zoom = (float)camera.get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| 
 | ||||
|     Size cnv_size = m_parent.get_canvas_size(); | ||||
|     float cnv_half_width = 0.5f * (float)cnv_size.get_width(); | ||||
|     float cnv_half_height = 0.5f * (float)cnv_size.get_height(); | ||||
|     if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f)) | ||||
|         return; | ||||
|     Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1)); | ||||
|     Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1)); | ||||
|     center = center * inv_zoom; | ||||
| 
 | ||||
|     glsafe(::glLineWidth(1.5f)); | ||||
|     float color[3]; | ||||
|     color[0] = 0.f; | ||||
|     color[1] = 1.f; | ||||
|     color[2] = 0.3f; | ||||
|     glsafe(::glColor3fv(color)); | ||||
|     glsafe(::glDisable(GL_DEPTH_TEST)); | ||||
| 
 | ||||
|     glsafe(::glPushMatrix()); | ||||
|     glsafe(::glLoadIdentity()); | ||||
|     // ensure that the circle is renderered inside the frustrum
 | ||||
|     glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); | ||||
|     // ensure that the overlay fits the frustrum near z plane
 | ||||
|     double gui_scale = camera.get_gui_scale(); | ||||
|     glsafe(::glScaled(gui_scale, gui_scale, 1.0)); | ||||
| 
 | ||||
|     glsafe(::glPushAttrib(GL_ENABLE_BIT)); | ||||
|     glsafe(::glLineStipple(4, 0xAAAA)); | ||||
|     glsafe(::glEnable(GL_LINE_STIPPLE)); | ||||
| 
 | ||||
|     ::glBegin(GL_LINE_LOOP); | ||||
|     for (double angle=0; angle<2*M_PI; angle+=M_PI/20.) | ||||
|         ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle))); | ||||
|     glsafe(::glEnd()); | ||||
| 
 | ||||
|     glsafe(::glPopAttrib()); | ||||
|     glsafe(::glPopMatrix()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::update_model_object() const | ||||
| { | ||||
|     bool updated = false; | ||||
|     ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|     int idx = -1; | ||||
|     for (ModelVolume* mv : mo->volumes) { | ||||
|         if (! mv->is_model_part()) | ||||
|             continue; | ||||
|         ++idx; | ||||
|         updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); | ||||
|     } | ||||
| 
 | ||||
|     if (updated) | ||||
|         m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::update_from_model_object() | ||||
| { | ||||
|     wxBusyCursor wait; | ||||
| 
 | ||||
|     const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|     m_triangle_selectors.clear(); | ||||
| 
 | ||||
|     int volume_id = -1; | ||||
|     for (const ModelVolume* mv : mo->volumes) { | ||||
|         if (! mv->is_model_part()) | ||||
|             continue; | ||||
| 
 | ||||
|         ++volume_id; | ||||
| 
 | ||||
|         // This mesh does not account for the possible Z up SLA offset.
 | ||||
|         const TriangleMesh* mesh = &mv->mesh(); | ||||
| 
 | ||||
|         m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh)); | ||||
|         m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| bool GLGizmoFdmSupports::is_mesh_point_clipped(const Vec3d& point) const | ||||
| { | ||||
|     if (m_c->object_clipper()->get_position() == 0.) | ||||
|         return false; | ||||
| 
 | ||||
|     auto sel_info = m_c->selection_info(); | ||||
|     int active_inst = m_c->selection_info()->get_active_instance(); | ||||
|     const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; | ||||
|     const Transform3d& trafo = mi->get_transformation().get_matrix(); | ||||
| 
 | ||||
|     Vec3d transformed_point =  trafo * point; | ||||
|     transformed_point(2) += sel_info->get_sla_shift(); | ||||
|     return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
 | ||||
| // The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
 | ||||
| // aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
 | ||||
| // concludes that the event was not intended for it, it should return false.
 | ||||
| bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) | ||||
| { | ||||
|     if (action == SLAGizmoEventType::MouseWheelUp | ||||
|      || action == SLAGizmoEventType::MouseWheelDown) { | ||||
|         if (control_down) { | ||||
|             double pos = m_c->object_clipper()->get_position(); | ||||
|             pos = action == SLAGizmoEventType::MouseWheelDown | ||||
|                       ? std::max(0., pos - 0.01) | ||||
|                       : std::min(1., pos + 0.01); | ||||
|             m_c->object_clipper()->set_position(pos, true); | ||||
|             return true; | ||||
|         } | ||||
|         else if (alt_down) { | ||||
|             m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown | ||||
|                     ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin) | ||||
|                     : std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax); | ||||
|             m_parent.set_as_dirty(); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (action == SLAGizmoEventType::ResetClippingPlane) { | ||||
|         m_c->object_clipper()->set_position(-1., false); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     if (action == SLAGizmoEventType::LeftDown | ||||
|      || action == SLAGizmoEventType::RightDown | ||||
|     || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { | ||||
| 
 | ||||
|         if (m_triangle_selectors.empty()) | ||||
|             return false; | ||||
| 
 | ||||
|         FacetSupportType new_state = FacetSupportType::NONE; | ||||
|         if (! shift_down) { | ||||
|             if (action == SLAGizmoEventType::Dragging) | ||||
|                 new_state = m_button_down == Button::Left | ||||
|                         ? FacetSupportType::ENFORCER | ||||
|                         : FacetSupportType::BLOCKER; | ||||
|             else | ||||
|                 new_state = action == SLAGizmoEventType::LeftDown | ||||
|                         ? FacetSupportType::ENFORCER | ||||
|                         : FacetSupportType::BLOCKER; | ||||
|         } | ||||
| 
 | ||||
|         const Camera& camera = wxGetApp().plater()->get_camera(); | ||||
|         const Selection& selection = m_parent.get_selection(); | ||||
|         const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|         const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; | ||||
|         const Transform3d& instance_trafo = mi->get_transformation().get_matrix(); | ||||
| 
 | ||||
|         // List of mouse positions that will be used as seeds for painting.
 | ||||
|         std::vector<Vec2d> mouse_positions{mouse_position}; | ||||
| 
 | ||||
|         // In case current mouse position is far from the last one,
 | ||||
|         // add several positions from between into the list, so there
 | ||||
|         // are no gaps in the painted region.
 | ||||
|         { | ||||
|             if (m_last_mouse_position == Vec2d::Zero()) | ||||
|                 m_last_mouse_position = mouse_position; | ||||
|             // resolution describes minimal distance limit using circle radius
 | ||||
|             // as a unit (e.g., 2 would mean the patches will be touching).
 | ||||
|             double resolution = 0.7; | ||||
|             double diameter_px =  resolution  * m_cursor_radius * camera.get_zoom(); | ||||
|             int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px); | ||||
|             if (patches_in_between > 0) { | ||||
|                 Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1); | ||||
|                 for (int i=1; i<=patches_in_between; ++i) | ||||
|                     mouse_positions.emplace_back(m_last_mouse_position + i*diff); | ||||
|             } | ||||
|         } | ||||
|         m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved
 | ||||
| 
 | ||||
|         // Now "click" into all the prepared points and spill paint around them.
 | ||||
|         for (const Vec2d& mp : mouse_positions) { | ||||
|             std::vector<std::vector<std::pair<Vec3f, size_t>>> hit_positions_and_facet_ids; | ||||
|             bool clipped_mesh_was_hit = false; | ||||
| 
 | ||||
|             Vec3f normal =  Vec3f::Zero(); | ||||
|             Vec3f hit = Vec3f::Zero(); | ||||
|             size_t facet = 0; | ||||
|             Vec3f closest_hit = Vec3f::Zero(); | ||||
|             double closest_hit_squared_distance = std::numeric_limits<double>::max(); | ||||
|             size_t closest_facet = 0; | ||||
|             int closest_hit_mesh_id = -1; | ||||
| 
 | ||||
|             // Transformations of individual meshes
 | ||||
|             std::vector<Transform3d> trafo_matrices; | ||||
| 
 | ||||
|             int mesh_id = -1; | ||||
|             // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
 | ||||
|             for (const ModelVolume* mv : mo->volumes) { | ||||
|                 if (! mv->is_model_part()) | ||||
|                     continue; | ||||
| 
 | ||||
|                 ++mesh_id; | ||||
| 
 | ||||
|                 trafo_matrices.push_back(instance_trafo * mv->get_matrix()); | ||||
|                 hit_positions_and_facet_ids.push_back(std::vector<std::pair<Vec3f, size_t>>()); | ||||
| 
 | ||||
|                 if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( | ||||
|                            mp, | ||||
|                            trafo_matrices[mesh_id], | ||||
|                            camera, | ||||
|                            hit, | ||||
|                            normal, | ||||
|                            m_clipping_plane.get(), | ||||
|                            &facet)) | ||||
|                 { | ||||
|                     // In case this hit is clipped, skip it.
 | ||||
|                     if (is_mesh_point_clipped(hit.cast<double>())) { | ||||
|                         clipped_mesh_was_hit = true; | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     // Is this hit the closest to the camera so far?
 | ||||
|                     double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm(); | ||||
|                     if (hit_squared_distance < closest_hit_squared_distance) { | ||||
|                         closest_hit_squared_distance = hit_squared_distance; | ||||
|                         closest_facet = facet; | ||||
|                         closest_hit_mesh_id = mesh_id; | ||||
|                         closest_hit = hit; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); | ||||
| 
 | ||||
|             // The mouse button click detection is enabled when there is a valid hit
 | ||||
|             // or when the user clicks the clipping plane. Missing the object entirely
 | ||||
|             // shall not capture the mouse.
 | ||||
|             if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) { | ||||
|                 if (m_button_down == Button::None) | ||||
|                     m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); | ||||
|             } | ||||
| 
 | ||||
|             if (closest_hit_mesh_id == -1) { | ||||
|                 // In case we have no valid hit, we can return. The event will
 | ||||
|                 // be stopped in following two cases:
 | ||||
|                 //  1. clicking the clipping plane
 | ||||
|                 //  2. dragging while painting (to prevent scene rotations and moving the object)
 | ||||
|                 return clipped_mesh_was_hit | ||||
|                     || dragging_while_painting; | ||||
|             } | ||||
| 
 | ||||
|             // Find respective mesh id.
 | ||||
|             mesh_id = -1; | ||||
|             for (const ModelVolume* mv : mo->volumes) { | ||||
|                 if (! mv->is_model_part()) | ||||
|                     continue; | ||||
|                 ++mesh_id; | ||||
|                 if (mesh_id == closest_hit_mesh_id) | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; | ||||
| 
 | ||||
|             // Calculate how far can a point be from the line (in mesh coords).
 | ||||
|             // FIXME: The scaling of the mesh can be non-uniform.
 | ||||
|             const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); | ||||
|             const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; | ||||
|             const float limit = m_cursor_radius/avg_scaling; | ||||
| 
 | ||||
|             // Calculate direction from camera to the hit (in mesh coords):
 | ||||
|             Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>(); | ||||
|             Vec3f dir = (closest_hit - camera_pos).normalized(); | ||||
| 
 | ||||
|             assert(mesh_id < int(m_triangle_selectors.size())); | ||||
|             m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, | ||||
|                                               dir, limit, new_state); | ||||
|             m_last_mouse_position = mouse_position; | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) | ||||
|       && m_button_down != Button::None) { | ||||
|         // Take snapshot and update ModelVolume data.
 | ||||
|         wxString action_name = shift_down | ||||
|                 ? _L("Remove selection") | ||||
|                 : (m_button_down == Button::Left | ||||
|                    ? _L("Add supports") | ||||
|                    : _L("Block supports")); | ||||
|         activate_internal_undo_redo_stack(true); | ||||
|         Plater::TakeSnapshot(wxGetApp().plater(), action_name); | ||||
|         update_model_object(); | ||||
| 
 | ||||
|         m_button_down = Button::None; | ||||
|         m_last_mouse_position = Vec2d::Zero(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) | ||||
| { | ||||
|     float threshold = (M_PI/180.)*threshold_deg; | ||||
|     const Selection& selection = m_parent.get_selection(); | ||||
|     const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|     const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; | ||||
| 
 | ||||
|     int mesh_id = -1; | ||||
|     for (const ModelVolume* mv : mo->volumes) { | ||||
|         if (! mv->is_model_part()) | ||||
|             continue; | ||||
| 
 | ||||
|         ++mesh_id; | ||||
| 
 | ||||
|         const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); | ||||
|         Vec3f down  = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized(); | ||||
|         Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized(); | ||||
| 
 | ||||
|         float dot_limit = limit.dot(down); | ||||
| 
 | ||||
|         // Now calculate dot product of vert_direction and facets' normals.
 | ||||
|         int idx = -1; | ||||
|         for (const stl_facet& facet : mv->mesh().stl.facet_start) { | ||||
|             ++idx; | ||||
|             if (facet.normal.dot(down) > dot_limit) | ||||
|                 m_triangle_selectors[mesh_id]->set_facet(idx, | ||||
|                                                          block | ||||
|                                                          ? FacetSupportType::BLOCKER | ||||
|                                                          : FacetSupportType::ENFORCER); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     activate_internal_undo_redo_stack(true); | ||||
| 
 | ||||
|     Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle") | ||||
|                                                     : _L("Add supports by angle")); | ||||
|     update_model_object(); | ||||
|     m_parent.set_as_dirty(); | ||||
|     m_setting_angle = false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit) | ||||
|  | @ -561,6 +131,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l | |||
|                     m_triangle_selectors[idx]->reset(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             update_model_object(); | ||||
|             m_parent.set_as_dirty(); | ||||
|         } | ||||
|  | @ -604,12 +175,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l | |||
|         } | ||||
| 
 | ||||
|         m_imgui->end(); | ||||
|         if (m_setting_angle) { | ||||
|             m_parent.show_slope(false); | ||||
|             m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); | ||||
|             m_parent.use_slope(true); | ||||
|             m_parent.set_as_dirty(); | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         std::string name = "Autoset custom supports"; | ||||
|  | @ -634,248 +199,91 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l | |||
|     } | ||||
| } | ||||
| 
 | ||||
| bool GLGizmoFdmSupports::on_is_activable() const | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) | ||||
| { | ||||
|     float threshold = (M_PI/180.)*threshold_deg; | ||||
|     const Selection& selection = m_parent.get_selection(); | ||||
|     const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|     const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; | ||||
| 
 | ||||
|     if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF | ||||
|         || !selection.is_single_full_instance()) | ||||
|         return false; | ||||
| 
 | ||||
|     // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
 | ||||
|     const Selection::IndicesList& list = selection.get_volume_idxs(); | ||||
|     for (const auto& idx : list) | ||||
|         if (selection.get_volume(idx)->is_outside) | ||||
|             return false; | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool GLGizmoFdmSupports::on_is_selectable() const | ||||
| { | ||||
|     return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF ); | ||||
| } | ||||
| 
 | ||||
| std::string GLGizmoFdmSupports::on_get_name() const | ||||
| { | ||||
|     return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| CommonGizmosDataID GLGizmoFdmSupports::on_get_requirements() const | ||||
| { | ||||
|     return CommonGizmosDataID( | ||||
|                 int(CommonGizmosDataID::SelectionInfo) | ||||
|               | int(CommonGizmosDataID::InstancesHider) | ||||
|               | int(CommonGizmosDataID::Raycaster) | ||||
|               | int(CommonGizmosDataID::ObjectClipper)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::on_set_state() | ||||
| { | ||||
|     if (m_state == m_old_state) | ||||
|         return; | ||||
| 
 | ||||
|     if (m_state == On && m_old_state != On) { // the gizmo was just turned on
 | ||||
|         if (! m_parent.get_gizmos_manager().is_serializing()) { | ||||
|             wxGetApp().CallAfter([this]() { | ||||
|                 activate_internal_undo_redo_stack(true); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|     if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
 | ||||
|         // we are actually shutting down
 | ||||
|         if (m_setting_angle) { | ||||
|             m_setting_angle = false; | ||||
|             m_parent.use_slope(false); | ||||
|         } | ||||
|         activate_internal_undo_redo_stack(false); | ||||
|         m_old_mo_id = -1; | ||||
|         //m_iva.release_geometry();
 | ||||
|         m_triangle_selectors.clear(); | ||||
|     } | ||||
|     m_old_state = m_state; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::on_start_dragging() | ||||
| { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::on_stop_dragging() | ||||
| { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive&) | ||||
| { | ||||
|     // We should update the gizmo from current ModelObject, but it is not
 | ||||
|     // possible at this point. That would require having updated selection and
 | ||||
|     // common gizmos data, which is not done at this point. Instead, save
 | ||||
|     // a flag to do the update in set_fdm_support_data, which will be called
 | ||||
|     // soon after.
 | ||||
|     m_schedule_update = true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const | ||||
| { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void TriangleSelectorGUI::render(ImGuiWrapper* imgui) | ||||
| { | ||||
|     int enf_cnt = 0; | ||||
|     int blc_cnt = 0; | ||||
| 
 | ||||
|     m_iva_enforcers.release_geometry(); | ||||
|     m_iva_blockers.release_geometry(); | ||||
| 
 | ||||
|     for (const Triangle& tr : m_triangles) { | ||||
|         if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) | ||||
|     int mesh_id = -1; | ||||
|     for (const ModelVolume* mv : mo->volumes) { | ||||
|         if (! mv->is_model_part()) | ||||
|             continue; | ||||
| 
 | ||||
|         GLIndexedVertexArray& va = tr.get_state() == FacetSupportType::ENFORCER | ||||
|                                    ? m_iva_enforcers | ||||
|                                    : m_iva_blockers; | ||||
|         int& cnt = tr.get_state() == FacetSupportType::ENFORCER | ||||
|                 ? enf_cnt | ||||
|                 : blc_cnt; | ||||
|         ++mesh_id; | ||||
| 
 | ||||
|         for (int i=0; i<3; ++i) | ||||
|             va.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), | ||||
|                              double(m_vertices[tr.verts_idxs[i]].v[1]), | ||||
|                              double(m_vertices[tr.verts_idxs[i]].v[2]), | ||||
|                              0., 0., 1.); | ||||
|         va.push_triangle(cnt, | ||||
|                          cnt+1, | ||||
|                          cnt+2); | ||||
|         cnt += 3; | ||||
|         const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); | ||||
|         Vec3f down  = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized(); | ||||
|         Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized(); | ||||
| 
 | ||||
|         float dot_limit = limit.dot(down); | ||||
| 
 | ||||
|         // Now calculate dot product of vert_direction and facets' normals.
 | ||||
|         int idx = -1; | ||||
|         for (const stl_facet& facet : mv->mesh().stl.facet_start) { | ||||
|             ++idx; | ||||
|             if (facet.normal.dot(down) > dot_limit) | ||||
|                 m_triangle_selectors[mesh_id]->set_facet(idx, | ||||
|                                                          block | ||||
|                                                          ? EnforcerBlockerType::BLOCKER | ||||
|                                                          : EnforcerBlockerType::ENFORCER); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     m_iva_enforcers.finalize_geometry(true); | ||||
|     m_iva_blockers.finalize_geometry(true); | ||||
|     activate_internal_undo_redo_stack(true); | ||||
| 
 | ||||
|     if (m_iva_enforcers.has_VBOs()) { | ||||
|         ::glColor4f(0.f, 0.f, 1.f, 0.2f); | ||||
|         m_iva_enforcers.render(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     if (m_iva_blockers.has_VBOs()) { | ||||
|         ::glColor4f(1.f, 0.f, 0.f, 0.2f); | ||||
|         m_iva_blockers.render(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG | ||||
|     if (imgui) | ||||
|         render_debug(imgui); | ||||
|     else | ||||
|         assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
 | ||||
| #endif | ||||
|     Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle") | ||||
|                                                     : _L("Add supports by angle")); | ||||
|     update_model_object(); | ||||
|     m_parent.set_as_dirty(); | ||||
|     m_setting_angle = false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG | ||||
| void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) | ||||
| void GLGizmoFdmSupports::update_model_object() const | ||||
| { | ||||
|     imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), | ||||
|                  ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); | ||||
|     static float edge_limit = 1.f; | ||||
|     imgui->text("Edge limit (mm): "); | ||||
|     imgui->slider_float("", &edge_limit, 0.1f, 8.f); | ||||
|     set_edge_limit(edge_limit); | ||||
|     imgui->checkbox("Show split triangles: ", m_show_triangles); | ||||
|     imgui->checkbox("Show invalid triangles: ", m_show_invalid); | ||||
| 
 | ||||
|     int valid_triangles = m_triangles.size() - m_invalid_triangles; | ||||
|     imgui->text("Valid triangles: " + std::to_string(valid_triangles) + | ||||
|                   "/" + std::to_string(m_triangles.size())); | ||||
|     imgui->text("Vertices: " + std::to_string(m_vertices.size())); | ||||
|     if (imgui->button("Force garbage collection")) | ||||
|         garbage_collect(); | ||||
| 
 | ||||
|     if (imgui->button("Serialize - deserialize")) { | ||||
|         auto map = serialize(); | ||||
|         deserialize(map); | ||||
|     bool updated = false; | ||||
|     ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|     int idx = -1; | ||||
|     for (ModelVolume* mv : mo->volumes) { | ||||
|         if (! mv->is_model_part()) | ||||
|             continue; | ||||
|         ++idx; | ||||
|         updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); | ||||
|     } | ||||
| 
 | ||||
|     imgui->end(); | ||||
| 
 | ||||
|     if (! m_show_triangles) | ||||
|         return; | ||||
| 
 | ||||
|     enum vtype { | ||||
|         ORIGINAL = 0, | ||||
|         SPLIT, | ||||
|         INVALID | ||||
|     }; | ||||
| 
 | ||||
|     for (auto& va : m_varrays) | ||||
|         va.release_geometry(); | ||||
| 
 | ||||
|     std::array<int, 3> cnts; | ||||
| 
 | ||||
|     ::glScalef(1.01f, 1.01f, 1.01f); | ||||
| 
 | ||||
|     for (int tr_id=0; tr_id<int(m_triangles.size()); ++tr_id) { | ||||
|         const Triangle& tr = m_triangles[tr_id]; | ||||
|         GLIndexedVertexArray* va = nullptr; | ||||
|         int* cnt = nullptr; | ||||
|         if (tr_id < m_orig_size_indices) { | ||||
|             va = &m_varrays[ORIGINAL]; | ||||
|             cnt = &cnts[ORIGINAL]; | ||||
|         } | ||||
|         else if (tr.valid) { | ||||
|             va = &m_varrays[SPLIT]; | ||||
|             cnt = &cnts[SPLIT]; | ||||
|         } | ||||
|         else { | ||||
|             if (! m_show_invalid) | ||||
|                 continue; | ||||
|             va = &m_varrays[INVALID]; | ||||
|             cnt = &cnts[INVALID]; | ||||
|         } | ||||
| 
 | ||||
|         for (int i=0; i<3; ++i) | ||||
|             va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), | ||||
|                               double(m_vertices[tr.verts_idxs[i]].v[1]), | ||||
|                               double(m_vertices[tr.verts_idxs[i]].v[2]), | ||||
|                               0., 0., 1.); | ||||
|         va->push_triangle(*cnt, | ||||
|                           *cnt+1, | ||||
|                           *cnt+2); | ||||
|         *cnt += 3; | ||||
|     } | ||||
| 
 | ||||
|     ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); | ||||
|     for (vtype i : {ORIGINAL, SPLIT, INVALID}) { | ||||
|         GLIndexedVertexArray& va = m_varrays[i]; | ||||
|         va.finalize_geometry(true); | ||||
|         if (va.has_VBOs()) { | ||||
|             switch (i) { | ||||
|             case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; | ||||
|             case SPLIT    : ::glColor3f(1.f, 0.f, 0.f); break; | ||||
|             case INVALID  : ::glColor3f(1.f, 1.f, 0.f); break; | ||||
|             } | ||||
|             va.render(); | ||||
|         } | ||||
|     } | ||||
|     ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); | ||||
|     if (updated) | ||||
|         m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::update_from_model_object() | ||||
| { | ||||
|     wxBusyCursor wait; | ||||
| 
 | ||||
|     const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|     m_triangle_selectors.clear(); | ||||
| 
 | ||||
|     int volume_id = -1; | ||||
|     for (const ModelVolume* mv : mo->volumes) { | ||||
|         if (! mv->is_model_part()) | ||||
|             continue; | ||||
| 
 | ||||
|         ++volume_id; | ||||
| 
 | ||||
|         // This mesh does not account for the possible Z up SLA offset.
 | ||||
|         const TriangleMesh* mesh = &mv->mesh(); | ||||
| 
 | ||||
|         m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh)); | ||||
|         m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -1,127 +1,46 @@ | |||
| #ifndef slic3r_GLGizmoFdmSupports_hpp_ | ||||
| #define slic3r_GLGizmoFdmSupports_hpp_ | ||||
| 
 | ||||
| #include "GLGizmoBase.hpp" | ||||
| 
 | ||||
| #include "slic3r/GUI/3DScene.hpp" | ||||
| 
 | ||||
| #include "libslic3r/ObjectID.hpp" | ||||
| #include "libslic3r/TriangleSelector.hpp" | ||||
| 
 | ||||
| #include <cereal/types/vector.hpp> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #include "GLGizmoPainterBase.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| enum class FacetSupportType : int8_t; | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| enum class SLAGizmoEventType : unsigned char; | ||||
| class ClippingPlane; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class TriangleSelectorGUI : public TriangleSelector { | ||||
| public: | ||||
|     explicit TriangleSelectorGUI(const TriangleMesh& mesh) | ||||
|         : TriangleSelector(mesh) {} | ||||
| 
 | ||||
|     // Render current selection. Transformation matrices are supposed
 | ||||
|     // to be already set.
 | ||||
|     void render(ImGuiWrapper* imgui = nullptr); | ||||
| 
 | ||||
| #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG | ||||
|     void render_debug(ImGuiWrapper* imgui); | ||||
|     bool m_show_triangles{false}; | ||||
|     bool m_show_invalid{false}; | ||||
| #endif | ||||
| 
 | ||||
| private: | ||||
|     GLIndexedVertexArray m_iva_enforcers; | ||||
|     GLIndexedVertexArray m_iva_blockers; | ||||
|     std::array<GLIndexedVertexArray, 3> m_varrays; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class GLGizmoFdmSupports : public GLGizmoBase | ||||
| class GLGizmoFdmSupports : public GLGizmoPainterBase | ||||
| { | ||||
| private: | ||||
|     ObjectID m_old_mo_id; | ||||
|     size_t m_old_volumes_size = 0; | ||||
| 
 | ||||
|     GLUquadricObj* m_quadric; | ||||
| 
 | ||||
|     float m_cursor_radius = 2.f; | ||||
|     static constexpr float CursorRadiusMin  = 0.4f; // cannot be zero
 | ||||
|     static constexpr float CursorRadiusMax  = 8.f; | ||||
|     static constexpr float CursorRadiusStep = 0.2f; | ||||
| 
 | ||||
|     // For each model-part volume, store status and division of the triangles.
 | ||||
|     std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors; | ||||
| 
 | ||||
| public: | ||||
|     GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
|     ~GLGizmoFdmSupports() override; | ||||
|     void set_fdm_support_data(ModelObject* model_object, const Selection& selection); | ||||
|     bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); | ||||
|     GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
|         : GLGizmoPainterBase(parent, icon_filename, sprite_id) {} | ||||
| 
 | ||||
| protected: | ||||
|     void on_render_input_window(float x, float y, float bottom_limit) override; | ||||
|     std::string on_get_name() const override; | ||||
| 
 | ||||
| private: | ||||
|     bool on_init() override; | ||||
|     void on_render() const override; | ||||
|     void on_render_for_picking() const override {} | ||||
| 
 | ||||
|     void render_triangles(const Selection& selection) const; | ||||
|     void render_cursor_circle() const; | ||||
|     void update_model_object() const override; | ||||
|     void update_from_model_object() override; | ||||
| 
 | ||||
|     void update_model_object() const; | ||||
|     void update_from_model_object(); | ||||
|     void activate_internal_undo_redo_stack(bool activate); | ||||
|     void on_opening() override {} | ||||
|     void on_shutdown() override; | ||||
| 
 | ||||
|     void select_facets_by_angle(float threshold, bool block); | ||||
|     float m_angle_threshold_deg = 45.f; | ||||
| 
 | ||||
|     bool is_mesh_point_clipped(const Vec3d& point) const; | ||||
| 
 | ||||
|     float m_clipping_plane_distance = 0.f; | ||||
|     std::unique_ptr<ClippingPlane> m_clipping_plane; | ||||
|     bool m_setting_angle = false; | ||||
|     bool m_internal_stack_active = false; | ||||
|     bool m_schedule_update = false; | ||||
|     Vec2d m_last_mouse_position = Vec2d::Zero(); | ||||
| 
 | ||||
|     // This map holds all translated description texts, so they can be easily referenced during layout calculations
 | ||||
|     // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
 | ||||
|     std::map<std::string, wxString> m_desc; | ||||
| 
 | ||||
|     enum class Button { | ||||
|         None, | ||||
|         Left, | ||||
|         Right | ||||
|     }; | ||||
| 
 | ||||
|     Button m_button_down = Button::None; | ||||
|     EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
 | ||||
| 
 | ||||
| protected: | ||||
|     void on_set_state() override; | ||||
|     void on_start_dragging() override; | ||||
|     void on_stop_dragging() override; | ||||
|     void on_render_input_window(float x, float y, float bottom_limit) override; | ||||
|     std::string on_get_name() const override; | ||||
|     bool on_is_activable() const override; | ||||
|     bool on_is_selectable() const override; | ||||
|     void on_load(cereal::BinaryInputArchive& ar) override; | ||||
|     void on_save(cereal::BinaryOutputArchive& ar) const override; | ||||
|     CommonGizmosDataID on_get_requirements() const override; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| 
 | ||||
| #endif // slic3r_GLGizmoFdmSupports_hpp_
 | ||||
|  |  | |||
							
								
								
									
										597
									
								
								src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										597
									
								
								src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,597 @@ | |||
| // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
 | ||||
| #include "GLGizmoPainterBase.hpp" | ||||
| #include "slic3r/GUI/GLCanvas3D.hpp" | ||||
| #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" | ||||
| 
 | ||||
| #include <GL/glew.h> | ||||
| 
 | ||||
| #include "slic3r/GUI/GUI_App.hpp" | ||||
| #include "slic3r/GUI/Camera.hpp" | ||||
| #include "slic3r/GUI/Plater.hpp" | ||||
| #include "libslic3r/PresetBundle.hpp" | ||||
| #include "libslic3r/Model.hpp" | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| 
 | ||||
| GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, icon_filename, sprite_id) | ||||
| { | ||||
|     m_clipping_plane.reset(new ClippingPlane()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) | ||||
| { | ||||
|     if (activate && ! m_internal_stack_active) { | ||||
|         Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on")); | ||||
|         wxGetApp().plater()->enter_gizmos_stack(); | ||||
|         m_internal_stack_active = true; | ||||
|     } | ||||
|     if (! activate && m_internal_stack_active) { | ||||
|         wxGetApp().plater()->leave_gizmos_stack(); | ||||
|         Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off")); | ||||
|         m_internal_stack_active = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) | ||||
| { | ||||
|     if (m_state != On) | ||||
|         return; | ||||
| 
 | ||||
|     const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; | ||||
| 
 | ||||
|     if (mo && selection.is_from_single_instance() | ||||
|      && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) | ||||
|     { | ||||
|         update_from_model_object(); | ||||
|         m_old_mo_id = mo->id(); | ||||
|         m_old_volumes_size = mo->volumes.size(); | ||||
|         m_schedule_update = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoPainterBase::render_triangles(const Selection& selection) const | ||||
| { | ||||
|     const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
| 
 | ||||
|     glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); | ||||
|     ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); } ); | ||||
|     glsafe(::glPolygonOffset(-1.0, 1.0)); | ||||
| 
 | ||||
|     // Take care of the clipping plane. The normal of the clipping plane is
 | ||||
|     // saved with opposite sign than we need to pass to OpenGL (FIXME)
 | ||||
|     bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; | ||||
|     if (clipping_plane_active) { | ||||
|         const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); | ||||
|         double clp_data[4]; | ||||
|         memcpy(clp_data, clp->get_data(), 4 * sizeof(double)); | ||||
|         for (int i=0; i<3; ++i) | ||||
|             clp_data[i] = -1. * clp_data[i]; | ||||
| 
 | ||||
|         glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)clp_data)); | ||||
|         glsafe(::glEnable(GL_CLIP_PLANE0)); | ||||
|     } | ||||
| 
 | ||||
|     int mesh_id = -1; | ||||
|     for (const ModelVolume* mv : mo->volumes) { | ||||
|         if (! mv->is_model_part()) | ||||
|             continue; | ||||
| 
 | ||||
|         ++mesh_id; | ||||
| 
 | ||||
|         const Transform3d trafo_matrix = | ||||
|             mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * | ||||
|             mv->get_matrix(); | ||||
| 
 | ||||
|         bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; | ||||
|         if (is_left_handed) | ||||
|             glsafe(::glFrontFace(GL_CW)); | ||||
| 
 | ||||
|         glsafe(::glPushMatrix()); | ||||
|         glsafe(::glMultMatrixd(trafo_matrix.data())); | ||||
| 
 | ||||
|         m_triangle_selectors[mesh_id]->render(m_imgui); | ||||
| 
 | ||||
|         glsafe(::glPopMatrix()); | ||||
|         if (is_left_handed) | ||||
|             glsafe(::glFrontFace(GL_CCW)); | ||||
|     } | ||||
|     if (clipping_plane_active) | ||||
|         glsafe(::glDisable(GL_CLIP_PLANE0)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoPainterBase::render_cursor_circle() const | ||||
| { | ||||
|     const Camera& camera = wxGetApp().plater()->get_camera(); | ||||
|     float zoom = (float)camera.get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| 
 | ||||
|     Size cnv_size = m_parent.get_canvas_size(); | ||||
|     float cnv_half_width = 0.5f * (float)cnv_size.get_width(); | ||||
|     float cnv_half_height = 0.5f * (float)cnv_size.get_height(); | ||||
|     if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f)) | ||||
|         return; | ||||
|     Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1)); | ||||
|     Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1)); | ||||
|     center = center * inv_zoom; | ||||
| 
 | ||||
|     glsafe(::glLineWidth(1.5f)); | ||||
|     float color[3]; | ||||
|     color[0] = 0.f; | ||||
|     color[1] = 1.f; | ||||
|     color[2] = 0.3f; | ||||
|     glsafe(::glColor3fv(color)); | ||||
|     glsafe(::glDisable(GL_DEPTH_TEST)); | ||||
| 
 | ||||
|     glsafe(::glPushMatrix()); | ||||
|     glsafe(::glLoadIdentity()); | ||||
|     // ensure that the circle is renderered inside the frustrum
 | ||||
|     glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); | ||||
|     // ensure that the overlay fits the frustrum near z plane
 | ||||
|     double gui_scale = camera.get_gui_scale(); | ||||
|     glsafe(::glScaled(gui_scale, gui_scale, 1.0)); | ||||
| 
 | ||||
|     glsafe(::glPushAttrib(GL_ENABLE_BIT)); | ||||
|     glsafe(::glLineStipple(4, 0xAAAA)); | ||||
|     glsafe(::glEnable(GL_LINE_STIPPLE)); | ||||
| 
 | ||||
|     ::glBegin(GL_LINE_LOOP); | ||||
|     for (double angle=0; angle<2*M_PI; angle+=M_PI/20.) | ||||
|         ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle))); | ||||
|     glsafe(::glEnd()); | ||||
| 
 | ||||
|     glsafe(::glPopAttrib()); | ||||
|     glsafe(::glPopMatrix()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point) const | ||||
| { | ||||
|     if (m_c->object_clipper()->get_position() == 0.) | ||||
|         return false; | ||||
| 
 | ||||
|     auto sel_info = m_c->selection_info(); | ||||
|     int active_inst = m_c->selection_info()->get_active_instance(); | ||||
|     const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; | ||||
|     const Transform3d& trafo = mi->get_transformation().get_matrix(); | ||||
| 
 | ||||
|     Vec3d transformed_point =  trafo * point; | ||||
|     transformed_point(2) += sel_info->get_sla_shift(); | ||||
|     return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
 | ||||
| // The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
 | ||||
| // aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
 | ||||
| // concludes that the event was not intended for it, it should return false.
 | ||||
| bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) | ||||
| { | ||||
|     if (action == SLAGizmoEventType::MouseWheelUp | ||||
|      || action == SLAGizmoEventType::MouseWheelDown) { | ||||
|         if (control_down) { | ||||
|             double pos = m_c->object_clipper()->get_position(); | ||||
|             pos = action == SLAGizmoEventType::MouseWheelDown | ||||
|                       ? std::max(0., pos - 0.01) | ||||
|                       : std::min(1., pos + 0.01); | ||||
|             m_c->object_clipper()->set_position(pos, true); | ||||
|             return true; | ||||
|         } | ||||
|         else if (alt_down) { | ||||
|             m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown | ||||
|                     ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin) | ||||
|                     : std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax); | ||||
|             m_parent.set_as_dirty(); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (action == SLAGizmoEventType::ResetClippingPlane) { | ||||
|         m_c->object_clipper()->set_position(-1., false); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     if (action == SLAGizmoEventType::LeftDown | ||||
|      || action == SLAGizmoEventType::RightDown | ||||
|     || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { | ||||
| 
 | ||||
|         if (m_triangle_selectors.empty()) | ||||
|             return false; | ||||
| 
 | ||||
|         EnforcerBlockerType new_state = EnforcerBlockerType::NONE; | ||||
|         if (! shift_down) { | ||||
|             if (action == SLAGizmoEventType::Dragging) | ||||
|                 new_state = m_button_down == Button::Left | ||||
|                         ? EnforcerBlockerType::ENFORCER | ||||
|                         : EnforcerBlockerType::BLOCKER; | ||||
|             else | ||||
|                 new_state = action == SLAGizmoEventType::LeftDown | ||||
|                         ? EnforcerBlockerType::ENFORCER | ||||
|                         : EnforcerBlockerType::BLOCKER; | ||||
|         } | ||||
| 
 | ||||
|         const Camera& camera = wxGetApp().plater()->get_camera(); | ||||
|         const Selection& selection = m_parent.get_selection(); | ||||
|         const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|         const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; | ||||
|         const Transform3d& instance_trafo = mi->get_transformation().get_matrix(); | ||||
| 
 | ||||
|         // List of mouse positions that will be used as seeds for painting.
 | ||||
|         std::vector<Vec2d> mouse_positions{mouse_position}; | ||||
| 
 | ||||
|         // In case current mouse position is far from the last one,
 | ||||
|         // add several positions from between into the list, so there
 | ||||
|         // are no gaps in the painted region.
 | ||||
|         { | ||||
|             if (m_last_mouse_position == Vec2d::Zero()) | ||||
|                 m_last_mouse_position = mouse_position; | ||||
|             // resolution describes minimal distance limit using circle radius
 | ||||
|             // as a unit (e.g., 2 would mean the patches will be touching).
 | ||||
|             double resolution = 0.7; | ||||
|             double diameter_px =  resolution  * m_cursor_radius * camera.get_zoom(); | ||||
|             int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px); | ||||
|             if (patches_in_between > 0) { | ||||
|                 Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1); | ||||
|                 for (int i=1; i<=patches_in_between; ++i) | ||||
|                     mouse_positions.emplace_back(m_last_mouse_position + i*diff); | ||||
|             } | ||||
|         } | ||||
|         m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved
 | ||||
| 
 | ||||
|         // Now "click" into all the prepared points and spill paint around them.
 | ||||
|         for (const Vec2d& mp : mouse_positions) { | ||||
|             std::vector<std::vector<std::pair<Vec3f, size_t>>> hit_positions_and_facet_ids; | ||||
|             bool clipped_mesh_was_hit = false; | ||||
| 
 | ||||
|             Vec3f normal =  Vec3f::Zero(); | ||||
|             Vec3f hit = Vec3f::Zero(); | ||||
|             size_t facet = 0; | ||||
|             Vec3f closest_hit = Vec3f::Zero(); | ||||
|             double closest_hit_squared_distance = std::numeric_limits<double>::max(); | ||||
|             size_t closest_facet = 0; | ||||
|             int closest_hit_mesh_id = -1; | ||||
| 
 | ||||
|             // Transformations of individual meshes
 | ||||
|             std::vector<Transform3d> trafo_matrices; | ||||
| 
 | ||||
|             int mesh_id = -1; | ||||
|             // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
 | ||||
|             for (const ModelVolume* mv : mo->volumes) { | ||||
|                 if (! mv->is_model_part()) | ||||
|                     continue; | ||||
| 
 | ||||
|                 ++mesh_id; | ||||
| 
 | ||||
|                 trafo_matrices.push_back(instance_trafo * mv->get_matrix()); | ||||
|                 hit_positions_and_facet_ids.push_back(std::vector<std::pair<Vec3f, size_t>>()); | ||||
| 
 | ||||
|                 if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( | ||||
|                            mp, | ||||
|                            trafo_matrices[mesh_id], | ||||
|                            camera, | ||||
|                            hit, | ||||
|                            normal, | ||||
|                            m_clipping_plane.get(), | ||||
|                            &facet)) | ||||
|                 { | ||||
|                     // In case this hit is clipped, skip it.
 | ||||
|                     if (is_mesh_point_clipped(hit.cast<double>())) { | ||||
|                         clipped_mesh_was_hit = true; | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     // Is this hit the closest to the camera so far?
 | ||||
|                     double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm(); | ||||
|                     if (hit_squared_distance < closest_hit_squared_distance) { | ||||
|                         closest_hit_squared_distance = hit_squared_distance; | ||||
|                         closest_facet = facet; | ||||
|                         closest_hit_mesh_id = mesh_id; | ||||
|                         closest_hit = hit; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); | ||||
| 
 | ||||
|             // The mouse button click detection is enabled when there is a valid hit
 | ||||
|             // or when the user clicks the clipping plane. Missing the object entirely
 | ||||
|             // shall not capture the mouse.
 | ||||
|             if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) { | ||||
|                 if (m_button_down == Button::None) | ||||
|                     m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); | ||||
|             } | ||||
| 
 | ||||
|             if (closest_hit_mesh_id == -1) { | ||||
|                 // In case we have no valid hit, we can return. The event will
 | ||||
|                 // be stopped in following two cases:
 | ||||
|                 //  1. clicking the clipping plane
 | ||||
|                 //  2. dragging while painting (to prevent scene rotations and moving the object)
 | ||||
|                 return clipped_mesh_was_hit | ||||
|                     || dragging_while_painting; | ||||
|             } | ||||
| 
 | ||||
|             // Find respective mesh id.
 | ||||
|             mesh_id = -1; | ||||
|             for (const ModelVolume* mv : mo->volumes) { | ||||
|                 if (! mv->is_model_part()) | ||||
|                     continue; | ||||
|                 ++mesh_id; | ||||
|                 if (mesh_id == closest_hit_mesh_id) | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; | ||||
| 
 | ||||
|             // Calculate how far can a point be from the line (in mesh coords).
 | ||||
|             // FIXME: The scaling of the mesh can be non-uniform.
 | ||||
|             const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); | ||||
|             const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; | ||||
|             const float limit = m_cursor_radius/avg_scaling; | ||||
| 
 | ||||
|             // Calculate direction from camera to the hit (in mesh coords):
 | ||||
|             Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>(); | ||||
|             Vec3f dir = (closest_hit - camera_pos).normalized(); | ||||
| 
 | ||||
|             assert(mesh_id < int(m_triangle_selectors.size())); | ||||
|             m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, | ||||
|                                               dir, limit, new_state); | ||||
|             m_last_mouse_position = mouse_position; | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) | ||||
|       && m_button_down != Button::None) { | ||||
|         // Take snapshot and update ModelVolume data.
 | ||||
|         wxString action_name = shift_down | ||||
|                 ? _L("Remove selection") | ||||
|                 : (m_button_down == Button::Left | ||||
|                    ? _L("Add supports") | ||||
|                    : _L("Block supports")); | ||||
|         activate_internal_undo_redo_stack(true); | ||||
|         Plater::TakeSnapshot(wxGetApp().plater(), action_name); | ||||
|         update_model_object(); | ||||
| 
 | ||||
|         m_button_down = Button::None; | ||||
|         m_last_mouse_position = Vec2d::Zero(); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| bool GLGizmoPainterBase::on_is_activable() const | ||||
| { | ||||
|     const Selection& selection = m_parent.get_selection(); | ||||
| 
 | ||||
|     if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF | ||||
|         || !selection.is_single_full_instance()) | ||||
|         return false; | ||||
| 
 | ||||
|     // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
 | ||||
|     const Selection::IndicesList& list = selection.get_volume_idxs(); | ||||
|     for (const auto& idx : list) | ||||
|         if (selection.get_volume(idx)->is_outside) | ||||
|             return false; | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool GLGizmoPainterBase::on_is_selectable() const | ||||
| { | ||||
|     return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF | ||||
|          && wxGetApp().get_mode() != comSimple ); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const | ||||
| { | ||||
|     return CommonGizmosDataID( | ||||
|                 int(CommonGizmosDataID::SelectionInfo) | ||||
|               | int(CommonGizmosDataID::InstancesHider) | ||||
|               | int(CommonGizmosDataID::Raycaster) | ||||
|               | int(CommonGizmosDataID::ObjectClipper)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoPainterBase::on_set_state() | ||||
| { | ||||
|     if (m_state == m_old_state) | ||||
|         return; | ||||
| 
 | ||||
|     if (m_state == On && m_old_state != On) { // the gizmo was just turned on
 | ||||
|         on_opening(); | ||||
|         if (! m_parent.get_gizmos_manager().is_serializing()) { | ||||
|             wxGetApp().CallAfter([this]() { | ||||
|                 activate_internal_undo_redo_stack(true); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|     if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
 | ||||
|         // we are actually shutting down
 | ||||
|         on_shutdown(); | ||||
|         activate_internal_undo_redo_stack(false); | ||||
|         m_old_mo_id = -1; | ||||
|         //m_iva.release_geometry();
 | ||||
|         m_triangle_selectors.clear(); | ||||
|     } | ||||
|     m_old_state = m_state; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&) | ||||
| { | ||||
|     // We should update the gizmo from current ModelObject, but it is not
 | ||||
|     // possible at this point. That would require having updated selection and
 | ||||
|     // common gizmos data, which is not done at this point. Instead, save
 | ||||
|     // a flag to do the update in set_painter_gizmo_data, which will be called
 | ||||
|     // soon after.
 | ||||
|     m_schedule_update = true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void TriangleSelectorGUI::render(ImGuiWrapper* imgui) | ||||
| { | ||||
|     int enf_cnt = 0; | ||||
|     int blc_cnt = 0; | ||||
| 
 | ||||
|     m_iva_enforcers.release_geometry(); | ||||
|     m_iva_blockers.release_geometry(); | ||||
| 
 | ||||
|     for (const Triangle& tr : m_triangles) { | ||||
|         if (! tr.valid || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE) | ||||
|             continue; | ||||
| 
 | ||||
|         GLIndexedVertexArray& va = tr.get_state() == EnforcerBlockerType::ENFORCER | ||||
|                                    ? m_iva_enforcers | ||||
|                                    : m_iva_blockers; | ||||
|         int& cnt = tr.get_state() == EnforcerBlockerType::ENFORCER | ||||
|                 ? enf_cnt | ||||
|                 : blc_cnt; | ||||
| 
 | ||||
|         for (int i=0; i<3; ++i) | ||||
|             va.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), | ||||
|                              double(m_vertices[tr.verts_idxs[i]].v[1]), | ||||
|                              double(m_vertices[tr.verts_idxs[i]].v[2]), | ||||
|                              0., 0., 1.); | ||||
|         va.push_triangle(cnt, | ||||
|                          cnt+1, | ||||
|                          cnt+2); | ||||
|         cnt += 3; | ||||
|     } | ||||
| 
 | ||||
|     m_iva_enforcers.finalize_geometry(true); | ||||
|     m_iva_blockers.finalize_geometry(true); | ||||
| 
 | ||||
|     if (m_iva_enforcers.has_VBOs()) { | ||||
|         ::glColor4f(0.f, 0.f, 1.f, 0.2f); | ||||
|         m_iva_enforcers.render(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     if (m_iva_blockers.has_VBOs()) { | ||||
|         ::glColor4f(1.f, 0.f, 0.f, 0.2f); | ||||
|         m_iva_blockers.render(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG | ||||
|     if (imgui) | ||||
|         render_debug(imgui); | ||||
|     else | ||||
|         assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
 | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG | ||||
| void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) | ||||
| { | ||||
|     imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), | ||||
|                  ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); | ||||
|     static float edge_limit = 1.f; | ||||
|     imgui->text("Edge limit (mm): "); | ||||
|     imgui->slider_float("", &edge_limit, 0.1f, 8.f); | ||||
|     set_edge_limit(edge_limit); | ||||
|     imgui->checkbox("Show split triangles: ", m_show_triangles); | ||||
|     imgui->checkbox("Show invalid triangles: ", m_show_invalid); | ||||
| 
 | ||||
|     int valid_triangles = m_triangles.size() - m_invalid_triangles; | ||||
|     imgui->text("Valid triangles: " + std::to_string(valid_triangles) + | ||||
|                   "/" + std::to_string(m_triangles.size())); | ||||
|     imgui->text("Vertices: " + std::to_string(m_vertices.size())); | ||||
|     if (imgui->button("Force garbage collection")) | ||||
|         garbage_collect(); | ||||
| 
 | ||||
|     if (imgui->button("Serialize - deserialize")) { | ||||
|         auto map = serialize(); | ||||
|         deserialize(map); | ||||
|     } | ||||
| 
 | ||||
|     imgui->end(); | ||||
| 
 | ||||
|     if (! m_show_triangles) | ||||
|         return; | ||||
| 
 | ||||
|     enum vtype { | ||||
|         ORIGINAL = 0, | ||||
|         SPLIT, | ||||
|         INVALID | ||||
|     }; | ||||
| 
 | ||||
|     for (auto& va : m_varrays) | ||||
|         va.release_geometry(); | ||||
| 
 | ||||
|     std::array<int, 3> cnts; | ||||
| 
 | ||||
|     ::glScalef(1.01f, 1.01f, 1.01f); | ||||
| 
 | ||||
|     for (int tr_id=0; tr_id<int(m_triangles.size()); ++tr_id) { | ||||
|         const Triangle& tr = m_triangles[tr_id]; | ||||
|         GLIndexedVertexArray* va = nullptr; | ||||
|         int* cnt = nullptr; | ||||
|         if (tr_id < m_orig_size_indices) { | ||||
|             va = &m_varrays[ORIGINAL]; | ||||
|             cnt = &cnts[ORIGINAL]; | ||||
|         } | ||||
|         else if (tr.valid) { | ||||
|             va = &m_varrays[SPLIT]; | ||||
|             cnt = &cnts[SPLIT]; | ||||
|         } | ||||
|         else { | ||||
|             if (! m_show_invalid) | ||||
|                 continue; | ||||
|             va = &m_varrays[INVALID]; | ||||
|             cnt = &cnts[INVALID]; | ||||
|         } | ||||
| 
 | ||||
|         for (int i=0; i<3; ++i) | ||||
|             va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), | ||||
|                               double(m_vertices[tr.verts_idxs[i]].v[1]), | ||||
|                               double(m_vertices[tr.verts_idxs[i]].v[2]), | ||||
|                               0., 0., 1.); | ||||
|         va->push_triangle(*cnt, | ||||
|                           *cnt+1, | ||||
|                           *cnt+2); | ||||
|         *cnt += 3; | ||||
|     } | ||||
| 
 | ||||
|     ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); | ||||
|     for (vtype i : {ORIGINAL, SPLIT, INVALID}) { | ||||
|         GLIndexedVertexArray& va = m_varrays[i]; | ||||
|         va.finalize_geometry(true); | ||||
|         if (va.has_VBOs()) { | ||||
|             switch (i) { | ||||
|             case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; | ||||
|             case SPLIT    : ::glColor3f(1.f, 0.f, 0.f); break; | ||||
|             case INVALID  : ::glColor3f(1.f, 1.f, 0.f); break; | ||||
|             } | ||||
|             va.render(); | ||||
|         } | ||||
|     } | ||||
|     ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
							
								
								
									
										118
									
								
								src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,118 @@ | |||
| #ifndef slic3r_GLGizmoPainterBase_hpp_ | ||||
| #define slic3r_GLGizmoPainterBase_hpp_ | ||||
| 
 | ||||
| #include "GLGizmoBase.hpp" | ||||
| 
 | ||||
| #include "slic3r/GUI/3DScene.hpp" | ||||
| 
 | ||||
| #include "libslic3r/ObjectID.hpp" | ||||
| #include "libslic3r/TriangleSelector.hpp" | ||||
| 
 | ||||
| #include <cereal/types/vector.hpp> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| enum class EnforcerBlockerType : int8_t; | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| enum class SLAGizmoEventType : unsigned char; | ||||
| class ClippingPlane; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class TriangleSelectorGUI : public TriangleSelector { | ||||
| public: | ||||
|     explicit TriangleSelectorGUI(const TriangleMesh& mesh) | ||||
|         : TriangleSelector(mesh) {} | ||||
| 
 | ||||
|     // Render current selection. Transformation matrices are supposed
 | ||||
|     // to be already set.
 | ||||
|     void render(ImGuiWrapper* imgui = nullptr); | ||||
| 
 | ||||
| #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG | ||||
|     void render_debug(ImGuiWrapper* imgui); | ||||
|     bool m_show_triangles{false}; | ||||
|     bool m_show_invalid{false}; | ||||
| #endif | ||||
| 
 | ||||
| private: | ||||
|     GLIndexedVertexArray m_iva_enforcers; | ||||
|     GLIndexedVertexArray m_iva_blockers; | ||||
|     std::array<GLIndexedVertexArray, 3> m_varrays; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // Following class is a base class for a gizmo with ability to paint on mesh
 | ||||
| // using circular blush (such as FDM supports gizmo and seam painting gizmo).
 | ||||
| // The purpose is not to duplicate code related to mesh painting.
 | ||||
| class GLGizmoPainterBase : public GLGizmoBase | ||||
| { | ||||
| private: | ||||
|     ObjectID m_old_mo_id; | ||||
|     size_t m_old_volumes_size = 0; | ||||
| 
 | ||||
| public: | ||||
|     GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
|     ~GLGizmoPainterBase() override {} | ||||
|     void set_painter_gizmo_data(const Selection& selection); | ||||
|     bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); | ||||
| 
 | ||||
| protected: | ||||
|     void render_triangles(const Selection& selection) const; | ||||
|     void render_cursor_circle() const; | ||||
|     virtual void update_model_object() const = 0; | ||||
|     virtual void update_from_model_object() = 0; | ||||
|     void activate_internal_undo_redo_stack(bool activate); | ||||
| 
 | ||||
|     float m_cursor_radius = 2.f; | ||||
|     static constexpr float CursorRadiusMin  = 0.4f; // cannot be zero
 | ||||
|     static constexpr float CursorRadiusMax  = 8.f; | ||||
|     static constexpr float CursorRadiusStep = 0.2f; | ||||
| 
 | ||||
|     // For each model-part volume, store status and division of the triangles.
 | ||||
|     std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors; | ||||
| 
 | ||||
| 
 | ||||
| private: | ||||
|     bool is_mesh_point_clipped(const Vec3d& point) const; | ||||
| 
 | ||||
|     float m_clipping_plane_distance = 0.f; | ||||
|     std::unique_ptr<ClippingPlane> m_clipping_plane; | ||||
| 
 | ||||
|     bool m_internal_stack_active = false; | ||||
|     bool m_schedule_update = false; | ||||
|     Vec2d m_last_mouse_position = Vec2d::Zero(); | ||||
| 
 | ||||
|     enum class Button { | ||||
|         None, | ||||
|         Left, | ||||
|         Right | ||||
|     }; | ||||
| 
 | ||||
|     Button m_button_down = Button::None; | ||||
|     EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
 | ||||
| 
 | ||||
| protected: | ||||
|     void on_set_state() override; | ||||
|     void on_start_dragging() override {} | ||||
|     void on_stop_dragging() override {} | ||||
| 
 | ||||
|     virtual void on_opening() = 0; | ||||
|     virtual void on_shutdown() = 0; | ||||
| 
 | ||||
|     bool on_is_activable() const override; | ||||
|     bool on_is_selectable() const override; | ||||
|     void on_load(cereal::BinaryInputArchive& ar) override; | ||||
|     void on_save(cereal::BinaryOutputArchive& ar) const override {} | ||||
|     CommonGizmosDataID on_get_requirements() const override; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #endif // slic3r_GLGizmoPainterBase_hpp_
 | ||||
							
								
								
									
										208
									
								
								src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,208 @@ | |||
| #include "GLGizmoSeam.hpp" | ||||
| 
 | ||||
| #include "libslic3r/Model.hpp" | ||||
| 
 | ||||
| //#include "slic3r/GUI/3DScene.hpp"
 | ||||
| #include "slic3r/GUI/GLCanvas3D.hpp" | ||||
| #include "slic3r/GUI/GUI_App.hpp" | ||||
| #include "slic3r/GUI/ImGuiWrapper.hpp" | ||||
| #include "slic3r/GUI/Plater.hpp" | ||||
| 
 | ||||
| 
 | ||||
| #include <GL/glew.h> | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| bool GLGizmoSeam::on_init() | ||||
| { | ||||
|     m_shortcut_key = WXK_CONTROL_P; | ||||
| 
 | ||||
|     m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; | ||||
|     m_desc["reset_direction"]  = _L("Reset direction"); | ||||
|     m_desc["cursor_size"]      = _L("Cursor size") + ": "; | ||||
|     m_desc["enforce_caption"]  = _L("Left mouse button") + ": "; | ||||
|     m_desc["enforce"]          = _L("Enforce seam"); | ||||
|     m_desc["block_caption"]    = _L("Right mouse button") + " "; | ||||
|     m_desc["block"]            = _L("Block seam"); | ||||
|     m_desc["remove_caption"]   = _L("Shift + Left mouse button") + ": "; | ||||
|     m_desc["remove"]           = _L("Remove selection"); | ||||
|     m_desc["remove_all"]       = _L("Remove all selection"); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| std::string GLGizmoSeam::on_get_name() const | ||||
| { | ||||
|     return (_(L("Seam Editing")) + " [P]").ToUTF8().data(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoSeam::on_render() const | ||||
| { | ||||
|     const Selection& selection = m_parent.get_selection(); | ||||
| 
 | ||||
|     glsafe(::glEnable(GL_BLEND)); | ||||
|     glsafe(::glEnable(GL_DEPTH_TEST)); | ||||
| 
 | ||||
|     render_triangles(selection); | ||||
| 
 | ||||
|     m_c->object_clipper()->render_cut(); | ||||
|     render_cursor_circle(); | ||||
| 
 | ||||
|     glsafe(::glDisable(GL_BLEND)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) | ||||
| { | ||||
|     if (! m_c->selection_info()->model_object()) | ||||
|         return; | ||||
| 
 | ||||
|     const float approx_height = m_imgui->scaled(18.0f); | ||||
|     y = std::min(y, bottom_limit - approx_height); | ||||
|     m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); | ||||
| 
 | ||||
| 
 | ||||
|     m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); | ||||
| 
 | ||||
|     // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
 | ||||
|     const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); | ||||
|     const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); | ||||
|     const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); | ||||
|     const float minimal_slider_width = m_imgui->scaled(4.f); | ||||
| 
 | ||||
|     float caption_max = 0.f; | ||||
|     float total_text_max = 0.; | ||||
|     for (const std::string& t : {"enforce", "block", "remove"}) { | ||||
|         caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t+"_caption")).x); | ||||
|         total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x); | ||||
|     } | ||||
|     caption_max += m_imgui->scaled(1.f); | ||||
|     total_text_max += m_imgui->scaled(1.f); | ||||
| 
 | ||||
|     float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left); | ||||
|     window_width = std::max(window_width, total_text_max); | ||||
|     window_width = std::max(window_width, button_width); | ||||
| 
 | ||||
|     auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { | ||||
|         static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); | ||||
|         m_imgui->text_colored(ORANGE, caption); | ||||
|         ImGui::SameLine(caption_max); | ||||
|         m_imgui->text(text); | ||||
|     }; | ||||
| 
 | ||||
|     for (const std::string& t : {"enforce", "block", "remove"}) | ||||
|         draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); | ||||
| 
 | ||||
|     m_imgui->text(""); | ||||
| 
 | ||||
|     if (m_imgui->button(m_desc.at("remove_all"))) { | ||||
|         Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); | ||||
|         ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|         int idx = -1; | ||||
|         for (ModelVolume* mv : mo->volumes) { | ||||
|             if (mv->is_model_part()) { | ||||
|                 ++idx; | ||||
|                 m_triangle_selectors[idx]->reset(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         update_model_object(); | ||||
|         m_parent.set_as_dirty(); | ||||
|     } | ||||
| 
 | ||||
|     const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; | ||||
| 
 | ||||
|     m_imgui->text(m_desc.at("cursor_size")); | ||||
|     ImGui::SameLine(clipping_slider_left); | ||||
|     ImGui::PushItemWidth(window_width - clipping_slider_left); | ||||
|     ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); | ||||
|     if (ImGui::IsItemHovered()) { | ||||
|         ImGui::BeginTooltip(); | ||||
|         ImGui::PushTextWrapPos(max_tooltip_width); | ||||
|         ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); | ||||
|         ImGui::PopTextWrapPos(); | ||||
|         ImGui::EndTooltip(); | ||||
|     } | ||||
| 
 | ||||
|     ImGui::Separator(); | ||||
|     if (m_c->object_clipper()->get_position() == 0.f) | ||||
|         m_imgui->text(m_desc.at("clipping_of_view")); | ||||
|     else { | ||||
|         if (m_imgui->button(m_desc.at("reset_direction"))) { | ||||
|             wxGetApp().CallAfter([this](){ | ||||
|                     m_c->object_clipper()->set_position(-1., false); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     ImGui::SameLine(clipping_slider_left); | ||||
|     ImGui::PushItemWidth(window_width - clipping_slider_left); | ||||
|     float clp_dist = m_c->object_clipper()->get_position(); | ||||
|     if (ImGui::SliderFloat("  ", &clp_dist, 0.f, 1.f, "%.2f")) | ||||
|     m_c->object_clipper()->set_position(clp_dist, true); | ||||
|     if (ImGui::IsItemHovered()) { | ||||
|         ImGui::BeginTooltip(); | ||||
|         ImGui::PushTextWrapPos(max_tooltip_width); | ||||
|         ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); | ||||
|         ImGui::PopTextWrapPos(); | ||||
|         ImGui::EndTooltip(); | ||||
|     } | ||||
| 
 | ||||
|     m_imgui->end(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoSeam::update_model_object() const | ||||
| { | ||||
|     bool updated = false; | ||||
|     ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|     int idx = -1; | ||||
|     for (ModelVolume* mv : mo->volumes) { | ||||
|         if (! mv->is_model_part()) | ||||
|             continue; | ||||
|         ++idx; | ||||
|         updated |= mv->m_seam_facets.set(*m_triangle_selectors[idx].get()); | ||||
|     } | ||||
| 
 | ||||
|     if (updated) | ||||
|         m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoSeam::update_from_model_object() | ||||
| { | ||||
|     wxBusyCursor wait; | ||||
| 
 | ||||
|     const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|     m_triangle_selectors.clear(); | ||||
| 
 | ||||
|     int volume_id = -1; | ||||
|     for (const ModelVolume* mv : mo->volumes) { | ||||
|         if (! mv->is_model_part()) | ||||
|             continue; | ||||
| 
 | ||||
|         ++volume_id; | ||||
| 
 | ||||
|         // This mesh does not account for the possible Z up SLA offset.
 | ||||
|         const TriangleMesh* mesh = &mv->mesh(); | ||||
| 
 | ||||
|         m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh)); | ||||
|         m_triangle_selectors.back()->deserialize(mv->m_seam_facets.get_data()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
							
								
								
									
										42
									
								
								src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| #ifndef slic3r_GLGizmoSeam_hpp_ | ||||
| #define slic3r_GLGizmoSeam_hpp_ | ||||
| 
 | ||||
| #include "GLGizmoPainterBase.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| class GLGizmoSeam : public GLGizmoPainterBase | ||||
| { | ||||
| public: | ||||
|     GLGizmoSeam(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
|         : GLGizmoPainterBase(parent, icon_filename, sprite_id) {} | ||||
| 
 | ||||
| protected: | ||||
|     void on_render_input_window(float x, float y, float bottom_limit) override; | ||||
|     std::string on_get_name() const override; | ||||
| 
 | ||||
| private: | ||||
|     bool on_init() override; | ||||
|     void on_render() const override; | ||||
|     void on_render_for_picking() const override {} | ||||
| 
 | ||||
|     void update_model_object() const override; | ||||
|     void update_from_model_object() override; | ||||
| 
 | ||||
|     void on_opening() override {} | ||||
|     void on_shutdown() override {} | ||||
| 
 | ||||
|     // This map holds all translated description texts, so they can be easily referenced during layout calculations
 | ||||
|     // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
 | ||||
|     std::map<std::string, wxString> m_desc; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } // namespace GUI
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| 
 | ||||
| #endif // slic3r_GLGizmoSeam_hpp_
 | ||||
|  | @ -16,6 +16,7 @@ | |||
| #include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp" | ||||
| #include "slic3r/GUI/Gizmos/GLGizmoCut.hpp" | ||||
| #include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" | ||||
| #include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp" | ||||
| 
 | ||||
| #include "libslic3r/Model.hpp" | ||||
| #include "libslic3r/PresetBundle.hpp" | ||||
|  | @ -104,6 +105,7 @@ bool GLGizmosManager::init() | |||
|     m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5)); | ||||
|     m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6)); | ||||
|     m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "sla_supports.svg", 7)); | ||||
|     m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8)); | ||||
| 
 | ||||
|     m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent)); | ||||
| 
 | ||||
|  | @ -144,8 +146,11 @@ void GLGizmosManager::refresh_on_off_state() | |||
|     if (m_serializing || m_current == Undefined || m_gizmos.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     if (m_current != Undefined && ! m_gizmos[m_current]->is_activable()) | ||||
|     if (m_current != Undefined | ||||
|     && (! m_gizmos[m_current]->is_activable() || ! m_gizmos[m_current]->is_selectable())) { | ||||
|         activate_gizmo(Undefined); | ||||
|         update_data(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLGizmosManager::reset_all_states() | ||||
|  | @ -204,9 +209,10 @@ void GLGizmosManager::update_data() | |||
|         enable_grabber(Scale, i, enable_scale_xyz); | ||||
|     } | ||||
| 
 | ||||
|     m_common_gizmos_data->update(get_current() | ||||
|                            ? get_current()->get_requirements() | ||||
|                            : CommonGizmosDataID(0)); | ||||
|     if (m_common_gizmos_data) | ||||
|         m_common_gizmos_data->update(get_current() | ||||
|                                    ? get_current()->get_requirements() | ||||
|                                    : CommonGizmosDataID(0)); | ||||
| 
 | ||||
|     if (selection.is_single_full_instance()) | ||||
|     { | ||||
|  | @ -217,7 +223,7 @@ void GLGizmosManager::update_data() | |||
|         ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; | ||||
|         set_flattening_data(model_object); | ||||
|         set_sla_support_data(model_object); | ||||
|         set_fdm_support_data(model_object); | ||||
|         set_painter_gizmo_data(); | ||||
|     } | ||||
|     else if (selection.is_single_volume() || selection.is_single_modifier()) | ||||
|     { | ||||
|  | @ -226,7 +232,7 @@ void GLGizmosManager::update_data() | |||
|         set_rotation(Vec3d::Zero()); | ||||
|         set_flattening_data(nullptr); | ||||
|         set_sla_support_data(nullptr); | ||||
|         set_fdm_support_data(nullptr); | ||||
|         set_painter_gizmo_data(); | ||||
|     } | ||||
|     else if (is_wipe_tower) | ||||
|     { | ||||
|  | @ -235,7 +241,7 @@ void GLGizmosManager::update_data() | |||
|         set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast<const ConfigOptionFloat*>(config.option("wipe_tower_rotation_angle"))->value)); | ||||
|         set_flattening_data(nullptr); | ||||
|         set_sla_support_data(nullptr); | ||||
|         set_fdm_support_data(nullptr); | ||||
|         set_painter_gizmo_data(); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|  | @ -243,7 +249,7 @@ void GLGizmosManager::update_data() | |||
|         set_rotation(Vec3d::Zero()); | ||||
|         set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); | ||||
|         set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); | ||||
|         set_fdm_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); | ||||
|         set_painter_gizmo_data(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -378,12 +384,13 @@ void GLGizmosManager::set_sla_support_data(ModelObject* model_object) | |||
|     gizmo_supports->set_sla_support_data(model_object, m_parent.get_selection()); | ||||
| } | ||||
| 
 | ||||
| void GLGizmosManager::set_fdm_support_data(ModelObject* model_object) | ||||
| void GLGizmosManager::set_painter_gizmo_data() | ||||
| { | ||||
|     if (!m_enabled || m_gizmos.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->set_fdm_support_data(model_object, m_parent.get_selection()); | ||||
|     dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->set_painter_gizmo_data(m_parent.get_selection()); | ||||
|     dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->set_painter_gizmo_data(m_parent.get_selection()); | ||||
| } | ||||
| 
 | ||||
| // Returns true if the gizmo used the event to do something, false otherwise.
 | ||||
|  | @ -398,6 +405,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p | |||
|         return dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); | ||||
|     else if (m_current == FdmSupports) | ||||
|         return dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); | ||||
|     else if (m_current == Seam) | ||||
|         return dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); | ||||
|     else | ||||
|         return false; | ||||
| } | ||||
|  | @ -461,7 +470,7 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) | |||
| { | ||||
|     bool processed = false; | ||||
| 
 | ||||
|     if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) { | ||||
|     if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) { | ||||
|         float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); | ||||
|         if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) | ||||
|             processed = true; | ||||
|  | @ -603,7 +612,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) | |||
| 
 | ||||
|         if (evt.LeftDown()) | ||||
|         { | ||||
|             if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) | ||||
|             if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports ||m_current == Seam) | ||||
|               && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) | ||||
|                 // the gizmo got the event and took some action, there is no need to do anything more
 | ||||
|                 processed = true; | ||||
|  | @ -630,23 +639,24 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) | |||
|             // event was taken care of by the SlaSupports gizmo
 | ||||
|             processed = true; | ||||
|         } | ||||
|         else if (evt.RightDown() && (selected_object_idx != -1) && m_current == FdmSupports | ||||
|         else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == FdmSupports || m_current == Seam) | ||||
|               && gizmo_event(SLAGizmoEventType::RightDown, mouse_pos)) | ||||
|         { | ||||
|             // event was taken care of by the FdmSupports gizmo
 | ||||
|             // event was taken care of by the FdmSupports / Seam gizmo
 | ||||
|             processed = true; | ||||
|         } | ||||
|         else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow)) | ||||
|         else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) | ||||
|              && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam)) | ||||
|             // don't allow dragging objects with the Sla gizmo on
 | ||||
|             processed = true; | ||||
|         else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports ) | ||||
|         else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam ) | ||||
|               && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) | ||||
|         { | ||||
|             // the gizmo got the event and took some action, no need to do anything more here
 | ||||
|             m_parent.set_as_dirty(); | ||||
|             processed = true; | ||||
|         } | ||||
|         else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && !m_parent.is_mouse_dragging()) | ||||
|         else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) && !m_parent.is_mouse_dragging()) | ||||
|         { | ||||
|             // in case SLA/FDM gizmo is selected, we just pass the LeftUp event and stop processing - neither
 | ||||
|             // object moving or selecting is suppressed in that case
 | ||||
|  | @ -658,7 +668,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) | |||
|             // to avoid to loose the selection when user clicks an the white faces of a different object while the Flatten gizmo is active
 | ||||
|             processed = true; | ||||
|         } | ||||
|         else if (evt.RightUp() && m_current == FdmSupports && !m_parent.is_mouse_dragging()) | ||||
|         else if (evt.RightUp() && (m_current == FdmSupports || m_current == Seam) && !m_parent.is_mouse_dragging()) | ||||
|         { | ||||
|             gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); | ||||
|             processed = true; | ||||
|  | @ -748,7 +758,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) | |||
|         case 'r' : | ||||
|         case 'R' : | ||||
|         { | ||||
|             if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && gizmo_event(SLAGizmoEventType::ResetClippingPlane)) | ||||
|             if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) && gizmo_event(SLAGizmoEventType::ResetClippingPlane)) | ||||
|                 processed = true; | ||||
| 
 | ||||
|             break; | ||||
|  |  | |||
|  | @ -67,6 +67,7 @@ public: | |||
|         Hollow, | ||||
|         SlaSupports, | ||||
|         FdmSupports, | ||||
|         Seam, | ||||
|         Undefined | ||||
|     }; | ||||
| 
 | ||||
|  | @ -203,7 +204,7 @@ public: | |||
| 
 | ||||
|     void set_sla_support_data(ModelObject* model_object); | ||||
| 
 | ||||
|     void set_fdm_support_data(ModelObject* model_object); | ||||
|     void set_painter_gizmo_data(); | ||||
| 
 | ||||
|     bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false); | ||||
|     ClippingPlane get_clipping_plane() const; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lukas Matena
						Lukas Matena