mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 20:51:12 -06:00 
			
		
		
		
	Merge branch 'master' into tm_builtin_pad
This commit is contained in:
		
						commit
						af89bcee53
					
				
					 65 changed files with 2903 additions and 1706 deletions
				
			
		|  | @ -81,6 +81,8 @@ set(SLIC3R_GUI_SOURCES | |||
|     GUI/GUI_ObjectManipulation.hpp | ||||
|     GUI/GUI_ObjectSettings.cpp | ||||
|     GUI/GUI_ObjectSettings.hpp | ||||
|     GUI/GUI_ObjectLayers.cpp | ||||
|     GUI/GUI_ObjectLayers.hpp | ||||
|     GUI/LambdaObjectDialog.cpp | ||||
|     GUI/LambdaObjectDialog.hpp | ||||
|     GUI/Tab.cpp | ||||
|  |  | |||
|  | @ -274,8 +274,8 @@ void Bed3D::Axes::render_axis(double length) const | |||
| 
 | ||||
| Bed3D::Bed3D() | ||||
|     : m_type(Custom) | ||||
|     , m_requires_canvas_update(false) | ||||
| #if ENABLE_TEXTURES_FROM_SVG | ||||
|     , m_requires_canvas_update(false) | ||||
|     , m_vbo_id(0) | ||||
| #endif // ENABLE_TEXTURES_FROM_SVG
 | ||||
|     , m_scale_factor(1.0f) | ||||
|  | @ -330,12 +330,11 @@ Point Bed3D::point_projection(const Point& point) const | |||
| } | ||||
| 
 | ||||
| #if ENABLE_TEXTURES_FROM_SVG | ||||
| void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const | ||||
| void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const | ||||
| { | ||||
|     m_scale_factor = scale_factor; | ||||
| 
 | ||||
|     EType type = useVBOs ? m_type : Custom; | ||||
|     switch (type) | ||||
|     switch (m_type) | ||||
|     { | ||||
|     case MK2: | ||||
|     { | ||||
|  | @ -361,7 +360,7 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_fa | |||
|     } | ||||
| } | ||||
| #else | ||||
| void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const | ||||
| void Bed3D::render(float theta, float scale_factor) const | ||||
| { | ||||
|     m_scale_factor = scale_factor; | ||||
| 
 | ||||
|  | @ -372,17 +371,17 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_fa | |||
|     { | ||||
|     case MK2: | ||||
|     { | ||||
|         render_prusa(canvas, "mk2", theta, useVBOs); | ||||
|         render_prusa("mk2", theta); | ||||
|         break; | ||||
|     } | ||||
|     case MK3: | ||||
|     { | ||||
|         render_prusa(canvas, "mk3", theta, useVBOs); | ||||
|         render_prusa("mk3", theta); | ||||
|         break; | ||||
|     } | ||||
|     case SL1: | ||||
|     { | ||||
|         render_prusa(canvas, "sl1", theta, useVBOs); | ||||
|         render_prusa("sl1", theta); | ||||
|         break; | ||||
|     } | ||||
|     default: | ||||
|  | @ -546,7 +545,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom | |||
|     if (!bottom) | ||||
|     { | ||||
|         filename = model_path + "_bed.stl"; | ||||
|         if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, true)) { | ||||
|         if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { | ||||
|             Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); | ||||
|             if (key == "mk2") | ||||
|                 // hardcoded value to match the stl model
 | ||||
|  | @ -628,12 +627,12 @@ void Bed3D::render_prusa_shader(bool transparent) const | |||
|         if (position_id != -1) | ||||
|         { | ||||
|             glsafe(::glEnableVertexAttribArray(position_id)); | ||||
|             glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_position_offset())); | ||||
|             glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset())); | ||||
|         } | ||||
|         if (tex_coords_id != -1) | ||||
|         { | ||||
|             glsafe(::glEnableVertexAttribArray(tex_coords_id)); | ||||
|             glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_tex_coords_offset())); | ||||
|             glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset())); | ||||
|         } | ||||
| 
 | ||||
|         glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count())); | ||||
|  | @ -651,7 +650,7 @@ void Bed3D::render_prusa_shader(bool transparent) const | |||
|     } | ||||
| } | ||||
| #else | ||||
| void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) const | ||||
| void Bed3D::render_prusa(const std::string& key, float theta) const | ||||
| { | ||||
|     std::string tex_path = resources_dir() + "/icons/bed/" + key; | ||||
| 
 | ||||
|  | @ -677,7 +676,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons | |||
|     std::string filename = tex_path + "_top.png"; | ||||
|     if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) | ||||
|     { | ||||
|         if (!m_top_texture.load_from_file(filename, true)) | ||||
|         if (!m_top_texture.load_from_file(filename, true, true)) | ||||
|         { | ||||
|             render_custom(); | ||||
|             return; | ||||
|  | @ -694,7 +693,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons | |||
|     filename = tex_path + "_bottom.png"; | ||||
|     if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) | ||||
|     { | ||||
|         if (!m_bottom_texture.load_from_file(filename, true)) | ||||
|         if (!m_bottom_texture.load_from_file(filename, true, true)) | ||||
|         { | ||||
|             render_custom(); | ||||
|             return; | ||||
|  | @ -711,7 +710,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons | |||
|     if (theta <= 90.0f) | ||||
|     { | ||||
|         filename = model_path + "_bed.stl"; | ||||
|         if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, useVBOs)) { | ||||
|         if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { | ||||
|             Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); | ||||
|             if (key == "mk2") | ||||
|                 // hardcoded value to match the stl model
 | ||||
|  |  | |||
|  | @ -44,8 +44,8 @@ public: | |||
|     const float* get_vertices_data() const; | ||||
|     unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); } | ||||
|     unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); } | ||||
|     unsigned int get_position_offset() const { return 0; } | ||||
|     unsigned int get_tex_coords_offset() const { return (unsigned int)(3 * sizeof(float)); } | ||||
|     size_t get_position_offset() const { return 0; } | ||||
|     size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); } | ||||
|     unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); } | ||||
| #else | ||||
|     const float* get_vertices() const { return m_vertices.data(); } | ||||
|  | @ -128,7 +128,11 @@ public: | |||
|     bool contains(const Point& point) const; | ||||
|     Point point_projection(const Point& point) const; | ||||
| 
 | ||||
|     void render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const; | ||||
| #if ENABLE_TEXTURES_FROM_SVG | ||||
|     void render(GLCanvas3D* canvas, float theta, float scale_factor) const; | ||||
| #else | ||||
|     void render(float theta, float scale_factor) const; | ||||
| #endif // ENABLE_TEXTURES_FROM_SVG
 | ||||
|     void render_axes() const; | ||||
| 
 | ||||
| private: | ||||
|  | @ -140,7 +144,7 @@ private: | |||
|     void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const; | ||||
|     void render_prusa_shader(bool transparent) const; | ||||
| #else | ||||
|     void render_prusa(const std::string &key, float theta, bool useVBOs) const; | ||||
|     void render_prusa(const std::string& key, float theta) const; | ||||
| #endif // ENABLE_TEXTURES_FROM_SVG
 | ||||
|     void render_custom() const; | ||||
| #if ENABLE_TEXTURES_FROM_SVG | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -56,7 +56,7 @@ public: | |||
|         vertices_and_normals_interleaved_VBO_id(0), | ||||
|         triangle_indices_VBO_id(0), | ||||
|         quad_indices_VBO_id(0) | ||||
|         { this->setup_sizes(); } | ||||
|         {} | ||||
|     GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : | ||||
|         vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), | ||||
|         triangle_indices(rhs.triangle_indices), | ||||
|  | @ -64,7 +64,7 @@ public: | |||
|         vertices_and_normals_interleaved_VBO_id(0), | ||||
|         triangle_indices_VBO_id(0), | ||||
|         quad_indices_VBO_id(0) | ||||
|         { this->setup_sizes(); } | ||||
|         {} | ||||
|     GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : | ||||
|         vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), | ||||
|         triangle_indices(std::move(rhs.triangle_indices)), | ||||
|  | @ -72,7 +72,9 @@ public: | |||
|         vertices_and_normals_interleaved_VBO_id(0), | ||||
|         triangle_indices_VBO_id(0), | ||||
|         quad_indices_VBO_id(0) | ||||
|         { this->setup_sizes(); } | ||||
|         {} | ||||
| 
 | ||||
|     ~GLIndexedVertexArray() { release_geometry(); } | ||||
| 
 | ||||
|     GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) | ||||
|     { | ||||
|  | @ -82,7 +84,10 @@ public: | |||
|         this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; | ||||
|         this->triangle_indices                 = rhs.triangle_indices; | ||||
|         this->quad_indices                     = rhs.quad_indices; | ||||
|         this->setup_sizes(); | ||||
|         this->m_bounding_box                   = rhs.m_bounding_box; | ||||
|         vertices_and_normals_interleaved_size  = rhs.vertices_and_normals_interleaved_size; | ||||
|         triangle_indices_size                  = rhs.triangle_indices_size; | ||||
|         quad_indices_size                      = rhs.quad_indices_size; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|  | @ -94,30 +99,32 @@ public: | |||
|         this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); | ||||
|         this->triangle_indices                 = std::move(rhs.triangle_indices); | ||||
|         this->quad_indices                     = std::move(rhs.quad_indices); | ||||
|         this->setup_sizes(); | ||||
|         this->m_bounding_box                   = std::move(rhs.m_bounding_box); | ||||
|         vertices_and_normals_interleaved_size  = rhs.vertices_and_normals_interleaved_size; | ||||
|         triangle_indices_size                  = rhs.triangle_indices_size; | ||||
|         quad_indices_size                      = rhs.quad_indices_size; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x)
 | ||||
|     std::vector<float> vertices_and_normals_interleaved; | ||||
|     std::vector<int>   triangle_indices; | ||||
|     std::vector<int>   quad_indices; | ||||
|     mutable std::vector<float> vertices_and_normals_interleaved; | ||||
|     mutable std::vector<int>   triangle_indices; | ||||
|     mutable std::vector<int>   quad_indices; | ||||
| 
 | ||||
|     // When the geometry data is loaded into the graphics card as Vertex Buffer Objects,
 | ||||
|     // the above mentioned std::vectors are cleared and the following variables keep their original length.
 | ||||
|     size_t             vertices_and_normals_interleaved_size; | ||||
|     size_t             triangle_indices_size; | ||||
|     size_t             quad_indices_size; | ||||
|     size_t vertices_and_normals_interleaved_size{ 0 }; | ||||
|     size_t triangle_indices_size{ 0 }; | ||||
|     size_t quad_indices_size{ 0 }; | ||||
| 
 | ||||
|     // IDs of the Vertex Array Objects, into which the geometry has been loaded.
 | ||||
|     // Zero if the VBOs are not used.
 | ||||
|     unsigned int       vertices_and_normals_interleaved_VBO_id; | ||||
|     unsigned int       triangle_indices_VBO_id; | ||||
|     unsigned int       quad_indices_VBO_id; | ||||
|     // Zero if the VBOs are not sent to GPU yet.
 | ||||
|     mutable unsigned int       vertices_and_normals_interleaved_VBO_id{ 0 }; | ||||
|     mutable unsigned int       triangle_indices_VBO_id{ 0 }; | ||||
|     mutable unsigned int       quad_indices_VBO_id{ 0 }; | ||||
| 
 | ||||
|     void load_mesh_flat_shading(const TriangleMesh &mesh); | ||||
|     void load_mesh_full_shading(const TriangleMesh &mesh); | ||||
| 	void load_mesh(const TriangleMesh &mesh, bool use_VBOs) { use_VBOs ? this->load_mesh_full_shading(mesh) : this->load_mesh_flat_shading(mesh); } | ||||
|     void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } | ||||
| 
 | ||||
|     inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } | ||||
| 
 | ||||
|  | @ -128,6 +135,10 @@ public: | |||
|     } | ||||
| 
 | ||||
|     inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { | ||||
|         assert(this->vertices_and_normals_interleaved_VBO_id == 0); | ||||
|         if (this->vertices_and_normals_interleaved_VBO_id != 0) | ||||
|             return; | ||||
| 
 | ||||
|         if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) | ||||
|             this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); | ||||
|         this->vertices_and_normals_interleaved.push_back(nx); | ||||
|  | @ -136,6 +147,9 @@ public: | |||
|         this->vertices_and_normals_interleaved.push_back(x); | ||||
|         this->vertices_and_normals_interleaved.push_back(y); | ||||
|         this->vertices_and_normals_interleaved.push_back(z); | ||||
| 
 | ||||
|         this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); | ||||
|         m_bounding_box.merge(Vec3f(x, y, z).cast<double>()); | ||||
|     }; | ||||
| 
 | ||||
|     inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { | ||||
|  | @ -147,80 +161,66 @@ public: | |||
|     } | ||||
| 
 | ||||
|     inline void push_triangle(int idx1, int idx2, int idx3) { | ||||
|         assert(this->vertices_and_normals_interleaved_VBO_id == 0); | ||||
|         if (this->vertices_and_normals_interleaved_VBO_id != 0) | ||||
|             return; | ||||
| 
 | ||||
|         if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) | ||||
|             this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); | ||||
|         this->triangle_indices.push_back(idx1); | ||||
|         this->triangle_indices.push_back(idx2); | ||||
|         this->triangle_indices.push_back(idx3); | ||||
|         this->triangle_indices_size = this->triangle_indices.size(); | ||||
|     }; | ||||
| 
 | ||||
|     inline void push_quad(int idx1, int idx2, int idx3, int idx4) { | ||||
|         assert(this->vertices_and_normals_interleaved_VBO_id == 0); | ||||
|         if (this->vertices_and_normals_interleaved_VBO_id != 0) | ||||
|             return; | ||||
| 
 | ||||
|         if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) | ||||
|             this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); | ||||
|         this->quad_indices.push_back(idx1); | ||||
|         this->quad_indices.push_back(idx2); | ||||
|         this->quad_indices.push_back(idx3); | ||||
|         this->quad_indices.push_back(idx4); | ||||
|         this->quad_indices_size = this->quad_indices.size(); | ||||
|     }; | ||||
| 
 | ||||
|     // Finalize the initialization of the geometry & indices,
 | ||||
|     // upload the geometry and indices to OpenGL VBO objects
 | ||||
|     // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
 | ||||
|     void finalize_geometry(bool use_VBOs); | ||||
|     void finalize_geometry() const; | ||||
|     // Release the geometry data, release OpenGL VBOs.
 | ||||
|     void release_geometry(); | ||||
|     // Render either using an immediate mode, or the VBOs.
 | ||||
| 
 | ||||
|     void render() const; | ||||
|     void render(const std::pair<size_t, size_t> &tverts_range, const std::pair<size_t, size_t> &qverts_range) const; | ||||
|     void render(const std::pair<size_t, size_t>& tverts_range, const std::pair<size_t, size_t>& qverts_range) const; | ||||
| 
 | ||||
|     // Is there any geometry data stored?
 | ||||
|     bool empty() const { return vertices_and_normals_interleaved_size == 0; } | ||||
| 
 | ||||
|     // Is this object indexed, or is it just a set of triangles?
 | ||||
|     bool indexed() const { return ! this->empty() && this->triangle_indices_size + this->quad_indices_size > 0; } | ||||
| 
 | ||||
|     void clear() { | ||||
|         this->vertices_and_normals_interleaved.clear(); | ||||
|         this->triangle_indices.clear(); | ||||
|         this->quad_indices.clear(); | ||||
|         this->setup_sizes(); | ||||
|         this->m_bounding_box.reset(); | ||||
|         vertices_and_normals_interleaved_size = 0; | ||||
|         triangle_indices_size = 0; | ||||
|         quad_indices_size = 0; | ||||
|     } | ||||
| 
 | ||||
|     // Shrink the internal storage to tighly fit the data stored.
 | ||||
|     void shrink_to_fit() {  | ||||
|         if (! this->has_VBOs()) | ||||
|             this->setup_sizes(); | ||||
|     void shrink_to_fit() const { | ||||
|         this->vertices_and_normals_interleaved.shrink_to_fit(); | ||||
|         this->triangle_indices.shrink_to_fit(); | ||||
|         this->quad_indices.shrink_to_fit(); | ||||
|     } | ||||
| 
 | ||||
|     BoundingBoxf3 bounding_box() const { | ||||
|         BoundingBoxf3 bbox; | ||||
|         if (! this->vertices_and_normals_interleaved.empty()) { | ||||
|             bbox.defined = true; | ||||
|             bbox.min(0) = bbox.max(0) = this->vertices_and_normals_interleaved[3]; | ||||
|             bbox.min(1) = bbox.max(1) = this->vertices_and_normals_interleaved[4]; | ||||
|             bbox.min(2) = bbox.max(2) = this->vertices_and_normals_interleaved[5]; | ||||
|             for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) { | ||||
|                 const float *verts = this->vertices_and_normals_interleaved.data() + i; | ||||
|                 bbox.min(0) = std::min<coordf_t>(bbox.min(0), verts[0]); | ||||
|                 bbox.min(1) = std::min<coordf_t>(bbox.min(1), verts[1]); | ||||
|                 bbox.min(2) = std::min<coordf_t>(bbox.min(2), verts[2]); | ||||
|                 bbox.max(0) = std::max<coordf_t>(bbox.max(0), verts[0]); | ||||
|                 bbox.max(1) = std::max<coordf_t>(bbox.max(1), verts[1]); | ||||
|                 bbox.max(2) = std::max<coordf_t>(bbox.max(2), verts[2]); | ||||
|             } | ||||
|         } | ||||
|         return bbox; | ||||
|     } | ||||
|     const BoundingBoxf3& bounding_box() const { return m_bounding_box; } | ||||
| 
 | ||||
| private: | ||||
|     inline void setup_sizes() { | ||||
|         vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); | ||||
|         triangle_indices_size                 = this->triangle_indices.size(); | ||||
|         quad_indices_size                     = this->quad_indices.size(); | ||||
|     } | ||||
|     BoundingBoxf3 m_bounding_box; | ||||
| }; | ||||
| 
 | ||||
| class GLVolume { | ||||
|  | @ -263,8 +263,6 @@ private: | |||
|     mutable bool          m_transformed_convex_hull_bounding_box_dirty; | ||||
| 
 | ||||
| public: | ||||
|     // Bounding box of this volume, in unscaled coordinates.
 | ||||
|     BoundingBoxf3       bounding_box; | ||||
|     // Color of the triangles / quads held by this volume.
 | ||||
|     float               color[4]; | ||||
|     // Color used to render this volume.
 | ||||
|  | @ -329,6 +327,9 @@ public: | |||
|     // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
 | ||||
|     std::vector<size_t>         offsets; | ||||
| 
 | ||||
|     // Bounding box of this volume, in unscaled coordinates.
 | ||||
|     const BoundingBoxf3& bounding_box() const { return this->indexed_vertex_array.bounding_box(); } | ||||
| 
 | ||||
|     void set_render_color(float r, float g, float b, float a); | ||||
|     void set_render_color(const float* rgba, unsigned int size); | ||||
|     // Sets render color in dependence of current state
 | ||||
|  | @ -411,14 +412,13 @@ public: | |||
|     const BoundingBoxf3& transformed_convex_hull_bounding_box() const; | ||||
| 
 | ||||
|     bool                empty() const { return this->indexed_vertex_array.empty(); } | ||||
|     bool                indexed() const { return this->indexed_vertex_array.indexed(); } | ||||
| 
 | ||||
|     void                set_range(coordf_t low, coordf_t high); | ||||
|     void                render() const; | ||||
|     void                render_VBOs(int color_id, int detection_id, int worldmatrix_id) const; | ||||
|     void                render_legacy() const; | ||||
| 
 | ||||
|     void                finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); } | ||||
|     void                render() const; | ||||
|     void                render(int color_id, int detection_id, int worldmatrix_id) const; | ||||
| 
 | ||||
|     void                finalize_geometry() { this->indexed_vertex_array.finalize_geometry(); } | ||||
|     void                release_geometry() { this->indexed_vertex_array.release_geometry(); } | ||||
| 
 | ||||
|     void                set_bounding_boxes_as_dirty() { m_transformed_bounding_box_dirty = true; m_transformed_convex_hull_bounding_box_dirty = true; } | ||||
|  | @ -459,42 +459,38 @@ public: | |||
|     ~GLVolumeCollection() { clear(); }; | ||||
| 
 | ||||
|     std::vector<int> load_object( | ||||
|         const ModelObject       *model_object, | ||||
|         const ModelObject* model_object, | ||||
|         int                      obj_idx, | ||||
|         const std::vector<int>  &instance_idxs, | ||||
|         const std::string       &color_by, | ||||
|         bool                     use_VBOs); | ||||
|         const std::vector<int>& instance_idxs, | ||||
|         const std::string& color_by); | ||||
| 
 | ||||
|     int load_object_volume( | ||||
|         const ModelObject       *model_object, | ||||
|         const ModelObject* model_object, | ||||
|         int                      obj_idx, | ||||
|         int                      volume_idx, | ||||
|         int                      instance_idx, | ||||
|         const std::string       &color_by, | ||||
|         bool                     use_VBOs); | ||||
|         const std::string& color_by); | ||||
| 
 | ||||
|     // Load SLA auxiliary GLVolumes (for support trees or pad).
 | ||||
|     void load_object_auxiliary( | ||||
|         const SLAPrintObject           *print_object, | ||||
|         const SLAPrintObject* print_object, | ||||
|         int                             obj_idx, | ||||
|         // pairs of <instance_idx, print_instance_idx>
 | ||||
|         const std::vector<std::pair<size_t, size_t>> &instances, | ||||
|         const std::vector<std::pair<size_t, size_t>>& instances, | ||||
|         SLAPrintObjectStep              milestone, | ||||
|         // Timestamp of the last change of the milestone
 | ||||
|         size_t                          timestamp, | ||||
|         bool                            use_VBOs); | ||||
|         size_t                          timestamp); | ||||
| 
 | ||||
|     int load_wipe_tower_preview( | ||||
|         int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); | ||||
|         int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); | ||||
| 
 | ||||
|     // Render the volumes by OpenGL.
 | ||||
|     void render_VBOs(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const; | ||||
|     void render_legacy(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const; | ||||
|     void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const; | ||||
| 
 | ||||
|     // Finalize the initialization of the geometry & indices,
 | ||||
|     // upload the geometry and indices to OpenGL VBO objects
 | ||||
|     // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
 | ||||
|     void finalize_geometry(bool use_VBOs) { for (auto *v : volumes) v->finalize_geometry(use_VBOs); } | ||||
|     void finalize_geometry() { for (auto* v : volumes) v->finalize_geometry(); } | ||||
|     // Release the geometry data assigned to the volumes.
 | ||||
|     // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them.
 | ||||
|     void release_geometry() { for (auto *v : volumes) v->release_geometry(); } | ||||
|  | @ -533,17 +529,16 @@ class GLModel | |||
| { | ||||
| protected: | ||||
|     GLVolume m_volume; | ||||
|     bool m_useVBOs; | ||||
|     std::string m_filename; | ||||
| 
 | ||||
| public: | ||||
|     GLModel(); | ||||
|     virtual ~GLModel(); | ||||
| 
 | ||||
|     bool init(bool useVBOs) { return on_init(useVBOs); } | ||||
|     bool init_from_file(const std::string& filename, bool useVBOs) { return on_init_from_file(filename, useVBOs); } | ||||
|     bool init() { return on_init(); } | ||||
|     bool init_from_file(const std::string& filename) { return on_init_from_file(filename); } | ||||
| 
 | ||||
|     void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box.center()); } | ||||
|     void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box().center()); } | ||||
|     void set_color(const float* color, unsigned int size); | ||||
| 
 | ||||
|     const Vec3d& get_offset() const; | ||||
|  | @ -554,7 +549,7 @@ public: | |||
|     void set_scale(const Vec3d& scale); | ||||
| 
 | ||||
|     const std::string& get_filename() const { return m_filename; } | ||||
|     const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box; } | ||||
|     const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box(); } | ||||
|     const BoundingBoxf3& get_transformed_bounding_box() const { return m_volume.transformed_bounding_box(); } | ||||
| 
 | ||||
|     void reset(); | ||||
|  | @ -562,18 +557,14 @@ public: | |||
|     void render() const;  | ||||
| 
 | ||||
| protected: | ||||
|     virtual bool on_init(bool useVBOs) { return false; } | ||||
|     virtual bool on_init_from_file(const std::string& filename, bool useVBOs) { return false; } | ||||
| 
 | ||||
