diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index f6116eec7..b2bc915ab 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -8,16 +8,22 @@ varying vec2 intensity; varying vec3 delta_box_min; varying vec3 delta_box_max; -varying float world_z; +varying vec3 world_pos; uniform vec4 uniform_color; // x = min z, y = max z; uniform vec2 z_range; +// clipping plane (general orientation): +uniform vec4 clipping_plane; + void main() { - if ((world_z < z_range.x) || (z_range.y < world_z)) + if ((world_pos.z < z_range.x) || (z_range.y < world_pos.z)) + discard; + + if (world_pos.x*clipping_plane.x + world_pos.y*clipping_plane.y + world_pos.z*clipping_plane.z + clipping_plane.w < 0.0 ) discard; // if the fragment is outside the print volume -> use darker color diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index 84ae51391..a226cf312 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -34,7 +34,7 @@ varying vec2 intensity; varying vec3 delta_box_min; varying vec3 delta_box_max; -varying float world_z; +varying vec3 world_pos; void main() { @@ -69,5 +69,5 @@ void main() } gl_Position = ftransform(); - world_z = vec3(print_box.volume_world_matrix * gl_Vertex).z; + world_pos = vec3(print_box.volume_world_matrix * gl_Vertex); } diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 2c436b426..d682b2434 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -43,11 +43,21 @@ typedef Eigen::Matrix stl_normal; static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect"); static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect"); -typedef struct { +struct stl_facet { stl_normal normal; stl_vertex vertex[3]; char extra[2]; -} stl_facet; + + stl_facet rotated(const Eigen::Quaternion &rot) { + stl_facet out; + out.normal = rot * this->normal; + out.vertex[0] = rot * this->vertex[0]; + out.vertex[1] = rot * this->vertex[1]; + out.vertex[2] = rot * this->vertex[2]; + return out; + } +}; + #define SIZEOF_STL_FACET 50 static_assert(offsetof(stl_facet, normal) == 0, "stl_facet.normal has correct offset"); diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index e2939b8af..911f4f5e8 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -41,10 +41,12 @@ stl_open(stl_file *stl, const char *file) { stl_count_facets(stl, file); stl_allocate(stl); stl_read(stl, 0, true); - if (!stl->error) fclose(stl->fp); + if (stl->fp != nullptr) { + fclose(stl->fp); + stl->fp = nullptr; + } } - void stl_initialize(stl_file *stl) { memset(stl, 0, sizeof(stl_file)); @@ -118,7 +120,7 @@ stl_count_facets(stl_file *stl, const char *file) { } /* Read the int following the header. This should contain # of facets */ - bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp); + bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp) != 0; #ifndef BOOST_LITTLE_ENDIAN // Convert from little endian to big endian. stl_internal_reverse_quads((char*)&header_num_facets, 4); @@ -257,7 +259,6 @@ stl_reallocate(stl_file *stl) { time running this for the stl and therefore we should reset our max and min stats. */ void stl_read(stl_file *stl, int first_facet, bool first) { stl_facet facet; - int i; if (stl->error) return; @@ -268,7 +269,7 @@ void stl_read(stl_file *stl, int first_facet, bool first) { } char normal_buf[3][32]; - for(i = first_facet; i < stl->stats.number_of_facets; i++) { + for(uint32_t i = first_facet; i < stl->stats.number_of_facets; i++) { if(stl->stats.type == binary) /* Read a single facet from a binary .STL file */ { @@ -366,17 +367,19 @@ void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first) } } -void -stl_close(stl_file *stl) { - if (stl->error) return; +void stl_close(stl_file *stl) +{ + assert(stl->fp == nullptr); + assert(stl->heads == nullptr); + assert(stl->tail == nullptr); - if(stl->neighbors_start != NULL) - free(stl->neighbors_start); - if(stl->facet_start != NULL) - free(stl->facet_start); - if(stl->v_indices != NULL) - free(stl->v_indices); - if(stl->v_shared != NULL) - free(stl->v_shared); + if (stl->facet_start != NULL) + free(stl->facet_start); + if (stl->neighbors_start != NULL) + free(stl->neighbors_start); + if (stl->v_indices != NULL) + free(stl->v_indices); + if (stl->v_shared != NULL) + free(stl->v_shared); + memset(stl, 0, sizeof(stl_file)); } - diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index e634dd138..ac4cd73ef 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -897,13 +897,13 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); #endif // !ENABLE_GENERIC_SUBPARTS_PLACEMENT - TriangleMesh vol_mesh(v->mesh); #if ENABLE_GENERIC_SUBPARTS_PLACEMENT - vol_mesh.transform(inst_matrix * v->get_matrix()); - bb.merge(vol_mesh.bounding_box()); + bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); #else - vol_mesh.transform(v->get_matrix()); - bb.merge(this->instances.front()->transform_mesh_bounding_box(vol_mesh, true)); + // unmaintaned + assert(false); + // vol_mesh.transform(v->get_matrix()); + // bb.merge(this->instances.front()->transform_mesh_bounding_box(vol_mesh, true)); #endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT } return bb; @@ -920,13 +920,13 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_ { if (v->is_model_part()) { - TriangleMesh mesh(v->mesh); #if ENABLE_GENERIC_SUBPARTS_PLACEMENT - mesh.transform(inst_matrix * v->get_matrix()); - bb.merge(mesh.bounding_box()); + bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); #else - mesh.transform(v->get_matrix()); - bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(mesh, dont_translate)); + // not maintained + assert(false); + //mesh.transform(v->get_matrix()); + //bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(mesh, dont_translate)); #endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT } } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index a978a3175..66641a7f5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2528,14 +2528,17 @@ void PrintConfigDef::init_sla_params() def = this->add("pad_wall_height", coFloat); def->label = L("Pad wall height"); - def->tooltip = L("Defines the cavity depth. Set to zero to disable the cavity."); + def->tooltip = L("Defines the pad cavity depth. Set to zero to disable the cavity. " + "Be careful when enabling this feature, as some resins may " + "produce an extreme suction effect inside the cavity, " + "which makes pealing the print off the vat foil difficult."); def->category = L("Pad"); // def->tooltip = L(""); def->sidetext = L("mm"); def->min = 0; def->max = 30; - def->mode = comSimple; - def->default_value = new ConfigOptionFloat(5.0); + def->mode = comExpert; + def->default_value = new ConfigOptionFloat(0.); def = this->add("pad_max_merge_distance", coFloat); def->label = L("Max merge distance"); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index bfba364af..2d603661d 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -693,6 +693,16 @@ void TriangleMeshSlicer::init(TriangleMesh *_mesh, throw_on_cancel_callback_type } } + + +void TriangleMeshSlicer::set_up_direction(const Vec3f& up) +{ + m_quaternion.setFromTwoVectors(up, Vec3f::UnitZ()); + m_use_quaternion = true; +} + + + void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const { BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; @@ -795,7 +805,7 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const { - const stl_facet &facet = this->mesh->stl.facet_start[facet_idx]; + const stl_facet &facet = m_use_quaternion ? this->mesh->stl.facet_start[facet_idx].rotated(m_quaternion) : this->mesh->stl.facet_start[facet_idx]; // find facet extents const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); @@ -860,26 +870,43 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( IntersectionPoint points[3]; size_t num_points = 0; size_t point_on_layer = size_t(-1); - + // Reorder vertices so that the first one is the one with lowest Z. // This is needed to get all intersection lines in a consistent order // (external on the right of the line) const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0); + + // These are used only if the cut plane is tilted: + stl_vertex rotated_a; + stl_vertex rotated_b; + for (int j = i; j - i < 3; ++j) { // loop through facet edges int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; - const stl_vertex *a = &this->v_scaled_shared[a_id]; - const stl_vertex *b = &this->v_scaled_shared[b_id]; + + const stl_vertex *a; + const stl_vertex *b; + if (m_use_quaternion) { + rotated_a = m_quaternion * this->v_scaled_shared[a_id]; + rotated_b = m_quaternion * this->v_scaled_shared[b_id]; + a = &rotated_a; + b = &rotated_b; + } + else { + a = &this->v_scaled_shared[a_id]; + b = &this->v_scaled_shared[b_id]; + } // Is edge or face aligned with the cutting plane? if (a->z() == slice_z && b->z() == slice_z) { // Edge is horizontal and belongs to the current layer. - const stl_vertex &v0 = this->v_scaled_shared[vertices[0]]; - const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; - const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; - const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal; + // The following rotation of the three vertices may not be efficient, but this branch happens rarely. + const stl_vertex &v0 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[0]]) : this->v_scaled_shared[vertices[0]]; + const stl_vertex &v1 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[1]]) : this->v_scaled_shared[vertices[1]]; + const stl_vertex &v2 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[2]]) : this->v_scaled_shared[vertices[2]]; + const stl_normal &normal = facet.normal; // We may ignore this edge for slicing purposes, but we may still use it for object cutting. FacetSliceType result = Slicing; if (min_z == max_z) { @@ -995,7 +1022,9 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( if (i == line_out->a_id || i == line_out->b_id) i = vertices[2]; assert(i != line_out->a_id && i != line_out->b_id); - line_out->edge_type = (this->v_scaled_shared[i].z() < slice_z) ? feTop : feBottom; + line_out->edge_type = ((m_use_quaternion ? + (m_quaternion * this->v_scaled_shared[i]).z() + : this->v_scaled_shared[i].z()) < slice_z) ? feTop : feBottom; } #endif return Slicing; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index d389500c6..4bf5ce039 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -25,9 +25,10 @@ public: TriangleMesh(const Pointf3s &points, const std::vector &facets); TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; } TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); } - ~TriangleMesh() { stl_close(&this->stl); } + ~TriangleMesh() { clear(); } TriangleMesh& operator=(const TriangleMesh &other); TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; } + void clear() { stl_close(&this->stl); this->repaired = false; } void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); } void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); } void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); } @@ -171,6 +172,7 @@ public: FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, const float min_z, const float max_z, IntersectionLine *line_out) const; void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; + void set_up_direction(const Vec3f& up); private: const TriangleMesh *mesh; @@ -178,6 +180,10 @@ private: std::vector facets_edges; // Scaled copy of this->mesh->stl.v_shared std::vector v_scaled_shared; + // Quaternion that will be used to rotate every facet before the slicing + Eigen::Quaternion m_quaternion; + // Whether or not the above quaterion should be used + bool m_use_quaternion = false; void _slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const; void make_loops(std::vector &lines, Polygons* loops) const; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 1dd136517..9038e388c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -774,6 +774,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; GLint z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "z_range") : -1; + GLint clipping_plane_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "clipping_plane") : -1; GLint print_box_min_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.min") : -1; GLint print_box_max_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.max") : -1; GLint print_box_detection_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_detection") : -1; @@ -789,6 +790,9 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool if (z_range_id != -1) glsafe(::glUniform2fv(z_range_id, 1, (const GLfloat*)z_range)); + if (clipping_plane_id != -1) + glsafe(::glUniform4fv(clipping_plane_id, 1, (const GLfloat*)clipping_plane)); + GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index f8f29d270..88547359e 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -433,6 +433,9 @@ private: // z range for clipping in shaders float z_range[2]; + // plane coeffs for clipping in shaders + float clipping_plane[4]; + public: GLVolumePtrs volumes; @@ -491,6 +494,7 @@ public: } void set_z_range(float min_z, float max_z) { z_range[0] = min_z; z_range[1] = max_z; } + void set_clipping_plane(const double* coeffs) { clipping_plane[0] = coeffs[0]; clipping_plane[1] = coeffs[1]; clipping_plane[2] = coeffs[2]; clipping_plane[3] = coeffs[3]; } // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f911dd171..44181ffcb 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3521,7 +3521,15 @@ void GLCanvas3D::_picking_pass() const glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); + if (! m_use_VBOs) { + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); + ::glEnable(GL_CLIP_PLANE0); + } _render_volumes_for_picking(); + if (! m_use_VBOs) + ::glDisable(GL_CLIP_PLANE0); + m_gizmos.render_current_gizmo_for_picking_pass(m_selection); if (m_multisample_allowed) @@ -3602,6 +3610,8 @@ void GLCanvas3D::_render_axes() const m_bed.render_axes(); } + + void GLCanvas3D::_render_objects() const { if (m_volumes.empty()) @@ -3610,6 +3620,8 @@ void GLCanvas3D::_render_objects() const glsafe(::glEnable(GL_LIGHTING)); glsafe(::glEnable(GL_DEPTH_TEST)); + m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); + if (m_use_VBOs) { if (m_picking_enabled) @@ -3630,6 +3642,8 @@ void GLCanvas3D::_render_objects() const 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_layers_editing.is_enabled() && m_layers_editing.last_object_id != -1) { int object_id = m_layers_editing.last_object_id; @@ -3650,13 +3664,17 @@ void GLCanvas3D::_render_objects() const } else { + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); + ::glEnable(GL_CLIP_PLANE0); + if (m_use_clipping_planes) { - glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_clipping_planes[0].get_data())); - glsafe(::glEnable(GL_CLIP_PLANE0)); - glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[1].get_data())); + 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)); } + // 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) { @@ -3664,13 +3682,16 @@ void GLCanvas3D::_render_objects() const }); 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_PLANE0)); glsafe(::glDisable(GL_CLIP_PLANE1)); + glsafe(::glDisable(GL_CLIP_PLANE2)); } } - + + m_camera_clipping_plane = ClippingPlane::ClipsNothing(); glsafe(::glDisable(GL_LIGHTING)); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 228e3ca89..2486753e7 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -65,6 +65,35 @@ public: void set_scale_factor(int height); }; + +class ClippingPlane +{ + double m_data[4]; + +public: + ClippingPlane() + { + m_data[0] = 0.0; + m_data[1] = 0.0; + m_data[2] = 1.0; + m_data[3] = 0.0; + } + + ClippingPlane(const Vec3d& direction, double offset) + { + Vec3d norm_dir = direction.normalized(); + m_data[0] = norm_dir(0); + m_data[1] = norm_dir(1); + m_data[2] = norm_dir(2); + m_data[3] = offset; + } + + static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); } + + const double* get_data() const { return m_data; } +}; + + wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); using Vec2dEvent = Event; @@ -288,32 +317,6 @@ class GLCanvas3D } }; -public: - class ClippingPlane - { - double m_data[4]; - - public: - ClippingPlane() - { - m_data[0] = 0.0; - m_data[1] = 0.0; - m_data[2] = 1.0; - m_data[3] = 0.0; - } - - ClippingPlane(const Vec3d& direction, double offset) - { - Vec3d norm_dir = direction.normalized(); - m_data[0] = norm_dir(0); - m_data[1] = norm_dir(1); - m_data[2] = norm_dir(2); - m_data[3] = offset; - } - - const double* get_data() const { return m_data; } - }; - private: struct SlaCap { @@ -405,6 +408,7 @@ private: mutable GLGizmosManager m_gizmos; mutable GLToolbar m_toolbar; ClippingPlane m_clipping_planes[2]; + mutable ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; mutable SlaCap m_sla_caps[2]; std::string m_sidebar_field; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 438e9d236..206253451 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -818,8 +818,8 @@ void Preview::on_sliders_scroll_changed(wxEvent& event) } else if (tech == ptSLA) { - m_canvas->set_clipping_plane(0, GLCanvas3D::ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD())); - m_canvas->set_clipping_plane(1, GLCanvas3D::ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD())); + m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD())); + m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD())); m_canvas->set_use_clipping_planes(m_slider->GetHigherValue() != 0); m_canvas_widget->Refresh(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 74c6faa85..129db2715 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -10,6 +10,7 @@ #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/Tesselate.hpp" namespace Slic3r { @@ -55,6 +56,11 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S if (model_object && selection.is_from_single_instance()) { + // Cache the bb - it's needed for dealing with the clipping plane quite often + // It could be done inside update_mesh but one has to account for scaling of the instance. + //FIXME calling ModelObject::instance_bounding_box() is expensive! + m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius(); + if (is_mesh_update_necessary()) { update_mesh(); editing_mode_reload_cache(); @@ -83,12 +89,75 @@ void GLGizmoSlaSupports::on_render(const Selection& selection) const glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, false); + // we'll recover current look direction from the modelview matrix (in world coords): + Eigen::Matrix modelview_matrix; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); + Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]); + m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); + + if (m_quadric != nullptr && selection.is_from_single_instance()) + render_points(selection, direction_to_camera, false); + render_selection_rectangle(); + render_clipping_plane(selection, direction_to_camera); glsafe(::glDisable(GL_BLEND)); } + + +void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const Vec3d& direction_to_camera) const +{ + if (m_clipping_plane_distance == 0.f) + return; + + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + Transform3f instance_matrix = vol->get_instance_transformation().get_matrix().cast(); + Transform3f instance_matrix_no_translation_no_scaling = vol->get_instance_transformation().get_matrix(true,false,true).cast(); + Vec3f scaling = vol->get_instance_scaling_factor().cast(); + + Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera.cast(); + Vec3f up = Vec3f(up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2)); + float height_mesh = (m_active_instance_bb_radius - m_clipping_plane_distance * 2*m_active_instance_bb_radius) * (up_noscale.norm()/up.norm()); + + if (m_clipping_plane_distance != m_old_clipping_plane_distance + || m_old_direction_to_camera != direction_to_camera) { + + std::vector list_of_expolys; + if (! m_tms) { + m_tms.reset(new TriangleMeshSlicer); + m_tms->init(const_cast(&m_mesh), [](){}); + } + + m_tms->set_up_direction(up); + m_tms->slice(std::vector{height_mesh}, 0.f, &list_of_expolys, [](){}); + m_triangles = triangulate_expolygons_2f(list_of_expolys[0]); + + m_old_direction_to_camera = direction_to_camera; + m_old_clipping_plane_distance = m_clipping_plane_distance; + } + + if (! m_triangles.empty()) { + ::glPushMatrix(); + ::glTranslated(0.0, 0.0, m_z_shift); + ::glMultMatrixf(instance_matrix.data()); + Eigen::Quaternionf q; + q.setFromTwoVectors(Vec3f::UnitZ(), up); + Eigen::AngleAxisf aa(q); + ::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)); + ::glTranslatef(0.f, 0.f, -0.001f); // to make sure the cut is safely beyond the near clipping plane + ::glColor3f(1.0f, 0.37f, 0.0f); + ::glBegin(GL_TRIANGLES); + ::glColor3f(1.0f, 0.37f, 0.0f); + for (const Vec2f& point : m_triangles) + ::glVertex3f(point(0), point(1), height_mesh); + ::glEnd(); + ::glPopMatrix(); + } +} + + + void GLGizmoSlaSupports::render_selection_rectangle() const { if (m_selection_rectangle_status == srOff) @@ -138,24 +207,25 @@ void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const { glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); + // we'll recover current look direction from the modelview matrix (in world coords): + Eigen::Matrix modelview_matrix; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); + Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]); + + render_points(selection, direction_to_camera, true); } -void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const +void GLGizmoSlaSupports::render_points(const Selection& selection, const Vec3d& direction_to_camera, bool picking) const { - if (m_quadric == nullptr || !selection.is_from_single_instance()) - return; - if (!picking) glsafe(::glEnable(GL_LIGHTING)); const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_shift = vol->get_sla_shift_z(); const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, z_shift)); + glsafe(::glTranslated(0.0, 0.0, m_z_shift)); glsafe(::glMultMatrixd(instance_matrix.data())); float render_color[3]; @@ -164,6 +234,9 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point; const bool& point_selected = m_editing_mode_cache[i].selected; + if (is_point_clipped(support_point.pos.cast(), direction_to_camera)) + continue; + // First decide about the color of the point. if (picking) { std::array color = picking_color_component(i); @@ -233,6 +306,21 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) glsafe(::glPopMatrix()); } + + +bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera) const +{ + if (m_clipping_plane_distance == 0.f) + return false; + + Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point; + transformed_point(2) += m_z_shift; + return direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)) + m_active_instance_bb_radius + - m_clipping_plane_distance * 2*m_active_instance_bb_radius < direction_to_camera.dot(transformed_point); +} + + + bool GLGizmoSlaSupports::is_mesh_update_necessary() const { return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) @@ -244,10 +332,9 @@ void GLGizmoSlaSupports::update_mesh() wxBusyCursor wait; Eigen::MatrixXf& V = m_V; Eigen::MatrixXi& F = m_F; - // Composite mesh of all instances in the world coordinate system. // This mesh does not account for the possible Z up SLA offset. - TriangleMesh mesh = m_model_object->raw_mesh(); - const stl_file& stl = mesh.stl; + m_mesh = m_model_object->raw_mesh(); + const stl_file& stl = m_mesh.stl; V.resize(3 * stl.stats.number_of_facets, 3); F.resize(stl.stats.number_of_facets, 3); for (unsigned int i=0; i GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) { // if the gizmo doesn't have the V, F structures for igl, calculate them first: @@ -282,31 +371,53 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse ::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2)); ::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2)); - igl::Hit hit; + std::vector hits; const Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_offset = volume->get_sla_shift_z(); - point1(2) -= z_offset; - point2(2) -= z_offset; + // we'll recover current look direction from the modelview matrix (in world coords): + Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]); + + point1(2) -= m_z_shift; + point2(2) -= m_z_shift; Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); point1 = inv * point1; point2 = inv * point2; - if (!m_AABB.intersect_ray(m_V, m_F, point1.cast(), (point2-point1).cast(), hit)) + if (!m_AABB.intersect_ray(m_V, m_F, point1.cast(), (point2-point1).cast(), hits)) throw std::invalid_argument("unproject_on_mesh(): No intersection found."); - int fid = hit.id; // facet id - Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit - Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); - Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); + std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); + + // Now let's iterate through the points and find the first that is not clipped: + unsigned int i=0; + Vec3f bc; + Vec3f a; + Vec3f b; + Vec3f result; + for (i=0; i(), direction_to_camera)) + break; + } + + if (i==hits.size() || (hits.size()-i) % 2 != 0) { + // All hits are either clipped, or there is an odd number of unclipped + // hits - meaning the nearest must be from inside the mesh. + throw std::invalid_argument("unproject_on_mesh(): No intersection found."); + } // Calculate and return both the point and the facet normal. return std::make_pair( - bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)), + result, a.cross(b) ); } @@ -377,36 +488,64 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_offset = volume->get_sla_shift_z(); // bounding box created from the rectangle corners - will take care of order of the corners BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast()), Point(m_selection_rectangle_end_corner.cast())}); - const Transform3d& instance_matrix_no_translation = volume->get_instance_transformation().get_matrix(true); + const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true); + // we'll recover current look direction from the modelview matrix (in world coords)... Vec3f direction_to_camera = camera.get_dir_forward().cast(); // ...and transform it to model coords. - direction_to_camera = (instance_matrix_no_translation.inverse().cast() * direction_to_camera).normalized().eval(); + Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast() * direction_to_camera).normalized().eval(); + Vec3f scaling = volume->get_instance_scaling_factor().cast(); + direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh: for (unsigned int i=0; i() * support_point.pos; - pos(2) += z_offset; + pos(2) += m_z_shift; GLdouble out_x, out_y, out_z; ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), (GLdouble*)modelview_matrix.data(), (GLdouble*)projection_matrix.data(), (GLint*)viewport.data(), &out_x, &out_y, &out_z); out_y = m_canvas_height - out_y; - if (rectangle.contains(Point(out_x, out_y))) { + if (rectangle.contains(Point(out_x, out_y)) && !is_point_clipped(support_point.pos.cast(), direction_to_camera.cast())) { bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: std::vector hits; // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. - if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera * (support_point.head_front_radius + EPSILON), direction_to_camera, hits)) + if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) { + std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; }); + + if (m_clipping_plane_distance != 0.f) { + // If the closest hit facet normal points in the same direction as the ray, + // we are looking through the mesh and should therefore discard the point: + int fid = hits.front().id; // facet id + Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); + Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); + if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f) + is_obscured = true; + + // Eradicate all hits that are on clipped surfaces: + for (unsigned int j=0; j(), direction_to_camera.cast())) { + hits.erase(hits.begin()+j); + --j; + } + } + } + // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. // Also, the threshold is in mesh coordinates, not in actual dimensions. - if (hits.size() > 1 || hits.front().t > 0.001f) + if (!hits.empty()) is_obscured = true; + } if (!is_obscured) { if (m_selection_rectangle_status == srDeselect) @@ -564,6 +703,64 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const + +ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const +{ + if (!m_model_object) + return ClippingPlane::ClipsNothing(); + + Eigen::Matrix modelview_matrix; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); + + // we'll recover current look direction from the modelview matrix (in world coords): + Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]); + float dist = direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)); + + return ClippingPlane(-direction_to_camera.normalized(),(dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius)); +} + + +/* +void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& idxs) const +{ + if (aabb->is_leaf()) { // this is a facet + // corner.dot(normal) - offset + idxs.push_back(aabb->m_primitive); + } + else { // not a leaf + using CornerType = Eigen::AlignedBox::CornerType; + bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0)))); + for (unsigned int i=1; i<8; ++i) + if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) { + find_intersecting_facets(aabb->m_left, normal, offset, idxs); + find_intersecting_facets(aabb->m_right, normal, offset, idxs); + } + } +} + + + +void GLGizmoSlaSupports::make_line_segments() const +{ + TriangleMeshSlicer tms(&m_model_object->volumes.front()->mesh); + Vec3f normal(0.f, 1.f, 1.f); + double d = 0.; + + std::vector lines; + find_intersections(&m_AABB, normal, d, lines); + ExPolygons expolys; + tms.make_expolygons_simple(lines, &expolys); + + SVG svg("slice_loops.svg", get_extents(expolys)); + svg.draw(expolys); + //for (const IntersectionLine &l : lines[i]) + // svg.draw(l, "red", 0); + //svg.draw_outline(expolygons, "black", "blue", 0); + svg.Close(); +} +*/ + + void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) { if (!m_model_object) @@ -681,6 +878,13 @@ RENDER_AGAIN: (m_model_object->sla_points_status == sla::PointsStatus::Generating ? "Generation in progress..." : "UNKNOWN STATUS")))); } + + // Following is rendered in both editing and non-editing mode: + m_imgui->text("Clipping of view: "); + ImGui::SameLine(); + ImGui::PushItemWidth(150.0f); + bool value_changed = ImGui::SliderFloat(" ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f"); + m_imgui->end(); if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode @@ -752,8 +956,8 @@ void GLGizmoSlaSupports::on_set_state() m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - wxGetApp().CallAfter([this]() { - // Following is called through CallAfter, because otherwise there was a problem + wxGetApp().CallAfter([this]() { + // Following is called through CallAfter, because otherwise there was a problem // on OSX with the wxMessageDialog being shown several times when clicked into. if (m_model_object) { if (m_unsaved_changes) { @@ -765,10 +969,16 @@ void GLGizmoSlaSupports::on_set_state() editing_mode_discard_changes(); } } - m_parent.toggle_model_objects_visibility(true); m_editing_mode = false; // so it is not active next time the gizmo opens m_editing_mode_cache.clear(); + m_clipping_plane_distance = 0.f; + // Release copy of the mesh, triangle slicer and the AABB spatial search structure. + m_mesh.clear(); + m_AABB.deinit(); + m_V = Eigen::MatrixXf(); + m_F = Eigen::MatrixXi(); + m_tms.reset(nullptr); }); } m_old_state = m_state; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 0b66ed529..c74559e2f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -17,12 +17,17 @@ namespace Slic3r { namespace GUI { +class ClippingPlane; + + class GLGizmoSlaSupports : public GLGizmoBase { private: ModelObject* m_model_object = nullptr; ModelID m_current_mesh_model_id = 0; int m_active_instance = -1; + float m_active_instance_bb_radius; // to cache the bb + mutable float m_z_shift = 0.f; std::pair unproject_on_mesh(const Vec2d& mouse_pos); const float RenderPointScale = 1.f; @@ -31,6 +36,8 @@ private: Eigen::MatrixXf m_V; // vertices Eigen::MatrixXi m_F; // facets indices igl::AABB m_AABB; + TriangleMesh m_mesh; + mutable std::vector m_triangles; class CacheEntry { public: @@ -52,7 +59,7 @@ public: void set_sla_support_data(ModelObject* model_object, const Selection& selection); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(bool force = false); - std::pair get_sla_clipping_plane() const; + ClippingPlane get_sla_clipping_plane() const; private: bool on_init(); @@ -61,7 +68,8 @@ private: virtual void on_render_for_picking(const Selection& selection) const; void render_selection_rectangle() const; - void render_points(const Selection& selection, bool picking = false) const; + void render_points(const Selection& selection, const Vec3d& direction_to_camera, bool picking = false) const; + void render_clipping_plane(const Selection& selection, const Vec3d& direction_to_camera) const; bool is_mesh_update_necessary() const; void update_mesh(); void update_cache_entry_normal(unsigned int i) const; @@ -74,6 +82,8 @@ private: float m_density = 100.f; mutable std::vector m_editing_mode_cache; // a support point and whether it is currently selected float m_clipping_plane_distance = 0.f; + mutable float m_old_clipping_plane_distance = 0.f; + mutable Vec3d m_old_direction_to_camera; enum SelectionRectangleStatus { srOff = 0, @@ -90,7 +100,11 @@ private: int m_canvas_width; int m_canvas_height; + mutable std::unique_ptr m_tms; + std::vector get_config_options(const std::vector& keys) const; + bool is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera) const; + void find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& out) const; // Methods that do the model_object and editing cache synchronization, // editing mode selection, etc: diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index d00f3440a..cb2d6faed 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -467,6 +467,19 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return false; } +ClippingPlane GLGizmosManager::get_sla_clipping_plane() const +{ + if (!m_enabled || m_current != SlaSupports) + return ClippingPlane::ClipsNothing(); + + GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); + if (it != m_gizmos.end()) + return reinterpret_cast(it->second)->get_sla_clipping_plane(); + + return ClippingPlane::ClipsNothing(); +} + + void GLGizmosManager::render_current_gizmo(const Selection& selection) const { if (!m_enabled) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 16e902522..f7a1a980e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -13,6 +13,7 @@ namespace GUI { class Selection; class GLGizmoBase; class GLCanvas3D; +class ClippingPlane; class Rect { @@ -146,7 +147,7 @@ public: void set_sla_support_data(ModelObject* model_object, const Selection& selection); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false); - + ClippingPlane get_sla_clipping_plane() const; void render_current_gizmo(const Selection& selection) const; void render_current_gizmo_for_picking_pass(const Selection& selection) const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a34562d85..027db69f5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3711,6 +3711,7 @@ void Plater::paste_from_clipboard() { p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); } + bool Plater::can_paste_from_clipboard() const { const Selection& selection = p->view3D->get_canvas3d()->get_selection(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 41313d5ed..315aa487a 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1079,6 +1079,7 @@ void Selection::paste_from_clipboard() case Instance: { paste_objects_from_clipboard(); + break; } } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index c735a40ee..30e3bfe89 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3224,7 +3224,8 @@ void TabSLAMaterial::build() optgroup = page->new_optgroup(_(L("Corrections"))); optgroup->label_width = 19 * m_em_unit;//190; std::vector corrections = {"material_correction"}; - std::vector axes{ "X", "Y", "Z" }; +// std::vector axes{ "X", "Y", "Z" }; + std::vector axes{ "XY", "Z" }; for (auto& opt_key : corrections) { auto line = Line{ m_config->def()->get(opt_key)->full_label, "" }; int id = 0;