| private: | ||||
|     void render_VBOs() const; | ||||
|     void render_legacy() const; | ||||
|     virtual bool on_init() { return false; } | ||||
|     virtual bool on_init_from_file(const std::string& filename) { return false; } | ||||
| }; | ||||
| 
 | ||||
| class GLArrow : public GLModel | ||||
| { | ||||
| protected: | ||||
|     virtual bool on_init(bool useVBOs); | ||||
|     virtual bool on_init(); | ||||
| }; | ||||
| 
 | ||||
| class GLCurvedArrow : public GLModel | ||||
|  | @ -584,13 +575,13 @@ public: | |||
|     explicit GLCurvedArrow(unsigned int resolution); | ||||
| 
 | ||||
| protected: | ||||
|     virtual bool on_init(bool useVBOs); | ||||
|     virtual bool on_init(); | ||||
| }; | ||||
| 
 | ||||
| class GLBed : public GLModel | ||||
| { | ||||
| protected: | ||||
|     virtual bool on_init_from_file(const std::string& filename, bool useVBOs); | ||||
|     virtual bool on_init_from_file(const std::string& filename); | ||||
| }; | ||||
| 
 | ||||
| class _3DScene | ||||
|  |  | |||
|  | @ -53,15 +53,12 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) | |||
| 
 | ||||
| void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | ||||
| { | ||||
| //  on_change(nullptr);
 | ||||
| 
 | ||||
| 	auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape"))); | ||||
| 	auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL); | ||||
|     auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); | ||||
| 
 | ||||
| 	// shape options
 | ||||
|     m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition,  | ||||
|                            wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); | ||||
| 	sbsizer->Add(m_shape_options_book); | ||||
|     sbsizer->Add(m_shape_options_book); | ||||
| 
 | ||||
| 	auto optgroup = init_shape_options_page(_(L("Rectangular"))); | ||||
| 		ConfigOptionDef def; | ||||
|  | @ -92,13 +89,15 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | |||
| 		Line line{ "", "" }; | ||||
| 		line.full_width = 1; | ||||
| 		line.widget = [this](wxWindow* parent) { | ||||
| 			auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize); | ||||
| 			 | ||||
| 			auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
| 			sizer->Add(btn); | ||||
|             auto shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); | ||||
|             wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|             shape_sizer->Add(shape_btn, 1, wxEXPAND); | ||||
| 
 | ||||
| 			btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) | ||||
| 			{ | ||||
|             wxSizer* sizer = new wxBoxSizer(wxVERTICAL); | ||||
|             sizer->Add(shape_sizer, 1, wxEXPAND); | ||||
| 
 | ||||
|             shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) | ||||
|             { | ||||
| 				load_stl(); | ||||
| 			})); | ||||
| 
 | ||||
|  | @ -106,8 +105,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | |||
| 		}; | ||||
| 		optgroup->append_line(line); | ||||
| 
 | ||||
| 	Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e) | ||||
| 	{ | ||||
|     Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) | ||||
|     { | ||||
| 		update_shape(); | ||||
| 	})); | ||||
| 
 | ||||
|  | @ -117,8 +116,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | |||
| 
 | ||||
| 	// main sizer
 | ||||
| 	auto top_sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
| 	top_sizer->Add(sbsizer, 0, wxEXPAND | wxLeft | wxTOP | wxBOTTOM, 10); | ||||
| 	if (m_canvas) | ||||
|     top_sizer->Add(sbsizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); | ||||
|     if (m_canvas) | ||||
| 		top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ; | ||||
| 
 | ||||
| 	SetSizerAndFit(top_sizer); | ||||
|  | @ -135,8 +134,7 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) | |||
| // Create a panel for a rectangular / circular / custom bed shape.
 | ||||
| ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) | ||||
| { | ||||
| 
 | ||||
| 	auto panel = new wxPanel(m_shape_options_book); | ||||
|     auto panel = new wxPanel(m_shape_options_book); | ||||
| 	ConfigOptionsGroupShp optgroup; | ||||
| 	optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings"))); | ||||
| 
 | ||||
|  | @ -234,8 +232,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) | |||
| 	// This is a custom bed shape, use the polygon provided.
 | ||||
| 	m_shape_options_book->SetSelection(SHAPE_CUSTOM); | ||||
| 	// Copy the polygon to the canvas, make a copy of the array.
 | ||||
| 	m_canvas->m_bed_shape = points->values; | ||||
| 	update_shape(); | ||||
|     m_loaded_bed_shape = points->values; | ||||
|     update_shape(); | ||||
| } | ||||
| 
 | ||||
| void BedShapePanel::update_preview() | ||||
|  |  | |||
|  | @ -113,11 +113,19 @@ wxString Field::get_tooltip_text(const wxString& default_string) | |||
| 	wxString tooltip_text(""); | ||||
| 	wxString tooltip = _(m_opt.tooltip); | ||||
|     edit_tooltip(tooltip); | ||||
| 
 | ||||
|     std::string opt_id = m_opt_id; | ||||
|     auto hash_pos = opt_id.find("#"); | ||||
|     if (hash_pos != std::string::npos) { | ||||
|         opt_id.replace(hash_pos, 1,"["); | ||||
|         opt_id += "]"; | ||||
|     } | ||||
| 
 | ||||
| 	if (tooltip.length() > 0) | ||||
|         tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + | ||||
|         (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + | ||||
|         (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") +  | ||||
|         _(L("parameter name")) + "\t: " + m_opt_id; | ||||
|         (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string + | ||||
|         (boost::iends_with(opt_id, "_gcode") ? "" : "\n") +  | ||||
|         _(L("parameter name")) + "\t: " + opt_id; | ||||
| 
 | ||||
| 	return tooltip_text; | ||||
| } | ||||
|  |  | |||
|  | @ -524,7 +524,7 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G | |||
|         glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); | ||||
|         glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); | ||||
|         glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); | ||||
|         for (const GLVolume *glvolume : volumes.volumes) { | ||||
|         for (const GLVolume* glvolume : volumes.volumes) { | ||||
|             // Render the object using the layer editing shader and texture.
 | ||||
|             if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) | ||||
|                 continue; | ||||
|  | @ -543,8 +543,8 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G | |||
|     { | ||||
|         // Something went wrong. Just render the object.
 | ||||
|         assert(false); | ||||
| 		for (const GLVolume *glvolume : volumes.volumes) { | ||||
| 			// Render the object using the layer editing shader and texture.
 | ||||
|         for (const GLVolume* glvolume : volumes.volumes) { | ||||
|             // Render the object using the layer editing shader and texture.
 | ||||
| 			if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) | ||||
| 				continue; | ||||
|             glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast<float>().data())); | ||||
|  | @ -912,7 +912,8 @@ GLCanvas3D::LegendTexture::LegendTexture() | |||
| void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas,  | ||||
|                                                                std::vector<std::pair<double, double>>& cp_legend_values) | ||||
| { | ||||
|     if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint)  | ||||
|     if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint &&  | ||||
|         wxGetApp().extruders_edited_cnt() == 1) // show color change legend only for single-material presets
 | ||||
|     { | ||||
|         auto& config = wxGetApp().preset_bundle->project_config; | ||||
|         const std::vector<double>& color_print_values = config.option<ConfigOptionFloats>("colorprint_heights")->values; | ||||
|  | @ -1211,7 +1212,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar | |||
|     , m_model(nullptr) | ||||
|     , m_dirty(true) | ||||
|     , m_initialized(false) | ||||
|     , m_use_VBOs(false) | ||||
|     , m_apply_zoom_to_volumes_filter(false) | ||||
|     , m_legend_texture_enabled(false) | ||||
|     , m_picking_enabled(false) | ||||
|  | @ -1252,7 +1252,7 @@ void GLCanvas3D::post_event(wxEvent &&event) | |||
|     wxPostEvent(m_canvas, event); | ||||
| } | ||||
| 
 | ||||
| bool GLCanvas3D::init(bool useVBOs) | ||||
| bool GLCanvas3D::init() | ||||
| { | ||||
|     if (m_initialized) | ||||
|         return true; | ||||
|  | @ -1303,30 +1303,30 @@ bool GLCanvas3D::init(bool useVBOs) | |||
|     if (m_multisample_allowed) | ||||
|         glsafe(::glEnable(GL_MULTISAMPLE)); | ||||
| 
 | ||||
|     if (useVBOs && !m_shader.init("gouraud.vs", "gouraud.fs")) | ||||
|     if (!m_shader.init("gouraud.vs", "gouraud.fs")) | ||||
|     { | ||||
|         std::cout << "Unable to initialize gouraud shader: please, check that the files gouraud.vs and gouraud.fs are available" << std::endl; | ||||
|         return false; | ||||
| 
 | ||||
|     if (m_toolbar.is_enabled() && useVBOs && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) | ||||
|         return false; | ||||
| 
 | ||||
|     m_use_VBOs = useVBOs; | ||||
| 
 | ||||
|     // on linux the gl context is not valid until the canvas is not shown on screen
 | ||||
|     // we defer the geometry finalization of volumes until the first call to render()
 | ||||
|     if (!m_volumes.empty()) | ||||
|         m_volumes.finalize_geometry(m_use_VBOs); | ||||
| 
 | ||||
|     if (m_gizmos.is_enabled()) { | ||||
|         if (! m_gizmos.init(*this)) {  | ||||
|             std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (m_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) | ||||
|     { | ||||
|         std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl; | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| //    // on linux the gl context is not valid until the canvas is not shown on screen
 | ||||
| //    // we defer the geometry finalization of volumes until the first call to render()
 | ||||
| //    if (!m_volumes.empty())
 | ||||
| //        m_volumes.finalize_geometry();
 | ||||
| 
 | ||||
|     if (m_gizmos.is_enabled() && !m_gizmos.init(*this)) | ||||
|         std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; | ||||
| 
 | ||||
|     if (!_init_toolbar()) | ||||
|         return false; | ||||
| 
 | ||||
|     if (m_selection.is_enabled() && !m_selection.init(m_use_VBOs)) | ||||
|     if (m_selection.is_enabled() && !m_selection.init()) | ||||
|         return false; | ||||
| 
 | ||||
|     post_event(SimpleEvent(EVT_GLCANVAS_INIT)); | ||||
|  | @ -1356,7 +1356,6 @@ void GLCanvas3D::reset_volumes() | |||
|     if (!m_volumes.empty()) | ||||
|     { | ||||
|         m_selection.clear(); | ||||
|         m_volumes.release_geometry(); | ||||
|         m_volumes.clear(); | ||||
|         m_dirty = true; | ||||
|     } | ||||
|  | @ -1778,7 +1777,7 @@ std::vector<int> GLCanvas3D::load_object(const ModelObject& model_object, int ob | |||
|             instance_idxs.push_back(i); | ||||
|         } | ||||
|     } | ||||
|     return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_use_VBOs && m_initialized); | ||||
|     return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by); | ||||
| } | ||||
| 
 | ||||
| std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx) | ||||
|  | @ -1918,7 +1917,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re | |||
|                     assert(volume_idx_wipe_tower_old == -1); | ||||
|                     volume_idx_wipe_tower_old = (int)volume_id; | ||||
|                 } | ||||
|                 volume->release_geometry(); | ||||
|                 if (! m_reload_delayed) | ||||
|                     delete volume; | ||||
|             } else { | ||||
|  | @ -1966,8 +1964,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re | |||
| 					assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); | ||||
|                     if (it->new_geometry()) { | ||||
|                         // New volume.
 | ||||
|                         m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_use_VBOs && m_initialized); | ||||
| 						m_volumes.volumes.back()->geometry_id = key.geometry_id; | ||||
|                         m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by); | ||||
|                         m_volumes.volumes.back()->geometry_id = key.geometry_id; | ||||
| 						update_object_list = true; | ||||
|                     } else { | ||||
| 						// Recycling an old GLVolume.
 | ||||
|  | @ -2031,7 +2029,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re | |||
| 
 | ||||
|                 for (size_t istep = 0; istep < sla_steps.size(); ++istep) | ||||
|                     if (!instances[istep].empty()) | ||||
|                         m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_use_VBOs && m_initialized); | ||||
|                         m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); | ||||
|             } | ||||
| 
 | ||||
| 			// Shift-up all volumes of the object so that it has the right elevation with respect to the print bed
 | ||||
|  | @ -2068,9 +2066,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re | |||
|                 float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); | ||||
| 
 | ||||
|                 if (!print->is_step_done(psWipeTower)) | ||||
|                     depth = (900.f/w) * (float)(extruders_count - 1) ; | ||||
|                     depth = (900.f/w) * (float)(extruders_count - 1); | ||||
|                 int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( | ||||
|                     1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), | ||||
|                     1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), | ||||
|                     brim_spacing * 4.5f); | ||||
|                 if (volume_idx_wipe_tower_old != -1) | ||||
|                     map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; | ||||
|  | @ -3323,6 +3321,12 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) | ||||
| { | ||||
|     std::string field = "layer_" + std::to_string(type) + "_" + std::to_string(range.first) + "_" + std::to_string(range.second); | ||||
|     handle_sidebar_focus_event(field, true); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::update_ui_from_settings() | ||||
| { | ||||
|     m_camera.set_type(wxGetApp().app_config->get("use_perspective_camera")); | ||||
|  | @ -3356,7 +3360,7 @@ arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const | |||
|             wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"), | ||||
|                             m_config->opt_float("wipe_tower_y")); | ||||
|             wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); | ||||
|             const BoundingBoxf3& bb = vol->bounding_box; | ||||
|             const BoundingBoxf3& bb = vol->bounding_box(); | ||||
|             wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); | ||||
|             break; | ||||
|         } | ||||
|  | @ -3821,7 +3825,11 @@ void GLCanvas3D::_render_bed(float theta) const | |||
| #if ENABLE_RETINA_GL | ||||
|     scale_factor = m_retina_helper->get_scale_factor(); | ||||
| #endif // ENABLE_RETINA_GL
 | ||||
|     m_bed.render(const_cast<GLCanvas3D*>(this), theta, m_use_VBOs, scale_factor); | ||||
| #if ENABLE_TEXTURES_FROM_SVG | ||||
|     m_bed.render(const_cast<GLCanvas3D*>(this), theta, scale_factor); | ||||
| #else | ||||
|     m_bed.render(theta, scale_factor); | ||||
| #endif // ENABLE_TEXTURES_FROM_SVG
 | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::_render_axes() const | ||||
|  | @ -3829,8 +3837,6 @@ void GLCanvas3D::_render_axes() const | |||
|     m_bed.render_axes(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLCanvas3D::_render_objects() const | ||||
| { | ||||
|     if (m_volumes.empty()) | ||||
|  | @ -3841,75 +3847,44 @@ void GLCanvas3D::_render_objects() const | |||
| 
 | ||||
|     m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); | ||||
| 
 | ||||
|     if (m_use_VBOs) | ||||
|     if (m_picking_enabled) | ||||
|     { | ||||
|         if (m_picking_enabled) | ||||
|         // Update the layer editing selection to the first object selected, update the current object maximum Z.
 | ||||
|         const_cast<LayersEditing&>(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); | ||||
| 
 | ||||
|         if (m_config != nullptr) | ||||
|         { | ||||
|             // Update the layer editing selection to the first object selected, update the current object maximum Z.
 | ||||
|             const_cast<LayersEditing&>(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); | ||||
| 
 | ||||
|             if (m_config != nullptr) | ||||
|             { | ||||
|                 const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(false); | ||||
|                 m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); | ||||
|                 m_volumes.check_outside_state(m_config, nullptr); | ||||
|             } | ||||
|             const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(false); | ||||
|             m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); | ||||
|             m_volumes.check_outside_state(m_config, nullptr); | ||||
|         } | ||||
| 
 | ||||
|         if (m_use_clipping_planes) | ||||
|             m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); | ||||
|         else | ||||
|             m_volumes.set_z_range(-FLT_MAX, FLT_MAX); | ||||
| 
 | ||||
|         m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); | ||||
| 
 | ||||
|         m_shader.start_using(); | ||||
|         if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { | ||||
|             int object_id = m_layers_editing.last_object_id; | ||||
|             m_volumes.render_VBOs(GLVolumeCollection::Opaque, false, m_camera.get_view_matrix(), [object_id](const GLVolume &volume) { | ||||
|                 // Which volume to paint without the layer height profile shader?
 | ||||
|                 return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); | ||||
|             }); | ||||
|             // Let LayersEditing handle rendering of the active object using the layer height profile shader.
 | ||||
|             m_layers_editing.render_volumes(*this, this->m_volumes); | ||||
|         } else { | ||||
|             // do not cull backfaces to show broken geometry, if any
 | ||||
|             m_volumes.render_VBOs(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { | ||||
|                 return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); | ||||
|             }); | ||||
|         } | ||||
|         m_volumes.render_VBOs(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); | ||||
|         m_shader.stop_using(); | ||||
|     } | ||||
| 
 | ||||
|     if (m_use_clipping_planes) | ||||
|         m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); | ||||
|     else | ||||
|     { | ||||
|         ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); | ||||
|         ::glEnable(GL_CLIP_PLANE0); | ||||
|         m_volumes.set_z_range(-FLT_MAX, FLT_MAX); | ||||
| 
 | ||||
|         if (m_use_clipping_planes) | ||||
|         { | ||||
|             glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[0].get_data())); | ||||
|             glsafe(::glEnable(GL_CLIP_PLANE1)); | ||||
|             glsafe(::glClipPlane(GL_CLIP_PLANE2, (GLdouble*)m_clipping_planes[1].get_data())); | ||||
|             glsafe(::glEnable(GL_CLIP_PLANE2)); | ||||
|         } | ||||
|          | ||||
|     m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); | ||||
| 
 | ||||
|     m_shader.start_using(); | ||||
|     if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { | ||||
|         int object_id = m_layers_editing.last_object_id; | ||||
|         m_volumes.render(GLVolumeCollection::Opaque, false, m_camera.get_view_matrix(), [object_id](const GLVolume& volume) { | ||||
|             // Which volume to paint without the layer height profile shader?
 | ||||
|             return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); | ||||
|         }); | ||||
|         // Let LayersEditing handle rendering of the active object using the layer height profile shader.
 | ||||
|         m_layers_editing.render_volumes(*this, this->m_volumes); | ||||
|     } else { | ||||
|         // do not cull backfaces to show broken geometry, if any
 | ||||
|         m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { | ||||
|         m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { | ||||
|             return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); | ||||
|         }); | ||||
|         m_volumes.render_legacy(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); | ||||
| 
 | ||||
|         ::glDisable(GL_CLIP_PLANE0); | ||||
| 
 | ||||
|         if (m_use_clipping_planes) | ||||
|         { | ||||
|             glsafe(::glDisable(GL_CLIP_PLANE1)); | ||||
|             glsafe(::glDisable(GL_CLIP_PLANE2)); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     m_volumes.render(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); | ||||
|     m_shader.stop_using(); | ||||
| 
 | ||||
|     m_camera_clipping_plane = ClippingPlane::ClipsNothing(); | ||||
|     glsafe(::glDisable(GL_LIGHTING)); | ||||
| } | ||||
|  | @ -4025,6 +4000,7 @@ void GLCanvas3D::_render_current_gizmo() const | |||
| 
 | ||||
| void GLCanvas3D::_render_gizmos_overlay() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
| #if ENABLE_RETINA_GL | ||||
| //     m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor());
 | ||||
|     const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); | ||||
|  | @ -4035,6 +4011,7 @@ void GLCanvas3D::_render_gizmos_overlay() const | |||
|     const float size = int(GLGizmosManager::Default_Icons_Size*wxGetApp().toolbar_icon_scale()); | ||||
|     m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment
 | ||||
| #endif /* __WXMSW__ */ | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     m_gizmos.render_overlay(*this, m_selection); | ||||
| } | ||||
|  | @ -4288,16 +4265,9 @@ void GLCanvas3D::_render_sla_slices() const | |||
| 
 | ||||
| void GLCanvas3D::_render_selection_sidebar_hints() const | ||||
| { | ||||
|     if (m_use_VBOs) | ||||
|         m_shader.start_using(); | ||||
| 
 | ||||
|     m_selection.render_sidebar_hints(m_sidebar_field); | ||||
| 
 | ||||
|     if (m_use_VBOs) | ||||
|         m_shader.stop_using(); | ||||
|     m_selection.render_sidebar_hints(m_sidebar_field, m_shader); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLCanvas3D::_update_volumes_hover_state() const | ||||
| { | ||||
|     for (GLVolume* v : m_volumes.volumes) | ||||
|  | @ -4506,8 +4476,6 @@ void GLCanvas3D::_load_print_toolpaths() | |||
| 
 | ||||
|         _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), volume); | ||||
|     } | ||||
|     volume.bounding_box = volume.indexed_vertex_array.bounding_box(); | ||||
|     volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors, const std::vector<double>& color_print_values) | ||||
|  | @ -4672,19 +4640,13 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
| 	                vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); | ||||
| 	                // Copy the content back to the old GLVolume.
 | ||||
| 	                vol.indexed_vertex_array = vol_new.indexed_vertex_array; | ||||
| 	                // Finalize a bounding box of the old GLVolume.
 | ||||
| 	                vol.bounding_box = vol.indexed_vertex_array.bounding_box(); | ||||
| 	                // Clear the buffers, but keep them pre-allocated.
 | ||||
|                      // Clear the buffers, but keep them pre-allocated.
 | ||||
| 	                vol_new.indexed_vertex_array.clear(); | ||||
| 	                // Just make sure that clear did not clear the reserved memory.
 | ||||
| 	                vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); | ||||
| 	            } | ||||
| 	        } | ||||
|         } | ||||
|         for (GLVolume *vol : vols) { | ||||
|         	vol->bounding_box = vol->indexed_vertex_array.bounding_box(); | ||||
|         	vol->indexed_vertex_array.shrink_to_fit(); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; | ||||
|  | @ -4693,8 +4655,6 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c | |||
|         std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), | ||||
|         [](const GLVolume *volume) { return volume->empty(); }), | ||||
|         m_volumes.volumes.end()); | ||||
|     for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) | ||||
|         m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; | ||||
| } | ||||
|  | @ -4844,18 +4804,12 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_ | |||
|                 vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); | ||||
|                 // Copy the content back to the old GLVolume.
 | ||||
|                 vol.indexed_vertex_array = vol_new.indexed_vertex_array; | ||||
|                 // Finalize a bounding box of the old GLVolume.
 | ||||
|                 vol.bounding_box = vol.indexed_vertex_array.bounding_box(); | ||||
|                 // Clear the buffers, but keep them pre-allocated.
 | ||||
|                 vol_new.indexed_vertex_array.clear(); | ||||
|                 // Just make sure that clear did not clear the reserved memory.
 | ||||
|                 vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); | ||||
|             } | ||||
|         } | ||||
|         for (GLVolume *vol : vols) { | ||||
|             vol->bounding_box = vol->indexed_vertex_array.bounding_box(); | ||||
|             vol->indexed_vertex_array.shrink_to_fit(); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; | ||||
|  | @ -4864,8 +4818,6 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_ | |||
|         std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), | ||||
|         [](const GLVolume *volume) { return volume->empty(); }), | ||||
|         m_volumes.volumes.end()); | ||||
|     for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) | ||||
|         m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; | ||||
| } | ||||
|  | @ -5042,17 +4994,6 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat | |||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // finalize volumes and sends geometry to gpu
 | ||||
|     if (m_volumes.volumes.size() > initial_volumes_count) | ||||
|     { | ||||
|         for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) | ||||
|         { | ||||
|             GLVolume* volume = m_volumes.volumes[i]; | ||||
|             volume->bounding_box = volume->indexed_vertex_array.bounding_box(); | ||||
|             volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) | ||||
|  | @ -5097,17 +5038,6 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, | |||
| 
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // finalize volumes and sends geometry to gpu
 | ||||
|     if (m_volumes.volumes.size() > initial_volumes_count) | ||||
|     { | ||||
|         for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) | ||||
|         { | ||||
|             GLVolume* volume = m_volumes.volumes[i]; | ||||
|             volume->bounding_box = volume->indexed_vertex_array.bounding_box(); | ||||
|             volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data) | ||||
|  | @ -5336,10 +5266,6 @@ void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data) | |||
| 
 | ||||
|             _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); | ||||
|         } | ||||
| 
 | ||||
|         // finalize volumes and sends geometry to gpu
 | ||||
|         volume->bounding_box = volume->indexed_vertex_array.bounding_box(); | ||||
|         volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -5367,10 +5293,6 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data) | |||
| 
 | ||||
|             _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); | ||||
|         } | ||||
| 
 | ||||
|         // finalize volumes and sends geometry to gpu
 | ||||
|         volume->bounding_box = volume->indexed_vertex_array.bounding_box(); | ||||
|         volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -5396,7 +5318,7 @@ void GLCanvas3D::_load_fff_shells() | |||
|             instance_ids[i] = i; | ||||
|         } | ||||
| 
 | ||||
|         m_volumes.load_object(model_obj, object_id, instance_ids, "object", m_use_VBOs && m_initialized); | ||||
|         m_volumes.load_object(model_obj, object_id, instance_ids, "object"); | ||||
| 
 | ||||
|         ++object_id; | ||||
|     } | ||||
|  | @ -5416,9 +5338,9 @@ void GLCanvas3D::_load_fff_shells() | |||
|             float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); | ||||
| 
 | ||||
|             if (!print->is_step_done(psWipeTower)) | ||||
|                 depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; | ||||
|                 depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1); | ||||
|             m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, | ||||
|                                               m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), brim_spacing * 4.5f); | ||||
|                 !print->is_step_done(psWipeTower), brim_spacing * 4.5f); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -5436,8 +5358,8 @@ void GLCanvas3D::_load_sla_shells() | |||
|         const TriangleMesh &mesh, const float color[4], bool outside_printer_detection_enabled) { | ||||
|         m_volumes.volumes.emplace_back(new GLVolume(color)); | ||||
|         GLVolume& v = *m_volumes.volumes.back(); | ||||
|         v.indexed_vertex_array.load_mesh(mesh, m_use_VBOs); | ||||
| 		v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; | ||||
|         v.indexed_vertex_array.load_mesh(mesh); | ||||
|         v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; | ||||
|         v.composite_id.volume_id = volume_id; | ||||
|         v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0)); | ||||
|         v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation)); | ||||
|  | @ -5462,9 +5384,6 @@ void GLCanvas3D::_load_sla_shells() | |||
|             double shift_z = obj->get_current_elevation(); | ||||
|             for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { | ||||
|                 GLVolume& v = *m_volumes.volumes[i]; | ||||
|                 // finalize volumes and sends geometry to gpu
 | ||||
|                 v.bounding_box = v.indexed_vertex_array.bounding_box(); | ||||
|                 v.indexed_vertex_array.finalize_geometry(m_use_VBOs); | ||||
|                 // apply shift z
 | ||||
|                 v.set_sla_shift_z(shift_z); | ||||
|             } | ||||
|  | @ -5555,7 +5474,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state() | |||
| 
 | ||||
|     for (GLVolume* volume : m_volumes.volumes) | ||||
|     { | ||||
|         volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box) : false; | ||||
|         volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box()) : false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -5640,7 +5559,7 @@ bool GLCanvas3D::_is_any_volume_outside() const | |||
| void GLCanvas3D::_resize_toolbars() const | ||||
| { | ||||
|     Size cnv_size = get_canvas_size(); | ||||
|     float zoom = get_camera_zoom(); | ||||
|     float zoom = (float)m_camera.get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| 
 | ||||
| #if ENABLE_RETINA_GL | ||||
|  | @ -5690,19 +5609,16 @@ void GLCanvas3D::_resize_toolbars() const | |||
|     } | ||||
|     } | ||||
| 
 | ||||
|     if (m_view_toolbar != nullptr) | ||||
|     { | ||||
| #if ENABLE_RETINA_GL | ||||
|         m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); | ||||
|     m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); | ||||
| #else | ||||
|         m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); | ||||
|     m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); | ||||
| #endif /* __WXMSW__ */ | ||||
| 
 | ||||
|         // places the toolbar on the bottom-left corner of the 3d scene
 | ||||
|         float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; | ||||
|         float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; | ||||
|         m_view_toolbar.set_position(top, left); | ||||
|     } | ||||
|     // places the toolbar on the bottom-left corner of the 3d scene
 | ||||
|     float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; | ||||
|     float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; | ||||
|     m_view_toolbar.set_position(top, left); | ||||
| } | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| #include "Camera.hpp" | ||||
| #include "Selection.hpp" | ||||
| #include "Gizmos/GLGizmosManager.hpp" | ||||
| #include "GUI_ObjectLayers.hpp" | ||||
| 
 | ||||
| #include <float.h> | ||||
| 
 | ||||
|  | @ -452,7 +453,6 @@ private: | |||
|     // Screen is only refreshed from the OnIdle handler if it is dirty.
 | ||||
|     bool m_dirty; | ||||
|     bool m_initialized; | ||||
|     bool m_use_VBOs; | ||||
|     bool m_apply_zoom_to_volumes_filter; | ||||
|     mutable std::vector<int> m_hover_volume_idxs; | ||||
|     bool m_warning_texture_enabled; | ||||
|  | @ -494,7 +494,7 @@ public: | |||
|     wxGLCanvas* get_wxglcanvas() { return m_canvas; } | ||||
| 	const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } | ||||
| 
 | ||||
|     bool init(bool useVBOs); | ||||
|     bool init(); | ||||
|     void post_event(wxEvent &&event); | ||||
| 
 | ||||
|     void set_as_dirty(); | ||||
|  | @ -608,6 +608,7 @@ public: | |||
|     void reset_all_gizmos() { m_gizmos.reset_all_states(); } | ||||
| 
 | ||||
|     void handle_sidebar_focus_event(const std::string& opt_key, bool focus_on); | ||||
|     void handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type); | ||||
| 
 | ||||
|     void update_ui_from_settings(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -192,7 +192,6 @@ GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; | |||
| GLCanvas3DManager::GLCanvas3DManager() | ||||
|     : m_context(nullptr) | ||||
|     , m_gl_initialized(false) | ||||
|     , m_use_VBOs(false) | ||||
| { | ||||
| } | ||||
| 
 | ||||
|  | @ -266,8 +265,6 @@ void GLCanvas3DManager::init_gl() | |||
|     if (!m_gl_initialized) | ||||
|     { | ||||
|         glewInit(); | ||||
|         const AppConfig* config = GUI::get_app_config(); | ||||
|         m_use_VBOs = s_gl_info.is_version_greater_or_equal_to(2, 0); | ||||
|         m_gl_initialized = true; | ||||
|         if (GLEW_EXT_texture_compression_s3tc) | ||||
|             s_compressed_textures_supported = true; | ||||
|  | @ -323,7 +320,7 @@ bool GLCanvas3DManager::init(GLCanvas3D& canvas) | |||
|     if (!m_gl_initialized) | ||||
|         init_gl(); | ||||
| 
 | ||||
|     return canvas.init(m_use_VBOs); | ||||
|     return canvas.init(); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3DManager::detect_multisample(int* attribList) | ||||
|  |  | |||
|  | @ -75,7 +75,6 @@ private: | |||
|     wxGLContext* m_context; | ||||
|     static GLInfo s_gl_info; | ||||
|     bool m_gl_initialized; | ||||
|     bool m_use_VBOs; | ||||
|     static EMultisampleState s_multisample; | ||||
|     static bool s_compressed_textures_supported; | ||||
| 
 | ||||
|  |  | |||
|  | @ -188,7 +188,7 @@ bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const Bac | |||
|         return true; | ||||
| 
 | ||||
|     std::string path = resources_dir() + "/icons/"; | ||||
|     bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false); | ||||
|     bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false, true); | ||||
|     if (res) | ||||
|         m_icons_texture.metadata = icons_texture; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|  |  | |||
|  | @ -934,6 +934,11 @@ ObjectList* GUI_App::obj_list() | |||
|     return sidebar().obj_list(); | ||||
| } | ||||
| 
 | ||||
| ObjectLayers* GUI_App::obj_layers() | ||||
| { | ||||
|     return sidebar().obj_layers(); | ||||
| } | ||||
| 
 | ||||
| Plater* GUI_App::plater() | ||||
| { | ||||
|     return plater_; | ||||
|  |  | |||
|  | @ -156,6 +156,7 @@ public: | |||
|     ObjectManipulation* obj_manipul(); | ||||
|     ObjectSettings*     obj_settings(); | ||||
|     ObjectList*         obj_list(); | ||||
|     ObjectLayers*       obj_layers(); | ||||
|     Plater*             plater(); | ||||
|     std::vector<ModelObject*> *model_objects(); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										341
									
								
								src/slic3r/GUI/GUI_ObjectLayers.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								src/slic3r/GUI/GUI_ObjectLayers.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,341 @@ | |||
| #include "GUI_ObjectLayers.hpp" | ||||
| #include "GUI_ObjectList.hpp" | ||||
| 
 | ||||
| #include "OptionsGroup.hpp" | ||||
| #include "PresetBundle.hpp" | ||||
| #include "libslic3r/Model.hpp" | ||||
| #include "GLCanvas3D.hpp" | ||||
| 
 | ||||
| #include <boost/algorithm/string.hpp> | ||||
| 
 | ||||
| #include "I18N.hpp" | ||||
| 
 | ||||
| #include <wx/wupdlock.h> | ||||
| 
 | ||||
| namespace Slic3r | ||||
| { | ||||
| namespace GUI | ||||
| { | ||||
| 
 | ||||
| ObjectLayers::ObjectLayers(wxWindow* parent) : | ||||
|     OG_Settings(parent, true) | ||||
| { | ||||
|     m_grid_sizer = new wxFlexGridSizer(3, 5, 5); // "Min Z", "Max Z", "Layer height" & buttons sizer
 | ||||
|     m_grid_sizer->SetFlexibleDirection(wxHORIZONTAL); | ||||
| 
 | ||||
|     // Legend for object layers
 | ||||
|     for (const std::string col : { "Min Z", "Max Z", "Layer height" }) { | ||||
|         auto temp = new wxStaticText(m_parent, wxID_ANY, _(L(col)), wxDefaultPosition, /*size*/wxDefaultSize, wxST_ELLIPSIZE_MIDDLE); | ||||
|         temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); | ||||
|         temp->SetBackgroundStyle(wxBG_STYLE_PAINT); | ||||
|         temp->SetFont(wxGetApp().bold_font()); | ||||
| 
 | ||||
|         m_grid_sizer->Add(temp); | ||||
|     } | ||||
| 
 | ||||
|     m_og->sizer->Clear(true); | ||||
|     m_og->sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); | ||||
| 
 | ||||
|     m_bmp_delete    = ScalableBitmap(parent, "remove_copies"/*"cross"*/); | ||||
|     m_bmp_add       = ScalableBitmap(parent, "add_copies"); | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_edited_range) | ||||
| { | ||||
|     if (is_last_edited_range && m_selection_type == editor->type()) { | ||||
|     /* Workaround! Under OSX we should use CallAfter() for SetFocus() after LayerEditors "reorganizations", 
 | ||||
|      * because of selected control's strange behavior:  | ||||
|      * cursor is set to the control, but blue border - doesn't. | ||||
|      * And as a result we couldn't edit this control. | ||||
|      * */ | ||||
| #ifdef __WXOSX__ | ||||
|         wxTheApp->CallAfter([editor]() { | ||||
| #endif | ||||
|         editor->SetFocus(); | ||||
|         editor->SetInsertionPointEnd(); | ||||
| #ifdef __WXOSX__ | ||||
|         }); | ||||
| #endif | ||||
|     }     | ||||
| } | ||||
| 
 | ||||
| wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range)  | ||||
| { | ||||
|     const bool is_last_edited_range = range == m_selectable_range; | ||||
| 
 | ||||
|     auto set_focus_data = [range, this](const EditorType type) | ||||
|     { | ||||
|         m_selectable_range = range; | ||||
|         m_selection_type = type; | ||||
|     }; | ||||
| 
 | ||||
|     auto update_focus_data = [range, this](const t_layer_height_range& new_range, EditorType type, bool enter_pressed) | ||||
|     { | ||||
|         // change selectable range for new one, if enter was pressed or if same range was selected
 | ||||
|         if (enter_pressed || m_selectable_range == range) | ||||
|             m_selectable_range = new_range; | ||||
|         if (enter_pressed) | ||||
|             m_selection_type = type; | ||||
|     }; | ||||
| 
 | ||||
|     // Add control for the "Min Z"
 | ||||
| 
 | ||||
|     auto editor = new LayerRangeEditor(this, double_to_string(range.first), etMinZ, | ||||
|                                        set_focus_data, [range, update_focus_data, this](coordf_t min_z, bool enter_pressed) | ||||
|     { | ||||
|         if (fabs(min_z - range.first) < EPSILON) { | ||||
|             m_selection_type = etUndef; | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // data for next focusing
 | ||||
|         coordf_t max_z = min_z < range.second ? range.second : min_z + 0.5; | ||||
|         const t_layer_height_range& new_range = { min_z, max_z }; | ||||
|         update_focus_data(new_range, etMinZ, enter_pressed); | ||||
| 
 | ||||
|         return wxGetApp().obj_list()->edit_layer_range(range, new_range); | ||||
|     }); | ||||
| 
 | ||||
|     select_editor(editor, is_last_edited_range); | ||||
|     m_grid_sizer->Add(editor); | ||||
| 
 | ||||
|     // Add control for the "Max Z"
 | ||||
| 
 | ||||
|     editor = new LayerRangeEditor(this, double_to_string(range.second), etMaxZ, | ||||
|                                   set_focus_data, [range, update_focus_data, this](coordf_t max_z, bool enter_pressed) | ||||
|     { | ||||
|         if (fabs(max_z - range.second) < EPSILON || range.first > max_z) { | ||||
|             m_selection_type = etUndef; | ||||
|             return false;       // LayersList would not be updated/recreated
 | ||||
|         } | ||||
| 
 | ||||
|         // data for next focusing
 | ||||
|         const t_layer_height_range& new_range = { range.first, max_z }; | ||||
|         update_focus_data(new_range, etMaxZ, enter_pressed); | ||||
| 
 | ||||
|         return wxGetApp().obj_list()->edit_layer_range(range, new_range); | ||||
|     }); | ||||
| 
 | ||||
|     select_editor(editor, is_last_edited_range); | ||||
|     m_grid_sizer->Add(editor); | ||||
| 
 | ||||
|     // Add control for the "Layer height"
 | ||||
| 
 | ||||
|     editor = new LayerRangeEditor(this, | ||||
|                                 double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), | ||||
|                              etLayerHeight, set_focus_data, [range, this](coordf_t layer_height, bool) | ||||
|     { | ||||
|         return wxGetApp().obj_list()->edit_layer_range(range, layer_height); | ||||
|     }); | ||||
| 
 | ||||
|     select_editor(editor, is_last_edited_range); | ||||
| 
 | ||||
|     auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     sizer->Add(editor); | ||||
|     m_grid_sizer->Add(sizer); | ||||
| 
 | ||||
|     return sizer; | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::create_layers_list() | ||||
| { | ||||
|     for (const auto layer : m_object->layer_config_ranges) | ||||
|     { | ||||
|         const t_layer_height_range& range = layer.first; | ||||
|         auto sizer = create_layer(range); | ||||
| 
 | ||||
|         auto del_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_delete); | ||||
|         del_btn->SetToolTip(_(L("Remove layer"))); | ||||
| 
 | ||||
|         sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(m_parent)); | ||||
| 
 | ||||
|         del_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { | ||||
|             wxGetApp().obj_list()->del_layer_range(range); | ||||
|         }); | ||||
| 
 | ||||
|         auto add_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_add); | ||||
|         add_btn->SetToolTip(_(L("Add layer"))); | ||||
| 
 | ||||
|         sizer->Add(add_btn, 0, wxRIGHT, em_unit(m_parent)); | ||||
| 
 | ||||
|         add_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { | ||||
|             wxGetApp().obj_list()->add_layer_range_after_current(range); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::update_layers_list() | ||||
| { | ||||
|     ObjectList* objects_ctrl   = wxGetApp().obj_list(); | ||||
|     if (objects_ctrl->multiple_selection()) return; | ||||
| 
 | ||||
|     const auto item = objects_ctrl->GetSelection(); | ||||
|     if (!item) return; | ||||
| 
 | ||||
|     const int obj_idx = objects_ctrl->get_selected_obj_idx(); | ||||
|     if (obj_idx < 0) return; | ||||
| 
 | ||||
|     const ItemType type = objects_ctrl->GetModel()->GetItemType(item); | ||||
|     if (!(type & (itLayerRoot | itLayer))) return; | ||||
| 
 | ||||
|     m_object = objects_ctrl->object(obj_idx); | ||||
|     if (!m_object || m_object->layer_config_ranges.empty()) return; | ||||
| 
 | ||||
|     // Delete all controls from options group except of the legends
 | ||||
| 
 | ||||
|     const int cols = m_grid_sizer->GetEffectiveColsCount(); | ||||
|     const int rows = m_grid_sizer->GetEffectiveRowsCount(); | ||||
|     for (int idx = cols*rows-1; idx >= cols; idx--) { | ||||
|         wxSizerItem* t = m_grid_sizer->GetItem(idx); | ||||
|         if (t->IsSizer()) | ||||
|             t->GetSizer()->Clear(true); | ||||
|         else | ||||
|             t->DeleteWindows(); | ||||
|         m_grid_sizer->Remove(idx); | ||||
|     } | ||||
| 
 | ||||
|     // Add new control according to the selected item  
 | ||||
| 
 | ||||
|     if (type & itLayerRoot) | ||||
|         create_layers_list(); | ||||
|     else | ||||
|         create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item)); | ||||
| 
 | ||||
|     m_parent->Layout(); | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::update_scene_from_editor_selection() const | ||||
| { | ||||
|     // needed to show the visual hints in 3D scene
 | ||||
|     wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(m_selectable_range, m_selection_type); | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::UpdateAndShow(const bool show) | ||||
| { | ||||
|     if (show) | ||||
|         update_layers_list(); | ||||
| 
 | ||||
|     OG_Settings::UpdateAndShow(show); | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::msw_rescale() | ||||
| { | ||||
|     m_bmp_delete.msw_rescale(); | ||||
|     m_bmp_add.msw_rescale(); | ||||
| } | ||||
| 
 | ||||
| void ObjectLayers::reset_selection() | ||||
| { | ||||
|     m_selectable_range = { 0.0, 0.0 }; | ||||
|     m_selection_type = etLayerHeight; | ||||
| } | ||||
| 
 | ||||
| LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, | ||||
|                                     const wxString& value, | ||||
|                                     EditorType type, | ||||
|                                     std::function<void(EditorType)> set_focus_data_fn, | ||||
|                                     std::function<bool(coordf_t, bool enter_pressed)>   edit_fn | ||||
|                                     ) : | ||||
|     m_valid_value(value), | ||||
|     m_type(type), | ||||
|     m_set_focus_data(set_focus_data_fn), | ||||
|     wxTextCtrl(parent->m_parent, wxID_ANY, value, wxDefaultPosition,  | ||||
|                wxSize(8 * em_unit(parent->m_parent), wxDefaultCoord), wxTE_PROCESS_ENTER) | ||||
| { | ||||
|     this->SetFont(wxGetApp().normal_font()); | ||||
|      | ||||
|     this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&) | ||||
|     { | ||||
|         m_enter_pressed     = true; | ||||
|         // If LayersList wasn't updated/recreated, we can call wxEVT_KILL_FOCUS.Skip()
 | ||||
|         if (m_type&etLayerHeight) { | ||||
|             if (!edit_fn(get_value(), true)) | ||||
|                 SetValue(m_valid_value); | ||||
|             else | ||||
|                 m_valid_value = double_to_string(get_value()); | ||||
|             m_call_kill_focus = true; | ||||
|         } | ||||
|         else if (!edit_fn(get_value(), true)) { | ||||
|             SetValue(m_valid_value); | ||||
|             m_call_kill_focus = true; | ||||
|         } | ||||
|     }, this->GetId()); | ||||
| 
 | ||||
|     this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e) | ||||
|     { | ||||
|         if (!m_enter_pressed) { | ||||
| #ifndef __WXGTK__ | ||||
|             /* Update data for next editor selection.
 | ||||
|              * But under GTK it lucks like there is no information about selected control at e.GetWindow(), | ||||
|              * so we'll take it from wxEVT_LEFT_DOWN event | ||||
|              * */ | ||||
|             LayerRangeEditor* new_editor = dynamic_cast<LayerRangeEditor*>(e.GetWindow()); | ||||
|             if (new_editor) | ||||
|                 new_editor->set_focus_data(); | ||||
| #endif // not __WXGTK__
 | ||||
|             // If LayersList wasn't updated/recreated, we should call e.Skip()
 | ||||
|             if (m_type & etLayerHeight) { | ||||
|                 if (!edit_fn(get_value(), false)) | ||||
|                     SetValue(m_valid_value); | ||||
|                 else | ||||
|                     m_valid_value = double_to_string(get_value()); | ||||
|                 e.Skip(); | ||||
|             } | ||||
|             else if (!edit_fn(get_value(), false)) { | ||||
|                 SetValue(m_valid_value); | ||||
|                 e.Skip(); | ||||
|             }  | ||||
|         } | ||||
|         else if (m_call_kill_focus) { | ||||
|             m_call_kill_focus = false; | ||||
|             e.Skip(); | ||||
|         } | ||||
|     }, this->GetId()); | ||||
| 
 | ||||
|     this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e) | ||||
|     { | ||||
|         set_focus_data(); | ||||
|         parent->update_scene_from_editor_selection(); | ||||
|         e.Skip(); | ||||
|     }, this->GetId()); | ||||
| 
 | ||||
| #ifdef __WXGTK__ // Workaround! To take information about selectable range
 | ||||
|     this->Bind(wxEVT_LEFT_DOWN, [this](wxEvent& e) | ||||
|     { | ||||
|         set_focus_data(); | ||||
|         e.Skip(); | ||||
|     }, this->GetId()); | ||||
| #endif //__WXGTK__
 | ||||
| 
 | ||||
|     this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) | ||||
|     { | ||||
|         // select all text using Ctrl+A
 | ||||
|         if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) | ||||
|             this->SetSelection(-1, -1); //select all
 | ||||
|         event.Skip(); | ||||
|     })); | ||||
| } | ||||
| 
 | ||||
| coordf_t LayerRangeEditor::get_value() | ||||
| { | ||||
|     wxString str = GetValue(); | ||||
| 
 | ||||
|     coordf_t layer_height; | ||||
|     // Replace the first occurence of comma in decimal number.
 | ||||
|     str.Replace(",", ".", false); | ||||
|     if (str == ".") | ||||
|         layer_height = 0.0; | ||||
|     else | ||||
|     { | ||||
|         if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) | ||||
|         { | ||||
|             show_error(m_parent, _(L("Invalid numeric input."))); | ||||
|             SetValue(double_to_string(layer_height)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return layer_height; | ||||
| } | ||||
| 
 | ||||
| } //namespace GUI
 | ||||
| } //namespace Slic3r 
 | ||||
							
								
								
									
										88
									
								
								src/slic3r/GUI/GUI_ObjectLayers.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/slic3r/GUI/GUI_ObjectLayers.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,88 @@ | |||
| #ifndef slic3r_GUI_ObjectLayers_hpp_ | ||||
| #define slic3r_GUI_ObjectLayers_hpp_ | ||||
| 
 | ||||
| #include "GUI_ObjectSettings.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| 
 | ||||
| #ifdef __WXOSX__ | ||||
| #include "../libslic3r/PrintConfig.hpp" | ||||
| #endif | ||||
| 
 | ||||
| class wxBoxSizer; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| class ModelObject; | ||||
| 
 | ||||
| namespace GUI { | ||||
| class ConfigOptionsGroup; | ||||
| 
 | ||||
| typedef double                          coordf_t; | ||||
| typedef std::pair<coordf_t, coordf_t>   t_layer_height_range; | ||||
| 
 | ||||
| class ObjectLayers; | ||||
| 
 | ||||
| enum EditorType | ||||
| { | ||||
|     etUndef         = 0, | ||||
|     etMinZ          = 1, | ||||
|     etMaxZ          = 2, | ||||
|     etLayerHeight   = 4, | ||||
| }; | ||||
| 
 | ||||
| class LayerRangeEditor : public wxTextCtrl | ||||
| { | ||||
|     bool                m_enter_pressed     { false }; | ||||
|     bool                m_call_kill_focus   { false }; | ||||
|     wxString            m_valid_value; | ||||
|     EditorType          m_type; | ||||
| 
 | ||||
|     std::function<void(EditorType)> m_set_focus_data; | ||||
| 
 | ||||
| public: | ||||
|     LayerRangeEditor(   ObjectLayers* parent, | ||||
|                         const wxString& value = wxEmptyString, | ||||
|                         EditorType type = etUndef, | ||||
|                         std::function<void(EditorType)>     set_focus_data_fn   = [](EditorType)      {;}, | ||||
|                         std::function<bool(coordf_t, bool)> edit_fn             = [](coordf_t, bool) {return false; } | ||||
|                         ); | ||||
|     ~LayerRangeEditor() {} | ||||
| 
 | ||||
|     EditorType          type() const {return m_type;} | ||||
|     void                set_focus_data() const { m_set_focus_data(m_type);} | ||||
| 
 | ||||
| private: | ||||
|     coordf_t            get_value(); | ||||
| }; | ||||
| 
 | ||||
| class ObjectLayers : public OG_Settings | ||||
| { | ||||
|     ScalableBitmap  m_bmp_delete; | ||||
|     ScalableBitmap  m_bmp_add; | ||||
|     ModelObject*    m_object {nullptr}; | ||||
| 
 | ||||
|     wxFlexGridSizer*       m_grid_sizer; | ||||
|     t_layer_height_range   m_selectable_range; | ||||
|     EditorType             m_selection_type {etUndef}; | ||||
| 
 | ||||
| public: | ||||
|     ObjectLayers(wxWindow* parent); | ||||
|     ~ObjectLayers() {} | ||||
| 
 | ||||
|     void        select_editor(LayerRangeEditor* editor, const bool is_last_edited_range); | ||||
|     wxSizer*    create_layer(const t_layer_height_range& range);    // without_buttons
 | ||||
|     void        create_layers_list(); | ||||
|     void        update_layers_list(); | ||||
| 
 | ||||
|     void        update_scene_from_editor_selection() const; | ||||
| 
 | ||||
|     void        UpdateAndShow(const bool show) override; | ||||
|     void        msw_rescale(); | ||||
|     void        reset_selection(); | ||||
|     void        set_selectable_range(const t_layer_height_range& range) { m_selectable_range = range; } | ||||
| 
 | ||||
|     friend class LayerRangeEditor; | ||||
| }; | ||||
| 
 | ||||
| }} | ||||
| 
 | ||||
| #endif // slic3r_GUI_ObjectLayers_hpp_
 | ||||
|  | @ -1,6 +1,7 @@ | |||
| #include "libslic3r/libslic3r.h" | ||||
| #include "GUI_ObjectList.hpp" | ||||
| #include "GUI_ObjectManipulation.hpp" | ||||
| #include "GUI_ObjectLayers.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| #include "I18N.hpp" | ||||
| 
 | ||||
|  | @ -147,10 +148,10 @@ ObjectList::ObjectList(wxWindow* parent) : | |||
|         wxAcceleratorTable accel(6, entries); | ||||
|         SetAcceleratorTable(accel); | ||||
| 
 | ||||
|         this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); }, wxID_COPY); | ||||
|         this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); }, wxID_PASTE); | ||||
|         this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL); | ||||
|         this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); | ||||
|         this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy();                      }, wxID_COPY); | ||||
|         this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->paste();                     }, wxID_PASTE); | ||||
|         this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children();  }, wxID_SELECTALL); | ||||
|         this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove();                    }, wxID_DELETE); | ||||
|     } | ||||
| #else __WXOSX__ | ||||
|     Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX
 | ||||
|  | @ -350,12 +351,13 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons | |||
|     const ItemType type = m_objects_model->GetItemType(item); | ||||
| 
 | ||||
|     const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : | ||||
|         m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); | ||||
|                         m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); | ||||
| 
 | ||||
|     const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; | ||||
| 
 | ||||
|     assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0)); | ||||
|     return type & itVolume ?(*m_objects)[obj_idx]->volumes[vol_idx]->config : | ||||
|            type & itLayer  ?(*m_objects)[obj_idx]->layer_config_ranges[m_objects_model->GetLayerRangeByItem(item)] : | ||||
|                             (*m_objects)[obj_idx]->config; | ||||
| } | ||||
| 
 | ||||
|  | @ -441,16 +443,23 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item) | |||
| { | ||||
|     if (m_prevent_update_extruder_in_config) | ||||
|         return; | ||||
|     if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { | ||||
| 
 | ||||
|     const ItemType item_type = m_objects_model->GetItemType(item); | ||||
|     if (item_type & itObject) { | ||||
|         const int obj_idx = m_objects_model->GetIdByItem(item); | ||||
|         m_config = &(*m_objects)[obj_idx]->config; | ||||
|     } | ||||
|     else { | ||||
|         const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); | ||||
|         const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); | ||||
|         if (item_type & itVolume) | ||||
|         { | ||||
|         const int volume_id = m_objects_model->GetVolumeIdByItem(item); | ||||
|         if (obj_idx < 0 || volume_id < 0) | ||||
|             return; | ||||
|         m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; | ||||
|         } | ||||
|         else if (item_type & itLayer) | ||||
|             m_config = &get_item_config(item); | ||||
|     } | ||||
| 
 | ||||
|     wxVariant variant; | ||||
|  | @ -569,9 +578,75 @@ void ObjectList::selection_changed() | |||
|         wxPostEvent(this, event); | ||||
|     } | ||||
| 
 | ||||
|     if (const wxDataViewItem item = GetSelection()) | ||||
|     { | ||||
|         const ItemType type = m_objects_model->GetItemType(item); | ||||
|         // to correct visual hints for layers editing on the Scene
 | ||||
|         if (type & (itLayer|itLayerRoot)) { | ||||
|             wxGetApp().obj_layers()->reset_selection(); | ||||
|              | ||||
|             if (type & itLayerRoot) | ||||
|                 wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); | ||||
|             else { | ||||
|                 wxGetApp().obj_layers()->set_selectable_range(m_objects_model->GetLayerRangeByItem(item)); | ||||
|                 wxGetApp().obj_layers()->update_scene_from_editor_selection(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     part_selection_changed(); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::fill_layer_config_ranges_cache() | ||||
| { | ||||
|     wxDataViewItemArray sel_layers; | ||||
|     GetSelections(sel_layers); | ||||
| 
 | ||||
|     const int obj_idx = m_objects_model->GetObjectIdByItem(sel_layers[0]); | ||||
|     if (obj_idx < 0 || (int)m_objects->size() <= obj_idx) | ||||
|         return; | ||||
| 
 | ||||
|     const t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; | ||||
|     m_layer_config_ranges_cache.clear(); | ||||
| 
 | ||||
|     for (const auto layer_item : sel_layers) | ||||
|         if (m_objects_model->GetItemType(layer_item) & itLayer) { | ||||
|             auto range = m_objects_model->GetLayerRangeByItem(layer_item); | ||||
|             auto it = ranges.find(range); | ||||
|             if (it != ranges.end()) | ||||
|                 m_layer_config_ranges_cache[it->first] = it->second; | ||||
|         } | ||||
| } | ||||
| 
 | ||||
| void ObjectList::paste_layers_into_list() | ||||
| { | ||||
|     const int obj_idx = m_objects_model->GetObjectIdByItem(GetSelection()); | ||||
| 
 | ||||
|     if (obj_idx < 0 || (int)m_objects->size() <= obj_idx ||  | ||||
|         m_layer_config_ranges_cache.empty() || printer_technology() == ptSLA) | ||||
|         return; | ||||
| 
 | ||||
|     const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx); | ||||
|     wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); | ||||
|     if (layers_item) | ||||
|         m_objects_model->Delete(layers_item); | ||||
| 
 | ||||
|     t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; | ||||
| 
 | ||||
|     // and create Layer item(s) according to the layer_config_ranges
 | ||||
|     for (const auto range : m_layer_config_ranges_cache) | ||||
|         ranges.emplace(range); | ||||
| 
 | ||||
|     layers_item = add_layer_root_item(object_item); | ||||
| 
 | ||||
|     changed_object(obj_idx); | ||||
| 
 | ||||
|     select_item(layers_item); | ||||
| #ifndef __WXOSX__ | ||||
|     selection_changed(); | ||||
| #endif //no __WXOSX__
 | ||||
| } | ||||
| 
 | ||||
| void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) | ||||
| { | ||||
|     if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx)) | ||||
|  | @ -653,7 +728,7 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) | |||
|     const wxPoint pt = get_mouse_position_in_control(); | ||||
|     HitTest(pt, item, col); | ||||
|     if (!item) | ||||
| #ifdef __WXOSX__ // #ys_FIXME temporary workaround for OSX 
 | ||||
| #ifdef __WXOSX__ // temporary workaround for OSX 
 | ||||
|         // after Yosemite OS X version, HitTest return undefined item
 | ||||
|         item = GetSelection(); | ||||
|     if (item) | ||||
|  | @ -699,10 +774,11 @@ void ObjectList::show_context_menu() | |||
|     if (item) | ||||
|     { | ||||
|         const ItemType type = m_objects_model->GetItemType(item); | ||||
|         if (!(type & (itObject | itVolume | itInstance))) | ||||
|         if (!(type & (itObject | itVolume | itLayer | itInstance))) | ||||
|             return; | ||||
| 
 | ||||
|         wxMenu* menu = type & itInstance ? &m_menu_instance : | ||||
|                        type & itLayer ? &m_menu_layer : | ||||
|                        m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : | ||||
|                        printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; | ||||
| 
 | ||||
|  | @ -713,6 +789,22 @@ void ObjectList::show_context_menu() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void ObjectList::copy() | ||||
| { | ||||
|     if (m_selection_mode & smLayer) | ||||
|         fill_layer_config_ranges_cache(); | ||||
|     else | ||||
|         wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::paste() | ||||
| { | ||||
|     if (!m_layer_config_ranges_cache.empty()) | ||||
|         paste_layers_into_list(); | ||||
|     else | ||||
|         wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); | ||||
| } | ||||
| 
 | ||||
| #ifndef __WXOSX__ | ||||
| void ObjectList::key_event(wxKeyEvent& event) | ||||
| { | ||||
|  | @ -727,10 +819,10 @@ void ObjectList::key_event(wxKeyEvent& event) | |||
|     } | ||||
|     else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/)) | ||||
|         select_item_all_children(); | ||||
|     else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) | ||||
|         wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); | ||||
|     else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL))  | ||||
|         copy(); | ||||
|     else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL)) | ||||
|         wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); | ||||
|         paste(); | ||||
|     else | ||||
|         event.Skip(); | ||||
| } | ||||
|  | @ -1033,7 +1125,17 @@ void ObjectList::get_settings_choice(const wxString& category_name) | |||
| 
 | ||||
| void ObjectList::get_freq_settings_choice(const wxString& bundle_name) | ||||
| { | ||||
|     const std::vector<std::string>& options = get_options_for_bundle(bundle_name); | ||||
|     std::vector<std::string> options = get_options_for_bundle(bundle_name); | ||||
| 
 | ||||
|     /* Because of we couldn't edited layer_height for ItVolume from settings list,
 | ||||
|      * correct options according to the selected item type : | ||||
|      * remove "layer_height" option | ||||
|      */ | ||||
|     if ((m_objects_model->GetItemType(GetSelection()) & itVolume) && bundle_name == _("Layers and Perimeters")) { | ||||
|         const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); | ||||
|         if (layer_height_it != options.end()) | ||||
|             options.erase(layer_height_it); | ||||
|     } | ||||
| 
 | ||||
|     assert(m_config); | ||||
|     auto opt_keys = m_config->keys(); | ||||
|  | @ -1137,6 +1239,12 @@ wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) | |||
|         [this]() { return is_splittable(); }, wxGetApp().plater()); | ||||
| } | ||||
| 
 | ||||
| wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu)  | ||||
| { | ||||
|     return append_menu_item(menu, wxID_ANY, _(L("Edit Layers")), "", | ||||
|         [this](wxCommandEvent&) { layers_editing(); }, "layers", menu); | ||||
| } | ||||
| 
 | ||||
| wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_)  | ||||
| { | ||||
|     MenuWithSeparators* menu = dynamic_cast<MenuWithSeparators*>(menu_); | ||||
|  | @ -1301,7 +1409,11 @@ void ObjectList::create_object_popupmenu(wxMenu *menu) | |||
|     append_menu_item_scale_selection_to_fit_print_volume(menu); | ||||
| 
 | ||||
|     // Split object to parts
 | ||||
|     m_menu_item_split = append_menu_item_split(menu); | ||||
|     append_menu_item_split(menu); | ||||
|     menu->AppendSeparator(); | ||||
| 
 | ||||
|     // Layers Editing for object
 | ||||
|     append_menu_item_layers_editing(menu); | ||||
|     menu->AppendSeparator(); | ||||
| 
 | ||||
|     // rest of a object_menu will be added later in:
 | ||||
|  | @ -1330,7 +1442,7 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) | |||
|     append_menu_item_fix_through_netfabb(menu); | ||||
|     append_menu_item_export_stl(menu); | ||||
| 
 | ||||
|     m_menu_item_split_part = append_menu_item_split(menu); | ||||
|     append_menu_item_split(menu); | ||||
| 
 | ||||
|     // Append change part type
 | ||||
|     menu->AppendSeparator(); | ||||
|  | @ -1576,38 +1688,52 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) | |||
|     ItemType type; | ||||
| 
 | ||||
|     m_objects_model->GetItemInfo(item, type, obj_idx, idx); | ||||
|     if (type == itUndef) | ||||
|     if (type & itUndef) | ||||
|         return; | ||||
| 
 | ||||
|     if (type == itSettings) | ||||
|         del_settings_from_config(); | ||||
|     else if (type == itInstanceRoot && obj_idx != -1) | ||||
|     if (type & itSettings) | ||||
|         del_settings_from_config(m_objects_model->GetParent(item)); | ||||
|     else if (type & itInstanceRoot && obj_idx != -1) | ||||
|         del_instances_from_object(obj_idx); | ||||
|     else if (type & itLayerRoot && obj_idx != -1) | ||||
|         del_layers_from_object(obj_idx); | ||||
|     else if (type & itLayer && obj_idx != -1) | ||||
|         del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item)); | ||||
|     else if (idx == -1) | ||||
|         return; | ||||
|     else if (!del_subobject_from_object(obj_idx, idx, type)) | ||||
|         return; | ||||
| 
 | ||||
|     // If last volume item with warning was deleted, unmark object item
 | ||||
|     if (type == itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) | ||||
|     if (type & itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) | ||||
|         m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); | ||||
| 
 | ||||
|     m_objects_model->Delete(item); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::del_settings_from_config() | ||||
| void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) | ||||
| { | ||||
|     auto opt_keys = m_config->keys(); | ||||
|     if (opt_keys.size() == 1 && opt_keys[0] == "extruder") | ||||
|     const bool is_layer_settings = m_objects_model->GetItemType(parent_item) == itLayer; | ||||
| 
 | ||||
|     const int opt_cnt = m_config->keys().size(); | ||||
|     if (opt_cnt == 1 && m_config->has("extruder") ||  | ||||
|         is_layer_settings && opt_cnt == 2 && m_config->has("extruder") && m_config->has("layer_height")) | ||||
|         return; | ||||
| 
 | ||||
|     int extruder = -1; | ||||
|     if (m_config->has("extruder")) | ||||
|         extruder = m_config->option<ConfigOptionInt>("extruder")->value; | ||||
| 
 | ||||
|     coordf_t layer_height = 0.0; | ||||
|     if (is_layer_settings) | ||||
|         layer_height = m_config->opt_float("layer_height"); | ||||
| 
 | ||||
|     m_config->clear(); | ||||
| 
 | ||||
|     if (extruder >= 0) | ||||
|         m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); | ||||
|     if (is_layer_settings) | ||||
|         m_config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::del_instances_from_object(const int obj_idx) | ||||
|  | @ -1624,6 +1750,24 @@ void ObjectList::del_instances_from_object(const int obj_idx) | |||
|     changed_object(obj_idx); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range) | ||||
| { | ||||
|     const auto del_range = object(obj_idx)->layer_config_ranges.find(layer_range); | ||||
|     if (del_range == object(obj_idx)->layer_config_ranges.end()) | ||||
|         return; | ||||
|          | ||||
|     object(obj_idx)->layer_config_ranges.erase(del_range); | ||||
| 
 | ||||
|     changed_object(obj_idx); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::del_layers_from_object(const int obj_idx) | ||||
| { | ||||
|     object(obj_idx)->layer_config_ranges.clear(); | ||||
| 
 | ||||
|     changed_object(obj_idx); | ||||
| } | ||||
| 
 | ||||
| bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) | ||||
| { | ||||
| 	if (obj_idx == 1000) | ||||
|  | @ -1719,6 +1863,70 @@ void ObjectList::split() | |||
|     changed_object(obj_idx); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::layers_editing() | ||||
| { | ||||
|     const auto item = GetSelection(); | ||||
|     const int obj_idx = get_selected_obj_idx(); | ||||
|     if (!item || obj_idx < 0) | ||||
|         return; | ||||
| 
 | ||||
|     const wxDataViewItem obj_item = m_objects_model->GetTopParent(item); | ||||
|     wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(obj_item); | ||||
| 
 | ||||
|     // if it doesn't exist now
 | ||||
|     if (!layers_item.IsOk()) | ||||
|     { | ||||
|         t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; | ||||
| 
 | ||||
|         // set some default value
 | ||||
|         if (ranges.empty()) | ||||
|             ranges[{ 0.0f, 2.0f }] = get_default_layer_config(obj_idx); | ||||
| 
 | ||||
|         // create layer root item
 | ||||
|         layers_item = add_layer_root_item(obj_item); | ||||
|     } | ||||
|     if (!layers_item.IsOk()) | ||||
|         return; | ||||
| 
 | ||||
|     // to correct visual hints for layers editing on the Scene, reset previous selection
 | ||||
|     wxGetApp().obj_layers()->reset_selection(); | ||||
|     wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); | ||||
| 
 | ||||
|     // select LayerRoor item and expand
 | ||||
|     select_item(layers_item); | ||||
|     Expand(layers_item); | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item) | ||||
| { | ||||
|     const int obj_idx = m_objects_model->GetIdByItem(obj_item); | ||||
|     if (obj_idx < 0 ||  | ||||
|         object(obj_idx)->layer_config_ranges.empty() || | ||||
|         printer_technology() == ptSLA) | ||||
|         return wxDataViewItem(0); | ||||
| 
 | ||||
|     // create LayerRoot item
 | ||||
|     wxDataViewItem layers_item = m_objects_model->AddLayersRoot(obj_item); | ||||
| 
 | ||||
|     // and create Layer item(s) according to the layer_config_ranges
 | ||||
|     for (const auto range : object(obj_idx)->layer_config_ranges) | ||||
|         add_layer_item(range.first, layers_item); | ||||
| 
 | ||||
|     return layers_item; | ||||
| } | ||||
| 
 | ||||
| DynamicPrintConfig ObjectList::get_default_layer_config(const int obj_idx) | ||||
| { | ||||
|     DynamicPrintConfig config; | ||||
|     coordf_t layer_height = object(obj_idx)->config.has("layer_height") ?  | ||||
|                             object(obj_idx)->config.opt_float("layer_height") :  | ||||
|                             wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_float("layer_height"); | ||||
|     config.set_key_value("layer_height",new ConfigOptionFloat(layer_height)); | ||||
|     config.set_key_value("extruder",    new ConfigOptionInt(0)); | ||||
| 
 | ||||
|     return config; | ||||
| } | ||||
| 
 | ||||
| bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume) | ||||
| { | ||||
|     auto obj_idx = get_selected_obj_idx(); | ||||
|  | @ -1788,6 +1996,7 @@ void ObjectList::part_selection_changed() | |||
| 
 | ||||
|     bool update_and_show_manipulations = false; | ||||
|     bool update_and_show_settings = false; | ||||
|     bool update_and_show_layers = false; | ||||
| 
 | ||||
|     const auto item = GetSelection(); | ||||
| 
 | ||||
|  | @ -1810,36 +2019,47 @@ void ObjectList::part_selection_changed() | |||
|                 update_and_show_manipulations = true; | ||||
|             } | ||||
|             else { | ||||
|                 auto parent = m_objects_model->GetParent(item); | ||||
|                 // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene
 | ||||
|                 obj_idx = m_objects_model->GetIdByItem(parent); | ||||
|                 if (m_objects_model->GetItemType(item) == itSettings) { | ||||
|                     if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) { | ||||
|                 obj_idx = m_objects_model->GetObjectIdByItem(item); | ||||
|                  | ||||
|                 const ItemType type = m_objects_model->GetItemType(item); | ||||
|                 if (type & itSettings) { | ||||
|                     const auto parent = m_objects_model->GetParent(item); | ||||
|                     const ItemType parent_type = m_objects_model->GetItemType(parent); | ||||
| 
 | ||||
|                     if (parent_type & itObject) { | ||||
|                         og_name = _(L("Object Settings to modify")); | ||||
|                         m_config = &(*m_objects)[obj_idx]->config; | ||||
|                     } | ||||
|                     else { | ||||
|                     else if (parent_type & itVolume) { | ||||
|                         og_name = _(L("Part Settings to modify")); | ||||
|                         auto main_parent = m_objects_model->GetParent(parent); | ||||
|                         obj_idx = m_objects_model->GetIdByItem(main_parent); | ||||
|                         const auto volume_id = m_objects_model->GetVolumeIdByItem(parent); | ||||
|                         volume_id = m_objects_model->GetVolumeIdByItem(parent); | ||||
|                         m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; | ||||
|                     } | ||||
|                     else if (parent_type & itLayer) { | ||||
|                         og_name = _(L("Layer range Settings to modify")); | ||||
|                         m_config = &get_item_config(parent); | ||||
|                     } | ||||
|                     update_and_show_settings = true; | ||||
|                 } | ||||
|                 else if (m_objects_model->GetItemType(item) == itVolume) { | ||||
|                 else if (type & itVolume) { | ||||
|                     og_name = _(L("Part manipulation")); | ||||
|                     volume_id = m_objects_model->GetVolumeIdByItem(item); | ||||
|                     m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; | ||||
|                     update_and_show_manipulations = true; | ||||
|                 } | ||||
|                 else if (m_objects_model->GetItemType(item) == itInstance) { | ||||
|                 else if (type & itInstance) { | ||||
|                     og_name = _(L("Instance manipulation")); | ||||
|                     update_and_show_manipulations = true; | ||||
| 
 | ||||
|                     // fill m_config by object's values
 | ||||
|                     const int obj_idx_ = m_objects_model->GetObjectIdByItem(item); | ||||
|                     m_config = &(*m_objects)[obj_idx_]->config; | ||||
|                     m_config = &(*m_objects)[obj_idx]->config; | ||||
|                 } | ||||
|                 else if (type & (itLayerRoot|itLayer)) { | ||||
|                     og_name = type & itLayerRoot ? _(L("Layers Editing")) : _(L("Layer Editing")); | ||||
|                     update_and_show_layers = true; | ||||
| 
 | ||||
|                     if (type & itLayer) | ||||
|                         m_config = &get_item_config(item); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -1859,11 +2079,18 @@ void ObjectList::part_selection_changed() | |||
|     if (update_and_show_settings) | ||||
|         wxGetApp().obj_settings()->get_og()->set_name(" " + og_name + " "); | ||||
| 
 | ||||
|     if (printer_technology() == ptSLA) | ||||
|         update_and_show_layers = false; | ||||
|     else if (update_and_show_layers) | ||||
|         wxGetApp().obj_layers()->get_og()->set_name(" " + og_name + " "); | ||||
| 
 | ||||
|     Sidebar& panel = wxGetApp().sidebar(); | ||||
|     panel.Freeze(); | ||||
| 
 | ||||
|     wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); | ||||
|     wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); | ||||
|     wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); | ||||
|     wxGetApp().obj_layers()  ->UpdateAndShow(update_and_show_layers); | ||||
|     wxGetApp().sidebar().show_info_sizer(); | ||||
| 
 | ||||
|     panel.Layout(); | ||||
|  | @ -1909,6 +2136,9 @@ void ObjectList::add_object_to_list(size_t obj_idx) | |||
|         Expand(item); | ||||
|     } | ||||
| 
 | ||||
|     // Add layers if it has
 | ||||
|     add_layer_root_item(item); | ||||
| 
 | ||||
| #ifndef __WXOSX__  | ||||
|     selection_changed(); | ||||
| #endif //__WXMSW__
 | ||||
|  | @ -2053,16 +2283,196 @@ void ObjectList::remove() | |||
|     wxDataViewItemArray sels; | ||||
|     GetSelections(sels); | ||||
| 
 | ||||
|     wxDataViewItem  parent = wxDataViewItem(0); | ||||
| 
 | ||||
|     for (auto& item : sels) | ||||
|     { | ||||
|         if (m_objects_model->GetParent(item) == wxDataViewItem(0)) | ||||
|             delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); | ||||
|         else { | ||||
|             if (sels.size() == 1) | ||||
|             if (m_objects_model->GetItemType(item) & itLayer) { | ||||
|                 parent = m_objects_model->GetParent(item); | ||||
|                 wxDataViewItemArray children; | ||||
|                 if (m_objects_model->GetChildren(parent, children) == 1) | ||||
|                     parent = m_objects_model->GetTopParent(item); | ||||
|             } | ||||
|             else if (sels.size() == 1) | ||||
|                 select_item(m_objects_model->GetParent(item)); | ||||
|              | ||||
|             del_subobject_item(item); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (parent) | ||||
|         select_item(parent); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::del_layer_range(const t_layer_height_range& range) | ||||
| { | ||||
|     const int obj_idx = get_selected_obj_idx(); | ||||
|     if (obj_idx < 0) return; | ||||
| 
 | ||||
|     t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; | ||||
| 
 | ||||
|     wxDataViewItem selectable_item = GetSelection(); | ||||
| 
 | ||||
|     if (ranges.size() == 1) | ||||
|         selectable_item = m_objects_model->GetParent(selectable_item); | ||||
| 
 | ||||
|     wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, range); | ||||
|     del_subobject_item(layer_item); | ||||
| 
 | ||||
|     select_item(selectable_item); | ||||
| } | ||||
| 
 | ||||
| double get_min_layer_height(const int extruder_idx) | ||||
| { | ||||
|     const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; | ||||
|     return config.opt_float("min_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); | ||||
| } | ||||
| 
 | ||||
| double get_max_layer_height(const int extruder_idx) | ||||
| { | ||||
|     const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; | ||||
|     return config.opt_float("max_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::add_layer_range_after_current(const t_layer_height_range& current_range) | ||||
| { | ||||
|     const int obj_idx = get_selected_obj_idx(); | ||||
|     if (obj_idx < 0) return; | ||||
| 
 | ||||
|     const wxDataViewItem layers_item = GetSelection(); | ||||
| 
 | ||||
|     t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; | ||||
| 
 | ||||
|     const t_layer_height_range& last_range = (--ranges.end())->first; | ||||
|      | ||||
|     if (current_range == last_range) | ||||
|     { | ||||
|         const t_layer_height_range& new_range = { last_range.second, last_range.second + 2.0f }; | ||||
|         ranges[new_range] = get_default_layer_config(obj_idx); | ||||
|         add_layer_item(new_range, layers_item); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         const t_layer_height_range& next_range = (++ranges.find(current_range))->first; | ||||
| 
 | ||||
|         if (current_range.second > next_range.first) | ||||
|             return; // range division has no sense
 | ||||
|          | ||||
|         const int layer_idx = m_objects_model->GetItemIdByLayerRange(obj_idx, next_range); | ||||
|         if (layer_idx < 0) | ||||
|             return; | ||||
| 
 | ||||
|         if (current_range.second == next_range.first) | ||||
|         { | ||||
|             const auto old_config = ranges.at(next_range); | ||||
| 
 | ||||
|             const coordf_t delta = (next_range.second - next_range.first); | ||||
|             if (delta < get_min_layer_height(old_config.opt_int("extruder"))/*0.05f*/) // next range division has no sense 
 | ||||
|                 return;  | ||||
| 
 | ||||
|             const coordf_t midl_layer = next_range.first + 0.5f * delta; | ||||
|              | ||||
|             t_layer_height_range new_range = { midl_layer, next_range.second }; | ||||
| 
 | ||||
|             // delete old layer
 | ||||
| 
 | ||||
|             wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, next_range); | ||||
|             del_subobject_item(layer_item); | ||||
| 
 | ||||
|             // create new 2 layers instead of deleted one
 | ||||
| 
 | ||||
|             ranges[new_range] = old_config; | ||||
|             add_layer_item(new_range, layers_item, layer_idx); | ||||
| 
 | ||||
|             new_range = { current_range.second, midl_layer }; | ||||
|             ranges[new_range] = get_default_layer_config(obj_idx); | ||||
|             add_layer_item(new_range, layers_item, layer_idx); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             const t_layer_height_range new_range = { current_range.second, next_range.first }; | ||||
|             ranges[new_range] = get_default_layer_config(obj_idx); | ||||
|             add_layer_item(new_range, layers_item, layer_idx); | ||||
|         }         | ||||
|     } | ||||
| 
 | ||||
|     changed_object(obj_idx); | ||||
| 
 | ||||
|     // select item to update layers sizer
 | ||||
|     select_item(layers_item); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::add_layer_item(const t_layer_height_range& range,  | ||||
|                                 const wxDataViewItem layers_item,  | ||||
|                                 const int layer_idx /* = -1*/) | ||||
| { | ||||
|     const int obj_idx = m_objects_model->GetObjectIdByItem(layers_item); | ||||
|     if (obj_idx < 0) return; | ||||
| 
 | ||||
|     const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range]; | ||||
|     if (!config.has("extruder")) | ||||
|         return; | ||||
| 
 | ||||
|     const auto layer_item = m_objects_model->AddLayersChild(layers_item,  | ||||
|                                                             range,  | ||||
|                                                             config.opt_int("extruder"), | ||||
|                                                             layer_idx); | ||||
| 
 | ||||
|     if (config.keys().size() > 2) | ||||
|         select_item(m_objects_model->AddSettingsChild(layer_item)); | ||||
| } | ||||
| 
 | ||||
| bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height) | ||||
| { | ||||
|     const int obj_idx = get_selected_obj_idx(); | ||||
|     if (obj_idx < 0)  | ||||
|         return false; | ||||
| 
 | ||||
|     DynamicPrintConfig* config = &object(obj_idx)->layer_config_ranges[range]; | ||||
|     if (fabs(layer_height - config->opt_float("layer_height")) < EPSILON) | ||||
|         return false; | ||||
| 
 | ||||
|     const int extruder_idx = config->opt_int("extruder"); | ||||
| 
 | ||||
|     if (layer_height >= get_min_layer_height(extruder_idx) &&  | ||||
|         layer_height <= get_max_layer_height(extruder_idx))  | ||||
|     { | ||||
|         config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range) | ||||
| { | ||||
|     const int obj_idx = get_selected_obj_idx(); | ||||
|     if (obj_idx < 0) return false; | ||||
| 
 | ||||
|     const ItemType sel_type = m_objects_model->GetItemType(GetSelection()); | ||||
| 
 | ||||
|     t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; | ||||
| 
 | ||||
|     const DynamicPrintConfig config = ranges[range]; | ||||
| 
 | ||||
|     ranges.erase(range); | ||||
|     ranges[new_range] = config; | ||||
| 
 | ||||
|     wxDataViewItem root_item = m_objects_model->GetLayerRootItem(m_objects_model->GetItemById(obj_idx)); | ||||
|     m_objects_model->DeleteChildren(root_item); | ||||
| 
 | ||||
|     if (root_item.IsOk()) | ||||
|         // create Layer item(s) according to the layer_config_ranges
 | ||||
|         for (const auto r : ranges) | ||||
|             add_layer_item(r.first, root_item); | ||||
| 
 | ||||
|     select_item(sel_type&itLayer ? m_objects_model->GetItemByLayerRange(obj_idx, new_range) : root_item); | ||||
|     Expand(root_item); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void ObjectList::init_objects() | ||||
|  | @ -2092,11 +2502,12 @@ void ObjectList::update_selections() | |||
|     m_selection_mode = smInstance; | ||||
| 
 | ||||
|     // We doesn't update selection if SettingsItem for the current object/part is selected
 | ||||
|     if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) | ||||
| //     if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings )
 | ||||
|     if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) & (itSettings | itLayerRoot | itLayer)) | ||||
|     { | ||||
|         const auto item = GetSelection(); | ||||
|         if (selection.is_single_full_object()) { | ||||
|             if ( m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) | ||||
|             if (m_objects_model->GetObjectIdByItem(item) == selection.get_object_idx()) | ||||
|                 return; | ||||
|             sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); | ||||
|         } | ||||
|  | @ -2217,22 +2628,18 @@ void ObjectList::update_selections_on_canvas() | |||
|     auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection) | ||||
|     { | ||||
|         const ItemType& type = m_objects_model->GetItemType(item); | ||||
|         if ( type == itInstanceRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) { | ||||
|             wxDataViewItem obj_item = type == itInstanceRoot ? m_objects_model->GetParent(item) : item; | ||||
|             selection.add_object(m_objects_model->GetIdByItem(obj_item), as_single_selection); | ||||
|             return; | ||||
|         } | ||||
|         const int obj_idx = m_objects_model->GetObjectIdByItem(item); | ||||
| 
 | ||||
|         if (type == itVolume) { | ||||
|             const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); | ||||
|             const int vol_idx = m_objects_model->GetVolumeIdByItem(item); | ||||
|             selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection); | ||||
|         } | ||||
|         else if (type == itInstance) { | ||||
|             const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); | ||||
|             const int inst_idx = m_objects_model->GetInstanceIdByItem(item); | ||||
|             selection.add_instance(obj_idx, inst_idx, as_single_selection); | ||||
|         } | ||||
|         else | ||||
|             selection.add_object(obj_idx, as_single_selection); | ||||
|     }; | ||||
| 
 | ||||
|     // stores current instance idx before to clear the selection
 | ||||
|  | @ -2240,7 +2647,7 @@ void ObjectList::update_selections_on_canvas() | |||
| 
 | ||||
|     if (sel_cnt == 1) { | ||||
|         wxDataViewItem item = GetSelection(); | ||||
|         if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) | ||||
|         if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) | ||||
|             add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true); | ||||
|         else | ||||
|             add_to_selection(item, selection, instance_idx, true); | ||||
|  | @ -2303,11 +2710,13 @@ void ObjectList::select_item_all_children() | |||
|     } | ||||
|     else { | ||||
|         const auto item = GetSelection(); | ||||
|         // Some volume(instance) is selected    =>  select all volumes(instances) inside the current object
 | ||||
|         if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) | ||||
|         const ItemType item_type = m_objects_model->GetItemType(item); | ||||
|         // Some volume/layer/instance is selected    =>  select all volumes/layers/instances inside the current object
 | ||||
|         if (item_type & (itVolume | itInstance | itLayer)) | ||||
|             m_objects_model->GetChildren(m_objects_model->GetParent(item), sels); | ||||
| 
 | ||||
|         m_selection_mode = m_objects_model->GetItemType(item)&itVolume ? smVolume : smInstance; | ||||
|         m_selection_mode = item_type&itVolume ? smVolume :  | ||||
|                            item_type&itLayer  ? smLayer  : smInstance; | ||||
|     } | ||||
| 
 | ||||
|     SetSelections(sels); | ||||
|  | @ -2326,8 +2735,9 @@ void ObjectList::update_selection_mode() | |||
|     } | ||||
| 
 | ||||
|     const ItemType type = m_objects_model->GetItemType(GetSelection()); | ||||
|     m_selection_mode =  type&itSettings ? smUndef   : | ||||
|                         type&itVolume   ? smVolume  : smInstance; | ||||
|     m_selection_mode =  type & itSettings ? smUndef  : | ||||
|                         type & itLayer    ? smLayer  : | ||||
|                         type & itVolume   ? smVolume : smInstance; | ||||
| } | ||||
| 
 | ||||
| // check last selected item. If is it possible to select it
 | ||||
|  | @ -2338,33 +2748,37 @@ bool ObjectList::check_last_selection(wxString& msg_str) | |||
|          | ||||
|     const bool is_shift_pressed = wxGetKeyState(WXK_SHIFT); | ||||
| 
 | ||||
|     /* We can't mix Parts and Objects/Instances.
 | ||||
|     /* We can't mix Volumes, Layers and Objects/Instances.
 | ||||
|      * So, show information about it | ||||
|      */ | ||||
|     const ItemType type = m_objects_model->GetItemType(m_last_selected_item); | ||||
| 
 | ||||
|     // check a case of a selection of the Parts from different Objects
 | ||||
|     bool impossible_multipart_selection = false; | ||||
|     if (type & itVolume && m_selection_mode == smVolume) | ||||
|     { | ||||
|     // check a case of a selection of the same type items from different Objects
 | ||||
|     auto impossible_multi_selection = [type, this](const ItemType item_type, const SELECTION_MODE selection_mode) { | ||||
|         if (!(type & item_type && m_selection_mode & selection_mode)) | ||||
|             return false; | ||||
| 
 | ||||
|         wxDataViewItemArray sels; | ||||
|         GetSelections(sels); | ||||
|         for (const auto& sel: sels) | ||||
|             if (sel != m_last_selected_item &&  | ||||
|                 m_objects_model->GetParent(sel) != m_objects_model->GetParent(m_last_selected_item)) | ||||
|             { | ||||
|                 impossible_multipart_selection = true; | ||||
|                 break; | ||||
|             } | ||||
|     } | ||||
|         for (const auto& sel : sels) | ||||
|             if (sel != m_last_selected_item && | ||||
|                 m_objects_model->GetTopParent(sel) != m_objects_model->GetTopParent(m_last_selected_item)) | ||||
|                 return true; | ||||
| 
 | ||||
|     if (impossible_multipart_selection || | ||||
|         return false; | ||||
|     }; | ||||
| 
 | ||||
|     if (impossible_multi_selection(itVolume, smVolume) || | ||||
|         impossible_multi_selection(itLayer,  smLayer ) || | ||||
|         type & itSettings || | ||||
|         type & itVolume && m_selection_mode == smInstance || | ||||
|         !(type & itVolume) && m_selection_mode == smVolume) | ||||
|         type & itVolume   && !(m_selection_mode & smVolume  ) || | ||||
|         type & itLayer    && !(m_selection_mode & smLayer   ) || | ||||
|         type & itInstance && !(m_selection_mode & smInstance) | ||||
|         ) | ||||
|     { | ||||
|         // Inform user why selection isn't complited
 | ||||
|         const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part")); | ||||
|         const wxString item_type = m_selection_mode & smInstance ? _(L("Object or Instance")) :  | ||||
|                                    m_selection_mode & smVolume   ? _(L("Part")) : _(L("Layer")); | ||||
| 
 | ||||
|         msg_str = wxString::Format( _(L("Unsupported selection")) + "\n\n" +  | ||||
|                                     _(L("You started your selection with %s Item.")) + "\n" + | ||||
|  | @ -2401,7 +2815,7 @@ void ObjectList::fix_multiselection_conflicts() | |||
|     wxDataViewItemArray sels; | ||||
|     GetSelections(sels); | ||||
| 
 | ||||
|     if (m_selection_mode == smVolume) | ||||
|     if (m_selection_mode & (smVolume|smLayer)) | ||||
|     { | ||||
|         // identify correct parent of the initial selected item
 | ||||
|         const wxDataViewItem& parent = m_objects_model->GetParent(m_last_selected_item == sels.front() ? sels.back() : sels.front()); | ||||
|  | @ -2410,8 +2824,10 @@ void ObjectList::fix_multiselection_conflicts() | |||
|         wxDataViewItemArray children; // selected volumes from current parent
 | ||||
|         m_objects_model->GetChildren(parent, children); | ||||
| 
 | ||||
|         const ItemType item_type = m_selection_mode & smVolume ? itVolume : itLayer; | ||||
| 
 | ||||
|         for (const auto child : children) | ||||
|             if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume) | ||||
|             if (IsSelected(child) && m_objects_model->GetItemType(child) & item_type) | ||||
|                 sels.Add(child); | ||||
| 
 | ||||
|         // If some part is selected, unselect all items except of selected parts of the current object
 | ||||
|  | @ -2576,6 +2992,87 @@ void ObjectList::update_settings_items() | |||
|     m_prevent_canvas_selection_update = false; | ||||
| } | ||||
| 
 | ||||
| // Update settings item for item had it
 | ||||
| void ObjectList::update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections) | ||||
| { | ||||
|     const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item); | ||||
|     select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); | ||||
| 
 | ||||
|     // If settings item was deleted from the list, 
 | ||||
|     // it's need to be deleted from selection array, if it was there
 | ||||
|     if (settings_item != m_objects_model->GetSettingsItem(item) && | ||||
|         selections.Index(settings_item) != wxNOT_FOUND) { | ||||
|         selections.Remove(settings_item); | ||||
| 
 | ||||
|         // Select item, if settings_item doesn't exist for item anymore, but was selected
 | ||||
|         if (selections.Index(item) == wxNOT_FOUND) | ||||
|             selections.Add(item); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ObjectList::update_object_list_by_printer_technology() | ||||
| { | ||||
|     m_prevent_canvas_selection_update = true; | ||||
|     wxDataViewItemArray sel; | ||||
|     GetSelections(sel); // stash selection
 | ||||
| 
 | ||||
|     wxDataViewItemArray object_items; | ||||
|     m_objects_model->GetChildren(wxDataViewItem(0), object_items); | ||||
| 
 | ||||
|     for (auto& object_item : object_items) { | ||||
|         // Update Settings Item for object
 | ||||
|         update_settings_item_and_selection(object_item, sel); | ||||
| 
 | ||||
|         // Update settings for Volumes
 | ||||
|         wxDataViewItemArray all_object_subitems; | ||||
|         m_objects_model->GetChildren(object_item, all_object_subitems); | ||||
|         for (auto item : all_object_subitems) | ||||
|             if (m_objects_model->GetItemType(item) & itVolume) | ||||
|                 // update settings for volume
 | ||||
|                 update_settings_item_and_selection(item, sel); | ||||
| 
 | ||||
|         // Update Layers Items
 | ||||
|         wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); | ||||
|         if (!layers_item) | ||||
|             layers_item = add_layer_root_item(object_item); | ||||
|         else if (printer_technology() == ptSLA) { | ||||
|             // If layers root item will be deleted from the list, so
 | ||||
|             // it's need to be deleted from selection array, if it was there
 | ||||
|             wxDataViewItemArray del_items; | ||||
|             bool some_layers_was_selected = false; | ||||
|             m_objects_model->GetAllChildren(layers_item, del_items); | ||||
|             for (auto& del_item:del_items) | ||||
|                 if (sel.Index(del_item) != wxNOT_FOUND) { | ||||
|                     some_layers_was_selected = true; | ||||
|                     sel.Remove(del_item); | ||||
|                 } | ||||
|             if (sel.Index(layers_item) != wxNOT_FOUND) { | ||||
|                 some_layers_was_selected = true; | ||||
|                 sel.Remove(layers_item); | ||||
|             } | ||||
| 
 | ||||
|             // delete all "layers" items
 | ||||
|             m_objects_model->Delete(layers_item); | ||||
| 
 | ||||
|             // Select object_item, if layers_item doesn't exist for item anymore, but was some of layer items was/were selected
 | ||||
|             if (some_layers_was_selected) | ||||
|                 sel.Add(object_item); | ||||
|         } | ||||
|         else { | ||||
|             wxDataViewItemArray all_obj_layers; | ||||
|             m_objects_model->GetChildren(layers_item, all_obj_layers); | ||||
| 
 | ||||
|             for (auto item : all_obj_layers) | ||||
|                 // update settings for layer
 | ||||
|                 update_settings_item_and_selection(item, sel); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // restore selection:
 | ||||
|     SetSelections(sel); | ||||
|     m_prevent_canvas_selection_update = false; | ||||
| } | ||||
| 
 | ||||
| void ObjectList::update_object_menu() | ||||
| { | ||||
|     append_menu_items_add_volume(&m_menu_object); | ||||
|  | @ -2749,7 +3246,8 @@ void ObjectList::msw_rescale() | |||
|     for (MenuWithSeparators* menu : { &m_menu_object,  | ||||
|                                       &m_menu_part,  | ||||
|                                       &m_menu_sla_object,  | ||||
|                                       &m_menu_instance }) | ||||
|                                       &m_menu_instance,  | ||||
|                                       &m_menu_layer }) | ||||
|         msw_rescale_menu(menu); | ||||
| 
 | ||||
|     Layout(); | ||||
|  | @ -2862,5 +3360,13 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const | |||
|     wxGetApp().plater()->update(); | ||||
| } | ||||
| 
 | ||||
| ModelObject* ObjectList::object(const int obj_idx) const | ||||
| { | ||||
|     if (obj_idx < 0) | ||||
|         return nullptr; | ||||
| 
 | ||||
|     return (*m_objects)[obj_idx]; | ||||
| } | ||||
| 
 | ||||
| } //namespace GUI
 | ||||
| } //namespace Slic3r 
 | ||||
|  | @ -33,6 +33,10 @@ typedef std::map< std::string, std::vector< std::pair<std::string, std::string> | |||
| 
 | ||||
| typedef std::vector<ModelVolume*> ModelVolumePtrs; | ||||
| 
 | ||||
| typedef double                                              coordf_t; | ||||
| typedef std::pair<coordf_t, coordf_t>                       t_layer_height_range; | ||||
| typedef std::map<t_layer_height_range, DynamicPrintConfig>  t_layer_config_ranges; | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); | ||||
|  | @ -64,9 +68,10 @@ class ObjectList : public wxDataViewCtrl | |||
| { | ||||
|     enum SELECTION_MODE | ||||
|     { | ||||
|         smUndef, | ||||
|         smVolume, | ||||
|         smInstance | ||||
|         smUndef     = 0, | ||||
|         smVolume    = 1, | ||||
|         smInstance  = 2, | ||||
|         smLayer     = 4 | ||||
|     } m_selection_mode {smUndef}; | ||||
| 
 | ||||
|     struct dragged_item_data | ||||
|  | @ -119,12 +124,17 @@ class ObjectList : public wxDataViewCtrl | |||
|     MenuWithSeparators  m_menu_part; | ||||
|     MenuWithSeparators  m_menu_sla_object; | ||||
|     MenuWithSeparators  m_menu_instance; | ||||
|     wxMenuItem* m_menu_item_split { nullptr }; | ||||
|     wxMenuItem* m_menu_item_split_part { nullptr }; | ||||
|     MenuWithSeparators  m_menu_layer; | ||||
|     wxMenuItem* m_menu_item_settings { nullptr }; | ||||
|     wxMenuItem* m_menu_item_split_instances { nullptr }; | ||||
| 
 | ||||
|     std::vector<wxBitmap*> m_bmp_vector; | ||||
|     ObjectDataViewModel         *m_objects_model{ nullptr }; | ||||
|     DynamicPrintConfig          *m_config {nullptr}; | ||||
|     std::vector<ModelObject*>   *m_objects{ nullptr }; | ||||
| 
 | ||||
|     std::vector<wxBitmap*>      m_bmp_vector; | ||||
| 
 | ||||
|     t_layer_config_ranges       m_layer_config_ranges_cache; | ||||
| 
 | ||||
|     int			m_selected_object_id = -1; | ||||
|     bool		m_prevent_list_events = false;		// We use this flag to avoid circular event handling Select() 
 | ||||
|  | @ -153,11 +163,11 @@ public: | |||
| 
 | ||||
|     std::map<std::string, wxBitmap> CATEGORY_ICON; | ||||
| 
 | ||||
|     ObjectDataViewModel	*m_objects_model{ nullptr }; | ||||
|     DynamicPrintConfig          *m_config {nullptr}; | ||||
| 
 | ||||
|     std::vector<ModelObject*>   *m_objects{ nullptr }; | ||||
|     ObjectDataViewModel*        GetModel() const    { return m_objects_model; } | ||||
|     DynamicPrintConfig*         config() const      { return m_config; } | ||||
|     std::vector<ModelObject*>*  objects() const     { return m_objects; } | ||||
| 
 | ||||
|     ModelObject*                object(const int obj_idx) const ; | ||||
| 
 | ||||
|     void                create_objects_ctrl(); | ||||
|     void                create_popup_menus(); | ||||
|  | @ -192,6 +202,9 @@ public: | |||
|     void                key_event(wxKeyEvent& event); | ||||
| #endif /* __WXOSX__ */ | ||||
| 
 | ||||
|     void                copy(); | ||||
|     void                paste(); | ||||
| 
 | ||||
|     void                get_settings_choice(const wxString& category_name); | ||||
|     void                get_freq_settings_choice(const wxString& bundle_name); | ||||
|     void                update_settings_item(); | ||||
|  | @ -199,6 +212,7 @@ public: | |||
|     wxMenu*             append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type); | ||||
|     void                append_menu_items_add_volume(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_split(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_layers_editing(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_settings(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_change_type(wxMenu* menu); | ||||
|     wxMenuItem*         append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); | ||||
|  | @ -222,10 +236,17 @@ public: | |||
| 	void                load_generic_subobject(const std::string& type_name, const ModelVolumeType type); | ||||
|     void                del_object(const int obj_idx); | ||||
|     void                del_subobject_item(wxDataViewItem& item); | ||||
|     void                del_settings_from_config(); | ||||
|     void                del_settings_from_config(const wxDataViewItem& parent_item); | ||||
|     void                del_instances_from_object(const int obj_idx); | ||||
|     void                del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range); | ||||
|     void                del_layers_from_object(const int obj_idx); | ||||
|     bool                del_subobject_from_object(const int obj_idx, const int idx, const int type); | ||||
|     void                split(); | ||||
|     void                layers_editing(); | ||||
| 
 | ||||
|     wxDataViewItem      add_layer_root_item(const wxDataViewItem obj_item); | ||||
| 
 | ||||
|     DynamicPrintConfig  get_default_layer_config(const int obj_idx); | ||||
|     bool                get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); | ||||
|     bool                is_splittable(); | ||||
|     bool                selected_instances_of_same_object(); | ||||
|  | @ -265,6 +286,14 @@ public: | |||
| 
 | ||||
|     // Remove objects/sub-object from the list
 | ||||
|     void remove(); | ||||
|     void del_layer_range(const t_layer_height_range& range); | ||||
|     void add_layer_range_after_current(const t_layer_height_range& current_range); | ||||
|     void add_layer_item (const t_layer_height_range& range,  | ||||
|                          const wxDataViewItem layers_item,  | ||||
|                          const int layer_idx = -1); | ||||
|     bool edit_layer_range(const t_layer_height_range& range, coordf_t layer_height); | ||||
|     bool edit_layer_range(const t_layer_height_range& range,  | ||||
|                           const t_layer_height_range& new_range); | ||||
| 
 | ||||
|     void init_objects(); | ||||
|     bool multiple_selection() const ; | ||||
|  | @ -286,6 +315,8 @@ public: | |||
|     void last_volume_is_deleted(const int obj_idx); | ||||
|     bool has_multi_part_objects(); | ||||
|     void update_settings_items(); | ||||
|     void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); | ||||
|     void update_object_list_by_printer_technology(); | ||||
|     void update_object_menu(); | ||||
| 
 | ||||
|     void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx); | ||||
|  | @ -295,6 +326,8 @@ public: | |||
|     void fix_through_netfabb(); | ||||
|     void update_item_error_icon(const int obj_idx, int vol_idx) const ; | ||||
| 
 | ||||
|     void fill_layer_config_ranges_cache(); | ||||
|     void paste_layers_into_list(); | ||||
|     void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); | ||||
|     void paste_objects_into_list(const std::vector<size_t>& object_idxs); | ||||
| 
 | ||||
|  |  | |||
|  | @ -441,7 +441,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) | |||
|         m_new_position = volume->get_volume_offset(); | ||||
|         m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); | ||||
|         m_new_scale    = volume->get_volume_scaling_factor() * 100.; | ||||
|         m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box.size()); | ||||
|         m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size()); | ||||
|         m_new_enabled = true; | ||||
|     } | ||||
|     else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) | ||||
|  | @ -721,8 +721,8 @@ void ObjectManipulation::change_size_value(int axis, double value) | |||
| 
 | ||||
|     Vec3d ref_size = m_cache.size; | ||||
| 	if (selection.is_single_volume() || selection.is_single_modifier()) | ||||
| 		ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box.size(); | ||||
| 	else if (selection.is_single_full_instance()) | ||||
|         ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box().size(); | ||||
|     else if (selection.is_single_full_instance()) | ||||
| 		ref_size = m_world_coordinates ?  | ||||
|             selection.get_unscaled_instance_bounding_box().size() : | ||||
|             (*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); | ||||
|  |  | |||
|  | @ -68,10 +68,12 @@ void ObjectSettings::update_settings_list() | |||
|     m_settings_list_sizer->Clear(true); | ||||
| 
 | ||||
|     auto objects_ctrl   = wxGetApp().obj_list(); | ||||
|     auto objects_model  = wxGetApp().obj_list()->m_objects_model; | ||||
|     auto config         = wxGetApp().obj_list()->m_config; | ||||
|     auto objects_model  = wxGetApp().obj_list()->GetModel(); | ||||
|     auto config         = wxGetApp().obj_list()->config(); | ||||
| 
 | ||||
|     const auto item = objects_ctrl->GetSelection(); | ||||
|     const bool is_layers_range_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itLayer; | ||||
| 
 | ||||
|     if (item && !objects_ctrl->multiple_selection() &&  | ||||
|         config && objects_model->IsSettingsItem(item)) | ||||
| 	{ | ||||
|  | @ -119,7 +121,8 @@ void ObjectSettings::update_settings_list() | |||
|             } | ||||
| 
 | ||||
|             for (auto& cat : cat_options) { | ||||
|                 if (cat.second.size() == 1 && cat.second[0] == "extruder") | ||||
|                 if (cat.second.size() == 1 &&  | ||||
|                     (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height")) | ||||
|                     continue; | ||||
| 
 | ||||
|                 auto optgroup = std::make_shared<ConfigOptionsGroup>(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); | ||||
|  | @ -129,14 +132,14 @@ void ObjectSettings::update_settings_list() | |||
|                 optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { | ||||
|                                         wxGetApp().obj_list()->changed_object(); }; | ||||
| 
 | ||||
|                 const bool is_extriders_cat = cat.first == "Extruders"; | ||||
|                 const bool is_extruders_cat = cat.first == "Extruders"; | ||||
|                 for (auto& opt : cat.second) | ||||
|                 { | ||||
|                     if (opt == "extruder") | ||||
|                     if (opt == "extruder" || is_layers_range_settings && opt == "layer_height") | ||||
|                         continue; | ||||
|                     Option option = optgroup->get_option(opt); | ||||
|                     option.opt.width = 12; | ||||
|                     if (is_extriders_cat) | ||||
|                     if (is_extruders_cat) | ||||
|                         option.opt.max = wxGetApp().extruders_cnt(); | ||||
|                     optgroup->append_single_option_line(option); | ||||
|                 } | ||||
|  |  | |||
|  | @ -541,6 +541,26 @@ void Preview::on_checkbox_shells(wxCommandEvent& evt) | |||
|     refresh_print(); | ||||
| } | ||||
| 
 | ||||
| void Preview::update_view_type() | ||||
| { | ||||
|     const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config; | ||||
| 
 | ||||
|     const wxString& choice = !config.option<ConfigOptionFloats>("colorprint_heights")->values.empty() &&  | ||||
|                              wxGetApp().extruders_edited_cnt()==1 ?  | ||||
|                                 _(L("Color Print")) : | ||||
|                                 config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1 ? | ||||
|                                     _(L("Tool")) :  | ||||
|                                     _(L("Feature type")); | ||||
| 
 | ||||
|     int type = m_choice_view_type->FindString(choice); | ||||
|     if (m_choice_view_type->GetSelection() != type) { | ||||
|         m_choice_view_type->SetSelection(type); | ||||
|         if (0 <= type && type < (int)GCodePreviewData::Extrusion::Num_View_Types) | ||||
|             m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; | ||||
|         m_preferred_color_mode = "feature"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Preview::create_double_slider() | ||||
| { | ||||
|     m_slider = new DoubleSlider(this, wxID_ANY, 0, 0, 0, 100); | ||||
|  | @ -553,23 +573,13 @@ void Preview::create_double_slider() | |||
| 
 | ||||
| 
 | ||||
|     Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { | ||||
|             auto& config = wxGetApp().preset_bundle->project_config; | ||||
|             ((config.option<ConfigOptionFloats>("colorprint_heights"))->values) = (m_slider->GetTicksValues()); | ||||
|             m_schedule_background_process(); | ||||
|         wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights")->values = m_slider->GetTicksValues(); | ||||
|         m_schedule_background_process(); | ||||
| 
 | ||||
|             const wxString& choise = !config.option<ConfigOptionFloats>("colorprint_heights")->values.empty() ? _(L("Color Print")) : | ||||
|                                       config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1  ?  | ||||
|                                       _(L("Tool")) : _(L("Feature type")); | ||||
|         update_view_type(); | ||||
| 
 | ||||
|             int type = m_choice_view_type->FindString(choise); | ||||
|             if (m_choice_view_type->GetSelection() != type) { | ||||
|                 m_choice_view_type->SetSelection(type); | ||||
|                 if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) | ||||
|                     m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; | ||||
|                 m_preferred_color_mode = "feature"; | ||||
|             } | ||||
|             reload_print(); | ||||
|         }); | ||||
|         reload_print(); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| // Find an index of a value in a sorted vector, which is in <z-eps, z+eps>.
 | ||||
|  | @ -787,9 +797,14 @@ void Preview::load_print_as_fff(bool keep_z_range) | |||
|             // Load the real G-code preview.
 | ||||
|             m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); | ||||
|             m_loaded = true; | ||||
|         } else | ||||
|         } else { | ||||
|             // disable color change information for multi-material presets
 | ||||
|             if (wxGetApp().extruders_edited_cnt() > 1) | ||||
|                 color_print_values.clear(); | ||||
| 
 | ||||
|             // Load the initial preview based on slices, not the final G-code.
 | ||||
|             m_canvas->load_preview(colors, color_print_values); | ||||
|         } | ||||
|         show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); | ||||
|         // recalculates zs and update sliders accordingly
 | ||||
|         std::vector<double> zs = m_canvas->get_current_print_zs(true); | ||||
|  |  | |||
|  | @ -127,6 +127,8 @@ public: | |||
|     void move_double_slider(wxKeyEvent& evt); | ||||
|     void edit_double_slider(wxKeyEvent& evt); | ||||
| 
 | ||||
|     void update_view_type(); | ||||
| 
 | ||||
| private: | ||||
|     bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model); | ||||
| 
 | ||||
|  |  | |||
|  | @ -136,7 +136,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const | |||
|         for (unsigned int idx : idxs) | ||||
|         { | ||||
|             const GLVolume* vol = selection.get_volume(idx); | ||||
|             m_box.merge(vol->bounding_box.transformed(vol->get_volume_transformation().get_matrix())); | ||||
|             m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix())); | ||||
|         } | ||||
| 
 | ||||
|         // gets transform from first selected volume
 | ||||
|  | @ -151,7 +151,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const | |||
|     else if (single_volume) | ||||
|     { | ||||
|         const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|         m_box = v->bounding_box; | ||||
|         m_box = v->bounding_box(); | ||||
|         m_transform = v->world_matrix(); | ||||
|         angles = Geometry::extract_euler_angles(m_transform); | ||||
|         // consider rotation+mirror only components of the transform for offsets
 | ||||
|  |  | |||
|  | @ -49,7 +49,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) | |||
| 
 | ||||
|     if (!m_icons_texture.metadata.filename.empty()) | ||||
|     { | ||||
|         if (!m_icons_texture.texture.load_from_file(resources_dir() + "/icons/" + m_icons_texture.metadata.filename, false)) | ||||
|         if (!m_icons_texture.texture.load_from_file(resources_dir() + "/icons/" + m_icons_texture.metadata.filename, false, true)) | ||||
|         { | ||||
|             reset(); | ||||
|             return false; | ||||
|  | @ -1072,7 +1072,7 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio | |||
| #if ENABLE_SVG_ICONS | ||||
|             it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); | ||||
| #else | ||||
|             it->second->render_input_window(2.0f * m_overlay_border + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); | ||||
|             it->second->render_input_window(2.0f * m_overlay_border + scaled_icons_size * zoom, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|         } | ||||
| #if ENABLE_SVG_ICONS | ||||
|  |  | |||
|  | @ -320,6 +320,17 @@ Line OptionsGroup::create_single_option_line(const Option& option) const { | |||
|     return retval; | ||||
| } | ||||
| 
 | ||||
| void OptionsGroup::clear_fields_except_of(const std::vector<std::string> left_fields) | ||||
| { | ||||
|     auto it = m_fields.begin(); | ||||
|     while (it != m_fields.end()) { | ||||
|         if (std::find(left_fields.begin(), left_fields.end(), it->first) == left_fields.end()) | ||||
|             it = m_fields.erase(it); | ||||
|         else  | ||||
|             it++; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void OptionsGroup::on_set_focus(const std::string& opt_key) | ||||
| { | ||||
|     if (m_set_focus != nullptr) | ||||
|  |  | |||
|  | @ -160,6 +160,8 @@ public: | |||
| 		                m_show_modified_btns = show; | ||||
|     } | ||||
| 
 | ||||
|     void            clear_fields_except_of(const std::vector<std::string> left_fields); | ||||
| 
 | ||||
| 	OptionsGroup(	wxWindow* _parent, const wxString& title, bool is_tab_opt = false,  | ||||
| 					column_t extra_clmn = nullptr) : | ||||
| 					m_parent(_parent), title(title),  | ||||
|  |  | |||
|  | @ -50,6 +50,7 @@ | |||
| #include "GUI_App.hpp" | ||||
| #include "GUI_ObjectList.hpp" | ||||
| #include "GUI_ObjectManipulation.hpp" | ||||
| #include "GUI_ObjectLayers.hpp" | ||||
| #include "GUI_Utils.hpp" | ||||
| #include "wxExtensions.hpp" | ||||
| #include "MainFrame.hpp" | ||||
|  | @ -618,6 +619,7 @@ struct Sidebar::priv | |||
|     ObjectList          *object_list{ nullptr }; | ||||
|     ObjectManipulation  *object_manipulation{ nullptr }; | ||||
|     ObjectSettings      *object_settings{ nullptr }; | ||||
|     ObjectLayers        *object_layers{ nullptr }; | ||||
|     ObjectInfo *object_info; | ||||
|     SlicedInfo *sliced_info; | ||||
| 
 | ||||
|  | @ -641,6 +643,9 @@ Sidebar::priv::~priv() | |||
| 
 | ||||
|     if (frequently_changed_parameters != nullptr) | ||||
|         delete frequently_changed_parameters; | ||||
| 
 | ||||
|     if (object_layers != nullptr) | ||||
|         delete object_layers; | ||||
| } | ||||
| 
 | ||||
| void Sidebar::priv::show_preset_comboboxes() | ||||
|  | @ -749,6 +754,11 @@ Sidebar::Sidebar(Plater *parent) | |||
|     p->object_settings = new ObjectSettings(p->scrolled); | ||||
|     p->object_settings->Hide(); | ||||
|     p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); | ||||
|   | ||||
|     // Object Layers
 | ||||
|     p->object_layers = new ObjectLayers(p->scrolled); | ||||
|     p->object_layers->Hide(); | ||||
|     p->sizer_params->Add(p->object_layers->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); | ||||
| 
 | ||||
|     // Info boxes
 | ||||
|     p->object_info = new ObjectInfo(p->scrolled); | ||||
|  | @ -942,6 +952,7 @@ void Sidebar::msw_rescale() | |||
|     p->object_list->msw_rescale(); | ||||
|     p->object_manipulation->msw_rescale(); | ||||
|     p->object_settings->msw_rescale(); | ||||
|     p->object_layers->msw_rescale(); | ||||
| 
 | ||||
|     p->object_info->msw_rescale(); | ||||
| 
 | ||||
|  | @ -963,6 +974,11 @@ ObjectSettings* Sidebar::obj_settings() | |||
|     return p->object_settings; | ||||
| } | ||||
| 
 | ||||
| ObjectLayers* Sidebar::obj_layers() | ||||
| { | ||||
|     return p->object_layers; | ||||
| } | ||||
| 
 | ||||
| wxScrolledWindow* Sidebar::scrolled_panel() | ||||
| { | ||||
|     return p->scrolled; | ||||
|  | @ -2177,9 +2193,6 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode | |||
|         } | ||||
| 
 | ||||
|         object->ensure_on_bed(); | ||||
| 
 | ||||
|         // print.auto_assign_extruders(object);
 | ||||
|         // print.add_model_object(object);
 | ||||
|     } | ||||
| 
 | ||||
| #ifdef AUTOPLACEMENT_ON_LOAD | ||||
|  | @ -2949,8 +2962,14 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) | |||
| 
 | ||||
|     // update plater with new config
 | ||||
|     wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config()); | ||||
|     /* Settings list can be changed after printer preset changing, so
 | ||||
|      * update all settings items for all item had it. | ||||
|      * Furthermore, Layers editing is implemented only for FFF printers  | ||||
|      * and for SLA presets they should be deleted | ||||
|      */ | ||||
|     if (preset_type == Preset::TYPE_PRINTER) | ||||
|         wxGetApp().obj_list()->update_settings_items(); | ||||
| //        wxGetApp().obj_list()->update_settings_items();
 | ||||
|         wxGetApp().obj_list()->update_object_list_by_printer_technology(); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) | ||||
|  | @ -3295,6 +3314,10 @@ bool Plater::priv::complit_init_object_menu() | |||
|         [this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q); | ||||
|     object_menu.AppendSeparator(); | ||||
| 
 | ||||
|     // Layers Editing for object
 | ||||
|     sidebar->obj_list()->append_menu_item_layers_editing(&object_menu); | ||||
|     object_menu.AppendSeparator(); | ||||
| 
 | ||||
|     // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume()
 | ||||
| 
 | ||||
|     return true; | ||||
|  | @ -3947,6 +3970,9 @@ void Plater::reslice() | |||
|     } | ||||
|     else if (!p->background_process.empty() && !p->background_process.idle()) | ||||
|         p->show_action_buttons(true); | ||||
| 
 | ||||
|     // update type of preview
 | ||||
|     p->preview->update_view_type(); | ||||
| } | ||||
| 
 | ||||
| void Plater::reslice_SLA_supports(const ModelObject &object) | ||||
|  | @ -4250,6 +4276,11 @@ void Plater::msw_rescale() | |||
|     GetParent()->Layout(); | ||||
| } | ||||
| 
 | ||||
| const Camera& Plater::get_camera() const | ||||
| { | ||||
|     return p->camera; | ||||
| } | ||||
| 
 | ||||
| bool Plater::can_delete() const { return p->can_delete(); } | ||||
| bool Plater::can_delete_all() const { return p->can_delete_all(); } | ||||
| bool Plater::can_increase_instances() const { return p->can_increase_instances(); } | ||||
|  |  | |||
|  | @ -33,6 +33,7 @@ class MainFrame; | |||
| class ConfigOptionsGroup; | ||||
| class ObjectManipulation; | ||||
| class ObjectSettings; | ||||
| class ObjectLayers; | ||||
| class ObjectList; | ||||
| class GLCanvas3D; | ||||
| 
 | ||||
|  | @ -93,6 +94,7 @@ public: | |||
|     ObjectManipulation*     obj_manipul(); | ||||
|     ObjectList*             obj_list(); | ||||
|     ObjectSettings*         obj_settings(); | ||||
|     ObjectLayers*           obj_layers(); | ||||
|     wxScrolledWindow*       scrolled_panel(); | ||||
|     wxPanel*                presets_panel(); | ||||
| 
 | ||||
|  | @ -218,6 +220,8 @@ public: | |||
| 
 | ||||
|     void msw_rescale(); | ||||
| 
 | ||||
|     const Camera& get_camera() const; | ||||
| 
 | ||||
| private: | ||||
|     struct priv; | ||||
|     std::unique_ptr<priv> p; | ||||
|  |  | |||
|  | @ -829,11 +829,25 @@ const Preset* PresetCollection::get_selected_preset_parent() const | |||
|     if (this->get_selected_idx() == -1) | ||||
|         // This preset collection has no preset activated yet. Only the get_edited_preset() is valid.
 | ||||
|         return nullptr; | ||||
|     const std::string &inherits = this->get_edited_preset().inherits(); | ||||
| //    const std::string &inherits = this->get_edited_preset().inherits();
 | ||||
| //    if (inherits.empty())
 | ||||
| //		return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; 
 | ||||
| 
 | ||||
|     std::string inherits = this->get_edited_preset().inherits(); | ||||
|     if (inherits.empty()) | ||||
| 		return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr;  | ||||
|     { | ||||
| 		if (this->get_selected_preset().is_system || this->get_selected_preset().is_default)  | ||||
|             return &this->get_selected_preset(); | ||||
|         if (this->get_selected_preset().is_external) | ||||
|             return nullptr; | ||||
|          | ||||
|         inherits = m_type != Preset::Type::TYPE_PRINTER ? "- default -" : | ||||
|                    this->get_edited_preset().printer_technology() == ptFFF ?  | ||||
|                    "- default FFF -" : "- default SLA -" ; | ||||
|     } | ||||
| 
 | ||||
|     const Preset* preset = this->find_preset(inherits, false); | ||||
|     return (preset == nullptr || preset->is_default || preset->is_external) ? nullptr : preset; | ||||
|     return (preset == nullptr/* || preset->is_default*/ || preset->is_external) ? nullptr : preset; | ||||
| } | ||||
| 
 | ||||
| const Preset* PresetCollection::get_preset_parent(const Preset& child) const | ||||
|  |  | |||
|  | @ -6,7 +6,8 @@ | |||
| #include "GUI_ObjectManipulation.hpp" | ||||
| #include "GUI_ObjectList.hpp" | ||||
| #include "Gizmos/GLGizmoBase.hpp" | ||||
| #include "slic3r/GUI/3DScene.hpp" | ||||
| #include "3DScene.hpp" | ||||
| #include "Camera.hpp" | ||||
| 
 | ||||
| #include <GL/glew.h> | ||||
| 
 | ||||
|  | @ -99,14 +100,14 @@ void Selection::set_volumes(GLVolumePtrs* volumes) | |||
|     update_valid(); | ||||
| } | ||||
| 
 | ||||
| bool Selection::init(bool useVBOs) | ||||
| bool Selection::init() | ||||
| { | ||||
|     if (!m_arrow.init(useVBOs)) | ||||
|     if (!m_arrow.init()) | ||||
|         return false; | ||||
| 
 | ||||
|     m_arrow.set_scale(5.0 * Vec3d::Ones()); | ||||
| 
 | ||||
|     if (!m_curved_arrow.init(useVBOs)) | ||||
|     if (!m_curved_arrow.init()) | ||||
|         return false; | ||||
| 
 | ||||
|     m_curved_arrow.set_scale(5.0 * Vec3d::Ones()); | ||||
|  | @ -331,6 +332,9 @@ void Selection::clear() | |||
| 
 | ||||
|     // resets the cache in the sidebar
 | ||||
|     wxGetApp().obj_manipul()->reset_cache(); | ||||
| 
 | ||||
|     // #et_FIXME fake KillFocus from sidebar
 | ||||
|     wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); | ||||
| } | ||||
| 
 | ||||
| // Update the selection based on the new instance IDs.
 | ||||
|  | @ -1070,61 +1074,68 @@ void Selection::render_center(bool gizmo_is_dragging) const | |||
| } | ||||
| #endif // ENABLE_RENDER_SELECTION_CENTER
 | ||||
| 
 | ||||
| void Selection::render_sidebar_hints(const std::string& sidebar_field) const | ||||
| void Selection::render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const | ||||
| { | ||||
|     if (sidebar_field.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); | ||||
|     glsafe(::glEnable(GL_DEPTH_TEST)); | ||||
|     if (!boost::starts_with(sidebar_field, "layer")) | ||||
|     { | ||||
|         shader.start_using(); | ||||
|         glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); | ||||
|         glsafe(::glEnable(GL_LIGHTING)); | ||||
|     } | ||||
| 
 | ||||
|     glsafe(::glEnable(GL_LIGHTING)); | ||||
|     glsafe(::glEnable(GL_DEPTH_TEST)); | ||||
| 
 | ||||
|     glsafe(::glPushMatrix()); | ||||
| 
 | ||||
|     const Vec3d& center = get_bounding_box().center(); | ||||
| 
 | ||||
|     if (is_single_full_instance() && ! wxGetApp().obj_manipul()->get_world_coordinates()) | ||||
|     if (!boost::starts_with(sidebar_field, "layer")) | ||||
|     { | ||||
|         glsafe(::glTranslated(center(0), center(1), center(2))); | ||||
|         if (!boost::starts_with(sidebar_field, "position")) | ||||
|         const Vec3d& center = get_bounding_box().center(); | ||||
| 
 | ||||
|         if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) | ||||
|         { | ||||
|             Transform3d orient_matrix = Transform3d::Identity(); | ||||
|             if (boost::starts_with(sidebar_field, "scale")) | ||||
|                 orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); | ||||
|             else if (boost::starts_with(sidebar_field, "rotation")) | ||||
|             glsafe(::glTranslated(center(0), center(1), center(2))); | ||||
|             if (!boost::starts_with(sidebar_field, "position")) | ||||
|             { | ||||
|                 if (boost::ends_with(sidebar_field, "x")) | ||||
|                 Transform3d orient_matrix = Transform3d::Identity(); | ||||
|                 if (boost::starts_with(sidebar_field, "scale")) | ||||
|                     orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); | ||||
|                 else if (boost::ends_with(sidebar_field, "y")) | ||||
|                 else if (boost::starts_with(sidebar_field, "rotation")) | ||||
|                 { | ||||
|                     const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); | ||||
|                     if (rotation(0) == 0.0) | ||||
|                     if (boost::ends_with(sidebar_field, "x")) | ||||
|                         orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); | ||||
|                     else | ||||
|                         orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); | ||||
|                     else if (boost::ends_with(sidebar_field, "y")) | ||||
|                     { | ||||
|                         const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); | ||||
|                         if (rotation(0) == 0.0) | ||||
|                             orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); | ||||
|                         else | ||||
|                             orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 glsafe(::glMultMatrixd(orient_matrix.data())); | ||||
|             } | ||||
|         } | ||||
|         else if (is_single_volume() || is_single_modifier()) | ||||
|         { | ||||
|             glsafe(::glTranslated(center(0), center(1), center(2))); | ||||
|             Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); | ||||
|             if (!boost::starts_with(sidebar_field, "position")) | ||||
|                 orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); | ||||
| 
 | ||||
|             glsafe(::glMultMatrixd(orient_matrix.data())); | ||||
|         } | ||||
|     } | ||||
|     else if (is_single_volume() || is_single_modifier()) | ||||
|     { | ||||
|         glsafe(::glTranslated(center(0), center(1), center(2))); | ||||
|         Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); | ||||
|         if (!boost::starts_with(sidebar_field, "position")) | ||||
|             orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); | ||||
| 
 | ||||
|         glsafe(::glMultMatrixd(orient_matrix.data())); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         glsafe(::glTranslated(center(0), center(1), center(2))); | ||||
|         if (requires_local_axes()) | ||||
|         else | ||||
|         { | ||||
|             Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); | ||||
|             glsafe(::glMultMatrixd(orient_matrix.data())); | ||||
|             glsafe(::glTranslated(center(0), center(1), center(2))); | ||||
|             if (requires_local_axes()) | ||||
|             { | ||||
|                 Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); | ||||
|                 glsafe(::glMultMatrixd(orient_matrix.data())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -1136,10 +1147,16 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const | |||
|         render_sidebar_scale_hints(sidebar_field); | ||||
|     else if (boost::starts_with(sidebar_field, "size")) | ||||
|         render_sidebar_size_hints(sidebar_field); | ||||
|     else if (boost::starts_with(sidebar_field, "layer")) | ||||
|         render_sidebar_layers_hints(sidebar_field); | ||||
| 
 | ||||
|     glsafe(::glPopMatrix()); | ||||
| 
 | ||||
|     glsafe(::glDisable(GL_LIGHTING)); | ||||
|     if (!boost::starts_with(sidebar_field, "layer")) | ||||
|     { | ||||
|         glsafe(::glDisable(GL_LIGHTING)); | ||||
|         shader.stop_using(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Selection::requires_local_axes() const | ||||
|  | @ -1163,7 +1180,7 @@ void Selection::copy_to_clipboard() | |||
|         dst_object->config               = src_object->config; | ||||
|         dst_object->sla_support_points   = src_object->sla_support_points; | ||||
|         dst_object->sla_points_status    = src_object->sla_points_status; | ||||
|         dst_object->layer_height_ranges  = src_object->layer_height_ranges; | ||||
|         dst_object->layer_config_ranges  = src_object->layer_config_ranges;     // #ys_FIXME_experiment
 | ||||
|         dst_object->layer_height_profile = src_object->layer_height_profile; | ||||
|         dst_object->origin_translation   = src_object->origin_translation; | ||||
| 
 | ||||
|  | @ -1709,6 +1726,78 @@ void Selection::render_sidebar_size_hints(const std::string& sidebar_field) cons | |||
|     render_sidebar_scale_hints(sidebar_field); | ||||
| } | ||||
| 
 | ||||
| void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const | ||||
| { | ||||
|     static const double Margin = 10.0; | ||||
| 
 | ||||
|     std::string field = sidebar_field; | ||||
| 
 | ||||
|     // extract max_z
 | ||||
|     std::string::size_type pos = field.rfind("_"); | ||||
|     if (pos == std::string::npos) | ||||
|         return; | ||||
| 
 | ||||
|     double max_z = std::stod(field.substr(pos + 1)); | ||||
| 
 | ||||
|     // extract min_z
 | ||||
|     field = field.substr(0, pos); | ||||
|     pos = field.rfind("_"); | ||||
|     if (pos == std::string::npos) | ||||
|         return; | ||||
| 
 | ||||
|     double min_z = std::stod(field.substr(pos + 1)); | ||||
| 
 | ||||
|     // extract type
 | ||||
|     field = field.substr(0, pos); | ||||
|     pos = field.rfind("_"); | ||||
|     if (pos == std::string::npos) | ||||
|         return; | ||||
| 
 | ||||
|     int type = std::stoi(field.substr(pos + 1)); | ||||
| 
 | ||||
|     const BoundingBoxf3& box = get_bounding_box(); | ||||
| 
 | ||||
|     const float min_x = box.min(0) - Margin; | ||||
|     const float max_x = box.max(0) + Margin; | ||||
|     const float min_y = box.min(1) - Margin; | ||||
|     const float max_y = box.max(1) + Margin; | ||||
| 
 | ||||
|     // view dependend order of rendering to keep correct transparency
 | ||||
|     bool camera_on_top = wxGetApp().plater()->get_camera().get_theta() <= 90.0f; | ||||
|     float z1 = camera_on_top ? min_z : max_z; | ||||
|     float z2 = camera_on_top ? max_z : min_z; | ||||
| 
 | ||||
|     glsafe(::glEnable(GL_DEPTH_TEST)); | ||||
|     glsafe(::glDisable(GL_CULL_FACE)); | ||||
|     glsafe(::glEnable(GL_BLEND)); | ||||
|     glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); | ||||
| 
 | ||||
|     ::glBegin(GL_QUADS); | ||||
|     if ((camera_on_top && (type == 1)) || (!camera_on_top && (type == 2))) | ||||
|         ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); | ||||
|     else | ||||
|         ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); | ||||
|     ::glVertex3f(min_x, min_y, z1); | ||||
|     ::glVertex3f(max_x, min_y, z1); | ||||
|     ::glVertex3f(max_x, max_y, z1); | ||||
|     ::glVertex3f(min_x, max_y, z1); | ||||
|     glsafe(::glEnd()); | ||||
| 
 | ||||
|     ::glBegin(GL_QUADS); | ||||
|     if ((camera_on_top && (type == 2)) || (!camera_on_top && (type == 1))) | ||||
|         ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); | ||||
|     else | ||||
|         ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); | ||||
|     ::glVertex3f(min_x, min_y, z2); | ||||
|     ::glVertex3f(max_x, min_y, z2); | ||||
|     ::glVertex3f(max_x, max_y, z2); | ||||
|     ::glVertex3f(min_x, max_y, z2); | ||||
|     glsafe(::glEnd()); | ||||
| 
 | ||||
|     glsafe(::glEnable(GL_CULL_FACE)); | ||||
|     glsafe(::glDisable(GL_BLEND)); | ||||
| } | ||||
| 
 | ||||
| void Selection::render_sidebar_position_hint(Axis axis) const | ||||
| { | ||||
|     m_arrow.set_color(AXES_COLOR[axis], 3); | ||||
|  |  | |||
|  | @ -11,8 +11,8 @@ typedef class GLUquadric GLUquadricObj; | |||
| #endif // ENABLE_RENDER_SELECTION_CENTER
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| class Shader; | ||||
| namespace GUI { | ||||
| 
 | ||||
| class TransformationType | ||||
| { | ||||
| public: | ||||
|  | @ -212,7 +212,7 @@ public: | |||
| #endif // ENABLE_RENDER_SELECTION_CENTER
 | ||||
| 
 | ||||
|     void set_volumes(GLVolumePtrs* volumes); | ||||
|     bool init(bool useVBOs); | ||||
|     bool init(); | ||||
| 
 | ||||
|     bool is_enabled() const { return m_enabled; } | ||||
|     void set_enabled(bool enable) { m_enabled = enable; } | ||||
|  | @ -302,7 +302,7 @@ public: | |||
| #if ENABLE_RENDER_SELECTION_CENTER | ||||
|     void render_center(bool gizmo_is_dragging) const; | ||||
| #endif // ENABLE_RENDER_SELECTION_CENTER
 | ||||
|     void render_sidebar_hints(const std::string& sidebar_field) const; | ||||
|     void render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const; | ||||
| 
 | ||||
|     bool requires_local_axes() const; | ||||
| 
 | ||||
|  | @ -332,6 +332,7 @@ private: | |||
|     void render_sidebar_rotation_hints(const std::string& sidebar_field) const; | ||||
|     void render_sidebar_scale_hints(const std::string& sidebar_field) const; | ||||
|     void render_sidebar_size_hints(const std::string& sidebar_field) const; | ||||
|     void render_sidebar_layers_hints(const std::string& sidebar_field) const; | ||||
|     void render_sidebar_position_hint(Axis axis) const; | ||||
|     void render_sidebar_rotation_hint(Axis axis) const; | ||||
|     void render_sidebar_scale_hint(Axis axis) const; | ||||
|  |  | |||
|  | @ -423,7 +423,7 @@ void Tab::update_changed_ui() | |||
| 		const ScalableBitmap *sys_icon =	&m_bmp_value_lock; | ||||
| 		const ScalableBitmap *icon =		&m_bmp_value_revert; | ||||
| 
 | ||||
| 		const wxColour *color =		&m_sys_label_clr; | ||||
| 		const wxColour *color =		m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr; | ||||
| 
 | ||||
| 		const wxString *sys_tt =	&m_tt_value_lock; | ||||
| 		const wxString *tt =		&m_tt_value_revert; | ||||
|  | @ -590,7 +590,7 @@ void Tab::update_changed_tree_ui() | |||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			const wxColor *clr = sys_page		?	&m_sys_label_clr : | ||||
| 			const wxColor *clr = sys_page		?	(m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr) : | ||||
| 								 modified_page	?	&m_modified_label_clr :  | ||||
| 													&m_default_text_clr; | ||||
| 
 | ||||
|  | @ -2584,11 +2584,14 @@ void Tab::load_current_preset() | |||
|     // Reload preset pages with the new configuration values.
 | ||||
|     reload_config(); | ||||
| 
 | ||||
| 	m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; | ||||
| 	m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; | ||||
| 	m_tt_non_system = m_presets->get_selected_preset_parent()  ? &m_tt_value_unlock  : &m_ttg_white_bullet_ns; | ||||
|     const Preset* selected_preset_parent = m_presets->get_selected_preset_parent(); | ||||
|     m_is_default_preset = selected_preset_parent != nullptr && selected_preset_parent->is_default; | ||||
| 
 | ||||
| 	m_undo_to_sys_btn->Enable(!preset.is_default); | ||||
| 	m_bmp_non_system = selected_preset_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; | ||||
| 	m_ttg_non_system = selected_preset_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; | ||||
| 	m_tt_non_system  = selected_preset_parent ? &m_tt_value_unlock  : &m_ttg_white_bullet_ns; | ||||
| 
 | ||||
| //	m_undo_to_sys_btn->Enable(!preset.is_default);
 | ||||
| 
 | ||||
| #if 0 | ||||
| 	// use CallAfter because some field triggers schedule on_change calls using CallAfter,
 | ||||
|  | @ -3174,18 +3177,18 @@ void Tab::fill_icon_descriptions() | |||
| { | ||||
| 	m_icon_descriptions.emplace_back(&m_bmp_value_lock, L("LOCKED LOCK"), | ||||
|         // TRN Description for "LOCKED LOCK"
 | ||||
| 		L("indicates that the settings are the same as the system values for the current option group")); | ||||
| 		L("indicates that the settings are the same as the system (or default) values for the current option group")); | ||||
| 
 | ||||
|     m_icon_descriptions.emplace_back(&m_bmp_value_unlock, L("UNLOCKED LOCK"), | ||||
|         // TRN Description for "UNLOCKED LOCK"
 | ||||
| 		L("indicates that some settings were changed and are not equal to the system values for " | ||||
| 		L("indicates that some settings were changed and are not equal to the system (or default) values for " | ||||
| 		"the current option group.\n" | ||||
| 		"Click the UNLOCKED LOCK icon to reset all settings for current option group to " | ||||
| 		"the system values.")); | ||||
| 		"the system (or default) values.")); | ||||
| 
 | ||||
|     m_icon_descriptions.emplace_back(&m_bmp_white_bullet, L("WHITE BULLET"), | ||||
|         // TRN Description for "WHITE BULLET"
 | ||||
|         L("for the left button: \tindicates a non-system preset,\n" | ||||
|         L("for the left button: \tindicates a non-system (or non-default) preset,\n" | ||||
| 		"for the right button: \tindicates that the settings hasn't been modified.")); | ||||
| 
 | ||||
|     m_icon_descriptions.emplace_back(&m_bmp_value_revert, L("BACK ARROW"), | ||||
|  | @ -3198,29 +3201,14 @@ void Tab::fill_icon_descriptions() | |||
| 
 | ||||
| void Tab::set_tooltips_text() | ||||
| { | ||||
| // 	m_undo_to_sys_btn->SetToolTip(_(L(	"LOCKED LOCK icon indicates that the settings are the same as the system values "
 | ||||
| // 										"for the current option group.\n"
 | ||||
| // 										"UNLOCKED LOCK icon indicates that some settings were changed and are not equal "
 | ||||
| // 										"to the system values for the current option group.\n"
 | ||||
| // 										"WHITE BULLET icon indicates a non system preset.\n\n"
 | ||||
| // 										"Click the UNLOCKED LOCK icon to reset all settings for current option group to "
 | ||||
| // 										"the system values.")));
 | ||||
| // 
 | ||||
| // 	m_undo_btn->SetToolTip(_(L(	"WHITE BULLET icon indicates that the settings are the same as in the last saved"
 | ||||
| // 								"preset  for the current option group.\n"
 | ||||
| // 								"BACK ARROW icon indicates that the settings were changed and are not equal to "
 | ||||
| // 								"the last saved preset for the current option group.\n\n"
 | ||||
| // 								"Click the BACK ARROW icon to reset all settings for the current option group to "
 | ||||
| // 								"the last saved preset.")));
 | ||||
| 
 | ||||
| 	// --- Tooltip text for reset buttons (for whole options group)
 | ||||
| 	// Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field.
 | ||||
| 	m_ttg_value_lock =		_(L("LOCKED LOCK icon indicates that the settings are the same as the system values " | ||||
| 	m_ttg_value_lock =		_(L("LOCKED LOCK icon indicates that the settings are the same as the system (or default) values " | ||||
| 								"for the current option group")); | ||||
| 	m_ttg_value_unlock =	_(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " | ||||
| 								"to the system values for the current option group.\n" | ||||
| 								"Click to reset all settings for current option group to the system values.")); | ||||
| 	m_ttg_white_bullet_ns =	_(L("WHITE BULLET icon indicates a non system preset.")); | ||||
| 								"to the system (or default) values for the current option group.\n" | ||||
| 								"Click to reset all settings for current option group to the system (or default) values.")); | ||||
| 	m_ttg_white_bullet_ns =	_(L("WHITE BULLET icon indicates a non system (or non default) preset.")); | ||||
| 	m_ttg_non_system =		&m_ttg_white_bullet_ns; | ||||
| 	// Text to be shown on the "Undo user changes" button next to each input field.
 | ||||
| 	m_ttg_white_bullet =	_(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " | ||||
|  | @ -3231,10 +3219,10 @@ void Tab::set_tooltips_text() | |||
| 
 | ||||
| 	// --- Tooltip text for reset buttons (for each option in group)
 | ||||
| 	// Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field.
 | ||||
| 	m_tt_value_lock =		_(L("LOCKED LOCK icon indicates that the value is the same as the system value.")); | ||||
| 	m_tt_value_lock =		_(L("LOCKED LOCK icon indicates that the value is the same as the system (or default) value.")); | ||||
| 	m_tt_value_unlock =		_(L("UNLOCKED LOCK icon indicates that the value was changed and is not equal " | ||||
| 								"to the system value.\n" | ||||
| 								"Click to reset current value to the system value.")); | ||||
| 								"to the system (or default) value.\n" | ||||
| 								"Click to reset current value to the system (or default) value.")); | ||||
| 	// 	m_tt_white_bullet_ns=	_(L("WHITE BULLET icon indicates a non system preset."));
 | ||||
| 	m_tt_non_system =		&m_ttg_white_bullet_ns; | ||||
| 	// Text to be shown on the "Undo user changes" button next to each input field.
 | ||||
|  | @ -3488,9 +3476,9 @@ void TabSLAMaterial::reload_config() | |||
| void TabSLAMaterial::update() | ||||
| { | ||||
|     if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) | ||||
|         return; // #ys_FIXME
 | ||||
|         return; | ||||
|      | ||||
| // #ys_FIXME
 | ||||
| // #ys_FIXME. Just a template for this function
 | ||||
| //     m_update_cnt++;
 | ||||
| //     ! something to update
 | ||||
| //     m_update_cnt--;
 | ||||
|  | @ -3593,12 +3581,10 @@ void TabSLAPrint::reload_config() | |||
| 
 | ||||
| void TabSLAPrint::update() | ||||
| { | ||||
|     if (m_preset_bundle->printers.get_selected_preset().printer_technology() | ||||
|         == ptFFF) | ||||
|         return; // #ys_FIXME
 | ||||
|     if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) | ||||
|         return; | ||||
| 
 | ||||
|     // #ys_FIXME
 | ||||
|     m_update_cnt++; | ||||
|      m_update_cnt++; | ||||
| 
 | ||||
|     double head_penetration = m_config->opt_float("support_head_penetration"); | ||||
|     double head_width       = m_config->opt_float("support_head_width"); | ||||
|  |  | |||
|  | @ -142,6 +142,12 @@ protected: | |||
| 	PresetDependencies 	m_compatible_printers; | ||||
| 	PresetDependencies 	m_compatible_prints; | ||||
| 
 | ||||
|     /* Indicates, that default preset or preset inherited from default is selected
 | ||||
|      * This value is used for a options color updating  | ||||
|      * (use green color only for options, which values are equal to system values) | ||||
|      */ | ||||
|     bool                    m_is_default_preset {false}; | ||||
| 
 | ||||
| 	ScalableButton*			m_undo_btn; | ||||
| 	ScalableButton*			m_undo_to_sys_btn; | ||||
| 	ScalableButton*			m_question_btn; | ||||
|  |  | |||
|  | @ -437,27 +437,69 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent | |||
|     m_type(type), | ||||
|     m_extruder(wxEmptyString) | ||||
| { | ||||
|     if (type == itSettings) { | ||||
|     if (type == itSettings) | ||||
|         m_name = "Settings to modified"; | ||||
|     } | ||||
|     else if (type == itInstanceRoot) { | ||||
|     else if (type == itInstanceRoot) | ||||
|         m_name = _(L("Instances")); | ||||
| #ifdef __WXGTK__ | ||||
|         m_container = true; | ||||
| #endif  //__WXGTK__
 | ||||
|     } | ||||
|     else if (type == itInstance) { | ||||
|     else if (type == itInstance) | ||||
|     { | ||||
|         m_idx = parent->GetChildCount(); | ||||
|         m_name = wxString::Format(_(L("Instance %d")), m_idx + 1); | ||||
| 
 | ||||
|         set_action_icon(); | ||||
|     } | ||||
|     else if (type == itLayerRoot) | ||||
|     { | ||||
|         m_bmp = create_scaled_bitmap(nullptr, "layers");    // FIXME: pass window ptr
 | ||||
|         m_name = _(L("Layers")); | ||||
|     } | ||||
| 
 | ||||
| #ifdef __WXGTK__ | ||||
|     // it's necessary on GTK because of control have to know if this item will be container
 | ||||
|     // in another case you couldn't to add subitem for this item
 | ||||
|     // it will be produce "segmentation fault"
 | ||||
|     if (type & (itInstanceRoot | itLayerRoot)) | ||||
|         m_container = true; | ||||
| #endif  //__WXGTK__
 | ||||
| } | ||||
| 
 | ||||
| ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,  | ||||
|                                                  const t_layer_height_range& layer_range, | ||||
|                                                  const int idx /*= -1 */,  | ||||
|                                                  const wxString& extruder) : | ||||
|     m_parent(parent), | ||||
|     m_type(itLayer), | ||||
|     m_idx(idx), | ||||
|     m_layer_range(layer_range), | ||||
|     m_extruder(extruder) | ||||
| { | ||||
|     const int children_cnt = parent->GetChildCount(); | ||||
|     if (idx < 0) | ||||
|         m_idx = children_cnt; | ||||
|     else | ||||
|     { | ||||
|         // update indexes for another Laeyr Nodes
 | ||||
|         for (int i = m_idx; i < children_cnt; i++) | ||||
|             parent->GetNthChild(i)->SetIdx(i + 1); | ||||
|     } | ||||
|     const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); | ||||
|     m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; | ||||
|     m_bmp = create_scaled_bitmap(nullptr, "layers_white");    // FIXME: pass window ptr
 | ||||
| 
 | ||||
| #ifdef __WXGTK__ | ||||
|     // it's necessary on GTK because of control have to know if this item will be container
 | ||||
|     // in another case you couldn't to add subitem for this item
 | ||||
|     // it will be produce "segmentation fault"
 | ||||
|     m_container = true; | ||||
| #endif  //__WXGTK__
 | ||||
| 
 | ||||
|     set_action_icon(); | ||||
| } | ||||
| 
 | ||||
| void ObjectDataViewModelNode::set_action_icon() | ||||
| { | ||||
|     m_action_icon_name = m_type == itObject ? "advanced_plus" :  | ||||
|                          m_type == itVolume ? "cog"           : "set_separate_obj"; | ||||
|     m_action_icon_name = m_type & itObject              ? "advanced_plus" :  | ||||
|                          m_type & (itVolume | itLayer)  ? "cog" : /*m_type & itInstance*/ "set_separate_obj"; | ||||
|     m_action_icon = create_scaled_bitmap(nullptr, m_action_icon_name);    // FIXME: pass window ptr
 | ||||
| } | ||||
| 
 | ||||
|  | @ -523,6 +565,22 @@ void ObjectDataViewModelNode::SetIdx(const int& idx) | |||
| // ObjectDataViewModel
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType root_type) | ||||
| { | ||||
|     // because of istance_root and layers_root are at the end of the list, so
 | ||||
|     // start locking from the end
 | ||||
|     for (int root_idx = parent_node->GetChildCount() - 1; root_idx >= 0; root_idx--) | ||||
|     { | ||||
|         // if there is SettingsItem or VolumeItem, then RootItems don't exist in current ObjectItem 
 | ||||
|         if (parent_node->GetNthChild(root_idx)->GetType() & (itSettings | itVolume)) | ||||
|             break; | ||||
|         if (parent_node->GetNthChild(root_idx)->GetType() & root_type) | ||||
|             return root_idx; | ||||
|     } | ||||
| 
 | ||||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| ObjectDataViewModel::ObjectDataViewModel() | ||||
| { | ||||
|     m_bitmap_cache = new Slic3r::GUI::BitmapCache; | ||||
|  | @ -567,10 +625,10 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent | |||
| 
 | ||||
|     wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); | ||||
| 
 | ||||
|     // because of istance_root is a last item of the object
 | ||||
|     int insert_position = root->GetChildCount() - 1; | ||||
|     if (insert_position < 0 || root->GetNthChild(insert_position)->m_type != itInstanceRoot) | ||||
|         insert_position = -1; | ||||
|     // get insertion position according to the existed Layers and/or Instances Items
 | ||||
|     int insert_position = get_root_idx(root, itLayerRoot); | ||||
|     if (insert_position < 0) | ||||
|         insert_position = get_root_idx(root, itInstanceRoot); | ||||
| 
 | ||||
|     const bool obj_errors = root->m_bmp.IsOk(); | ||||
| 
 | ||||
|  | @ -619,15 +677,30 @@ wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &paren | |||
|     return child; | ||||
| } | ||||
| 
 | ||||
| int get_istances_root_idx(ObjectDataViewModelNode *parent_node) | ||||
| /* return values:
 | ||||
|  * true     => root_node is created and added to the parent_root | ||||
|  * false    => root node alredy exists | ||||
| */ | ||||
| static bool append_root_node(ObjectDataViewModelNode *parent_node,  | ||||
|                              ObjectDataViewModelNode **root_node,  | ||||
|                              const ItemType root_type) | ||||
| { | ||||
|     // because of istance_root is a last item of the object
 | ||||
|     const int inst_root_idx = parent_node->GetChildCount()-1; | ||||
|     const int inst_root_id = get_root_idx(parent_node, root_type); | ||||
| 
 | ||||
|     if (inst_root_idx < 0 || parent_node->GetNthChild(inst_root_idx)->GetType() == itInstanceRoot)  | ||||
|         return inst_root_idx; | ||||
|     *root_node = inst_root_id < 0 ? | ||||
|                 new ObjectDataViewModelNode(parent_node, root_type) : | ||||
|                 parent_node->GetNthChild(inst_root_id); | ||||
|      | ||||
|     return -1; | ||||
|     if (inst_root_id < 0) { | ||||
|         if ((root_type&itInstanceRoot) || | ||||
|             (root_type&itLayerRoot) && get_root_idx(parent_node, itInstanceRoot)<0) | ||||
|             parent_node->Append(*root_node); | ||||
|         else if (root_type&itLayerRoot) | ||||
|             parent_node->Insert(*root_node, static_cast<unsigned int>(get_root_idx(parent_node, itInstanceRoot))); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) | ||||
|  | @ -635,20 +708,15 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &paren | |||
|     ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); | ||||
|     if (!parent_node) return wxDataViewItem(0); | ||||
| 
 | ||||
|     // Check and create/get instances root node
 | ||||
|     const int inst_root_id = get_istances_root_idx(parent_node); | ||||
|     // get InstanceRoot node
 | ||||
|     ObjectDataViewModelNode *inst_root_node { nullptr }; | ||||
| 
 | ||||
|     ObjectDataViewModelNode *inst_root_node = inst_root_id < 0 ?  | ||||
|                                                    new ObjectDataViewModelNode(parent_node, itInstanceRoot) : | ||||
|                                                    parent_node->GetNthChild(inst_root_id); | ||||
|     const bool appended = append_root_node(parent_node, &inst_root_node, itInstanceRoot); | ||||
|     const wxDataViewItem inst_root_item((void*)inst_root_node); | ||||
|     if (!inst_root_node) return wxDataViewItem(0); | ||||
| 
 | ||||
|     if (inst_root_id < 0) { | ||||
|         parent_node->Append(inst_root_node); | ||||
|         // notify control
 | ||||
|         ItemAdded(parent_item, inst_root_item); | ||||
| //         if (num == 1) num++;
 | ||||
|     } | ||||
|     if (appended) | ||||
|         ItemAdded(parent_item, inst_root_item);// notify control
 | ||||
| 
 | ||||
|     // Add instance nodes
 | ||||
|     ObjectDataViewModelNode *instance_node = nullptr;     | ||||
|  | @ -665,6 +733,63 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &paren | |||
|     return wxDataViewItem((void*)instance_node); | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_item) | ||||
| { | ||||
|     ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); | ||||
|     if (!parent_node) return wxDataViewItem(0); | ||||
| 
 | ||||
|     // get LayerRoot node
 | ||||
|     ObjectDataViewModelNode *layer_root_node{ nullptr }; | ||||
|     const bool appended = append_root_node(parent_node, &layer_root_node, itLayerRoot); | ||||
|     if (!layer_root_node) return wxDataViewItem(0); | ||||
| 
 | ||||
|     const wxDataViewItem layer_root_item((void*)layer_root_node); | ||||
| 
 | ||||
|     if (appended) | ||||
|         ItemAdded(parent_item, layer_root_item);// notify control
 | ||||
| 
 | ||||
|     return layer_root_item; | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item,  | ||||
|                                                    const t_layer_height_range& layer_range, | ||||
|                                                    const int extruder/* = 0*/,  | ||||
|                                                    const int index /* = -1*/) | ||||
| { | ||||
|     ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); | ||||
|     if (!parent_node) return wxDataViewItem(0); | ||||
| 
 | ||||
|     wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); | ||||
| 
 | ||||
|     // get LayerRoot node
 | ||||
|     ObjectDataViewModelNode *layer_root_node; | ||||
|     wxDataViewItem layer_root_item; | ||||
| 
 | ||||
|     if (parent_node->GetType() & itLayerRoot) { | ||||
|         layer_root_node = parent_node; | ||||
|         layer_root_item = parent_item; | ||||
|     } | ||||
|     else { | ||||
|         const int root_idx = get_root_idx(parent_node, itLayerRoot); | ||||
|         if (root_idx < 0) return wxDataViewItem(0); | ||||
|         layer_root_node = parent_node->GetNthChild(root_idx); | ||||
|         layer_root_item = wxDataViewItem((void*)layer_root_node); | ||||
|     } | ||||
| 
 | ||||
|     // Add layer node
 | ||||
|     ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index, extruder_str); | ||||
|     if (index < 0) | ||||
|         layer_root_node->Append(layer_node); | ||||
|     else | ||||
|         layer_root_node->Insert(layer_node, index); | ||||
| 
 | ||||
|     // notify control
 | ||||
|     const wxDataViewItem layer_item((void*)layer_node); | ||||
|     ItemAdded(layer_root_item, layer_item); | ||||
| 
 | ||||
|     return layer_item; | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) | ||||
| { | ||||
| 	auto ret_item = wxDataViewItem(0); | ||||
|  | @ -679,9 +804,9 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) | |||
| 	// NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_
 | ||||
| 	//       thus removing the node from it doesn't result in freeing it
 | ||||
| 	if (node_parent) { | ||||
|         if (node->m_type == itInstanceRoot) | ||||
|         if (node->m_type & (itInstanceRoot|itLayerRoot)) | ||||
|         { | ||||
|             for (int i = node->GetChildCount() - 1; i > 0; i--) | ||||
|             for (int i = node->GetChildCount() - 1; i >= (node->m_type & itInstanceRoot ? 1 : 0); i--) | ||||
|                 Delete(wxDataViewItem(node->GetNthChild(i))); | ||||
|             return parent; | ||||
|         } | ||||
|  | @ -690,7 +815,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) | |||
|         auto idx = node->GetIdx(); | ||||
| 
 | ||||
| 
 | ||||
|         if (node->m_type == itVolume) { | ||||
|         if (node->m_type & (itVolume|itLayer)) { | ||||
|             node_parent->m_volumes_cnt--; | ||||
|             DeleteSettings(item); | ||||
|         } | ||||
|  | @ -726,6 +851,22 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) | |||
|             delete node_parent; | ||||
|             ret_item = wxDataViewItem(obj_node); | ||||
| 
 | ||||
| #ifndef __WXGTK__ | ||||
|             if (obj_node->GetChildCount() == 0) | ||||
|                 obj_node->m_container = false; | ||||
| #endif //__WXGTK__
 | ||||
|             ItemDeleted(ret_item, wxDataViewItem(node_parent)); | ||||
|             return ret_item; | ||||
|         } | ||||
| 
 | ||||
|         // if there was last layer item, delete this one and layers root item
 | ||||
|         if (node_parent->GetChildCount() == 0 && node_parent->m_type == itLayerRoot) | ||||
|         { | ||||
|             ObjectDataViewModelNode *obj_node = node_parent->GetParent(); | ||||
|             obj_node->GetChildren().Remove(node_parent); | ||||
|             delete node_parent; | ||||
|             ret_item = wxDataViewItem(obj_node); | ||||
| 
 | ||||
| #ifndef __WXGTK__ | ||||
|             if (obj_node->GetChildCount() == 0) | ||||
|                 obj_node->m_container = false; | ||||
|  | @ -735,7 +876,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) | |||
|         } | ||||
| 
 | ||||
|         // if there is last volume item after deleting, delete this last volume too
 | ||||
|         if (node_parent->GetChildCount() <= 3) | ||||
|         if (node_parent->GetChildCount() <= 3) // 3??? #ys_FIXME
 | ||||
|         { | ||||
|             int vol_cnt = 0; | ||||
|             int vol_idx = 0; | ||||
|  | @ -817,7 +958,7 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par | |||
|     ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); | ||||
|     if (!parent_node) return ret_item; | ||||
| 
 | ||||
|     const int inst_root_id = get_istances_root_idx(parent_node); | ||||
|     const int inst_root_id = get_root_idx(parent_node, itInstanceRoot); | ||||
|     if (inst_root_id < 0) return ret_item; | ||||
| 
 | ||||
|     wxDataViewItemArray items; | ||||
|  | @ -974,28 +1115,67 @@ wxDataViewItem ObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_id | |||
|     return wxDataViewItem(0); | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) | ||||
| wxDataViewItem ObjectDataViewModel::GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type) | ||||
| { | ||||
|     if (obj_idx >= m_objects.size() || obj_idx < 0) { | ||||
|         printf("Error! Out of objects range.\n"); | ||||
|         return wxDataViewItem(0); | ||||
|     } | ||||
| 
 | ||||
|     auto instances_item = GetInstanceRootItem(wxDataViewItem(m_objects[obj_idx])); | ||||
|     if (!instances_item) | ||||
|     auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), parent_type); | ||||
|     if (!item) | ||||
|         return wxDataViewItem(0); | ||||
| 
 | ||||
|     auto parent = (ObjectDataViewModelNode*)instances_item.GetID();; | ||||
|     auto parent = (ObjectDataViewModelNode*)item.GetID(); | ||||
|     for (size_t i = 0; i < parent->GetChildCount(); i++) | ||||
|         if (parent->GetNthChild(i)->m_idx == inst_idx) | ||||
|         if (parent->GetNthChild(i)->m_idx == sub_obj_idx) | ||||
|             return wxDataViewItem(parent->GetNthChild(i)); | ||||
| 
 | ||||
|     return wxDataViewItem(0); | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) | ||||
| { | ||||
|     return GetItemById(obj_idx, inst_idx, itInstanceRoot); | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::GetItemByLayerId(int obj_idx, int layer_idx) | ||||
| { | ||||
|     return GetItemById(obj_idx, layer_idx, itLayerRoot); | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) | ||||
| { | ||||
|     if (obj_idx >= m_objects.size() || obj_idx < 0) { | ||||
|         printf("Error! Out of objects range.\n"); | ||||
|         return wxDataViewItem(0); | ||||
|     } | ||||
| 
 | ||||
|     auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), itLayerRoot); | ||||
|     if (!item) | ||||
|         return wxDataViewItem(0); | ||||
| 
 | ||||
|     auto parent = (ObjectDataViewModelNode*)item.GetID(); | ||||
|     for (size_t i = 0; i < parent->GetChildCount(); i++) | ||||
|         if (parent->GetNthChild(i)->m_layer_range == layer_range) | ||||
|             return wxDataViewItem(parent->GetNthChild(i)); | ||||
| 
 | ||||
|     return wxDataViewItem(0); | ||||
| } | ||||
| 
 | ||||
| int  ObjectDataViewModel::GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) | ||||
| { | ||||
|     wxDataViewItem item = GetItemByLayerRange(obj_idx, layer_range); | ||||
|     if (!item) | ||||
|         return -1; | ||||
| 
 | ||||
|     return GetLayerIdByItem(item); | ||||
| } | ||||
| 
 | ||||
| int ObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const | ||||
| { | ||||
| 	wxASSERT(item.IsOk()); | ||||
| 	if(!item.IsOk()) | ||||
|         return -1; | ||||
| 
 | ||||
| 	ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); | ||||
| 	auto it = find(m_objects.begin(), m_objects.end(), node); | ||||
|  | @ -1030,13 +1210,28 @@ int ObjectDataViewModel::GetInstanceIdByItem(const wxDataViewItem& item) const | |||
|     return GetIdByItemAndType(item, itInstance); | ||||
| } | ||||
| 
 | ||||
| int ObjectDataViewModel::GetLayerIdByItem(const wxDataViewItem& item) const  | ||||
| { | ||||
|     return GetIdByItemAndType(item, itLayer); | ||||
| } | ||||
| 
 | ||||
| t_layer_height_range ObjectDataViewModel::GetLayerRangeByItem(const wxDataViewItem& item) const | ||||
| { | ||||
|     wxASSERT(item.IsOk()); | ||||
| 
 | ||||
|     ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); | ||||
|     if (!node || node->m_type != itLayer) | ||||
|         return { 0.0f, 0.0f }; | ||||
|     return node->GetLayerRange(); | ||||
| } | ||||
| 
 | ||||
| void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx) | ||||
| { | ||||
|     wxASSERT(item.IsOk()); | ||||
|     type = itUndef; | ||||
| 
 | ||||
|     ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); | ||||
|     if (!node || node->GetIdx() <-1 || node->GetIdx() ==-1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot))) | ||||
|     if (!node || node->GetIdx() <-1 || node->GetIdx() == -1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot | itLayerRoot/* | itLayer*/))) | ||||
|         return; | ||||
| 
 | ||||
|     idx = node->GetIdx(); | ||||
|  | @ -1044,9 +1239,10 @@ void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type | |||
| 
 | ||||
|     ObjectDataViewModelNode *parent_node = node->GetParent(); | ||||
|     if (!parent_node) return; | ||||
|     if (type == itInstance) | ||||
|         parent_node = node->GetParent()->GetParent(); | ||||
|     if (!parent_node || parent_node->m_type != itObject) { type = itUndef; return; } | ||||
| 
 | ||||
|     // get top parent (Object) node
 | ||||
|     while (parent_node->m_type != itObject) | ||||
|         parent_node = parent_node->GetParent(); | ||||
| 
 | ||||
|     auto it = find(m_objects.begin(), m_objects.end(), parent_node); | ||||
|     if (it != m_objects.end()) | ||||
|  | @ -1214,10 +1410,7 @@ wxDataViewItem ObjectDataViewModel::GetTopParent(const wxDataViewItem &item) con | |||
| 
 | ||||
|     ObjectDataViewModelNode *parent_node = node->GetParent(); | ||||
|     while (parent_node->m_type != itObject) | ||||
|     { | ||||
|         node = parent_node; | ||||
|         parent_node = node->GetParent(); | ||||
|     } | ||||
|         parent_node = parent_node->GetParent(); | ||||
| 
 | ||||
|     return wxDataViewItem((void*)parent_node); | ||||
| } | ||||
|  | @ -1318,6 +1511,11 @@ wxDataViewItem ObjectDataViewModel::GetInstanceRootItem(const wxDataViewItem &it | |||
|     return GetItemByType(item, itInstanceRoot); | ||||
| } | ||||
| 
 | ||||
| wxDataViewItem ObjectDataViewModel::GetLayerRootItem(const wxDataViewItem &item) const | ||||
| { | ||||
|     return GetItemByType(item, itLayerRoot); | ||||
| } | ||||
| 
 | ||||
| bool ObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const | ||||
| { | ||||
|     if (!item.IsOk()) | ||||
|  | @ -2027,6 +2225,9 @@ void DoubleSlider::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord | |||
| 
 | ||||
| void DoubleSlider::draw_ticks(wxDC& dc) | ||||
| { | ||||
|     if (!m_is_enabled_tick_manipulation) | ||||
|         return; | ||||
| 
 | ||||
|     dc.SetPen(m_is_enabled_tick_manipulation ? DARK_GREY_PEN : LIGHT_GREY_PEN ); | ||||
|     int height, width; | ||||
|     get_size(&width, &height); | ||||
|  | @ -2044,6 +2245,9 @@ void DoubleSlider::draw_ticks(wxDC& dc) | |||
| 
 | ||||
| void DoubleSlider::draw_colored_band(wxDC& dc) | ||||
| { | ||||
|     if (!m_is_enabled_tick_manipulation) | ||||
|         return; | ||||
| 
 | ||||
|     int height, width; | ||||
|     get_size(&width, &height); | ||||
| 
 | ||||
|  | @ -2113,7 +2317,7 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc) | |||
| 
 | ||||
| void DoubleSlider::draw_revert_icon(wxDC& dc) | ||||
| { | ||||
|     if (m_ticks.empty()) | ||||
|     if (m_ticks.empty() || !m_is_enabled_tick_manipulation) | ||||
|         return; | ||||
| 
 | ||||
|     int width, height; | ||||
|  | @ -2218,7 +2422,7 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event) | |||
|         m_selection == ssLower ? correct_lower_value() : correct_higher_value(); | ||||
|         if (!m_selection) m_selection = ssHigher; | ||||
|     } | ||||
|     else if (is_point_in_rect(pos, m_rect_revert_icon)) { | ||||
|     else if (is_point_in_rect(pos, m_rect_revert_icon) && m_is_enabled_tick_manipulation) { | ||||
|         // discard all color changes
 | ||||
|         SetLowerValue(m_min_value); | ||||
|         SetHigherValue(m_max_value); | ||||
|  | @ -2647,7 +2851,7 @@ ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 10*/) : | |||
|         m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first));; | ||||
| #endif // __WXOSX__
 | ||||
|          | ||||
|         m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, m_mode_btns.size() - 1)); | ||||
|         m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, int(m_mode_btns.size() - 1))); | ||||
|         Add(m_mode_btns.back()); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -20,6 +20,9 @@ namespace Slic3r { | |||
| 	enum class ModelVolumeType : int; | ||||
| }; | ||||
| 
 | ||||
| typedef double                          coordf_t; | ||||
| typedef std::pair<coordf_t, coordf_t>   t_layer_height_range; | ||||
| 
 | ||||
| #ifdef __WXMSW__ | ||||
| void                msw_rescale_menu(wxMenu* menu); | ||||
| #else /* __WXMSW__ */ | ||||
|  | @ -159,12 +162,14 @@ DECLARE_VARIANT_OBJECT(DataViewBitmapText) | |||
| // ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| enum ItemType { | ||||
|     itUndef = 0, | ||||
|     itObject = 1, | ||||
|     itVolume = 2, | ||||
|     itInstanceRoot = 4, | ||||
|     itInstance = 8, | ||||
|     itSettings = 16 | ||||
|     itUndef         = 0, | ||||
|     itObject        = 1, | ||||
|     itVolume        = 2, | ||||
|     itInstanceRoot  = 4, | ||||
|     itInstance      = 8, | ||||
|     itSettings      = 16, | ||||
|     itLayerRoot     = 32, | ||||
|     itLayer         = 64, | ||||
| }; | ||||
| 
 | ||||
| class ObjectDataViewModelNode; | ||||
|  | @ -177,6 +182,7 @@ class ObjectDataViewModelNode | |||
|     wxBitmap                        m_empty_bmp; | ||||
|     size_t                          m_volumes_cnt = 0; | ||||
|     std::vector< std::string >      m_opt_categories; | ||||
|     t_layer_height_range            m_layer_range = { 0.0f, 0.0f }; | ||||
| 
 | ||||
|     wxString				        m_name; | ||||
|     wxBitmap&                       m_bmp = m_empty_bmp; | ||||
|  | @ -229,6 +235,11 @@ public: | |||
|         set_action_icon(); | ||||
|     } | ||||
| 
 | ||||
| 	ObjectDataViewModelNode(ObjectDataViewModelNode* parent, | ||||
| 							const t_layer_height_range& layer_range, | ||||
|                             const int idx = -1, | ||||
|                             const wxString& extruder = wxEmptyString ); | ||||
| 
 | ||||
|     ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); | ||||
| 
 | ||||
| 	~ObjectDataViewModelNode() | ||||
|  | @ -318,6 +329,7 @@ public: | |||
|     ItemType        GetType() const                 { return m_type; } | ||||
| 	void			SetIdx(const int& idx); | ||||
| 	int             GetIdx() const                  { return m_idx; } | ||||
| 	t_layer_height_range    GetLayerRange() const   { return m_layer_range; } | ||||
| 
 | ||||
| 	// use this function only for childrens
 | ||||
| 	void AssignAllVal(ObjectDataViewModelNode& from_node) | ||||
|  | @ -348,7 +360,7 @@ public: | |||
| 	} | ||||
| 
 | ||||
| 	// Set action icons for node
 | ||||
|     void set_action_icon(); | ||||
|     void        set_action_icon(); | ||||
| 
 | ||||
|     void        update_settings_digest_bitmaps(); | ||||
| 	bool        update_settings_digest(const std::vector<std::string>& categories); | ||||
|  | @ -388,6 +400,11 @@ public: | |||
|                                     const bool create_frst_child = true); | ||||
|     wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); | ||||
|     wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); | ||||
|     wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); | ||||
|     wxDataViewItem AddLayersChild(  const wxDataViewItem &parent_item,  | ||||
|                                     const t_layer_height_range& layer_range, | ||||
|                                     const int extruder = 0,  | ||||
|                                     const int index = -1); | ||||
| 	wxDataViewItem Delete(const wxDataViewItem &item); | ||||
| 	wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); | ||||
| 	void DeleteAll(); | ||||
|  | @ -395,13 +412,18 @@ public: | |||
|     void DeleteVolumeChildren(wxDataViewItem& parent); | ||||
|     void DeleteSettings(const wxDataViewItem& parent); | ||||
| 	wxDataViewItem GetItemById(int obj_idx); | ||||
|     wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type); | ||||
| 	wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); | ||||
| 	wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); | ||||
|     wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx); | ||||
|     wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); | ||||
|     int  GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); | ||||
| 	int  GetIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; | ||||
|     int  GetObjectIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetVolumeIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetInstanceIdByItem(const wxDataViewItem& item) const; | ||||
|     int  GetLayerIdByItem(const wxDataViewItem& item) const; | ||||
|     void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); | ||||
|     int  GetRowByItem(const wxDataViewItem& item) const; | ||||
|     bool IsEmpty() { return m_objects.empty(); } | ||||
|  | @ -450,6 +472,7 @@ public: | |||
|                                     ItemType type) const; | ||||
|     wxDataViewItem  GetSettingsItem(const wxDataViewItem &item) const; | ||||
|     wxDataViewItem  GetInstanceRootItem(const wxDataViewItem &item) const; | ||||
|     wxDataViewItem  GetLayerRootItem(const wxDataViewItem &item) const; | ||||
|     bool    IsSettingsItem(const wxDataViewItem &item) const; | ||||
|     void    UpdateSettingsDigest(   const wxDataViewItem &item,  | ||||
|                                     const std::vector<std::string>& categories); | ||||
|  | @ -465,6 +488,7 @@ public: | |||
|     wxBitmap    GetVolumeIcon(const Slic3r::ModelVolumeType vol_type,  | ||||
|                               const bool is_marked = false); | ||||
|     void        DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); | ||||
|     t_layer_height_range    GetLayerRangeByItem(const wxDataViewItem& item) const; | ||||
| }; | ||||
| 
 | ||||
| // ----------------------------------------------------------------------------
 | ||||
|  |  | |||
|  | @ -402,15 +402,8 @@ Updates PresetUpdater::priv::get_config_updates() const | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		copy_file_fix(idx.path(), bundle_path_idx); | ||||
| 
 | ||||
| 		const auto ver_current = idx.find(vp.config_version); | ||||
| 		const bool ver_current_found = ver_current != idx.end(); | ||||
| 		if (! ver_current_found) { | ||||
| 			auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); | ||||
| 			BOOST_LOG_TRIVIAL(error) << message; | ||||
| 			GUI::show_error(nullptr, GUI::from_u8(message)); | ||||
| 		} | ||||
| 
 | ||||
| 		BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%%3%, version cached: %4%") | ||||
| 			% vp.name | ||||
|  | @ -418,6 +411,13 @@ Updates PresetUpdater::priv::get_config_updates() const | |||
| 			% (ver_current_found ? "" : " (not found in index!)") | ||||
| 			% recommended->config_version.to_string(); | ||||
| 
 | ||||
| 		if (! ver_current_found) { | ||||
| 			auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); | ||||
| 			BOOST_LOG_TRIVIAL(error) << message; | ||||
| 			GUI::show_error(nullptr, GUI::from_u8(message)); | ||||
| 			continue; | ||||
| 		} | ||||
| 
 | ||||
| 		if (ver_current_found && !ver_current->is_current_slic3r_supported()) { | ||||
| 			BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); | ||||
| 			updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name); | ||||
|  | @ -459,10 +459,16 @@ Updates PresetUpdater::priv::get_config_updates() const | |||
| 					found = true; | ||||
| 				} | ||||
| 			} | ||||
| 			if (! found) | ||||
| 
 | ||||
| 			if (found) { | ||||
| 				// 'Install' the index in the vendor directory. This is used to memoize
 | ||||
| 				// offered updates and to not offer the same update again if it was cancelled by the user.
 | ||||
| 				copy_file_fix(idx.path(), bundle_path_idx); | ||||
| 			} else { | ||||
| 				BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources") | ||||
| 					% idx.vendor() | ||||
| 					% recommended->config_version.to_string(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 tamasmeszaros
						tamasmeszaros