From fd1f9d65fb28901a98c7ba4ea388916344a5c896 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 11 Mar 2019 10:35:23 +0100 Subject: [PATCH 01/57] First steps on SLA clipping plane --- src/slic3r/GUI/GLCanvas3D.cpp | 75 +++++++++++----- src/slic3r/GUI/GLCanvas3D.hpp | 4 + src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 90 ++++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 1 + 4 files changed, 150 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index cc17942168..6fdad8795b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1317,6 +1317,22 @@ bool GLCanvas3D::Gizmos::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return false; } + + +std::pair GLCanvas3D::Gizmos::get_sla_clipping_plane() const +{ + if (!m_enabled) + return std::make_pair(0.f, 0.f); + + GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); + if (it != m_gizmos.end()) + return reinterpret_cast(it->second)->get_sla_clipping_plane(); + + return std::make_pair(0.f, 0.f);; +} + + + void GLCanvas3D::Gizmos::render_current_gizmo(const Selection& selection) const { if (!m_enabled) @@ -2566,7 +2582,27 @@ void GLCanvas3D::render() if (early_bed_render) _render_bed(theta); +//////////////////////// + //Eigen::Matrix stashed_projection_matrix; + //::glGetDoublev(GL_PROJECTION_MATRIX, stashed_projection_matrix.data()); + const Size& cnv_size = get_canvas_size(); + if (m_gizmos.get_current_type() == Gizmos::SlaSupports) { + std::pair clipping_limits = m_gizmos.get_sla_clipping_plane(); + set_ortho_projection((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height(), clipping_limits.first, clipping_limits.second); + } + +//////////////////// _render_objects(); +////////////////////////////////// + if (m_gizmos.get_current_type() == Gizmos::SlaSupports) { + //::glMatrixMode(GL_PROJECTION); + //::glLoadIdentity(); + //::glMultMatrixd(stashed_projection_matrix.data()); + //::glMatrixMode(GL_MODELVIEW); + _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + } +////////////////////////////////// + _render_sla_slices(); _render_selection(); @@ -4416,29 +4452,13 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) _set_current(); ::glViewport(0, 0, w, h); - ::glMatrixMode(GL_PROJECTION); - ::glLoadIdentity(); - - const BoundingBoxf3& bbox = _max_bounding_box(); - switch (m_camera.type) { case Camera::Ortho: { - float w2 = w; - float h2 = h; - float two_zoom = 2.0f * get_camera_zoom(); - if (two_zoom != 0.0f) - { - float inv_two_zoom = 1.0f / two_zoom; - w2 *= inv_two_zoom; - h2 *= inv_two_zoom; - } - // FIXME: calculate a tighter value for depth will improve z-fighting - float depth = 5.0f * (float)bbox.max_size(); - ::glOrtho(-w2, w2, -h2, h2, -depth, depth); - + float depth = 5.0f * (float)(_max_bounding_box().max_size()); + set_ortho_projection(w, h, -depth, depth); break; } // case Camera::Perspective: @@ -4470,8 +4490,6 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) } } - ::glMatrixMode(GL_MODELVIEW); - m_dirty = false; } @@ -4685,6 +4703,23 @@ void GLCanvas3D::_render_axes() const m_bed.render_axes(); } + +void GLCanvas3D::set_ortho_projection(float w, float h, float near, float far) const +{ + float two_zoom = 2.0f * get_camera_zoom(); + if (two_zoom != 0.0f) + { + float inv_two_zoom = 1.0f / two_zoom; + w *= inv_two_zoom; + h *= inv_two_zoom; + } + ::glMatrixMode(GL_PROJECTION); + ::glLoadIdentity(); + ::glOrtho(-w, w, -h, h, near, far); + ::glMatrixMode(GL_MODELVIEW); +} + + void GLCanvas3D::_render_objects() const { if (m_volumes.empty()) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 7e414d52a8..acaae8dbd5 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -448,6 +448,7 @@ private: 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); + std::pair get_sla_clipping_plane() const; void render_current_gizmo(const Selection& selection) const; void render_current_gizmo_for_picking_pass(const Selection& selection) const; @@ -774,6 +775,9 @@ private: // Returns the view ray line, in world coordinate, at the given mouse position. Linef3 mouse_ray(const Point& mouse_pos); + // Sets current projection matrix to ortho, accounting for current camera zoom. + void set_ortho_projection(float w, float h, float near, float far) const; + void _start_timer(); void _stop_timer(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 2264c541e8..e2478bc0cb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -56,6 +56,10 @@ 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. + m_active_instance_bb = m_model_object->instance_bounding_box(m_active_instance); + if (is_mesh_update_necessary()) { update_mesh(); editing_mode_reload_cache(); @@ -232,6 +236,21 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) ::glPopMatrix(); } + + +bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera, float z_shift) 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) += z_shift; + return direction_to_camera.dot(m_active_instance_bb.center()) + 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()) @@ -559,6 +578,69 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const + +std::pair GLGizmoSlaSupports::get_sla_clipping_plane() const +{ + if (!m_model_object) + return std::make_pair(0.f, 0.f);; + + Eigen::Matrix modelview_matrix; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); + + // clipping space origin transformed to world coords: + Vec3d clipping_origin = (modelview_matrix.inverse() * Eigen::Matrix{0, 0, 0, 1}).block<3,1>(0,0); + + // 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(clipping_origin) - direction_to_camera.dot(m_active_instance_bb.center()); + + return std::make_pair((dist - m_active_instance_bb.radius()) + m_clipping_plane_distance * 2*m_active_instance_bb.radius(), dist + 5.f*m_active_instance_bb.radius()); +} + + +/* +void GLGizmoSlaSupports::find_intersections(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 + unsigned int facet_idx = aabb->m_primitive; + + Vec3f a = m_V.row(m_F(facet_idx, 0)); + Vec3f b = m_V.row(m_F(facet_idx, 1)); + Vec3f c = m_V.row(m_F(facet_idx, 2)); + } + 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_intersections(aabb->m_left, normal, offset, idxs); + find_intersections(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) @@ -676,6 +758,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 @@ -761,6 +850,7 @@ void GLGizmoSlaSupports::on_set_state() 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; } m_old_state = m_state; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index bb3cf06ce4..a5f77a21b0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -22,6 +22,7 @@ private: ModelObject* m_model_object = nullptr; ModelObject* m_old_model_object = nullptr; int m_active_instance = -1; + BoundingBoxf3 m_active_instance_bb; // to cache the bb std::pair unproject_on_mesh(const Vec2d& mouse_pos); const float RenderPointScale = 1.f; From bc9164e40ca11f8b42e4421b3207f58ac044b1ee Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 20 Mar 2019 08:48:42 +0100 Subject: [PATCH 02/57] SLA gizmo now respects the clipping plane when rendering points and raycasting mouse onto mesh --- src/slic3r/GUI/GLCanvas3D.cpp | 50 ++++++++++++-------- src/slic3r/GUI/GLCanvas3D.hpp | 3 ++ src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 47 +++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 1 + 4 files changed, 74 insertions(+), 27 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6fdad8795b..6f55e4059b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2582,27 +2582,7 @@ void GLCanvas3D::render() if (early_bed_render) _render_bed(theta); -//////////////////////// - //Eigen::Matrix stashed_projection_matrix; - //::glGetDoublev(GL_PROJECTION_MATRIX, stashed_projection_matrix.data()); - const Size& cnv_size = get_canvas_size(); - if (m_gizmos.get_current_type() == Gizmos::SlaSupports) { - std::pair clipping_limits = m_gizmos.get_sla_clipping_plane(); - set_ortho_projection((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height(), clipping_limits.first, clipping_limits.second); - } - -//////////////////// _render_objects(); -////////////////////////////////// - if (m_gizmos.get_current_type() == Gizmos::SlaSupports) { - //::glMatrixMode(GL_PROJECTION); - //::glLoadIdentity(); - //::glMultMatrixd(stashed_projection_matrix.data()); - //::glMatrixMode(GL_MODELVIEW); - _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); - } -////////////////////////////////// - _render_sla_slices(); _render_selection(); @@ -4720,6 +4700,28 @@ void GLCanvas3D::set_ortho_projection(float w, float h, float near, float far) c } +void GLCanvas3D::set_sla_clipping(bool enable) const +{ + if (m_gizmos.get_current_type() != Gizmos::SlaSupports) + return; + + if (enable) { + ::glMatrixMode(GL_PROJECTION); + ::glPushMatrix(); + ::glMatrixMode(GL_MODELVIEW); + const Size& cnv_size = get_canvas_size(); + std::pair clipping_limits = m_gizmos.get_sla_clipping_plane(); + set_ortho_projection((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height(), clipping_limits.first, clipping_limits.second); + } + else { + ::glMatrixMode(GL_PROJECTION); + ::glPopMatrix(); + ::glMatrixMode(GL_MODELVIEW); + ::glClear(GL_DEPTH_BUFFER_BIT); + } +} + + void GLCanvas3D::_render_objects() const { if (m_volumes.empty()) @@ -4728,6 +4730,8 @@ void GLCanvas3D::_render_objects() const ::glEnable(GL_LIGHTING); ::glEnable(GL_DEPTH_TEST); + set_sla_clipping(true); + if (m_use_VBOs) { if (m_picking_enabled) @@ -4789,6 +4793,8 @@ void GLCanvas3D::_render_objects() const } } + set_sla_clipping(false); + ::glDisable(GL_LIGHTING); } @@ -4831,6 +4837,8 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const if (!fake_colors) ::glEnable(GL_LIGHTING); + set_sla_clipping(true); + // do not cull backfaces to show broken geometry, if any ::glDisable(GL_CULL_FACE); @@ -4869,6 +4877,8 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const ::glEnable(GL_CULL_FACE); + set_sla_clipping(false); + if (!fake_colors) ::glDisable(GL_LIGHTING); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index acaae8dbd5..44c43a1e3a 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -778,6 +778,9 @@ private: // Sets current projection matrix to ortho, accounting for current camera zoom. void set_ortho_projection(float w, float h, float near, float far) const; + // Set/unset near clipping plane according to SLA gizmo requirements. + void set_sla_clipping(bool enable) const; + void _start_timer(); void _stop_timer(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index e2478bc0cb..07eec27e25 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -157,6 +157,11 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) 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(); + // 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]); + ::glPushMatrix(); ::glTranslated(0.0, 0.0, z_shift); ::glMultMatrixd(instance_matrix.data()); @@ -167,6 +172,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, z_shift)) + continue; + // First decide about the color of the point. if (picking) { std::array color = picking_color_component(i); @@ -285,6 +293,8 @@ void GLGizmoSlaSupports::update_mesh() m_AABB.init(m_V, m_F); } +// Unprojects the mouse position on the mesh and return the hit point and normal of the facet. +// The function throws if no intersection if found. std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) { // if the gizmo doesn't have the V, F structures for igl, calculate them first: @@ -303,12 +313,15 @@ 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(); + // 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) -= z_offset; point2(2) -= z_offset; @@ -317,17 +330,37 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse 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, z_offset)) + 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) ); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index a5f77a21b0..872e3541fa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -86,6 +86,7 @@ private: int m_canvas_height; std::vector get_config_options(const std::vector& keys) const; + bool is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera, float z_shift) const; // Methods that do the model_object and editing cache synchronization, // editing mode selection, etc: From 273fcf68a134b0c7526663049fad2f5636b67488 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 25 Mar 2019 10:47:23 +0100 Subject: [PATCH 03/57] SLA gizmo now uses glClipPlane instead of touching projection matrix Messing with the projection matrix invalidates the z-buffer This currently only works in OpenGL legacy mode --- src/slic3r/GUI/GLCanvas3D.cpp | 36 +++++++++----------- src/slic3r/GUI/GLCanvas3D.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 25 ++++++-------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 3 +- 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6f55e4059b..3e4824cdef 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1319,16 +1319,16 @@ bool GLCanvas3D::Gizmos::gizmo_event(SLAGizmoEventType action, const Vec2d& mous -std::pair GLCanvas3D::Gizmos::get_sla_clipping_plane() const +GLCanvas3D::ClippingPlane GLCanvas3D::Gizmos::get_sla_clipping_plane() const { if (!m_enabled) - return std::make_pair(0.f, 0.f); + return ClippingPlane(); GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); if (it != m_gizmos.end()) return reinterpret_cast(it->second)->get_sla_clipping_plane(); - return std::make_pair(0.f, 0.f);; + return ClippingPlane(); } @@ -4706,19 +4706,17 @@ void GLCanvas3D::set_sla_clipping(bool enable) const return; if (enable) { - ::glMatrixMode(GL_PROJECTION); - ::glPushMatrix(); - ::glMatrixMode(GL_MODELVIEW); - const Size& cnv_size = get_canvas_size(); - std::pair clipping_limits = m_gizmos.get_sla_clipping_plane(); - set_ortho_projection((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height(), clipping_limits.first, clipping_limits.second); - } - else { - ::glMatrixMode(GL_PROJECTION); - ::glPopMatrix(); - ::glMatrixMode(GL_MODELVIEW); - ::glClear(GL_DEPTH_BUFFER_BIT); + ClippingPlane gizmo_clipping_plane; + try { + gizmo_clipping_plane = m_gizmos.get_sla_clipping_plane(); + } + catch (...) { return; } + + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)gizmo_clipping_plane.get_data()); + ::glEnable(GL_CLIP_PLANE0); } + else + ::glDisable(GL_CLIP_PLANE0); } @@ -4774,10 +4772,10 @@ void GLCanvas3D::_render_objects() const { if (m_use_clipping_planes) { - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_clipping_planes[0].get_data()); - ::glEnable(GL_CLIP_PLANE0); - ::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[1].get_data()); + ::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[0].get_data()); ::glEnable(GL_CLIP_PLANE1); + ::glClipPlane(GL_CLIP_PLANE2, (GLdouble*)m_clipping_planes[1].get_data()); + ::glEnable(GL_CLIP_PLANE2); } // do not cull backfaces to show broken geometry, if any @@ -4788,8 +4786,8 @@ void GLCanvas3D::_render_objects() const if (m_use_clipping_planes) { - ::glDisable(GL_CLIP_PLANE0); ::glDisable(GL_CLIP_PLANE1); + ::glDisable(GL_CLIP_PLANE2); } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 44c43a1e3a..b09f3746f0 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -448,7 +448,7 @@ private: 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); - std::pair get_sla_clipping_plane() const; + 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/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 07eec27e25..65be22b44d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -612,47 +612,42 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const -std::pair GLGizmoSlaSupports::get_sla_clipping_plane() const +GLCanvas3D::ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const { if (!m_model_object) - return std::make_pair(0.f, 0.f);; + throw std::invalid_argument("GLGizmoSlaSupports::get_sla_clipping_plane() has no model object pointer."); Eigen::Matrix modelview_matrix; ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); - // clipping space origin transformed to world coords: - Vec3d clipping_origin = (modelview_matrix.inverse() * Eigen::Matrix{0, 0, 0, 1}).block<3,1>(0,0); - // 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(clipping_origin) - direction_to_camera.dot(m_active_instance_bb.center()); + float dist = direction_to_camera.dot(m_active_instance_bb.center()); - return std::make_pair((dist - m_active_instance_bb.radius()) + m_clipping_plane_distance * 2*m_active_instance_bb.radius(), dist + 5.f*m_active_instance_bb.radius()); + return GLCanvas3D::ClippingPlane(-direction_to_camera.normalized(),(dist - (-m_active_instance_bb.radius()) - m_clipping_plane_distance * 2*m_active_instance_bb.radius())); } /* -void GLGizmoSlaSupports::find_intersections(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& idxs) const +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 - unsigned int facet_idx = aabb->m_primitive; - - Vec3f a = m_V.row(m_F(facet_idx, 0)); - Vec3f b = m_V.row(m_F(facet_idx, 1)); - Vec3f c = m_V.row(m_F(facet_idx, 2)); + 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_intersections(aabb->m_left, normal, offset, idxs); - find_intersections(aabb->m_right, normal, offset, idxs); + 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); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 872e3541fa..f61709d615 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -52,7 +52,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); void delete_selected_points(bool force = false); - std::pair get_sla_clipping_plane() const; + GLCanvas3D::ClippingPlane get_sla_clipping_plane() const; private: bool on_init(); @@ -87,6 +87,7 @@ private: std::vector get_config_options(const std::vector& keys) const; bool is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera, float z_shift) 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: From 9b7857aaab416a18cf67d273e46308e21d3295a2 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 25 Mar 2019 12:01:02 +0100 Subject: [PATCH 04/57] SLA gizmo clipping plane logic moved to fragment shader This means the clipping now works again with both legacy and modern OpenGL --- resources/shaders/gouraud.fs | 10 +++- resources/shaders/gouraud.vs | 4 +- src/slic3r/GUI/3DScene.cpp | 4 ++ src/slic3r/GUI/3DScene.hpp | 4 ++ src/slic3r/GUI/GLCanvas3D.cpp | 49 ++++++++------------ src/slic3r/GUI/GLCanvas3D.hpp | 6 +-- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- 7 files changed, 41 insertions(+), 38 deletions(-) diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index f6116eec70..5b4ef0480d 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.f ) 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 84ae513913..a226cf312c 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/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 7ba61bdb69..57c227e078 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -785,6 +785,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; @@ -799,6 +800,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)); + GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, filter_func); for (GLVolumeWithZ& volume : to_render) { volume.first->set_render_color(); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 5cc301a393..0ed8d2e6b1 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -427,6 +427,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; @@ -485,6 +488,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 3e4824cdef..4214abc60b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1321,14 +1321,14 @@ bool GLCanvas3D::Gizmos::gizmo_event(SLAGizmoEventType action, const Vec2d& mous GLCanvas3D::ClippingPlane GLCanvas3D::Gizmos::get_sla_clipping_plane() const { - if (!m_enabled) - return ClippingPlane(); + 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(); + return ClippingPlane::ClipsNothing(); } @@ -4602,7 +4602,12 @@ void GLCanvas3D::_picking_pass() const ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); + ::glEnable(GL_CLIP_PLANE0); _render_volumes(true); + ::glDisable(GL_CLIP_PLANE0); + m_gizmos.render_current_gizmo_for_picking_pass(m_selection); if (m_multisample_allowed) @@ -4700,25 +4705,6 @@ void GLCanvas3D::set_ortho_projection(float w, float h, float near, float far) c } -void GLCanvas3D::set_sla_clipping(bool enable) const -{ - if (m_gizmos.get_current_type() != Gizmos::SlaSupports) - return; - - if (enable) { - ClippingPlane gizmo_clipping_plane; - try { - gizmo_clipping_plane = m_gizmos.get_sla_clipping_plane(); - } - catch (...) { return; } - - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)gizmo_clipping_plane.get_data()); - ::glEnable(GL_CLIP_PLANE0); - } - else - ::glDisable(GL_CLIP_PLANE0); -} - void GLCanvas3D::_render_objects() const { @@ -4728,7 +4714,7 @@ void GLCanvas3D::_render_objects() const ::glEnable(GL_LIGHTING); ::glEnable(GL_DEPTH_TEST); - set_sla_clipping(true); + m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); if (m_use_VBOs) { @@ -4750,6 +4736,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; @@ -4770,6 +4758,9 @@ 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) { ::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[0].get_data()); @@ -4777,6 +4768,7 @@ void GLCanvas3D::_render_objects() const ::glClipPlane(GL_CLIP_PLANE2, (GLdouble*)m_clipping_planes[1].get_data()); ::glEnable(GL_CLIP_PLANE2); } + // do not cull backfaces to show broken geometry, if any m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, [this](const GLVolume& volume) { @@ -4784,15 +4776,16 @@ void GLCanvas3D::_render_objects() const }); m_volumes.render_legacy(GLVolumeCollection::Transparent, false); + ::glDisable(GL_CLIP_PLANE0); + if (m_use_clipping_planes) { ::glDisable(GL_CLIP_PLANE1); ::glDisable(GL_CLIP_PLANE2); } } - - set_sla_clipping(false); - + + m_camera_clipping_plane = ClippingPlane::ClipsNothing(); ::glDisable(GL_LIGHTING); } @@ -4835,8 +4828,6 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const if (!fake_colors) ::glEnable(GL_LIGHTING); - set_sla_clipping(true); - // do not cull backfaces to show broken geometry, if any ::glDisable(GL_CULL_FACE); @@ -4875,8 +4866,6 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const ::glEnable(GL_CULL_FACE); - set_sla_clipping(false); - if (!fake_colors) ::glDisable(GL_LIGHTING); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index b09f3746f0..0245b8d6cf 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -354,6 +354,8 @@ public: m_data[3] = offset; } + static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); } + const double* get_data() const { return m_data; } }; @@ -561,6 +563,7 @@ private: mutable Gizmos 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; @@ -778,9 +781,6 @@ private: // Sets current projection matrix to ortho, accounting for current camera zoom. void set_ortho_projection(float w, float h, float near, float far) const; - // Set/unset near clipping plane according to SLA gizmo requirements. - void set_sla_clipping(bool enable) const; - void _start_timer(); void _stop_timer(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 65be22b44d..dce7f64dcb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -615,7 +615,7 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const GLCanvas3D::ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const { if (!m_model_object) - throw std::invalid_argument("GLGizmoSlaSupports::get_sla_clipping_plane() has no model object pointer."); + return GLCanvas3D::ClippingPlane::ClipsNothing(); Eigen::Matrix modelview_matrix; ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); From bbda1896f9d4f78146af57d8587d4533db00716c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 27 Mar 2019 07:44:02 +0100 Subject: [PATCH 05/57] The gizmo is now able to triangulate and show the cut, the triangulated cut is cached --- src/libslic3r/TriangleMesh.cpp | 53 ++++++++++-- src/libslic3r/TriangleMesh.hpp | 5 ++ src/slic3r/GUI/GLCanvas3D.cpp | 38 +++++---- src/slic3r/GUI/GLCanvas3D.hpp | 3 - src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 90 ++++++++++++++++---- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 9 +- 6 files changed, 150 insertions(+), 48 deletions(-) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index bfba364af6..44c638a408 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,14 @@ 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_orig = this->mesh->stl.facet_start[facet_idx]; + stl_facet facet = facet_orig; + + if (m_use_quaternion) { + facet.vertex[0] = m_quaternion * facet.vertex[0]; + facet.vertex[1] = m_quaternion * facet.vertex[1]; + facet.vertex[2] = m_quaternion * facet.vertex[2]; + } // find facet extents const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); @@ -860,26 +877,42 @@ 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 inclined: + 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; + 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 = m_use_quaternion ? stl_vertex(m_quaternion * this->mesh->stl.facet_start[facet_idx].normal) : this->mesh->stl.facet_start[facet_idx].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 +1028,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 d389500c69..a0020255dd 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -171,6 +171,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 +179,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/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4214abc60b..717ebcddaf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4432,13 +4432,29 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) _set_current(); ::glViewport(0, 0, w, h); + ::glMatrixMode(GL_PROJECTION); + ::glLoadIdentity(); + + const BoundingBoxf3& bbox = _max_bounding_box(); + switch (m_camera.type) { case Camera::Ortho: { + float w2 = w; + float h2 = h; + float two_zoom = 2.0f * get_camera_zoom(); + if (two_zoom != 0.0f) + { + float inv_two_zoom = 1.0f / two_zoom; + w2 *= inv_two_zoom; + h2 *= inv_two_zoom; + } + // FIXME: calculate a tighter value for depth will improve z-fighting - float depth = 5.0f * (float)(_max_bounding_box().max_size()); - set_ortho_projection(w, h, -depth, depth); + float depth = 5.0f * (float)bbox.max_size(); + ::glOrtho(-w2, w2, -h2, h2, -depth, depth); + break; } // case Camera::Perspective: @@ -4470,6 +4486,8 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) } } + ::glMatrixMode(GL_MODELVIEW); + m_dirty = false; } @@ -4689,22 +4707,6 @@ void GLCanvas3D::_render_axes() const } -void GLCanvas3D::set_ortho_projection(float w, float h, float near, float far) const -{ - float two_zoom = 2.0f * get_camera_zoom(); - if (two_zoom != 0.0f) - { - float inv_two_zoom = 1.0f / two_zoom; - w *= inv_two_zoom; - h *= inv_two_zoom; - } - ::glMatrixMode(GL_PROJECTION); - ::glLoadIdentity(); - ::glOrtho(-w, w, -h, h, near, far); - ::glMatrixMode(GL_MODELVIEW); -} - - void GLCanvas3D::_render_objects() const { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 0245b8d6cf..656b6e2294 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -778,9 +778,6 @@ private: // Returns the view ray line, in world coordinate, at the given mouse position. Linef3 mouse_ray(const Point& mouse_pos); - // Sets current projection matrix to ortho, accounting for current camera zoom. - void set_ortho_projection(float w, float h, float near, float far) const; - void _start_timer(); void _stop_timer(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index dce7f64dcb..78719c7767 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -9,6 +9,7 @@ #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/Tesselate.hpp" namespace Slic3r { @@ -91,12 +92,71 @@ void GLGizmoSlaSupports::on_render(const Selection& selection) const ::glEnable(GL_BLEND); ::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]); + + 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); ::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()); + double z_shift = vol->get_sla_shift_z(); + 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(); + Transform3f instance_matrix_no_translation = vol->get_instance_transformation().get_matrix(true).cast(); + Vec3f scaling = vol->get_instance_scaling_factor().cast(); + + Vec3f up = instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera.cast().normalized(); + up = Vec3f(up(0)*scaling(0), up(1)*scaling(1), up(2)*scaling(2)); + float height = m_active_instance_bb.radius() - m_clipping_plane_distance * 2*m_active_instance_bb.radius(); + float height_mesh = height; + + if (m_clipping_plane_distance != m_old_clipping_plane_distance + || m_old_direction_to_camera != direction_to_camera) { + + std::vector list_of_expolys; + 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; + } + + ::glPushMatrix(); + ::glTranslated(0.0, 0.0, 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 + + ::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_active) @@ -141,14 +201,16 @@ void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const { ::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) ::glEnable(GL_LIGHTING); @@ -157,11 +219,6 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) 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(); - // 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]); - ::glPushMatrix(); ::glTranslated(0.0, 0.0, z_shift); ::glMultMatrixd(instance_matrix.data()); @@ -263,9 +320,6 @@ bool GLGizmoSlaSupports::is_mesh_update_necessary() const { return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) && ((m_model_object != m_old_model_object) || m_V.size()==0); - - //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix)) - // return false; } void GLGizmoSlaSupports::update_mesh() @@ -273,10 +327,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(); m_AABB.init(m_V, m_F); + + m_tms.reset(new TriangleMeshSlicer); + m_tms->init(&m_mesh, [](){}); } // Unprojects the mouse position on the mesh and return the hit point and normal of the facet. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index f61709d615..3a5e903c16 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -31,6 +31,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: @@ -61,7 +63,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 +77,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; bool m_selection_rectangle_active = false; Vec2d m_selection_rectangle_start_corner; @@ -85,6 +90,8 @@ 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, float z_shift) const; void find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& out) const; From 7531f2d5e769ea1dae162559b769ab8fcd01e047 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 3 Apr 2019 14:23:04 +0200 Subject: [PATCH 06/57] Selection rectangle now respects the clipping plane position --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 41 +++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 78719c7767..f727572cba 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -490,11 +490,14 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // 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(modelview_matrix[2], modelview_matrix[6], modelview_matrix[10]); // ...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(), direction_to_camera.cast(), z_offset)) { 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 (hits.front().t < 0.001f) + hits.erase(hits.begin()); + + 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(), z_offset)) { + 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) select_point(i); From 09cf1b9b0070644bf48eb6ef06bfa1f3f59b3995 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 5 Apr 2019 16:38:54 +0200 Subject: [PATCH 07/57] Allowed general object transformation and SLA z-shift (clipping plane) --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 47 +++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 5 ++- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index f727572cba..82b08a5e5a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -59,7 +59,7 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S { // 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. - m_active_instance_bb = m_model_object->instance_bounding_box(m_active_instance); + m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius(); if (is_mesh_update_necessary()) { update_mesh(); @@ -96,6 +96,7 @@ void GLGizmoSlaSupports::on_render(const Selection& selection) const 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); @@ -114,16 +115,13 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const return; const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_shift = vol->get_sla_shift_z(); 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(); - Transform3f instance_matrix_no_translation = vol->get_instance_transformation().get_matrix(true).cast(); Vec3f scaling = vol->get_instance_scaling_factor().cast(); - Vec3f up = instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera.cast().normalized(); - up = Vec3f(up(0)*scaling(0), up(1)*scaling(1), up(2)*scaling(2)); - float height = m_active_instance_bb.radius() - m_clipping_plane_distance * 2*m_active_instance_bb.radius(); - float height_mesh = height; + 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) { @@ -138,7 +136,7 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const } ::glPushMatrix(); - ::glTranslated(0.0, 0.0, z_shift); + ::glTranslated(0.0, 0.0, m_z_shift); ::glMultMatrixf(instance_matrix.data()); Eigen::Quaternionf q; q.setFromTwoVectors(Vec3f::UnitZ(), up); @@ -215,12 +213,11 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, const Vec3d& ::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(); ::glPushMatrix(); - ::glTranslated(0.0, 0.0, z_shift); + ::glTranslated(0.0, 0.0, m_z_shift); ::glMultMatrixd(instance_matrix.data()); float render_color[3]; @@ -229,7 +226,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, const Vec3d& 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, z_shift)) + if (is_point_clipped(support_point.pos.cast(), direction_to_camera)) continue; // First decide about the color of the point. @@ -303,15 +300,15 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, const Vec3d& -bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera, float z_shift) const +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) += z_shift; - return direction_to_camera.dot(m_active_instance_bb.center()) + m_active_instance_bb.radius() - - m_clipping_plane_distance * 2*m_active_instance_bb.radius() < direction_to_camera.dot(transformed_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); } @@ -373,13 +370,12 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse 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(); // 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) -= z_offset; - point2(2) -= z_offset; + point1(2) -= m_z_shift; + point2(2) -= m_z_shift; Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); @@ -404,7 +400,7 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); result = 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)); - if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast(), direction_to_camera, z_offset)) + if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast(), direction_to_camera)) break; } @@ -485,7 +481,6 @@ 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())}); @@ -503,20 +498,18 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous 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), modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z); out_y = m_canvas_height - out_y; - if (rectangle.contains(Point(out_x, out_y)) && !is_point_clipped(support_point.pos.cast(), direction_to_camera.cast(), z_offset)) { + 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_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 (hits.front().t < 0.001f) - hits.erase(hits.begin()); if (m_clipping_plane_distance != 0.f) { // If the closest hit facet normal points in the same direction as the ray, @@ -534,7 +527,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit Vec3f hit_pos = 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)); - if (is_point_clipped(hit_pos.cast(), direction_to_camera.cast(), z_offset)) { + if (is_point_clipped(hit_pos.cast(), direction_to_camera.cast())) { hits.erase(hits.begin()+j); --j; } @@ -709,9 +702,9 @@ GLCanvas3D::ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const // 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_active_instance_bb.center()); + float dist = direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)); - return GLCanvas3D::ClippingPlane(-direction_to_camera.normalized(),(dist - (-m_active_instance_bb.radius()) - m_clipping_plane_distance * 2*m_active_instance_bb.radius())); + return GLCanvas3D::ClippingPlane(-direction_to_camera.normalized(),(dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 3a5e903c16..d6df55f934 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -22,7 +22,8 @@ private: ModelObject* m_model_object = nullptr; ModelObject* m_old_model_object = nullptr; int m_active_instance = -1; - BoundingBoxf3 m_active_instance_bb; // to cache the bb + 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; @@ -93,7 +94,7 @@ private: 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, float z_shift) 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, From 3a09f66e513c363198d34714747a6e799ee9a318 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 8 Apr 2019 16:05:18 +0200 Subject: [PATCH 08/57] Fixed a literal in vertex shader that refused to compile on OSX --- resources/shaders/gouraud.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 5b4ef0480d..b2bc915ab6 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -23,7 +23,7 @@ void main() 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.f ) + 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 From 6cbf9d25234c8a12586d0b7a555513ee2333a5ea Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Apr 2019 08:40:58 +0200 Subject: [PATCH 09/57] 1st installment of copy and paste -> prototype for volumes copy and paste --- src/slic3r/GUI/GUI_ObjectList.cpp | 37 +++++++++++++ src/slic3r/GUI/GUI_ObjectList.hpp | 8 +++ src/slic3r/GUI/MainFrame.cpp | 19 +++++++ src/slic3r/GUI/MainFrame.hpp | 2 + src/slic3r/GUI/Plater.cpp | 15 +++++ src/slic3r/GUI/Plater.hpp | 4 ++ src/slic3r/GUI/Selection.cpp | 92 +++++++++++++++++++++++++++++++ src/slic3r/GUI/Selection.hpp | 32 +++++++++++ 8 files changed, 209 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index dc39093513..b0ccf6290b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -436,6 +436,43 @@ void ObjectList::selection_changed() part_selection_changed(); } +void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) +{ + if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx)) + return; + + if (volumes.empty()) + return; + + ModelObject& model_object = *(*m_objects)[obj_idx]; + const auto object_item = m_objects_model->GetItemById(obj_idx); + + wxDataViewItemArray items; + + for (const ModelVolume* volume : volumes) + { + auto vol_item = m_objects_model->AddVolumeChild(object_item, volume->name, volume->type(), + volume->config.has("extruder") ? volume->config.option("extruder")->value : 0, false); + auto opt_keys = volume->config.keys(); + if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) + select_item(m_objects_model->AddSettingsChild(vol_item)); + + items.Add(vol_item); + } + + m_parts_changed = true; + parts_changed(obj_idx); + + select_items(items); +#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME + selection_changed(); +#endif //no __WXOSX__ //__WXMSW__ +} + +void ObjectList::paste_object_into_list(const ModelObject& object) +{ +} + void ObjectList::OnChar(wxKeyEvent& event) { if (event.GetKeyCode() == WXK_BACK){ diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 2f465a4ab5..a30f76332c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -31,6 +31,10 @@ typedef std::map> FreqSettingsBundle; // category -> vector ( option ; label ) typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +typedef std::vector ModelVolumePtrs; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + namespace GUI { wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); @@ -285,6 +289,10 @@ public: void rename_item(); void fix_through_netfabb() const; void update_item_error_icon(const int obj_idx, int vol_idx) const ; + + void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); + void paste_object_into_list(const ModelObject& object); + private: void OnChar(wxKeyEvent& event); void OnContextMenu(wxDataViewEvent &event); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index eac367669a..13f1287a3a 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -256,6 +256,16 @@ bool MainFrame::can_delete_all() const return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; } +bool MainFrame::can_copy() const +{ + return (m_plater != nullptr) ? !m_plater->is_selection_empty() : false; +} + +bool MainFrame::can_paste() const +{ + return (m_plater != nullptr) ? !m_plater->is_selection_clipboard_empty() : false; +} + void MainFrame::on_dpi_changed(const wxRect &suggested_rect) { // TODO @@ -379,9 +389,18 @@ void MainFrame::init_menubar() wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete, _(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset(); }, ""); + editMenu->AppendSeparator(); + + wxMenuItem* item_copy = append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + "\tCtrl+C", _(L("Copy selection to clipboard")), + [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, ""); + wxMenuItem* item_paste = append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + "\tCtrl+V", _(L("Paste clipboard")), + [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, ""); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_select()); }, item_select_all->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete_sel->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete_all()); }, item_delete_all->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_copy()); }, item_copy->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_paste()); }, item_paste->GetId()); } // Window menu diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 625e70b83b..16116ec433 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -68,6 +68,8 @@ class MainFrame : public DPIFrame bool can_select() const; bool can_delete() const; bool can_delete_all() const; + bool can_copy() const; + bool can_paste() const; protected: virtual void on_dpi_changed(const wxRect &suggested_rect); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cffec2062c..cedca34aa5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3704,6 +3704,21 @@ void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) void Plater::update_object_menu() { p->update_object_menu(); } +void Plater::copy_selection_to_clipboard() +{ + p->view3D->get_canvas3d()->get_selection().copy_to_clipboard(); +} + +void Plater::paste_from_clipboard() +{ + p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); +} + +bool Plater::is_selection_clipboard_empty() const +{ + return p->view3D->get_canvas3d()->get_selection().is_clipboard_empty(); +} + 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(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index f830edce3f..3d3ba8b3ec 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -182,6 +182,10 @@ public: PrinterTechnology printer_technology() const; void set_printer_technology(PrinterTechnology printer_technology); + void copy_selection_to_clipboard(); + void paste_from_clipboard(); + bool is_selection_clipboard_empty() const; + bool can_delete() const; bool can_delete_all() const; bool can_increase_instances() const; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index dedf55a45a..e0a5365610 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -47,6 +47,23 @@ Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_trans { } +Selection::Clipboard::Clipboard() + : m_object(nullptr) +{ + m_object = m_model.add_object(); +} + +void Selection::Clipboard::add_volume(const ModelVolume& volume) +{ + ModelVolume* v = m_object->add_volume(volume); + v->config = volume.config; +} + +const ModelVolume* Selection::Clipboard::get_volume(unsigned int id) const +{ + return (id < (unsigned int)m_object->volumes.size()) ? m_object->volumes[id] : nullptr; +} + Selection::Selection() : m_volumes(nullptr) , m_model(nullptr) @@ -1022,6 +1039,53 @@ bool Selection::requires_local_axes() const return (m_mode == Volume) && is_from_single_instance(); } +void Selection::copy_to_clipboard() +{ + if (!m_valid) + return; + + m_clipboard.reset(); + + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + int obj_idx = volume->object_idx(); + if ((0 <= obj_idx) && (obj_idx < (int)m_model->objects.size())) + { + const ModelObject* model_object = m_model->objects[obj_idx]; + int vol_idx = volume->volume_idx(); + if ((0 <= vol_idx) && (vol_idx < (int)model_object->volumes.size())) + m_clipboard.add_volume(*model_object->volumes[vol_idx]); + } + } + + int obj_idx = get_object_idx(); + if ((0 <= obj_idx) && (obj_idx < (int)m_model->objects.size())) + m_clipboard.get_object()->config = m_model->objects[obj_idx]->config; + + m_clipboard.set_mode(m_mode); + m_clipboard.set_type(m_type); +} + +void Selection::paste_from_clipboard() +{ + if (!m_valid) + return; + + if (m_clipboard.is_empty()) + return; + + if ((m_clipboard.get_mode() == Volume) && is_from_single_instance()) + paste_volumes_from_clipboard(); + else + paste_object_from_clipboard(); +} + +bool Selection::is_clipboard_empty() +{ + return m_clipboard.is_empty(); +} + void Selection::update_valid() { m_valid = (m_volumes != nullptr) && (m_model != nullptr); @@ -1697,5 +1761,33 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const return count == (unsigned int)m_model->objects[object_idx]->volumes.size(); } +void Selection::paste_volumes_from_clipboard() +{ + int obj_idx = get_object_idx(); + if ((obj_idx < 0) || ((int)m_model->objects.size() <= obj_idx)) + return; + + ModelObject& model_object = *m_model->objects[obj_idx]; + unsigned int count = m_clipboard.get_volumes_count(); + ModelVolumePtrs volumes; + for (unsigned int i = 0; i < count; ++i) + { + const ModelVolume* volume = m_clipboard.get_volume(i); + ModelVolume* new_volume = model_object.add_volume(*volume); + new_volume->config = volume->config; + new_volume->set_new_unique_id(); + volumes.push_back(new_volume); + } + wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes); + int a = 0; +} + +void Selection::paste_object_from_clipboard() +{ + ModelObject* model_object = m_clipboard.get_object(); + if (model_object != nullptr) + wxGetApp().obj_list()->paste_object_into_list(*model_object); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index b03a8e89ac..3eb63486b1 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -151,6 +151,31 @@ private: ObjectIdxsToInstanceIdxsMap content; }; + class Clipboard + { + Model m_model; + ModelObject* m_object; + Selection::EMode m_mode; + Selection::EType m_type; + + public: + Clipboard(); + + void reset() { if (m_object != nullptr) m_object->clear_volumes(); } + void add_volume(const ModelVolume& volume); + const ModelVolume* get_volume(unsigned int id) const; + ModelObject* get_object() { return m_object; } + const ModelObject* get_object() const { return m_object; } + const unsigned int get_volumes_count() const { return (unsigned int)m_object->volumes.size(); } + + bool is_empty() const { return (m_object == nullptr) || m_object->volumes.empty(); } + + Selection::EMode get_mode() const { return m_mode; } + void set_mode(Selection::EMode mode) { m_mode = mode; } + Selection::EType get_type() const { return m_type; } + void set_type(Selection::EType type) { m_type = type; } + }; + // Volumes owned by GLCanvas3D. GLVolumePtrs* m_volumes; // Model, not owned. @@ -163,6 +188,7 @@ private: // set of indices to m_volumes IndicesList m_list; Cache m_cache; + Clipboard m_clipboard; mutable BoundingBoxf3 m_bounding_box; mutable bool m_bounding_box_dirty; @@ -267,6 +293,10 @@ public: bool requires_local_axes() const; + void copy_to_clipboard(); + void paste_from_clipboard(); + bool is_clipboard_empty(); + private: void update_valid(); void update_type(); @@ -301,6 +331,8 @@ private: void synchronize_unselected_volumes(); void ensure_on_bed(); bool is_from_fully_selected_instance(unsigned int volume_idx) const; + void paste_volumes_from_clipboard(); + void paste_object_from_clipboard(); }; } // namespace GUI From 6f6b78d6615f8660611e3d03f7f02ac3ae846383 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Apr 2019 09:27:42 +0200 Subject: [PATCH 10/57] Copy and paste -> Copy a volume from an object and paste to another --- src/slic3r/GUI/GUI_ObjectList.cpp | 14 ++++++-------- src/slic3r/GUI/GUI_ObjectList.hpp | 2 -- src/slic3r/GUI/wxExtensions.cpp | 11 +++++------ src/slic3r/GUI/wxExtensions.hpp | 11 +++++------ 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b0ccf6290b..d53d0809ed 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -452,7 +452,7 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol for (const ModelVolume* volume : volumes) { auto vol_item = m_objects_model->AddVolumeChild(object_item, volume->name, volume->type(), - volume->config.has("extruder") ? volume->config.option("extruder")->value : 0, false); + volume->config.has("extruder") ? volume->config.option("extruder")->value : 0); auto opt_keys = volume->config.keys(); if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) select_item(m_objects_model->AddSettingsChild(vol_item)); @@ -1610,11 +1610,10 @@ void ObjectList::split() for (auto id = 0; id < model_object->volumes.size(); id++) { const auto vol_item = m_objects_model->AddVolumeChild(parent, from_u8(model_object->volumes[id]->name), - model_object->volumes[id]->is_modifier() ? - ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, - model_object->volumes[id]->config.has("extruder") ? - model_object->volumes[id]->config.option("extruder")->value : 0, - false); + model_object->volumes[id]->is_modifier() ? + ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, + model_object->volumes[id]->config.has("extruder") ? + model_object->volumes[id]->config.option("extruder")->value : 0); // add settings to the part, if it has those auto opt_keys = model_object->volumes[id]->config.keys(); if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { @@ -1808,8 +1807,7 @@ void ObjectList::add_object_to_list(size_t obj_idx) from_u8(model_object->volumes[id]->name), model_object->volumes[id]->type(), !model_object->volumes[id]->config.has("extruder") ? 0 : - model_object->volumes[id]->config.option("extruder")->value, - false); + model_object->volumes[id]->config.option("extruder")->value); auto opt_keys = model_object->volumes[id]->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { select_item(m_objects_model->AddSettingsChild(vol_item)); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index a30f76332c..c5e097af8d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -31,9 +31,7 @@ typedef std::map> FreqSettingsBundle; // category -> vector ( option ; label ) typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ typedef std::vector ModelVolumePtrs; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ namespace GUI { diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 7f5b5ad9c4..a6b8792978 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -529,10 +529,9 @@ wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int ext } wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &parent_item, - const wxString &name, - const Slic3r::ModelVolumeType volume_type, - const int extruder/* = 0*/, - const bool create_frst_child/* = true*/) + const wxString &name, + const Slic3r::ModelVolumeType volume_type, + const int extruder/* = 0*/) { PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); if (!root) return wxDataViewItem(0); @@ -544,8 +543,8 @@ wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &pa if (insert_position < 0 || root->GetNthChild(insert_position)->m_type != itInstanceRoot) insert_position = -1; - if (create_frst_child && root->m_volumes_cnt == 0) - { + if (root->m_volumes_cnt == 0) + { const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, *m_volume_bmps[0], extruder_str, 0); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // notify control diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 4a3482abcf..19c4e77594 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -454,12 +454,11 @@ public: ~PrusaObjectDataViewModel(); wxDataViewItem Add(const wxString &name, const int extruder); - wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item, - const wxString &name, - const Slic3r::ModelVolumeType volume_type, - const int extruder = 0, - const bool create_frst_child = true); - wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); + wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item, + const wxString &name, + const Slic3r::ModelVolumeType volume_type, + const int extruder = 0); + wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); From 7b8b8b97ef3f12c065e1f9e4195a77b8cef77b36 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 10 Apr 2019 10:16:04 +0200 Subject: [PATCH 11/57] Fixed Cmd-A selection in the SLA gizmo. --- src/slic3r/GUI/GLCanvas3D.cpp | 3 +++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 29c64b9a5c..c63d265faa 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2222,9 +2222,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) //#endif /* __APPLE__ */ if ((evt.GetModifiers() & ctrlMask) != 0) { switch (keyCode) { +#ifdef __APPLE__ case 'a': case 'A': +#else /* __APPLE__ */ case WXK_CONTROL_A: +#endif /* __APPLE__ */ post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); break; #ifdef __APPLE__ diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index ad2d786ffc..d00f3440a5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -713,7 +713,12 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas) { switch (keyCode) { +#ifdef __APPLE__ + case 'a': + case 'A': +#else /* __APPLE__ */ case WXK_CONTROL_A: +#endif /* __APPLE__ */ { // Sla gizmo selects all support points if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::SelectAll)) From e61be7d260d40caf2b9d4edf128e086ca4d3144f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Apr 2019 11:20:09 +0200 Subject: [PATCH 12/57] Render picking pass renders volumes in the same order as the regular render pass --- src/slic3r/GUI/3DScene.cpp | 25 +++++++++-------- src/slic3r/GUI/3DScene.hpp | 4 +++ src/slic3r/GUI/GLCanvas3D.cpp | 51 +++++++++++++++++------------------ src/slic3r/GUI/GLCanvas3D.hpp | 2 +- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 3dd680820c..61920220e5 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -721,32 +721,31 @@ int GLVolumeCollection::load_wipe_tower_preview( return int(this->volumes.size() - 1); } -typedef std::pair GLVolumeWithZ; -typedef std::vector GLVolumesWithZList; -static GLVolumesWithZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func) +GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func) { - GLVolumesWithZList list; + GLVolumeWithIdAndZList list; list.reserve(volumes.size()); - for (GLVolume* volume : volumes) + for (unsigned int i = 0; i < (unsigned int)volumes.size(); ++i) { + GLVolume* volume = volumes[i]; bool is_transparent = (volume->render_color[3] < 1.0f); if ((((type == GLVolumeCollection::Opaque) && !is_transparent) || ((type == GLVolumeCollection::Transparent) && is_transparent) || (type == GLVolumeCollection::All)) && (! filter_func || filter_func(*volume))) - list.emplace_back(std::make_pair(volume, 0.0)); + list.emplace_back(std::make_pair(volume, std::make_pair(i, 0.0))); } if ((type == GLVolumeCollection::Transparent) && (list.size() > 1)) { - for (GLVolumeWithZ& volume : list) + for (GLVolumeWithIdAndZ& volume : list) { - volume.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2); + volume.second.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2); } std::sort(list.begin(), list.end(), - [](const GLVolumeWithZ& v1, const GLVolumeWithZ& v2) -> bool { return v1.second < v2.second; } + [](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.second.second < v2.second.second; } ); } @@ -784,8 +783,8 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool if (z_range_id != -1) glsafe(::glUniform2fv(z_range_id, 1, (const GLfloat*)z_range)); - GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); - for (GLVolumeWithZ& volume : to_render) { + GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); + for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); volume.first->render_VBOs(color_id, print_box_detection_id, print_box_worldmatrix_id); } @@ -814,8 +813,8 @@ void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface, glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); - for (GLVolumeWithZ& volume : to_render) + GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); + for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); volume.first->render_legacy(); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index ce7bf8e975..f8f29d2705 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -412,6 +412,8 @@ public: }; typedef std::vector GLVolumePtrs; +typedef std::pair> GLVolumeWithIdAndZ; +typedef std::vector GLVolumeWithIdAndZList; class GLVolumeCollection { @@ -505,6 +507,8 @@ private: GLVolumeCollection& operator=(const GLVolumeCollection &); }; +GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func = nullptr); + class GLModel { protected: diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 29c64b9a5c..ac802d217e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3490,7 +3490,7 @@ void GLCanvas3D::_picking_pass() const glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - _render_volumes(true); + _render_volumes_for_picking(); m_gizmos.render_current_gizmo_for_picking_pass(m_selection); if (m_multisample_allowed) @@ -3675,13 +3675,10 @@ void GLCanvas3D::_render_legend_texture() const m_legend_texture.render(*this); } -void GLCanvas3D::_render_volumes(bool fake_colors) const +void GLCanvas3D::_render_volumes_for_picking() const { static const GLfloat INV_255 = 1.0f / 255.0f; - if (!fake_colors) - glsafe(::glEnable(GL_LIGHTING)); - // do not cull backfaces to show broken geometry, if any glsafe(::glDisable(GL_CULL_FACE)); @@ -3691,27 +3688,31 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - unsigned int volume_id = 0; - for (GLVolume* vol : m_volumes.volumes) + const Transform3d& view_matrix = m_camera.get_view_matrix(); + GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Opaque, view_matrix); + for (const GLVolumeWithIdAndZ& volume : to_render) { - if (fake_colors) - { - // Object picking mode. Render the object with a color encoding the object index. - unsigned int r = (volume_id & 0x000000FF) >> 0; - unsigned int g = (volume_id & 0x0000FF00) >> 8; - unsigned int b = (volume_id & 0x00FF0000) >> 16; - glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); - } - else - { - vol->set_render_color(); - glsafe(::glColor4fv(vol->render_color)); - } + // Object picking mode. Render the object with a color encoding the object index. + unsigned int r = (volume.second.first & 0x000000FF) >> 0; + unsigned int g = (volume.second.first & 0x0000FF00) >> 8; + unsigned int b = (volume.second.first & 0x00FF0000) >> 16; + glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); - if ((!fake_colors || !vol->disabled) && (vol->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) - vol->render(); + if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) + volume.first->render(); + } - ++volume_id; + to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Transparent, view_matrix); + for (const GLVolumeWithIdAndZ& volume : to_render) + { + // Object picking mode. Render the object with a color encoding the object index. + unsigned int r = (volume.second.first & 0x000000FF) >> 0; + unsigned int g = (volume.second.first & 0x0000FF00) >> 8; + unsigned int b = (volume.second.first & 0x00FF0000) >> 16; + glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); + + if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) + volume.first->render(); } glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); @@ -3719,9 +3720,6 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const glsafe(::glDisable(GL_BLEND)); glsafe(::glEnable(GL_CULL_FACE)); - - if (!fake_colors) - glsafe(::glDisable(GL_LIGHTING)); } void GLCanvas3D::_render_current_gizmo() const @@ -3999,7 +3997,6 @@ void GLCanvas3D::_update_volumes_hover_state() const return; GLVolume* volume = m_volumes.volumes[m_hover_volume_id]; - switch (m_selection.get_mode()) { case Selection::Volume: diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 53551a4728..228e3ca896 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -605,7 +605,7 @@ private: #endif // ENABLE_RENDER_SELECTION_CENTER void _render_warning_texture() const; void _render_legend_texture() const; - void _render_volumes(bool fake_colors) const; + void _render_volumes_for_picking() const; void _render_current_gizmo() const; void _render_gizmos_overlay() const; void _render_toolbar() const; From 62d9a44a0300ff3e9418115b2e4488211c5cf39a Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 10 Apr 2019 12:10:59 +0200 Subject: [PATCH 13/57] Update SL1 icon --- .../icons/printers/PrusaResearch_SL1.png | Bin 55962 -> 63151 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/icons/printers/PrusaResearch_SL1.png b/resources/icons/printers/PrusaResearch_SL1.png index 3c15f95eae07c3e7bc77028317dbe0f111156982..b4425a705f313a0a7e707ba507966d10c5d2583e 100644 GIT binary patch literal 63151 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%0D^YK*CWu|F}8q8|$?C1HM5&W?WGtAnfbG~9Ln z!@TXzEs|ehl*Va8*R_%$lA@$;=5%9$4}N0BiDkXMQX@LQN&fg|Ni0JfiFzYrEpM#z za^Z)^@^wV{aUOnLkm8}^Q0sXk#K5Xskz$mt22I=bbgLF;Ev~9XDHp@Yj)TN(T2t2z zecMVHMO}0K@Bn2TuCiriCQ{p`b|pDBjK!Lw{5O?v_;v{^QS0>(Q-HI{v|%!>>Af!p zaK`t6x~bUSo^$>Bg0}6bogIlza*zn6xvuLnmMKDrBhh6*(MLaheWSnJz>pLg?WSw* zQM>-FZ`X6mYtlnT7fNQP1n?EsTFgl7Zr`l~_Hw54Jw{okvxdH3u+&rTxa$Eft)AiZ zg$1sv*{vGdzNPn`95XQoHm>Lq032Fvt+WVZHuTGSsQ0mmZ!}v}8!h(e+LYh;wQgcn z8-H(J&++R_^P;9pQT}{eq4OXJiPF~;+`}3%XMEXG2^)z*bLb2*PAy3!}iY1T&+U0_(+T~66y@NaME)FHeuFS{GnujKdVIfP zQXBf-Go4K6`%Z2?r9m4Gmn)pn_e#1R=y&E3@$ZTd2Qx-pVtm!O8v7>fol)5pW5->0zM1zv^bjHR#4IR#=sg&XK@v*pN(e)~2vMwY zH1&jKw?rAuY&s=1PWa))zg%krDWHvFHlO4B4&Qgu8N?v%yS7F6<55b~ea5Vf8&l3V z%<0Bx76&aT&Gyc`__Iao%HjJ^ES?lfi6@*e*WzgXHMq+RD7r07#gr2{4jTqF@&X2v z3^kZol*2|wvZErunc(&DW{4?a%8e0Hq{>->6=FiAKwZ}u1*^7`JnEc)L@q6DQ_$w2 zH%?g*ljxJJF@(;KdDlkC+!jJYdyg@ONwq~~BU)vS?HogsrM0z=oP#*TY9m=`z86x7 z_k zLu80^8jLB%TbzyVf)a{Zco*MJ&Jo`QF-Zbe4W3>xC7B^nR8>tVw}dTVN*eXdB2DeP zmgRE6WIAQJJVc>!mAH}m-V1?5OM7R{a9fx$&w4O$pqLWGlmXi(B-lhvRStsQ_c$lT z9(}|)@tuoaG~QNgtV%Q!huSeHBOYopqQ-GX&A?*0V9~ZHpznPNa*X&8X*=QHw{6SC zOINw=mb-cX!w(TdhYy*is#tU_IR^Tm2+>ROXkiOlD`_f|$rQ3@vAoW7GG|gXbiIr! zv&j^lp-(-18jK?aP1nkzbj16fsdI$T(Pgn?O4*RmVjnyDtglCv0qHkJRD;$!f{$Vj zF`HqG=J0TVbCu+qr3_k+2b50%qtuwYCP=(qM=>ddF?rmSh_{;_jVes(`iA|zfVcL@ zt@S1X-sIoI3Wl}&XedUBMk%yW^j#nVrW9toYME`F;QqJ1n`fSSl4??MaB#rE;XyHF zR%jN45SUG;)OAyAfYm739Mx3TvTB7H=3GU}ncnwkBkrSegVqu)6ukgMnX$cGc0Dml zWN-?-kZFuA?lz+B-kxoNT4AlYy7(w9?cuUR>k3pJWwO^7OSBc%SqQ$c zy((f73*_JSVw~)R>7*Wa_&YoELS?1IBg zg_H`KE?(o-Rz;UCRNLU!L<|PU6fHdR82W-242XG@n5VebLU)}ny!4V5Ln!N}gi?li zGbO1^?Wkh(Avsgk^DWSB*Rv8}yMvG`87W0TSm}J^ocGx-M z`#{(CvbV1%#Ng3rDsAvF(X}fvX|Q&E2Qr^21GgYOrPIGuo ztfsWmWz2%gI(*4btE%D0ik#&ip{~Y}5TuPN`RbCX2BY)#y2y{Bi*KGMm7JR)@^(N{ zl^X71$)7f5CR63+Pzownv%fp%^tto=@t^z&&YypcWxLH!{OCt{>`Pzar5Dff@Bh>X z**~_$E3cg6(MP|`$x|nZA@JI{m-)=6e}}E@El34HSK@-Il(6dRx~BJmoD#0I@*za( zx|Y3r7T$G^vQM^EjnNYI6QdA%hEPCfD^kh?FInr(SVBzVkggI+8}S=Anl!`)6@GRI zk;=-=CT0(2y!~eP3I>v?9&vX099e(mBf0Vtg z32m?V)nEN}E?hojIu+TaF|H6&kleMRwYDYfzVlIh&Ifgrk^(VCVw!UDMbjNiZkZal z1Ik)@-wOf68hk7iUIigTWkoDwZIRWE`gf6!#vtu+T~}yh=z5WBPNz*VNQ}igD@%@; z$v7)C;KfpO;pBu$MwZ`M39gVcA?0G973HRHmt)SF_Z>=EoHa$|6^pQ?B*%{2@?r^9 zh$=BnSUt0|xP!Wa4a z<4#%NKT7plN2sv%DK;W1}G@nZdI* zRP}__a)I(`WVxzl>NLaKx^@nFE_jz`nQw>=hK54&x|Ryxp=y1%E6)sc3i?4sqNWpc2wjyQ@cLg z1gT`5=^@Lj_ozyO@_g^{J{9FzV!2o_oA2;{`j7w5yngNsr_a2`WIpAV<9odD(mB5L zr7v^2Z~664{yOjb{txi{^Di*pKglyMp5>oE@>h819dBo`>Zs}(rxnYjxw=qPR#BC# zupO7^Lk1+`j&+04k`3;h#2P0bsBKliKrxKaKxH*CX~7qw zM=6V`O;Leme9RalPAs$8l!dFtfP+}})2g)TF@$o17kr|m!sY@Rh8S@e`qnd@TPCwc z-uED8jD6>+i=MpmqK&s!$$^m8oPQaoC{p7rD@bExQi*8{Ul1I^pxw7m>3mn*}Mb2>92j2|HV)L1$K|0;M(QaNPW+L_E&#~J05r!55D6q{6GG$f0eYe%bl~t|MblV zymZC!l`D>^j+|d94nl=BiDQ+JLHZEJbk&@NrDv;J?30veRzvXEVr^78@jeSh%wmiY zrJaJV?=U56J0UH!ga_Ip7?D5U`@STP+9J+Th5S?a#Pjwd)-7{>T~T zCR(9oMi!PXe%$ik){Y?3#;Q^Xsi==~&eW3$Kk~5;^Qm9^HNN!uCwS=H53{qg#gk7w z&$TO;xpw&qS1w=T>T754s|9DzoMATK=O=#rNBJW^{$WntaVKXkwfyC`9rCf;d+w`y z{>p;~{Kr#3_)G^bu*5WqzI1O>xzc+jFrvBu9_dnZEZ$n=h=s zAtNpsOUe=>)Tco39wNjfuYZ_r6$xj{p@YVUI2M{2ZtBqaZ|Bm7M`?q2A2YV=WqNi{x|>IzsT{E`+W9a{0qMNl`nDsTi(R& zcizMMKJ*d3=e~P+>BX~r=665KY-fu<{b&Cy^Q~?A9zOJ;53;!WI{)lt%jKoxFj*F> zL}fLNar}?&T=IkaD?)bS@oqO6W@_BiS}ob!A%tR2R7@_k-2N0J#1!(_idR)7GiS`;g@Tw8f+`N)|afQ#Vt#=3C;y-V6F4 zVg!}(g)VHAE~Ap?aB+wtQ#FTovQw$^I*TwB8hB%r>hIO{mQIUVEpWs7EDXl>_ zt;$UxEtydYANry<79vQ>iOFO_*LD2g{xAP2FTVH^Z+quk_~l>vB_8>m-{Xm|JXFv0Q;0vGrU6hKv^W6`#GvDTiKm1M(E}!G6#~ZOz!W@H;On ze(=_w_uQK~>=KWj*Zlpj@30I8r-ULJ;@Vm`kg04%;~a@3F+hN}y2SU$htd>_DG72E zaZ8K>zJ(BrSDW0}wyH!z+PC92!5EDh^)Q;Y^ITgjfJkNCNN=sGMsmxFjkPT?NY+*? zmIrj*lFC{t>#){=ks_I5@zT#3=gLSIBFad@i|E_aQB2a!1Fdfw z^cO~zVnk~PhLb98BSJ?VzAcf*o_dnmbjFGOJ^o+6{7*P`Y>%%$^>se`nNKsD&Uoav ze}flJzszdY@l!wbr|5gnCx89ddGrgPXVnJ&{Qvxa#);eR!ltu4bSm-cfnyQiNAB$T zjUT%B*-o~o{k)H8rKC{vM&frBZe9P1n@L_!wePAenG zzq0bZ*P7XEHnz{&$W4q7BN8sjvy88ASZ$?Zo(SupgQ_IbS}pI}BDEu+NO9J&WCQstP8p*jt;HEV)tBmAu zrNW3(D12E@Ib+z{-IEryZMo&vlcQ1p{)rO+JoD1aOr{N0)A0BH{?CuTa9i8kOzMii z_1FJ8+xvU`?dPYw?`ourhO-BXAG#;;J(I+vR}>e6W~b5Qn5dl-8mpI^rT1Z6jmeH* zB%-oM4-!&XfT64uNhxyZNU?Z_Ybg$iVRpEZ&y7|k|6F$Gwr+4b)2AL&bgC(9l$Lq7 zc8P_pj}1EVw|z^Emb9Z_Z$<`sv$FU8=06h87s4M@-kBC zHEC(MDDyaztMPE79L}gBzl{=KODRKAygeMo`Ug~Pi$4_e#T5OZTiW5~JD47dW zE~}iB3gBc2p_iLgi{pvanr!62N#!O?3XC$4vN)$0gF+Kh#1*lLu?F7?J`(BYSWq^KVShwH-NSnu$dvMOM<tTbRG|eA%tKS?tqeT=0>)7|1zJ0d$u#wZn7SL{F9vAL7M}VLs4>jd zsaQB0qlpDY7w@;Qk;Sgc`yiy95F#-|Vv0xY`c2cVXOh}z^YjfS3`i(Ff)Wqe*xDHC z8Uc@r>xnV8v~xo1gpA5+)NZSC+X`3{kz`w8l!YWpuI);soGAousdXvBMACB0XkBEk z+E79o89w?`i4=ZN(%(6_Ss24&S%*(aUP7s>V=5 zTSzep`8vr+WOD(bQdvP-t7)v0eQ^j(>xSt2a!W>*s|AzEl&NzZ93En=#u_Qos5Lrg zLe~;g#3d`=3sgBs^{CVv2(Gyl^UzL<_nRpOoUS0_g3NdQY8=NJS4qd?C6?HGPfQ7o z9_DGG=o($n`?75@8>X4VU#r*3wIQob0V#y~sK{>`XmM@UCmvmLy9>ahM%!IA1|hD) z6^xb-NbMRh!pzd>vYJ{OHBb^ql`*3;tTB!dJ%a=Ppg2=#lupH2MN8VJv7{_1o35%! zA&8pI7*dcz*V>YTm=U_Frt?CQsjG&r?;(jOMv>`5hgMcxQ=0v3L+4dqvsId=ZVJC%%o{^aBZe~AFK1WmH1k@QE}H{gZudNj zP_!A`Xj19?@~9$2t^MC5Y$0P5)RQTbN%PuKk>4ycxm5H<`?YoeHO7caEJj&fX-$Yh z=FkvO%7_tR--$6oOiU+}F=|OEQ^X;5EitubT-D^Pgvio&sH7l)bBd^>g=UmF7L}S4 z*BY!9XBgiJ!z%=g(^O48nn={nN{5ie(`>n16w0mVh23I=w-!K zRaOpBBx(}4QnmCJI7=nY-L|f0nEh5ED;qz zqU(CBv7@E&dc7rPh7vB}QCh~06ujKPg^zBnEyjmooY^IrG@v!OO8D^bnA3({ zDZKC5-rf-dis=h*bIcouj{>Gu&dL}Oyx18Vi?*&f zlBTi~k7VzQ>4MyXR%eW@h)E182Q`|~x;U-|nn^PnYFJmOuhAEjKQo(JCiRpQdz2~r zZiHPo2tHyGC0(sGy$^J&4iWn)QdBr&Q6;G*$Hbyt5{K5luo6>Qr)ebwpp=CVua(3k zp)$@Gd=!;h9|TPojzca7UZDn)&_=lN%VjIznl;oV{wTymRSjAJS-aJZ?B@Gbo61?|cW>u3zQ%zw!is>QDSR4zHf&)1Uh-wzqe%MnoRY z*@6n2F(O#7gkl{`9>WO+ql_2>N=d$VEE@uah9r9L;dT?2XTrY@2#|^YD9)AsUuQ6IVQGeQ@U8FrCH&ODGasDv}s5&O6sZB zl6jCa%37+rq4)hLpm9yDw^D=)S#b;5)s@Z#j7~J^%c3vO*i2Ig9XMwOTNn%!CeF z%mGlci_Ix>>&YGdi=X=i9(wCN{KOCc0FQj;lRWnL=Q%cSc89FR ztk$%v7H17f3mQ!^<|k4}E#Ndf_u@;u_uW6p2j24#Pe1zvlgX5i{@@>CZyLFH?i!t^ zWT%Us*?Tcju-1?iq~4Dxa8p%eC_7>S5Unvx>Iti^9ixT@Ybc5&RVhUb9_y-7JX#V% zeUEd7x{{SwA3X|9Wu>Q;`}mY}7`}tR6)r=)G_RW|LZ2XxSEliv%uMEBWbtUw8n?msz`L^9Q2{m6^B- zV-0;5=-bTVaK(l5mpEK3NGbEu=@;*-t$i}Yh^iZ=$M%Ki9*>Irrg>at3tdu|8%N-r zOeyBfI`FP|clW&?WsPrt@IlUAe2q{2%g0N~>5@sMxpL)@6Q}N^o>!8PsdSMq7Q%

9H^H+{>rwyN-{2WQ)&#`B8r1mwu6l zA9{$YX$VV+-L0G}h7z(?Q$|)xAq0szO2Pwh))xP_!AwyVqe_d?!h<(jcKp2;38nXP zI83J#oHg`8HqTw#;hd9gLZOlxBki;g9v?H#)`TE#v6H4IrAY4s)9Hj5dc03esv4K1 zlL%eQ*47TjiN0S&F?OtsWoK)P3s-K9&^yL)Kc z92NP^^28WsL;R7lvR2Z1WBCe%7^sXB4tSC{T={_ZJn*J_x##XXxqf&^>Uw5&#;WVs zy=9l#v>`?*YGXyx$ws2-z`1rJ4HI=pae9t1;VLQoV|zQK5NSKl@x48|zUQfDpXI#| zznA;obT_MZK@5qeuIW1QF0Y%3AmE|L7&ofcVrfN7GcNgH#ido1TirM<`Qg?TtE4E0 zj}y72HHyxQ{gkVwSZyVSxKM$!5@81+(A1_Ro(kh`I+@bymW;To)K0!HMm2=k(YA+T zh@lN3XI6bzoM?>PsNS;bw7#dRs=~}uWit>Zfu<4zOku;B(V|IdBP%@$4Yk-d zr{X_XHxnkay)Sjk-d{L>VgLHIL$tAMZOuCchjlZ#Hk)sqR>pqf`0?Yvy1l&<2M|%A zNWK?#q&xC>yJ?UZexJ~L zPCj@K&N;TWYI3gV+m2>7p=lL{bTr1@#3*WRoH*mQ7Y0cDHwU`P?hK{Vfmh?uXt_h>CZ; z<83_g#Fx2r`GC7_zn8j^Kmu!ob{j*W?>dapf>;|fw%>!WqN;0rzXD~b+$QHevCe1{ z%8}lCoU5rTS^Fua>Ac7H9n%9u2- z8-lCo`VLn$s4R%Qab}DnvI9w}_}|CEw2MF}p^a4~Tam$nsxR-UEctUuRE;QQX=Xc* z+iLq4CzJWlZ%yjgy1u3BdRY%TgPaR_gI1fyZA1Qc%0l|CX1tMEuDahyrhe<~ci#Os zyVc^)zkKEulPDBLV}z)o3^96gv4+x0qn)7dbycC2rY-qsOQ}$OQ_nre@%@u*Z*74B z-?j9l!p71lBDTan<&?;U9vi&qo^?@F#h9=vLr!#^?8em?;Y?|1!>HB^zAuE6fVBpd zqTIA8fyuPKr>-2PtnRY1g%Vn;;x8DBKPd@S0A8S|7+~u6u_?qIstrRrXRQTZ~pfD%VaOV>6 znQ|tTh@HZtqORoDZdV?qMPn@-YyrFS&<k-F7#ws+dh@%%(HWz5MF#^UuD_{_$gk=vlR5FD#j7Vog-n4wAIp zRXrQydJYd3#Z9C{2NhJ9(YRnLt!)q$Q;Mlr9a)q$L@#+~gR4tUY0U7dE7_Uv?H&_= zsc`KpD~NYSa@)kFD2$)Ci46d?-=%uv= zDr2duie=Z~oFuelBY&5*TF`04ma1uH1m9Ctj=HWnTpVH$y6<#0Bj&_vwG_}(Yo?Q? zB*_$CHzd+Xyesm^AsEt9^oBdj;AW$XON>ZpGbn?t_dccE^zYhc`cG3NC}YUUlY$*R z+`pTj@Ax8r`q}4;I3-ax4c88?ztDH#;R~-_VmezgnawD~c0DLXQ`M}x6)B15xh?H_A0tXjOj0PG zYo?^C#28EaY6{Uq#2bCz3t7WCNQDZkWN)k#CH*6h^N$-d`LCj`zLH2q zDVElJrlaS(qsX6m{)P1yYb{p~yASOiyY(+B!XJ(?+!;eU)pe~_1$;``aJX2}wktx2 z=Ydy1pN^TY8Qc7od+ynxPg!fFkCD}0jQ*~WxF@E3s_oiaCUt#6p-w2JjyH96%Ex$% zDs8ZHx~SC#|8RrV70XpCp5us(aZItKb{bJ*74=g{CEnMnv3p24;VM&R?_Oe$tizh( zz@lZ3-1~?r%Bz7RKnXEG8Jrb1obLxfOH2~i{_47jMl{woWbI`QXM`ia$OjmuCDW~P ztd>ivsxJI{%VajgmoCRv6-pbrRf};ARWlh~SqIdAbb&3mcFrDSYP6aEZg$gONXq`A zaZPKKgfo`@{ive98;g9>tVs*bSuS&My{hN`$$V@2pIp0k)vZ>mQ;Un&_Ka5BDko*# zgst7lVwTd?l=E4oRA-IjaJeiBDH+If&KEQJqEhPF;pQ1sZAxpDaimP+y}u1(Zb>?y zR7#zQF`a^R(pr0K^x^KT)2Wp6o;GIQhaMf3bPh&Z^$=xWpQFsJ+dI4TzLngvVs0?x zqKR{gSTmtCDSJX+lv-6Kin^{FS83!-4m}}eYNNPk+JK=`Xsf`P~2{-->VDQDGky`8NV_kWwJZ?!+sl&g87m47h48Dx{3?r8AM3 z1qs*x1A4$=6;txGTBXzrF{T$Js<;qgkny4GJgzc2rM#y!$CT11x)4rkpH5n9PIf7s zR9fFg)_40DPASD+PJX6}RZ>nFTVj)A5{y8=w=^cORN9R#{-lfv+B!&Cf(wm7_5GU5 zZ4EJJVUH!S{i- zk5sPV*nEo%{xvAwg_z5Fa?&)--j`i9`#Eg$@jj$iRm{bZlJyGk=s7C#|DlDi#?;C& zy7J%H2WcpLF;`%8AJM`$Pr#*|xuleNR;fhjWp0kS+;Fn$($=J7?P|5xM@~9tPE?h< z)yH_R&f(7B%V(-Voy>Q*UWrnr@x}i{O?xH{8N>YP=}m4au(sx(Q{Pf-^Nd2%N4PSF7vPiJ&}}B z7nRl*RZP#u!n6w!2x95f2T$WFbM@N6p3-JNMt@h=cK2ZPEza4KO6wEe`xC+Y6IERu zGg=?(eK#d1*)S1H?6MI#Zh0*+MIo&yjj1e6)8P9SZ7Xb5k$*RKAM4F?*@bkx za`uGQ>XeW1q%#I|W^Z=~XDTjrE!r8fQq&YU&Y77Y(7KV*`IauTmPXOQ(QVw_6SHjD)OV}4;nvE8_~am#c*MB$zhOVO8G*H z>4J~>X@Dgmw5I3`41l+ zE@V|UnXtRNLsiX~%(gMb<4tK%s8!C<)HT!TlyKv4*zwbUt(H)UA)UtJR;0(O1qn=JOdP2K#b zo2LG7RaJKyWA;-{lOd~2DIw=97K>%?!{y0z>lNeb&(+QLKe%#r^<>+wN^CO3zGwSH z#dIX>96kS92laP~obKp3dXAoN{v5%VN6*o7ROCm`(R1`175UL~^c+1$MSk=gJx9+` zksm!r&(U*K literal 55962 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 A2 z@T$9h9mVzQ7y`Kd9^aob1prX@-FIKs@AtK}R%xw`QYs_rWZv6#rPLn+^e1IeEL!U* z%QNIz24iyMd5$!ufP&HnL>$H~v4&<{O=uYmmbv)?*4EbO+_`i5kw+e}xD8^Dc)R1j z3)&!W@qTFX5PoZG>vn;5@xccldJ|A51pd=rB=;@|V3bnHr6H^qq@Y*@*(@`R*3fK{ zXh!CG^E0*X>(_rNe({&M1_%Srv0KYBT zGkJ?Uip|YU_U4x!@LqmzaB#~vep<;s=b*4DA|;fG;wmxhZ7;dNx#fW6%!(4s|)Ll6;4e?I6n&YU^J zue@???#dPEojWJ`gIr5Qx+0Rj&-bVg^Ez9!Xz?MidwF;H^y$+dI0!|=T)ZfqJN>#^ zv}kdM&>+J;%r*3#xK4`}Ek4F*)A1H9TC`} Date: Wed, 10 Apr 2019 12:16:17 +0200 Subject: [PATCH 14/57] Do not activate Delete command when the wipe tower is selected --- src/slic3r/GUI/Plater.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cffec2062c..78ed7fc91a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3046,7 +3046,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape) bool Plater::priv::can_delete() const { - return !get_selection().is_empty(); + return !get_selection().is_empty() && !get_selection().is_wipe_tower(); } bool Plater::priv::can_delete_all() const @@ -3297,7 +3297,7 @@ void Plater::set_number_of_copies(/*size_t num*/) bool Plater::is_selection_empty() const { - return p->get_selection().is_empty(); + return p->get_selection().is_empty() || p->get_selection().is_wipe_tower(); } void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower) From a80978f84ac4f8a0909e0a88202560bae7ce2da7 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Apr 2019 12:20:07 +0200 Subject: [PATCH 15/57] Do not show as hovered the entire instance when hovering on modifiers --- src/slic3r/GUI/GLCanvas3D.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fb064596f9..1fc75a579a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4000,14 +4000,9 @@ void GLCanvas3D::_update_volumes_hover_state() const return; GLVolume* volume = m_volumes.volumes[m_hover_volume_id]; - switch (m_selection.get_mode()) - { - case Selection::Volume: - { + if (volume->is_modifier) volume->hover = true; - break; - } - case Selection::Instance: + else { int object_idx = volume->object_idx(); int instance_idx = volume->instance_idx(); @@ -4017,9 +4012,6 @@ void GLCanvas3D::_update_volumes_hover_state() const if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) v->hover = true; } - - break; - } } } From e6439ad01038a47eb1069e2f689ae3ea703bc0f1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 10 Apr 2019 13:32:21 +0200 Subject: [PATCH 16/57] SLA gizmo fix: recalculation of the mesh was sometimes skipped --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 45 +++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 2 +- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 67abfdd35a..8c8f24ba04 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -47,11 +47,9 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S { if (selection.is_empty()) { m_model_object = nullptr; - m_old_model_object = nullptr; return; } - m_old_model_object = m_model_object; m_model_object = model_object; m_active_instance = selection.get_instance_idx(); @@ -62,9 +60,6 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S editing_mode_reload_cache(); } - if (m_model_object != m_old_model_object) - m_editing_mode = false; - if (m_editing_mode_cache.empty() && m_model_object->sla_points_status != sla::PointsStatus::UserModified) get_data_from_backend(); @@ -241,10 +236,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) bool GLGizmoSlaSupports::is_mesh_update_necessary() const { return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) - && ((m_model_object != m_old_model_object) || m_V.size()==0); - - //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix)) - // return false; + && ((m_model_object->id() != m_current_mesh_model_id) || m_V.size()==0); } void GLGizmoSlaSupports::update_mesh() @@ -267,6 +259,8 @@ void GLGizmoSlaSupports::update_mesh() F(i, 1) = 3*i+1; F(i, 2) = 3*i+2; } + m_current_mesh_model_id = m_model_object->id(); + m_editing_mode = false; m_AABB = igl::AABB(); m_AABB.init(m_V, m_F); @@ -740,10 +734,6 @@ std::string GLGizmoSlaSupports::on_get_name() const void GLGizmoSlaSupports::on_set_state() { - // Following is called through CallAfter, because otherwise there was a problem - // on OSX with the wxMessageDialog being shown several times when clicked into. - - wxGetApp().CallAfter([this]() { if (m_state == On && m_old_state != On) { // the gizmo was just turned on if (is_mesh_update_necessary()) @@ -762,23 +752,26 @@ 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 - if (m_model_object) { - if (m_unsaved_changes) { - wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points ?\n")), - _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); - if (dlg.ShowModal() == wxID_YES) - editing_mode_apply_changes(); - else - editing_mode_discard_changes(); + 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) { + wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points ?\n")), + _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + editing_mode_apply_changes(); + else + 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_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_old_state = m_state; - }); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index c8abf15f22..0b66ed529d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -21,7 +21,7 @@ class GLGizmoSlaSupports : public GLGizmoBase { private: ModelObject* m_model_object = nullptr; - ModelObject* m_old_model_object = nullptr; + ModelID m_current_mesh_model_id = 0; int m_active_instance = -1; std::pair unproject_on_mesh(const Vec2d& mouse_pos); From 9fd8461592b42f52166489d91da9e3b468968d88 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 10 Apr 2019 13:36:15 +0200 Subject: [PATCH 17/57] Changed the scene update on RELOAD_SLA_SUPPORT_POINTS to delay the loading if some transformation gizmo is in action. --- src/slic3r/GUI/Plater.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 78ed7fc91a..5d7436250a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2598,25 +2598,21 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) this->statusbar()->set_progress(evt.status.percent); this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8("…")); } - if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SCENE) { + if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE || PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { switch (this->printer_technology) { case ptFFF: this->update_fff_scene(); break; case ptSLA: + // If RELOAD_SLA_SUPPORT_POINTS, then the SLA gizmo is updated (reload_scene calls update_gizmos_data) if (view3D->is_dragging()) delayed_scene_refresh = true; else this->update_sla_scene(); break; } - } - if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS) { - // Update SLA gizmo (reload_scene calls update_gizmos_data) - q->canvas3D()->reload_scene(true); - } - if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) { - // Update the SLA preview + } else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) { + // Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways. this->preview->reload_print(); } } From 2dc88ab0be9fa61eafb1858b50c2d02a6807dfb9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Apr 2019 13:52:18 +0200 Subject: [PATCH 18/57] Fixed crash when typing on keyboard during the app start-up --- src/slic3r/GUI/GLCanvas3D.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c63d265faa..6d650029f8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2204,6 +2204,9 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) void GLCanvas3D::on_char(wxKeyEvent& evt) { + if (!m_initialized) + return; + // see include/wx/defs.h enum wxKeyCode int keyCode = evt.GetKeyCode(); int ctrlMask = wxMOD_CONTROL; From 7a1fab09d466d8d48eab7e33a8eac0ca981342ba Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Apr 2019 14:03:40 +0200 Subject: [PATCH 19/57] Copy and paste -> Clipboard refactored to accept more than one object --- src/slic3r/GUI/GUI_ObjectList.cpp | 1 + src/slic3r/GUI/Selection.cpp | 81 +++++++++++++------------------ src/slic3r/GUI/Selection.hpp | 17 ++----- 3 files changed, 38 insertions(+), 61 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d53d0809ed..c36b8b2883 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2167,6 +2167,7 @@ void ObjectList::update_selections_on_canvas() add_to_selection(item, selection, instance_idx, false); wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); + wxGetApp().plater()->canvas3D()->render(); } void ObjectList::select_item(const wxDataViewItem& item) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index e0a5365610..9179fce42d 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -47,23 +47,6 @@ Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_trans { } -Selection::Clipboard::Clipboard() - : m_object(nullptr) -{ - m_object = m_model.add_object(); -} - -void Selection::Clipboard::add_volume(const ModelVolume& volume) -{ - ModelVolume* v = m_object->add_volume(volume); - v->config = volume.config; -} - -const ModelVolume* Selection::Clipboard::get_volume(unsigned int id) const -{ - return (id < (unsigned int)m_object->volumes.size()) ? m_object->volumes[id] : nullptr; -} - Selection::Selection() : m_volumes(nullptr) , m_model(nullptr) @@ -1046,33 +1029,36 @@ void Selection::copy_to_clipboard() m_clipboard.reset(); - for (unsigned int i : m_list) + for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content) { - const GLVolume* volume = (*m_volumes)[i]; - int obj_idx = volume->object_idx(); - if ((0 <= obj_idx) && (obj_idx < (int)m_model->objects.size())) + ModelObject* src_object = m_model->objects[object.first]; + ModelObject* dst_object = m_clipboard.add_object(); + + for (int i : object.second) { - const ModelObject* model_object = m_model->objects[obj_idx]; - int vol_idx = volume->volume_idx(); - if ((0 <= vol_idx) && (vol_idx < (int)model_object->volumes.size())) - m_clipboard.add_volume(*model_object->volumes[vol_idx]); + dst_object->add_instance(*src_object->instances[i]); } + + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + if (volume->object_idx() == object.first) + { + ModelVolume* src_volume = src_object->volumes[i]; + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->set_new_unique_id(); + dst_volume->config = src_volume->config; + } + } + dst_object->config = src_object->config; } - int obj_idx = get_object_idx(); - if ((0 <= obj_idx) && (obj_idx < (int)m_model->objects.size())) - m_clipboard.get_object()->config = m_model->objects[obj_idx]->config; - m_clipboard.set_mode(m_mode); - m_clipboard.set_type(m_type); } void Selection::paste_from_clipboard() { - if (!m_valid) - return; - - if (m_clipboard.is_empty()) + if (!m_valid || m_clipboard.is_empty()) return; if ((m_clipboard.get_mode() == Volume) && is_from_single_instance()) @@ -1767,26 +1753,25 @@ void Selection::paste_volumes_from_clipboard() if ((obj_idx < 0) || ((int)m_model->objects.size() <= obj_idx)) return; - ModelObject& model_object = *m_model->objects[obj_idx]; - unsigned int count = m_clipboard.get_volumes_count(); - ModelVolumePtrs volumes; - for (unsigned int i = 0; i < count; ++i) + ModelObject* src_object = m_clipboard.get_object(0); + if (src_object != nullptr) { - const ModelVolume* volume = m_clipboard.get_volume(i); - ModelVolume* new_volume = model_object.add_volume(*volume); - new_volume->config = volume->config; - new_volume->set_new_unique_id(); - volumes.push_back(new_volume); + ModelObject* dst_object = m_model->objects[obj_idx]; + + ModelVolumePtrs volumes; + for (ModelVolume* src_volume : src_object->volumes) + { + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->config = src_volume->config; + dst_volume->set_new_unique_id(); + volumes.push_back(dst_volume); + } + wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes); } - wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes); - int a = 0; } void Selection::paste_object_from_clipboard() { - ModelObject* model_object = m_clipboard.get_object(); - if (model_object != nullptr) - wxGetApp().obj_list()->paste_object_into_list(*model_object); } } // namespace GUI diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 3eb63486b1..e04c37356e 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -154,26 +154,17 @@ private: class Clipboard { Model m_model; - ModelObject* m_object; Selection::EMode m_mode; - Selection::EType m_type; public: - Clipboard(); + void reset() { m_model.clear_objects(); } + bool is_empty() const { return m_model.objects.empty(); } - void reset() { if (m_object != nullptr) m_object->clear_volumes(); } - void add_volume(const ModelVolume& volume); - const ModelVolume* get_volume(unsigned int id) const; - ModelObject* get_object() { return m_object; } - const ModelObject* get_object() const { return m_object; } - const unsigned int get_volumes_count() const { return (unsigned int)m_object->volumes.size(); } - - bool is_empty() const { return (m_object == nullptr) || m_object->volumes.empty(); } + ModelObject* add_object() { return m_model.add_object(); } + ModelObject* get_object(unsigned int id) { return (id < (unsigned int)m_model.objects.size()) ? m_model.objects[id] : nullptr; } Selection::EMode get_mode() const { return m_mode; } void set_mode(Selection::EMode mode) { m_mode = mode; } - Selection::EType get_type() const { return m_type; } - void set_type(Selection::EType type) { m_type = type; } }; // Volumes owned by GLCanvas3D. From 096d23f9712496146bbdfd01229ffc16c290df8c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 10 Apr 2019 15:25:20 +0200 Subject: [PATCH 20/57] SLA gizmo fix: Discarding manual edits on autogenerated points removed all the points from the gizmo cache --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 8c8f24ba04..74c6faa85c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -820,11 +820,19 @@ void GLGizmoSlaSupports::unselect_point(int i) void GLGizmoSlaSupports::editing_mode_discard_changes() { - m_editing_mode_cache.clear(); - for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.emplace_back(point, false); - m_editing_mode = false; - m_unsaved_changes = false; + // If the points were autogenerated, they may not be on the ModelObject yet. + // Because the user probably messed with the cache, we will get the data + // from the backend again. + + if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated) + get_data_from_backend(); + else { + m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_editing_mode_cache.emplace_back(point, false); + } + m_editing_mode = false; + m_unsaved_changes = false; } From 8b9568797a5b7015185bf451c0cb98cf254ab1d9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Apr 2019 15:55:32 +0200 Subject: [PATCH 21/57] Copy and paste -> prototype of copy and paste for objects --- src/slic3r/GUI/GUI_ObjectList.cpp | 19 ++++++++++++++++++- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- src/slic3r/GUI/Selection.cpp | 28 +++++++++++++++++++++------- src/slic3r/GUI/Selection.hpp | 4 +++- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c36b8b2883..4d205fa5f2 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -469,8 +469,25 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol #endif //no __WXOSX__ //__WXMSW__ } -void ObjectList::paste_object_into_list(const ModelObject& object) +void ObjectList::paste_objects_into_list(const std::vector& object_idxs) { + if (object_idxs.empty()) + return; + + wxDataViewItemArray items; + for (const size_t object : object_idxs) + { + add_object_to_list(object); + m_parts_changed = true; + parts_changed(object); + + items.Add(m_objects_model->GetItemById(object)); + } + + select_items(items); +#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME + selection_changed(); +#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::OnChar(wxKeyEvent& event) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index c5e097af8d..a0343100ab 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -289,7 +289,7 @@ public: void update_item_error_icon(const int obj_idx, int vol_idx) const ; void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); - void paste_object_into_list(const ModelObject& object); + void paste_objects_into_list(const std::vector& object_idxs); private: void OnChar(wxKeyEvent& event); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 9179fce42d..cb5add462f 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1033,6 +1033,9 @@ void Selection::copy_to_clipboard() { ModelObject* src_object = m_model->objects[object.first]; ModelObject* dst_object = m_clipboard.add_object(); + dst_object->name = src_object->name; + dst_object->input_file = src_object->input_file; + dst_object->config = src_object->config; for (int i : object.second) { @@ -1044,13 +1047,16 @@ void Selection::copy_to_clipboard() const GLVolume* volume = (*m_volumes)[i]; if (volume->object_idx() == object.first) { - ModelVolume* src_volume = src_object->volumes[i]; - ModelVolume* dst_volume = dst_object->add_volume(*src_volume); - dst_volume->set_new_unique_id(); - dst_volume->config = src_volume->config; + int volume_idx = volume->volume_idx(); + if ((0 <= volume_idx) && (volume_idx < (int)src_object->volumes.size())) + { + ModelVolume* src_volume = src_object->volumes[volume->volume_idx()]; + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->set_new_unique_id(); + dst_volume->config = src_volume->config; + } } } - dst_object->config = src_object->config; } m_clipboard.set_mode(m_mode); @@ -1064,7 +1070,7 @@ void Selection::paste_from_clipboard() if ((m_clipboard.get_mode() == Volume) && is_from_single_instance()) paste_volumes_from_clipboard(); else - paste_object_from_clipboard(); + paste_objects_from_clipboard(); } bool Selection::is_clipboard_empty() @@ -1770,8 +1776,16 @@ void Selection::paste_volumes_from_clipboard() } } -void Selection::paste_object_from_clipboard() +void Selection::paste_objects_from_clipboard() { + std::vector object_idxs; + const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); + for (const ModelObject* src_object : src_objects) + { + ModelObject* dst_object = m_model->add_object(*src_object); + object_idxs.push_back(m_model->objects.size() - 1); + } + wxGetApp().obj_list()->paste_objects_into_list(object_idxs); } } // namespace GUI diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index e04c37356e..9fa9f18691 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -162,6 +162,7 @@ private: ModelObject* add_object() { return m_model.add_object(); } ModelObject* get_object(unsigned int id) { return (id < (unsigned int)m_model.objects.size()) ? m_model.objects[id] : nullptr; } + const ModelObjectPtrs& get_objects() const { return m_model.objects; } Selection::EMode get_mode() const { return m_mode; } void set_mode(Selection::EMode mode) { m_mode = mode; } @@ -322,8 +323,9 @@ private: void synchronize_unselected_volumes(); void ensure_on_bed(); bool is_from_fully_selected_instance(unsigned int volume_idx) const; + void paste_volumes_from_clipboard(); - void paste_object_from_clipboard(); + void paste_objects_from_clipboard(); }; } // namespace GUI From 4987e5a7d567d7cc1bbee3ade2bc9651e5f2886f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 11 Apr 2019 08:36:00 +0200 Subject: [PATCH 22/57] Render selected objects first --- src/slic3r/GUI/3DScene.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 61920220e5..1dd136517b 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -748,6 +748,12 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo [](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.second.second < v2.second.second; } ); } + else if ((type == GLVolumeCollection::Opaque) && (list.size() > 1)) + { + std::sort(list.begin(), list.end(), + [](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.first->selected && !v2.first->selected; } + ); + } return list; } From 99993170ebd1ea4fd1644972a2bbfd5708da966a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 11 Apr 2019 11:09:32 +0200 Subject: [PATCH 23/57] Copy and paste -> Fixed copy of multiple instances and volumes insertion into objects list --- src/slic3r/GUI/GUI_ObjectList.cpp | 6 ++++-- src/slic3r/GUI/Selection.cpp | 4 ++-- src/slic3r/GUI/wxExtensions.cpp | 5 +++-- src/slic3r/GUI/wxExtensions.hpp | 3 ++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4d205fa5f2..fd2c43806b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1630,7 +1630,8 @@ void ObjectList::split() model_object->volumes[id]->is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, model_object->volumes[id]->config.has("extruder") ? - model_object->volumes[id]->config.option("extruder")->value : 0); + model_object->volumes[id]->config.option("extruder")->value : 0, + false); // add settings to the part, if it has those auto opt_keys = model_object->volumes[id]->config.keys(); if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { @@ -1824,7 +1825,8 @@ void ObjectList::add_object_to_list(size_t obj_idx) from_u8(model_object->volumes[id]->name), model_object->volumes[id]->type(), !model_object->volumes[id]->config.has("extruder") ? 0 : - model_object->volumes[id]->config.option("extruder")->value); + model_object->volumes[id]->config.option("extruder")->value, + false); auto opt_keys = model_object->volumes[id]->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { select_item(m_objects_model->AddSettingsChild(vol_item)); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index cb5add462f..02149f2757 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1045,12 +1045,12 @@ void Selection::copy_to_clipboard() for (unsigned int i : m_list) { const GLVolume* volume = (*m_volumes)[i]; - if (volume->object_idx() == object.first) + if ((volume->object_idx() == object.first) && (volume->instance_idx() == *object.second.begin())) { int volume_idx = volume->volume_idx(); if ((0 <= volume_idx) && (volume_idx < (int)src_object->volumes.size())) { - ModelVolume* src_volume = src_object->volumes[volume->volume_idx()]; + ModelVolume* src_volume = src_object->volumes[volume_idx]; ModelVolume* dst_volume = dst_object->add_volume(*src_volume); dst_volume->set_new_unique_id(); dst_volume->config = src_volume->config; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index a6b8792978..356977b3a9 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -531,7 +531,8 @@ wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int ext wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &parent_item, const wxString &name, const Slic3r::ModelVolumeType volume_type, - const int extruder/* = 0*/) + const int extruder/* = 0*/, + const bool create_frst_child/* = true*/) { PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); if (!root) return wxDataViewItem(0); @@ -543,7 +544,7 @@ wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &pa if (insert_position < 0 || root->GetNthChild(insert_position)->m_type != itInstanceRoot) insert_position = -1; - if (root->m_volumes_cnt == 0) + if (create_frst_child && root->m_volumes_cnt == 0) { const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, *m_volume_bmps[0], extruder_str, 0); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 19c4e77594..b935448d95 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -457,7 +457,8 @@ public: wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item, const wxString &name, const Slic3r::ModelVolumeType volume_type, - const int extruder = 0); + const int extruder = 0, + const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem Delete(const wxDataViewItem &item); From bd2ac8f1f8edd9d933bd1504d987f15e1ad67aed Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 11 Apr 2019 11:27:15 +0200 Subject: [PATCH 24/57] Copy and paste -> Added offset to pasted objects/volumes --- src/slic3r/GUI/Selection.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 02149f2757..705f02d332 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1770,6 +1770,7 @@ void Selection::paste_volumes_from_clipboard() ModelVolume* dst_volume = dst_object->add_volume(*src_volume); dst_volume->config = src_volume->config; dst_volume->set_new_unique_id(); + dst_volume->translate(10.0, 10.0, 0.0); volumes.push_back(dst_volume); } wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes); @@ -1783,6 +1784,7 @@ void Selection::paste_objects_from_clipboard() for (const ModelObject* src_object : src_objects) { ModelObject* dst_object = m_model->add_object(*src_object); + dst_object->translate(10.0, 10.0, 0.0); object_idxs.push_back(m_model->objects.size() - 1); } wxGetApp().obj_list()->paste_objects_into_list(object_idxs); From 3b8ac62a9e6f2cdf2d2fe4c8ce7da04b4bcc6967 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 11 Apr 2019 12:55:56 +0200 Subject: [PATCH 25/57] Removed old png icons for gizmos --- resources/icons/overlay/cut_hover.png | Bin 1489 -> 0 bytes resources/icons/overlay/cut_off.png | Bin 1402 -> 0 bytes resources/icons/overlay/cut_on.png | Bin 1626 -> 0 bytes resources/icons/overlay/layflat_hover.png | Bin 2071 -> 0 bytes resources/icons/overlay/layflat_off.png | Bin 2018 -> 0 bytes resources/icons/overlay/layflat_on.png | Bin 2599 -> 0 bytes resources/icons/overlay/move_hover.png | Bin 1903 -> 0 bytes resources/icons/overlay/move_off.png | Bin 1750 -> 0 bytes resources/icons/overlay/move_on.png | Bin 2193 -> 0 bytes resources/icons/overlay/rotate_hover.png | Bin 2361 -> 0 bytes resources/icons/overlay/rotate_off.png | Bin 2132 -> 0 bytes resources/icons/overlay/rotate_on.png | Bin 3307 -> 0 bytes resources/icons/overlay/scale_hover.png | Bin 1837 -> 0 bytes resources/icons/overlay/scale_off.png | Bin 1776 -> 0 bytes resources/icons/overlay/scale_on.png | Bin 2243 -> 0 bytes .../icons/overlay/sla_support_points_hover.png | Bin 2057 -> 0 bytes .../icons/overlay/sla_support_points_off.png | Bin 1947 -> 0 bytes .../icons/overlay/sla_support_points_on.png | Bin 2869 -> 0 bytes 18 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 resources/icons/overlay/cut_hover.png delete mode 100644 resources/icons/overlay/cut_off.png delete mode 100644 resources/icons/overlay/cut_on.png delete mode 100644 resources/icons/overlay/layflat_hover.png delete mode 100644 resources/icons/overlay/layflat_off.png delete mode 100644 resources/icons/overlay/layflat_on.png delete mode 100644 resources/icons/overlay/move_hover.png delete mode 100644 resources/icons/overlay/move_off.png delete mode 100644 resources/icons/overlay/move_on.png delete mode 100644 resources/icons/overlay/rotate_hover.png delete mode 100644 resources/icons/overlay/rotate_off.png delete mode 100644 resources/icons/overlay/rotate_on.png delete mode 100644 resources/icons/overlay/scale_hover.png delete mode 100644 resources/icons/overlay/scale_off.png delete mode 100644 resources/icons/overlay/scale_on.png delete mode 100644 resources/icons/overlay/sla_support_points_hover.png delete mode 100644 resources/icons/overlay/sla_support_points_off.png delete mode 100644 resources/icons/overlay/sla_support_points_on.png diff --git a/resources/icons/overlay/cut_hover.png b/resources/icons/overlay/cut_hover.png deleted file mode 100644 index b026667c2877b5d750b42131770c63ff15ac7705..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1489 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 diff --git a/resources/icons/overlay/layflat_hover.png b/resources/icons/overlay/layflat_hover.png deleted file mode 100644 index 508916e480b5073803cda0fbc630b2cd7f0aa691..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2071 zcmah~dsGu=7M}zK#DrI|fVL8Y7HpZ!B$Ia&AtWH-RYWY*MaVFjkci3Tg$dLuq5{Q5 zZ3V5Zw1`+*d~{n`v0@cgOR1oy>sk?zVmUOP3zO5Z!aPi*W=1Y!NbF+LfgZP_47+}v!6LRxnUDR7$-PSq zF&U3lta(!A6HpmRDGWJb#u+>oAH>*P9z!5vvH1vBB#K~gV73r~g%BG7*?ch<7IQd^ zrw@~iW>#y&sfyUAvB;B@skK^-VhFO?Y%CkXBFwoETO<-eFbCprKvDy=6d0_i9W+=% zXFMoy3ue|CtvbTMaCt;?hv^vtor@{}l&GRTe^AvOz!^m7cLdqm> zSZbYGti}ZhjPr3&h-f$<7v^d}p@7W>F$~w_a8N$N5u!7GUSyBJT(%6B@naP{n9Yt6 z3SosT27zH%5i5%pDTFh&N`u9U8Zdk&wvLRAz({P2@c-C~Pg-T+^k!Vt7FZCp7EGzvF-&ZU>HRZP7WL7BP0!6 zjI9Ahd=U?Xc_NsLb1_VS!_24l>gO1I(y63`%)~eYNSu>{a``Y%2qFR=0-`)F2P6d? zkgeuu_UE61)K^~qB7j7UY%1n+<5K>6a^Q#Te)N3FwNs3;=k!lw!!ZZ+oP=Y?i*>nH>&pye`dL~ zq4#0fZe{key+zRF?Jq~ot(onhYAKDt>LLlN@ukF4`Q)lCI@qf_N*aE#s$wd!WNdO{ znESBr)$|9R=l8)uZflO$3^s9Q8@GAXTrB8Ex~hLWT`4_KAq`!6Mb7E|BBRKQe=NLX zq|WQt*C;!-2>2V97jui&!QG!vhP*GC8diqtK1w} zK;ogcvvns9)|k9~zNN%x`cbuC^(Iavyjwy>}@+s`TB@@@zSYtZ;#Vf!{V*wwEE$P=BRtqDSJ+gV5;Ne zMSDFp1ppE|ZxFC382sKYuz2??CPLsoXzO3?GxupEQ?}DY-yZVA9=O2C0^Y><1 z=V)@8&Hn9yfj$rK+%B(fQD^b#O_^>G@OapKcjFN{jX; sl>U1aBcyz)>E!d134l>&_MD~CfSbW%a`VozPS>wm8I!Cy7Og7&13x+>{{R30 diff --git a/resources/icons/overlay/layflat_off.png b/resources/icons/overlay/layflat_off.png deleted file mode 100644 index 6bfadd316576a0c6c5cc1e39cac7da55489635e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2018 zcmaJ?c~}!?8qZOVAVEQD7ol~C2P_YhnPifXq#Ox^5WrACR;g5Bm<%Rj4wA8iXfYB& zsR!7i?Ba!5(N&hRy0zF^Ih5)uiWe1yDj*hV-Ij7Jh?VSw!|fkFo#&bPzVH3s-~0aF z>zU%jgczpVd^ZY(!i-hOlF3!#cwL>z?@3M0LvryUqEm@w`Ya+BHR2ShN}q`Xv08Ku zo{XcayqtgIp%eSD4`K%1Bzwi1dxfZ(dfchzkFTK z0yL^H)(XB7RvMykwMJnv;>#=vDVQZ26RB7c;XtTaOd8PQ1PYk7>vSfuIgB-FS4{35 zVu%Gysu0;>tSM5d%0wVaZ^QvUTL5Al9v=u1u{i>SClUn%T$m$-U?IdoK#oAngT-7f zF#WJdZ$?#?I9Vp2_C=n;SZabWh#@F9Hi^&6@u2FRK7B-GfgLoOc`WG4G@P7Lt3pPu1ReZ zk&OSJ#%r}rDR~AQO2$q4^+t@WN7lj_FqykQcH~eb*@)jWYRICX>tuRty%yIIv9d50 zdBav|RALn#g21={2Zcx$7v#abEKnH2;eZ&1XJv9x0m2ocGj?8wM_@!AEtDd186W0w zqJ=_OCXGg57?#PUks_IJ1{K*R@Oz6j>wJPZrLVb(NW^}7sCbt)+#BQeYn5@%+jJORuXf=CD-0Z~4W3z7mZ z$Wd{#1OkkMVgj56krW|E^Zc%vW<+F%I<~JygFL(%E?h@Wl#v{rap%2P$cY?`l}S^~ z51#FdSIwuWUF-+FYpiP4WcU-m`*MDcXX1nhI-H-Ctkc6X|5* zt-VzkzhHOZ?Lx&z67NFqxy-V8)$$@Wt2F|VyjnW@$C7G@0yMcj!PN6Qm_5ayX9YS)OT-Yz)$7) zJKe)`L#d3ax!w{Bwe02nSZb&z^@_=JjJoT`{1U^WMIvrcf_xk5yLeXZPTzY;0SAs2 zm}9N+UEdVGP?Vyu^T)vok4{>EwEeA*C@&(mZiL!f0#5cvYk zv_2{?rtw^mr;%Pgvc;)1y>o~C?RhG%PX1c#)FpqH!XIO{{P3?1r^7pf6W-?;pc`ja z`!JTYA3E)9zPov(4O~{_#G8m%l4RYv?p(rA!PSqg*MUU{P1tjx!NUnktt@|;K3wrc z<<=EuzxI@Vf}1hluRO59`M;Z1d?UEJ&H5*`i%)U)qiYkb8yabhGkbFCTKaCU*fJ-X z-u33#%@d>bRV_R2$94QnFa40C_%*KeFSXHqc4_fM$!Yt=G8<&ELB^zco6p7!I>-e>%%iaYd)0)$_4XLz`3>Cn;hH1HF}`<{^%FS?F3 zjvTqbWru_x8LiKz4MaxyJmYov#LlsPH*1KMr0e6G-Ig{zg51vwkFBa0>?!NmMS0nE zwsGWw1bb-L+^Y3FOR4!HXY4ZbcCT-*eVf8J&B-mbcv)ITzRU4_%MUgL*wO-j@$bBM zePf>IySK)HI_J~RR;@%fy0QnoEZZgd z=#7OzopLJGQdjzqubOYTo3EaNmmfL^O3fRp)&2h4&pQotU=OsnepBw*NB`bq^l4(%hj&2#eWn-Iz6s7KL_`}etbk3Pxp^K%{R9k?43xznpbUALBa%-HX2 zZvW}pS=)xLUB`98$ZO52>nWyT4N$?#+EHB!!YLs|$&2^FbHvQR%mT^x5eBbBte%{PLe;*?Q z3j+WEjC@x!*vRNH^Xj9JZ>dn$hYa5-y*bKYi9o4>5E|iO30>(Q#6XqzD=STR$N&Y17AqGUS5<;s*(P9Nn?S}bemxjz|#5fH4lZrCR z4fB~4hZTtSlE`7SvlAK1!;_rRE>tHxnMk5i9nl03Pr-o{9G-~9lW8Q7Mj)WSJQ&2A zoG+lU8O$%fkd+%IQmK^Ea5$Ap<)k7yN#qeYJe5ktfdm|ZfJHR0ia4h_HA&HUmkbDT1&4Q8G{kx+XMT8A4KrTd*0!1?K!IlMd<#zmNqA4t)04??2Jv`r z3I$|%dJ{nqWH3EfQ5lq3tgl$1gv2~}*0&Jx{RKhr!Cuv{1m^OmJ2G~qW`fFML961aFQnTUje z#KQ})R5H~W3p!Il5=`RpTwoCM1<(IS1V2|QB0+W{oh3x$av>5Kbf#d5F3vu*6j?jh4 z7xK5Zj<`?1aD72lTI?Egd%e^wZ7e%W+e51e2n6hbg@6H|n@l=Lk#Wm=qTSu7)OTxn zJf2u48-0KN{Q2+zt)e|)2CCTDq*SPTQ(|Ibo!@e=r%#`nq7C9Q>SAG-DvBRc1eT-Cqc-0bP9wJijA_wL=3eB2gyb4$yO zM{h?8h7xo9&_}iH9UOw9A6&p%J30=z7QRB~_V)JvD!-Iqo4n(Vx*Dk2OfLyN8IqY=kj)0x?oOeTN?pc*HWkGal^fr2Cq%o ztr|;Ks#N2LXfcDeYf3t{E=gveSeRxPJVBfO&s-ZE4kuHqIr;ncaiH<~+(lC>V~0E% z8yl6wg|3|kSE>r@^H&|t&<@&BW11ewW-riQwZQg|D?Bg~ecuNCF(_wF@>EWCcGBzc zjLxyw54wFJ>2&|NII0jRD=RZO=zo0VPlohTFGlU9^_}#*|T;Qywx{r_`#DqJaL|`=ePE?fcKBw{_gP zlb*eN*%6&_K)S5|rMUf>{)BDVMPW>CTWMNF(~pY-k|0o0HXVj~GbL{=dw9FoIBA63 zSKUJ=zAvqJP5P)#_M2{MXkc6^1RQ{*K%L`s=MCFy6T#bJ+;=JUxhNnudm_QTEoiz* z$HS~~?oMNA}LT&4YNO1$n<@m_#4B(&%u? zW_YOOURFZ!h!HpEzpI%GT$?lW>*o`OEUK1zQ1YLxFbLl%E+$Tn)y^58R_``7o!aqQ z(M@Q&ds4bj3>H{gT-L08b*oWry%8wL+;#O_r7hXhu!QZf$RK^HyuAEOKAlT05Q#N) zI`6GU;!?a=w^x6kb?2k7L1u-T)IIYe^ns#am%x&+o~bz(`X@2EPg*Y7kXWvYj|`e` zvXm<4U#*OJWpb9YgrL{i)#cKC@80&xIT>q8Za{?nhL<*N_N4&s1@(jP60VJw)(+?u zbq{Q*=gIQlauV|Bd+ankdHqjk?t>A|wtL-cA|pg&ID2~!c5ras#KgpJ?d|QbNd~jn zbK`~$?FSB6oUg2m@8q7P81cE>r=0ccd(w)1a;Cg4>KY18mLx@vL2Gm(TAplevoQM^ z)i>|db+Z~($>QLis_$b{w-oADQ;dP2`j)mfA2yq9V&aggH-Aa@PN+LtA{p_BidrE? zKR%&{($$?S?!A~&^;|l!2?$MGTh*{*{EkO5hk8-Sy(OSw^W*Uozpr^6Goc!N4wt2x4?n-P z=JmVmvzHu>eEemxzJ^}VB1rjTd{OoIm2ykRu6-M7{S9u-@f{7dIrMB==IBy$^d{%W z{{F+^Zx>cqK0ckzK|TMqqoacsF`T|aJ7K>PwR(Hg)Rg3yuW4&0vF*XFf~h4xoU0Vc zww51dI~W7&PXyXO9*sNN%)jC>kT}r1$8O(7Esx0y&NkSnwH|}jcLmlojhl6^QQnA! zrX@(*D#-iviH8sDMnZI|MH>j({x?&f5^a*AHmCl@>=7G>4_Xyy4*F|NaBITXy}CYB ze0zUfnoH~VA*Bfvz2jQo0O|Q@VO diff --git a/resources/icons/overlay/move_hover.png b/resources/icons/overlay/move_hover.png deleted file mode 100644 index 933c34c2454a41859afb398532dff4f9c077f46f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1903 zcmaJ?c~sMO91l=AoS+V-sI$w;>gGn8^gg76LP5KY!fJgYQ)Wn0EVNBXgS4n9Dk@G9 zIdpDvYCQaMOv&BMHh~V0p=T0CIu)gGKxH7}aZqDH6L1}l8XU>T z@gM+jTV>M6&@qu;C@{jpL!CN2yM<)X01y;xCs8a3r@?qU!DLl(ZXd4WfF^^IGg};q zM3Q0n0@I8%3ZI##)njQ%nB2e#{uB(dD;NV7oJK*r#cZ`H>`KlHy9(y)6vG_wg$kXd zMBg?#+&ziVPb?6lG$Hf|}I?mSVxJv_`GuFkg5ElR;s?r9uRk z;E+sc6hI7j&kvW$5Va~?h#-hM zLKP}k%Z9NUtBppj7(VRV#P}Y<%HN1pgi$z36O^7H%)?y}y?~$z+X8|F!@`_ZmxCWg zqL|6*6i#&(YA|j!PMH?sh6swVfG;wwFujHaf}ld7Af69NgiIPl7~crVC2}!@h~s=x!z&pa>{Ld=L_!V|QpCrjA_*dvK|-lm2%%z;0Ad6Jh;I-WB@&E}ViKGK zGZbNG^Sshb!y=|boyV7>!Mwa2F5JpY6vd2A+}*c3nTcGgQLFU!uHLWelKfPDMat6o zp?x0X;u3zjgphMLvVOe;mPj~LT{T>ajM>(AA@HehV36z^KWYp&vmkR@RkY4Ccblh& z*VL6A+%=E#c6~&pZ+PbOK~8P$xz57cbB?|{9p^ebA9lB%sCn30)OMZLZ{qG0@Fc+C z>Kr%6eM4ye-Q}>wr(qiv2?N&A5Yv^1 zna$CAkAHY(#h*S0fzj*))RuSMqS`XnaZB3u6T50{%l+#$XKqZ={oxW`d~iwrzUk9` zD_RdOuFr{T%&bV<*phRxy6N7r_T-McUWxA2fg!=mkBppmwEtvm1mN}gmJHZs=DeP~ zDOE{p7g`^4V{OW<$)<3ua>ZV4Ly6wtnQJqRN{GdW9|0r9nbD(f3khd z!|%_s6O=@6dfv(=UZ1LIl(!pN{4Bg-j`{$c))IIpX5n{<8M_Z{+g{JU-EX_9YuDAYa~ganCQWMG zKc|V6iQam9^V+nPJ=pkw1Ar7V)S+_@_F6jO)jJol49^E#v!9(WfmS&iXmANW8DRMX XJ%{>~DOa^2&R?J=T&u1Koxl7)_}k`c diff --git a/resources/icons/overlay/move_off.png b/resources/icons/overlay/move_off.png deleted file mode 100644 index 6c921a042110fc8a271edee555c460d4532f6a28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1750 zcmaJ?c~BE)6c0oLatPuv;>8+dMwCr!u-)M2NOCS@M$}FY~j18j+PPCrV5rEo+r4w2L)8|@# zBEmTw?rLL7DwC>7RN|CLfVp%8c9WS!b2#CVb~A=&5e%Rs(v75w|K!9OK48?V_#aC( zkj5NKWEd0jXku|*QVO1zg(G@?WCRdySF#371cL!~Q#NT;+Ex5PyGnNN5)1jjpbC?v z;t!EZ)g%M46ion9fegf9i4>3{0$3)NAV?S>f?$OZQV3x&2+NcbNGTEl!w;YJM(Yhq zEgC=Ui#@6M84P1q3WYYCO<)rXC^}sTBM2gdL_(1WWHmr*F3DhakhBJmSU?FYP8-dP zks<+?MNCKKFe*OFbSMOqS)+Mtn6wTjicOi&j+uq901}!^uDAxZt&EoVpT>K&ttq)? zLZ~IIR1S@^^)LjFfZ5!A*pW+-Wush38`+{@*(inQmm9aE|F%+FbQQ0G1keop=lr@7g1F^BLsw2Q`4Tc*@ zmoU^-sG+z~f;N6Z=;LY11Po?cX?zb02*Si-kq!oBVm1vD95#T643UD66oDj!1jpqB z#2?1%-^t)mr?L_@67vWlrA~)QWRO$=ise!PD}HBNi@FQkg>9-S zmhHUCDEYQi-#Ej1z#mQi1Kh%ryDM8Cc{HCDk3W~FDfzB3%DeAd%T8X%gh|{Bj^53H z$5}zevpW&6$%`p2qT>@_7X)p0pK-#6qJr184i3QNa1 zx79w)THjMTu^To-ZTH@xUVV(a)#qBXs&+#`x7RJlwElBTs;$j!5tr!1>1$fIcwctR zFwjm1zw+>rip2ZU?ojP*Uf*j&6fYm2k~JZ0jbzWVJ*T6)uk>S%q-90C<%!zOu`}AX z)LGa4;Jv~(mD6+iit|QrK*x%7>%clyj;Wr5DtHI2!= zJH5NA=JBSWlFkjZ9Aun@e)nSA`@{RDCqLP+tEI+y;QEe9*0e>(Z~pGAkesZ{n^Z@A za-`vWYjqJ@4uNszBrm$_jE}_(bsu%{En-W)r&ej z&d}7$9LiZ||GT^e(_sceCL9Cs7xsO!M0 z8?$q4_TRpQkf5|K-_*tAi>159@9Q|w5PInJ+Lo#Q?c$j7$dIl5*5dNW(9GbmmkwSp zdY%(f*xZDS?LXGV{F-(D!K0wL@|E-I!=`PPsXU##8w)>g*cZLDSu{ d__TiXWKQK;LH}V?`_lENQO6~r^)YG1{{iNJpAG;3 diff --git a/resources/icons/overlay/move_on.png b/resources/icons/overlay/move_on.png deleted file mode 100644 index 80204b52eeec697f7d3d063228daca6708e15590..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2193 zcmaJ@dsGu=79WIalp-pYN6|72z9q@zK}aGI$%7Cv5F%J9k1`|^7)dfA83>>&(M8V+ zKENvCqfo>LbW7Dm9v0TJt5{tQKB_#7C`eVbqT&h^5!Rgu*#6<^oHO%%bHDre-Fts` z&g@WxNu6eVIs*UzCs~MCNv^AGH)SgM{1!{SM=lvXof-f5c% zCHgyz4{DntGYu$RiJI^yDM*UeZ%0a1ImWD}X!)y(e>(5|NRVq}I$V50SCKs9X^C5c{%nIW2B&=YuAIxA# zxLjBqBw@iYEDjD5^2FRpTbbTOAbJ%#85<*GPuMd5)Rr$Yq6mQ-BXL|i$pS?(PT;0w z+yIJ1wyyKQC2~ZC>21QLwnmNTEk=!4I;svf;yUnMrTN$gP{1(4VlfjLR1S+Q16#$= zP174c&;sic5O zWvH1N4oAg6R2&qdlMv}P^8A5JlOmF#w(a}TAYa}O7pf;G%1DmR?CxO}063Jgi6 z*1qVCAu*rNyLzF>-6f*zAit9O@4lXnoPxAMX3SFEce>+4)zxJ!mlnBf7rB?6PMd4} z*Z-F6+aHv-ZJji=(lw9T79x52Sao>!ZM|d5^$ z#l1(qy!!g88Nd2^?7Yuhii6Z4fKy=U4W@t&v!+n&asW=>#i^}_3%B>s7609BF|S*m zJM`mso$S$$SDveHWr`YSpt*e=KlFVaD6h-DYsH=jckr#5L90Tmv+Dif7dJh%Q*T8a z-JO2brGGHy_NkP}y$25I6$i@CS)6rCBBU!uc%hA#dd|lF<)G4h@9|^5(Pvo=bzHQx5b##7Ga`NM@moHPpm9s5Pp1UsX*}wWYWIfZDUN0)UvB{G@r$-1B z2e_6L7jM4x(D;$hd@1cf-jT3gWT(_6LH2_HJk<14&u^(dz?rNhU3`mJ^s=`;^pkB` zZG2I?@S5|y8xLk8E*9;xq4RD}n_DZ6Q}7Er`V+CD_E7S$(FJq$aJ9SKehrUkzZ1Kq z!pW`Wq~@PN(+*4BA5ELtZTEf1Ku&)A{L4`aE%*h9XUDH@-YzH_ycQMD{*oJC@$;|E z`&#h_S5^-Q+Lk*%+iYHQ51_`V&N!#V;>_LRRwpXxlr#s7?Fx+PG}p$}#Kv9kpL5sZlCi~exD4uk z^Je$Tk-$M~>-@|+kyxUPISV+_=-Aol^@*L_{??w7f$-v+<;qa~`8dU?n1$Ki1#Zrp z8mBijI*mr)TiJPUuS;8=OiyZTJK8)pt6m4uKC2s#s!IR3C!}J7f9)?vwh@&rUlsZ; zHZaj9kEK{w)ZbG=s}?S#W_N6yIR+1gcJB55_}*^`&%KYOjFe**%aunxETv>dx8Y6i z-52pyoSPlmROfAsE7lrmi_f}KGRQUL;9S_v^HI}iqg_CO@5&w`!|F27o)!(pn5zZ{ zR;<~bI`S$?c=E#K-rJ*sl&uuCZ?DUWd5#jwFHL!ty1R}G@)m{Af*k_{>?2q0CC>qpR#LBqLlJS&3z}oUqA!1|&EHUuriZ`WYI$q_wd{p< zi?Hu@gEIa`dROY$aR1JN%8-2ZUl({l6CVR1ko_#sZYq$zOvrbXT=%y9B*-LT;_rnC GTmKJnDR*c9 diff --git a/resources/icons/overlay/rotate_hover.png b/resources/icons/overlay/rotate_hover.png deleted file mode 100644 index 9df377fc7f3a3a90867f2b450183b66ce9594199..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2361 zcmaJ@c~leU77vR+P(Taviei~YWl<-Y>)n8!x@9YxNxI5ap)dmy`3D;=P`NKLv-`fMI`9rRWe4K!S)20>mH?$sm!&qChMX3HW^B zP;VNkj1|X;{Ok*TW8>0vIyH+x$jQmU=aBI#O)7!NWHJd5i9jNOs0OIbRqDigP^q0g zVS$5aVU1j^ldF_~)uLFU%GR-QDAVx}6ly;Ii(#eqbE43c5%gj;fry6)3WYVUPuf~t z9P)n}U)9zMbJYkT4$-QzH85Ha+3X20n!CSuWK~4juwpfGv?$_Cjtb6JAW9vV!^WXs z@KU*yB}M3D2%#Y$gDfL~6o?`N8FV5MgkeM`A&F^Z5<@&;=WBQ}MCL#gA{5D?LPTN& zg8^|uBgha0aUw&*m>k9gmaEk2#7Y>M@GVDuk7J?8zs0h`HHcWJ(g;{-ewW zI>pW@kKXEhDz@<;laD;tyihMv>}^tb@~UiUA#XL?lH zY9B0z4Y;M{m#4x$xXub@OU^~*EN!H^(~G_dv6(N7HM4o8T>GSy=w@$I{;XD)7vKhZ(pmN}XTHGOa1y87B*wR^d)vM6nGbF5(i*9kj(rx*at z0ka%eKvh`W7-_?%bi=E~^J?+eS@$~j1+v=$NVMb|xwn1eAGjRjy5-j?yA^Fn)JZ0eLWAHwm~&7qh#>=`)2j+Cl6v>lGfz@%x~J>g z=L5yc>2IpAch1)|-R~$2bG(>ZFG^3aP-ewTiIj?w_+5E!xgy_NE6bg?-O1jm7QJ6p zagzQtJF=tFw%^9I>RZCjTd*mqc#=ll6AAGV@cdDBd-rH%;KarXI6AZ_FEdJ7vQSB-}XMK9GhT z$fig2jNH#WYfrEn%-;5Eg4r#8mG6HX?0^67NM0E>sk@}wNK2XH?Fx*|)_ZN01jJ@6I9`dX~VtMwywnd<~J^PjXN z2McytTZ9Feok&f7?j%`LY4d*H#c)e`BX3W{+@7-YOvxqtI=P^yXPc-cS~hZQ=$p0R zLwVTX#}np{JZQIbY{_K7_9u;hRz0NEO4L`3U86k8F6R!{j?Cn#F2TxMvno4{d#}e{ z(roL4<%LH3_Ghk_ygyVI(V|cHr4}ybjd`YkuZ`c7XT97SGnVyhOGQiJ_}UtbcZM*c X(+4k)_%AY8e`dH5F`ScODQo@*MJB{e diff --git a/resources/icons/overlay/rotate_off.png b/resources/icons/overlay/rotate_off.png deleted file mode 100644 index f5b65297953bbe8619714e2a15b01b919f07aaf7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2132 zcmaJ?dsGu=7N3Oh3Irj7KmnZwS$QRyydRL7kbp!4(+C=>B11BP0C|`M2;x$T%EHK)QtSgCtFF2S1d0UKbp>jrYgJHatl}hM*6bZa7k_iC!n{qdHW$D~1Q4n883-uV!kLI1 zfmMZjZy?bC;83PXR+truTlh-7mIB*#C>E^&MFT+eMvDPf<{)M;1Ig6r1f;3Xt0YjP z5|DPV5}`zc7|GJa?=vFX_9Z1N_vI*gD$>UHz-S8}HK0YzFlf={>P&o#fb_~PAKlx< zR1)|~#hfD`y(Xndl!0Qs5dm2gHd#qyvOo@xLSr+SJYFP7hiF_X#HG?0WEz{#g!pti zxcrb%Z$_1xFPFqE`$A6wQkL0l;8Upu1qGA>21Reoq|$gi9u=Zf>2xxxK{gfY%&>*5 zGX<|$kRT?dQDZP`^g7UP5zf%(nFS=2>FW@*hQ!1-hIOXpM4>68T3`c}MuDhWtv#++ z+9tCc`9F=fYMYV^4G2|^nDlu@C0Y-4@Cq2s-LE^cE23=pTa6mDC~&SsugudTI=fW#?-iBvD3<<=9#BmZ9 zM5D!WxsXH@%YYzA5+{n`Nw_Omsm^4EbxLH#w+8jSjE(tKEMIIyV6)zstk>tRbb%~O zZ`PZ#^afBYwpX18h9<&Fjm|C%vlr@h+!Dm7$wyRiM!gn%m1(}_Ei51iW-#a(G%}lk zrh%!XsmVMxk41)9Jcx-fl}Zi*k(TkQUu5ugr=k)x62l51entk&WJ4@2nZaQ($S{jZ zC!+#7nWmzv*=!{ZRbZ1xjH&n0RT9a zR3b{YjQsrB&Uhb@-*rJ6Nk1(9-q8#1MkLV0y%d#9{uLsSIMvF*SOBrun#p(Zt7ktak~=*||33Kf&&4l4eBnCq@WG40!JFR?A6nPjFx;?M92Lcg zit2cvE0A#49r1#Tx!6EVC9p}TSU0pp+)b%7IQprt+qO~^IC_tJHJ&JR&kVpVHvjI5 zgRW)iJ%7>5fXUs(tHs_W^izmW$-kTH9lmVli#QgivL2`$Tak_*kUG1G=vfY5ix}q+ z;h~Zvl065FZC3Ryx9-wQ>Hg)6RFBd6Ef-crJNMaK>H{CoKN9B3X#K>Jg#;1jpAJ8y zcYX0Bz;%0R{+d61wk~4NyvYi*Yb$`q3BV^dU#{duN}eLLh+rXf2tTc|Ig29Z2tU>0 zg-s33&e2Z8+h<1}bOl^t9rG{#&iRBW$j_~~biN-43RcY?aJ!8^yS7`F$(zGub1jpm zY{EoPdM6PR7@eY6)nfkSVQUd>?ym)%cY>dCvuY^7)y!$r_VY!=zJey)t`{|NC*N;N z#@xPiU_*05S@^eB-bekf)e0#*6KK18#bG<@q zz_T%j+NF$--7m@~$0r)j_Z_>OU=&W=-g%%x)O68z|K}5oo2i7a=5O5hP&*&r;GB(7 z1$4E{q=+`KacR$Hc_HN{^^Es?xMkqG`AvqPKp@9zZc3{A=0l-2 z?ojXZCFarbE!Y9;T7zp>dKgnKu=$^^F3F2-t4#6gx_&s>t*wk{eRfoYx#zgO0^jab z8SYYiwW>`TMl3BJTnu^Ux0&kmJTkERiv&+E*38f8pGJkM#ePr@G z&z)m37OO(A?t+=$-irHIMZU|J9CJG4AhBy4n=G}BhI%g)Ug|qHj&&#U6}>p?MS7w1 zY}he^{|7k3HECqY2jYvBwm;)NYr$a4Q*2Ca$$k@e?r6(@t1r7j@$J|4co_(vKrY`wo(2)c%swlxBKh|&4`I@aIKq+*<0Pj<%i3piUA{)3(C z&7$`3#wJovURZmg{rHE+-Jkb)*<1`bF1GjOTtp9WZwQZj=-77M{-2k|CP~i4q?i5= DL+L7_ diff --git a/resources/icons/overlay/rotate_on.png b/resources/icons/overlay/rotate_on.png deleted file mode 100644 index 6d4c5bf31e4218b13e569c8adf01068fa06a5c93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3307 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 diff --git a/resources/icons/overlay/scale_hover.png b/resources/icons/overlay/scale_hover.png deleted file mode 100644 index 40a05fffdd263d51d188e794d6c47bb55136f917..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1837 zcmaJ?c~BE)6yJm(3W5S63Ru>)R%O`ik$WWqvY=?W0w^OMY&IK-kZjy+Bp_(C;ysFW z+FAj@76!_oVpXbQ3)FjRLGeC{SRJ)hD~`8C?MA`+hvUxde&6@L_j~X6uGv+|iIcoN zgFFEM@Q$BM>V!4Q_4e;4eCtf}JB4L9ADh9avPM3S;us*>!0H(=-b~G9bPQ$4pMRW* z006h;rnC$`BjIy`X3bK{r6YBitpXYVA|f4Diq2tpP|wUYS+vmore+8<8MM%JMFN^& zjbXA)lM6T|wIDH#F36!Z1}Jg@7~voU17?P&K!-WkVj~<{=#5=M*t^6C1in$>bF|P~ zQW*)!U<}JKphBvIX-uvFRT?R#l*u)ka1ci^HG--UOa^00LXHwR4)#8f;EgjF2^|^N z>q|Ikp=_SF5(tu)mnY4WNm*_#f@w4w1jP{?hXoDTmT%!H2W+v8>a##HHkvb8c@t{^ zT^1=lYv;9)!1QehW@|#iJHr-RZ=!^hAr8ulU{Vw@n_Y3e(YEnA=6@O=)V8JNTNy;h z*jPJ93-vIL>H`b8`?w>QqQHho;!HwOs9chz?PkWp$CFw}xRDx62ExFoWGJI#V71JM z!*Wz^gw-kxgK3&E>Tyab!_`!uoe$wa+$QA4Wx zu<;ffPg!WD&$mhN-HVO>D3*xf7>Z}PG?vZn>w@HLmS=6*tQCxjaaCOdj!mFwlf@+* z=PK0OxJiaHEnp0B9BT&OWSTI2fCY+DG8wMNV5Lk*gPg{Uutup-z^Fol${9ILs~8mO z#T(ws;M-0WBtj(CK0<_EPsx?2LJiAQ3K>i(HP3s^ z)F%=;)U|y#8p7e-a4{BPqBvo6Hf;=ngo)I~lhJ98OLsHdbNo{Vcf#Wy<(l1FD^xjW z*A&z>KAIO5{@~1(`r7L1rTq)Ckr~m&y5ic$k%OydNXF@Eo0EUY2=G-r%Gx%@bigrK z-to-)+pms(`GJO3DeIZ>)&udXR?zXc!P*6P=p1_VU8CyU6RmW4hq z%Kj~V)5sV88O={Fb%YWXBd%QWd3a>+EC0RbHhb68gAwA~36BRBu4t{vvIMW+EUv(x zT}Lb9vdnkM`dK};uGJ&EiUK3O5+>`RXrZV)Vq+!h6vE5(q zdVUd7-7nUJSYINQgTy!W9a$a?<9P{s^+xN4haN@SN7tlcL}%L@ijMdVDZqb zos&1DPHCFy-j>>Q>~P7`$$<-okv^NA{3Q-ulDM(~vJb1uZw(>mZHNfY-Qn&VBtCMg zsAa#O-|xe^w_YM!Y%7*jbCA<$s+oH1i9Fz*xO3r3H28Gjfq)BX>wreA8lHBeC-`sm zHT%Mq-SB53ysYH)RG-_nLH19e2#L?(YuX9z-j&-zM6}JW~q!s>H;U+~y{HlqwR(DJ<+wo_hv%IG2OwcvDA~`+iNm10G6XF-$-*_e_W$ufs zSl^Og5LyyGmxy2jIy<-2rL|Gx*Z1Hfq7T{AzZw82yeXaiLu%EpyY<81)MK#HdpPm=F5d-UhAMZGQvvynvaW2@R6LssF zoSxt=ubCyYJ=SIiCW{Q`MW)J3&abNc-0rD`iFLs4!}V?{4ZZ{BUtGC%6gvKS$+@f2 zCG9~y4e1xkO16%kcYRUYYHsnkHA4fk%cr#{_kp605cj6~Us*|+(Gpqhaq?A9ee3Bd ze)mpP+M`l|8MRe=_8Z7K-C_TBUCe!Xt!kY(%;{Mvt_8Tub42}s(>0ml5|uvD^#hBK LO(eHZ%v$;%@i(*w diff --git a/resources/icons/overlay/scale_off.png b/resources/icons/overlay/scale_off.png deleted file mode 100644 index 351d5a00466a43e42b37970df1e9fc57a49e6466..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1776 zcmaJ?c~BE)6yF3e9D=c;A}C!~ofgN|awbH597DO5CXdT*!j9T%6YQ6A^0+l~NL_9zcxc5gF1w6j66Z0qXfu<8J1Ia-(k2Sj+VF*xp2Er8 ztQsm70Q}Zi4D*O}W+XT2zN8q+OSTq2{PIEhO!b&lqiCSo}tDv^Z6A)-2RnT0C z4%Imls7%ZBrA}(r(isL~sg+QW(6p&wtXs(%uu%*Sx^0W?E~Q%q_1jgld!HDAz{hY~?@a&iPYA_47Oh+qnZ0zrj{PzbXcuq)Tj;BMINnlNZVL%9g2 z#lcu;JLt2Bo9Jvt1+h$DhhTH)bZ-pXT?2_?Q--*42Z9Mu#Afrw)vxVh^wj?}-l^>} zfi;*Zq z!wHMsCyenG>UG>2%4u0bkx5S42KHxKX?X_=6vag%p$UVfA~p?T0yD!3sX_vy5(O%z z!~`LuP-p;8zLmk(oytnsNF0NNlqM4{mZB0lERsn?FfI`bVOAi7F;ZxjN(l@nq!fg( z6cJzZywyyDBDO<)+c%@Z9^MQWWoIYK$&OCCXLl|;k$_gCHn{6scIvF*YW|vqRZW6AFfdcY9<-Qk%kZA1(<9^ zU;fR-XO5N3*=gSY$p!LZS(z7a{yr9m1y!z_Qun4^fgpDs0Obc7EATYG zuS*)cJ;_z|aU=aNq~7_fcY<+td)_}istudnKX)OP-r*PX5;}%0>P=qeIfJd5UjtRm zempMr^y;}=J3_qCH%_%G%I{6-K7YHnwCkgjeoefHD?JS*+-3F?*LvEjr2ERd_Y_if zVF&ZAKMvQACyhZ*A7;B7D%<>%b=b?|l!}dCdUn3ZwQu-)hu;SN9|yQu{#A81Lyk7O zn_m>vX3bd{bLrpfQ5ii$!}i0k`U;N3%-FZw8#k7da!$WLDj@H2-S&bC7Pidm)=ne;)AYP(xsIqxFY#pN^|*aUQR$<*w#?13J@4&PFZ2=FqKY z!@cVPK}bIJ0Qxh^k?SAJ8J)3*>uChm2Dpdb{XK)1QB0-zJ1WB>0N(D4oCF x`o=hA`lUr<6B=8e9A^En?dR*lfTMZbp#Z-ne)E0%;jeuEAZ_9d&B1tM!G8_`ql5qe diff --git a/resources/icons/overlay/scale_on.png b/resources/icons/overlay/scale_on.png deleted file mode 100644 index f8450e10587826bd9bc8f7e2183545a565c287e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2243 zcmaJ@Yg7|w8Xj(f%Kf5vgE4GHSdvVxB!QR!0b~ov0lGn~g~?*KqO+;aF z?z)?3C;-@$N|RGGDRHqJ1e23s3x=eXD{(degz~gX7|B94pa{*7D!7E<3kCux6>|wf zMjRBUj6gG`EAv!nQeJ#Al9z?B#RT38FjULI706Ky3~J>vg_@(~5(SIoE%aPjfAN($P_l4O@^psDwT*Mi0WL02G$Z4YQGr` z0#uEtq)LqxQ-BtYun1eP;Sz99r$Ue`vxGRww5wQF0=x#@4G4ydDz288V)`zcyq6;@)spsib&OV3`0z*2__aCPu&|;BQD` zshA^1gJ}@TM2Re#gi54CbP16aOra1F1eJ)WFq1}Q!83Y3$EQIw0YrybQ33`;p+vG+ zkRU9Q20@S@DlD8WV9ju26lx8uK+qZ6Qrz}5H~25PoCp;PYcN$ZhRJ5SAR!aeVCqav z2}VR%s?G+N$H9nHVG#yc3N;nC098q|QE`+ClY<{K&5?e_3kZT~G^&U~WYX|7&=HD+ z$Y!z`M2NwL=qMdQf>DSt%@_YEgHxS~OYlgPGY)Y?BACvE7%UinhYVo9_VLPDLLdZcZs9F-<0qcgkJQ=;M>)=A8RK?ab3nelWZIZ7A?| zz}b6iLTk3VxffwA=7K|%;aAD~TD*xq?+c?VY@?Q&pVaeq?wHTpZ(m7fc>NX_?BP)I zihOBueYf=fk6%HBWi`9jeMdUzvR&8rVkgaN(KoV^R~Z+!KD0Zg3b{PCW~d-!_sI|U z3%zwUhT?NIj%7D>vr5hOK02P)&o7=ipRxw}-hZEFZV7$UH}18sKdg83K$>q3cPzVl z$9}g)^PPGgR`Ir`@YU;EwPnVg6dD!UJSH?w7$dKv~i6xu;hm@jX_X^=~)oMQ?KDKe8F;e_a!5ll-tDCoOWHwOrGB@R`GG~76gL!aL`;m9Ux>DT<|DNWB zgU=k|egXFN``3(X)gaei)1H=qY#H<%F|`|Wp6^&JYAqi(mOG#B57Up1Jn|dB)L;9D z0QdS=)W()yleiC71zI=y4{aFxAO~%F+uC_X-whL^#Y3z4ySJ?tC+P*Dkn8yyH336cY@HCrG3%Wm)e5=Gw1ENPXTYi*#&C8m2j2Y?#E#_eLP=-lie-`Uu{!C z%V76TouTaO1tTM!2mK%P)HzFBUj|%Gyu1z9_ISg)@u756XqzYG4$QqoOioOEs>#lt zhuB)DK6*RwpxH3cbt2#NqO;zxnP68Gs(W$wh)eD8pv{1sH{-2|afxMMIVK}vF!1hy3tL>(P9$pK3J zO&n@6nHHUP2J-Xs?b3mSF3(b@wJt)4vuw+%(b0jl;CQb2z4GcmHF2; zjc&ex{A~Dj{ZAX+76H<{4J(f3ZHV&Y%x(QwLv%+`g*TwzvmoEMz`zwfvbxcc81-Xo zCa}E~*!OJNxAPDGLgb2UfakgG4o*JZcY>gWfZyqbkMo8fJz4V3u;Xzyf5IdjpQ7{v zDFgmPdm64BXNhcDzuDV)FR9|of~)g3>A#TZzvwR&4Nml@EtQm2HI_Isic_wI$$m>b z{nBr^yeA^n)h>0vRcbX*b&wn~W^#;OOc&bMh0&P$n7OI>@pw8Wd9J&b0Qy_A^lwc} z>f)z2ta|HzMXlfHJ%e_286}4D#Xw=;I#e|g=tzVUFgh{R tePPv;B-+#Z_UNpQe|)bh906-b;DF=ooD#{ASj%rkOk}*EDLj4C{{cx;mRtY; diff --git a/resources/icons/overlay/sla_support_points_hover.png b/resources/icons/overlay/sla_support_points_hover.png deleted file mode 100644 index 2b385c0e74dbe999bf598d19934b4a1cfbb2aa5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2057 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 Cb*qX1 From 4718c839f66dc6a0712e738390a08be3013143b7 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 11 Apr 2019 13:20:34 +0200 Subject: [PATCH 26/57] Copy and paste -> Added items for copy and paste on the toolbar --- resources/icons/copy.svg | 15 +++++++++++++++ resources/icons/paste.svg | 19 +++++++++++++++++++ src/slic3r/GUI/GLCanvas3D.cpp | 35 ++++++++++++++++++++++++++++++----- src/slic3r/GUI/GLToolbar.cpp | 2 ++ src/slic3r/GUI/GLToolbar.hpp | 2 ++ src/slic3r/GUI/MainFrame.cpp | 14 ++------------ src/slic3r/GUI/MainFrame.hpp | 2 -- src/slic3r/GUI/Plater.cpp | 4 ++++ src/slic3r/GUI/Plater.hpp | 2 ++ 9 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 resources/icons/copy.svg create mode 100644 resources/icons/paste.svg diff --git a/resources/icons/copy.svg b/resources/icons/copy.svg new file mode 100644 index 0000000000..fcb24122de --- /dev/null +++ b/resources/icons/copy.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/resources/icons/paste.svg b/resources/icons/paste.svg new file mode 100644 index 0000000000..ba698e5b5e --- /dev/null +++ b/resources/icons/paste.svg @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d1e5bbac6d..f911dd1718 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3232,12 +3232,37 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.add_separator()) return false; + item.name = "copy"; +#if ENABLE_SVG_ICONS + item.icon_filename = "copy.svg"; +#endif // ENABLE_SVG_ICONS + item.tooltip = GUI::L_str("Copy [Ctrl+C]"); + item.sprite_id = 4; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy(); }; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "paste"; +#if ENABLE_SVG_ICONS + item.icon_filename = "paste.svg"; +#endif // ENABLE_SVG_ICONS + item.tooltip = GUI::L_str("Paste [Ctrl+V]"); + item.sprite_id = 5; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste(); }; + if (!m_toolbar.add_item(item)) + return false; + + if (!m_toolbar.add_separator()) + return false; + item.name = "more"; #if ENABLE_SVG_ICONS item.icon_filename = "instance_add.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Add instance [+]"); - item.sprite_id = 4; + item.sprite_id = 6; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; @@ -3249,7 +3274,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "instance_remove.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Remove instance [-]"); - item.sprite_id = 5; + item.sprite_id = 7; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; @@ -3264,7 +3289,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "split_objects.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Split to objects"); - item.sprite_id = 6; + item.sprite_id = 8; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; @@ -3276,7 +3301,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "split_parts.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Split to parts"); - item.sprite_id = 7; + item.sprite_id = 9; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; @@ -3291,7 +3316,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "layers.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Layers editing"); - item.sprite_id = 8; + item.sprite_id = 10; item.is_toggable = true; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; }; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 144d02476e..842700aef8 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -21,6 +21,8 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 0f8b17e04f..24314d60ff 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -20,6 +20,8 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index a6c0148999..d24d41a48c 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -256,16 +256,6 @@ bool MainFrame::can_delete_all() const return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; } -bool MainFrame::can_copy() const -{ - return (m_plater != nullptr) ? !m_plater->is_selection_empty() : false; -} - -bool MainFrame::can_paste() const -{ - return (m_plater != nullptr) ? !m_plater->is_selection_clipboard_empty() : false; -} - void MainFrame::on_dpi_changed(const wxRect &suggested_rect) { // TODO @@ -399,8 +389,8 @@ void MainFrame::init_menubar() Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_select()); }, item_select_all->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete_sel->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete_all()); }, item_delete_all->GetId()); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_copy()); }, item_copy->GetId()); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_paste()); }, item_paste->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater->can_copy()); }, item_copy->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater->can_paste()); }, item_paste->GetId()); } // Window menu diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 16116ec433..625e70b83b 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -68,8 +68,6 @@ class MainFrame : public DPIFrame bool can_select() const; bool can_delete() const; bool can_delete_all() const; - bool can_copy() const; - bool can_paste() const; protected: virtual void on_dpi_changed(const wxRect &suggested_rect); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index daab7818ea..ebe7e2c758 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1406,6 +1406,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { reset(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); @@ -3723,5 +3725,7 @@ bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } bool Plater::can_arrange() const { return p->can_arrange(); } bool Plater::can_layers_editing() const { return p->can_layers_editing(); } +bool Plater::can_copy() const { return !is_selection_empty(); } +bool Plater::can_paste() const { return !is_selection_clipboard_empty(); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 3d3ba8b3ec..a70656c58f 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -194,6 +194,8 @@ public: bool can_split_to_volumes() const; bool can_arrange() const; bool can_layers_editing() const; + bool can_copy() const; + bool can_paste() const; private: struct priv; From 4046d517c9f9564e414a3309fbd5401c72a319b4 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 11 Apr 2019 14:21:08 +0200 Subject: [PATCH 27/57] Copy and paste -> Disabled paste of volumes when nothing is selected and fixed enabling/disabling of paste item in toolbar and edit menu --- src/slic3r/GUI/Plater.cpp | 10 ++++++---- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/Selection.cpp | 22 ++++++++++++++-------- src/slic3r/GUI/Selection.hpp | 29 +++++++++++++++-------------- 4 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ebe7e2c758..a34562d851 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3711,10 +3711,12 @@ void Plater::paste_from_clipboard() { p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); } - -bool Plater::is_selection_clipboard_empty() const +bool Plater::can_paste_from_clipboard() const { - return p->view3D->get_canvas3d()->get_selection().is_clipboard_empty(); + const Selection& selection = p->view3D->get_canvas3d()->get_selection(); + const Selection::Clipboard& clipboard = selection.get_clipboard(); + Selection::EMode mode = clipboard.get_mode(); + return !clipboard.is_empty() && ((mode == Selection::Instance) || selection.is_from_single_instance()); } bool Plater::can_delete() const { return p->can_delete(); } @@ -3726,6 +3728,6 @@ bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } bool Plater::can_arrange() const { return p->can_arrange(); } bool Plater::can_layers_editing() const { return p->can_layers_editing(); } bool Plater::can_copy() const { return !is_selection_empty(); } -bool Plater::can_paste() const { return !is_selection_clipboard_empty(); } +bool Plater::can_paste() const { return can_paste_from_clipboard(); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index a70656c58f..708c07f395 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -184,7 +184,7 @@ public: void copy_selection_to_clipboard(); void paste_from_clipboard(); - bool is_selection_clipboard_empty() const; + bool can_paste_from_clipboard() const; bool can_delete() const; bool can_delete_all() const; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 705f02d332..41313d5ed4 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1067,15 +1067,21 @@ void Selection::paste_from_clipboard() if (!m_valid || m_clipboard.is_empty()) return; - if ((m_clipboard.get_mode() == Volume) && is_from_single_instance()) - paste_volumes_from_clipboard(); - else - paste_objects_from_clipboard(); -} + switch (m_clipboard.get_mode()) + { + case Volume: + { + if (is_from_single_instance()) + paste_volumes_from_clipboard(); -bool Selection::is_clipboard_empty() -{ - return m_clipboard.is_empty(); + break; + } + case Instance: + { + paste_objects_from_clipboard(); + break; + } + } } void Selection::update_valid() diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 9fa9f18691..a8b0c06dcb 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -138,19 +138,6 @@ public: typedef std::set InstanceIdxsList; typedef std::map ObjectIdxsToInstanceIdxsMap; -private: - struct Cache - { - // Cache of GLVolume derived transformation matrices, valid during mouse dragging. - VolumesCache volumes_data; - // Center of the dragged selection, valid during mouse dragging. - Vec3d dragging_center; - // Map from indices of ModelObject instances in Model::objects - // to a set of indices of ModelVolume instances in ModelObject::instances - // Here the index means a position inside the respective std::vector, not ModelID. - ObjectIdxsToInstanceIdxsMap content; - }; - class Clipboard { Model m_model; @@ -168,6 +155,19 @@ private: void set_mode(Selection::EMode mode) { m_mode = mode; } }; +private: + struct Cache + { + // Cache of GLVolume derived transformation matrices, valid during mouse dragging. + VolumesCache volumes_data; + // Center of the dragged selection, valid during mouse dragging. + Vec3d dragging_center; + // Map from indices of ModelObject instances in Model::objects + // to a set of indices of ModelVolume instances in ModelObject::instances + // Here the index means a position inside the respective std::vector, not ModelID. + ObjectIdxsToInstanceIdxsMap content; + }; + // Volumes owned by GLCanvas3D. GLVolumePtrs* m_volumes; // Model, not owned. @@ -287,7 +287,8 @@ public: void copy_to_clipboard(); void paste_from_clipboard(); - bool is_clipboard_empty(); + + const Clipboard& get_clipboard() const { return m_clipboard; } private: void update_valid(); From 4a210aeecf3a720a12d3e249c6e2da5cea533085 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 11 Apr 2019 15:44:32 +0200 Subject: [PATCH 28/57] Vojtech's improvements in the SLA preview cutting dialog. --- src/admesh/stl.h | 14 +++++- src/admesh/stlinit.cpp | 37 ++++++++------- src/libslic3r/Model.cpp | 20 ++++---- src/libslic3r/TriangleMesh.cpp | 16 ++----- src/libslic3r/TriangleMesh.hpp | 5 +- src/slic3r/GUI/GLCanvas3D.cpp | 11 +++-- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 48 ++++++++++++-------- 7 files changed, 86 insertions(+), 65 deletions(-) diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 2c436b426b..d682b24347 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 e2939b8af8..911f4f5e82 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 e634dd1383..ac4cd73efc 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/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 44c638a408..2d603661dc 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -805,14 +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_orig = this->mesh->stl.facet_start[facet_idx]; - stl_facet facet = facet_orig; - - if (m_use_quaternion) { - facet.vertex[0] = m_quaternion * facet.vertex[0]; - facet.vertex[1] = m_quaternion * facet.vertex[1]; - facet.vertex[2] = m_quaternion * facet.vertex[2]; - } + 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))); @@ -884,7 +877,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( 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 inclined: + // These are used only if the cut plane is tilted: stl_vertex rotated_a; stl_vertex rotated_b; @@ -909,10 +902,11 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( // 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. + // 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 = m_use_quaternion ? stl_vertex(m_quaternion * this->mesh->stl.facet_start[facet_idx].normal) : this->mesh->stl.facet_start[facet_idx].normal; + 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) { @@ -1029,7 +1023,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( i = vertices[2]; assert(i != line_out->a_id && i != line_out->b_id); line_out->edge_type = ((m_use_quaternion ? - m_quaternion * this->v_scaled_shared[i].z() + (m_quaternion * this->v_scaled_shared[i]).z() : this->v_scaled_shared[i].z()) < slice_z) ? feTop : feBottom; } #endif diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index a0020255dd..4bf5ce039f 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, ""); } @@ -182,7 +183,7 @@ private: // 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; + 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/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 96a767c4e1..d5da8ac1b2 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3494,10 +3494,13 @@ 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(); - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); - ::glEnable(GL_CLIP_PLANE0); + if (! m_use_VBOs) { + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); + ::glEnable(GL_CLIP_PLANE0); + } _render_volumes(true); - ::glDisable(GL_CLIP_PLANE0); + if (! m_use_VBOs) + ::glDisable(GL_CLIP_PLANE0); m_gizmos.render_current_gizmo_for_picking_pass(m_selection); @@ -3661,7 +3664,7 @@ void GLCanvas3D::_render_objects() const } m_camera_clipping_plane = ClippingPlane::ClipsNothing(); - ::glDisable(GL_LIGHTING); + glsafe(::glDisable(GL_LIGHTING)); } void GLCanvas3D::_render_selection() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 21d1bb3a7d..087ec1b01b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -58,6 +58,7 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S { // 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()) { @@ -123,6 +124,11 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const || 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]); @@ -131,22 +137,23 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const m_old_clipping_plane_distance = m_clipping_plane_distance; } - ::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 - - ::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(); + 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(); + } } @@ -344,9 +351,6 @@ void GLGizmoSlaSupports::update_mesh() m_AABB = igl::AABB(); m_AABB.init(m_V, m_F); - - m_tms.reset(new TriangleMeshSlicer); - m_tms->init(&m_mesh, [](){}); } // Unprojects the mouse position on the mesh and return the hit point and normal of the facet. @@ -969,6 +973,12 @@ void GLGizmoSlaSupports::on_set_state() 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; From d8a3308f8a3f6f93780ae217280130605f3b3711 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 11 Apr 2019 15:45:14 +0200 Subject: [PATCH 29/57] Setting the SLA Pad wall height to zero and adding a warning to not set it to non zero, as it may be difficult to tear the object off the vat foil. --- src/libslic3r/PrintConfig.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index a978a31753..66641a7f54 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"); From a8fd1ceca074e05dc6190ff8b6440f37f62fb325 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 11 Apr 2019 15:51:12 +0200 Subject: [PATCH 30/57] New icons for copy and paste toolbar items --- resources/icons/copy.svg | 44 +++++++++++++++++++++++++++++---------- resources/icons/paste.svg | 40 +++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/resources/icons/copy.svg b/resources/icons/copy.svg index fcb24122de..9b8430dd79 100644 --- a/resources/icons/copy.svg +++ b/resources/icons/copy.svg @@ -1,15 +1,37 @@ - - - - + + + + - - + + + + + + + + + + + + diff --git a/resources/icons/paste.svg b/resources/icons/paste.svg index ba698e5b5e..028ffb8ea0 100644 --- a/resources/icons/paste.svg +++ b/resources/icons/paste.svg @@ -1,19 +1,27 @@ - - - - + + + + + - - + + + + + + From 59758fea550cd595e5343527201de3a49deacbc0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 11 Apr 2019 17:07:41 +0200 Subject: [PATCH 31/57] Material correction XY merged --- src/slic3r/GUI/Tab.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index c735a40ee0..30e3bfe896 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; From 6ff12111a60f4cef51d03033dd3b3da224424703 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 12 Apr 2019 08:49:24 +0200 Subject: [PATCH 32/57] Copy and paste -> Disabled paste of instances when selection is in Volume mode --- src/slic3r/GUI/Plater.cpp | 12 +++++++++++- src/slic3r/GUI/Selection.cpp | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 027db69f56..c18091e4b1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3717,7 +3717,17 @@ bool Plater::can_paste_from_clipboard() const const Selection& selection = p->view3D->get_canvas3d()->get_selection(); const Selection::Clipboard& clipboard = selection.get_clipboard(); Selection::EMode mode = clipboard.get_mode(); - return !clipboard.is_empty() && ((mode == Selection::Instance) || selection.is_from_single_instance()); + + if (clipboard.is_empty()) + return false; + + if ((mode == Selection::Volume) && !selection.is_from_single_instance()) + return false; + + if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance)) + return false; + + return true; } bool Plater::can_delete() const { return p->can_delete(); } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 315aa487ac..22df9ed196 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1078,7 +1078,8 @@ void Selection::paste_from_clipboard() } case Instance: { - paste_objects_from_clipboard(); + if (m_mode == Instance) + paste_objects_from_clipboard(); break; } From 5a1b9cd3826b653bfaca8fdec20e7cb353337f83 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 12 Apr 2019 08:57:53 +0200 Subject: [PATCH 33/57] Copy and paste -> Fixed tooltips for copy and paste toolbar items on Mac --- src/slic3r/GUI/GLCanvas3D.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 44181ffcb2..19ce28bed7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3236,7 +3236,7 @@ bool GLCanvas3D::_init_toolbar() #if ENABLE_SVG_ICONS item.icon_filename = "copy.svg"; #endif // ENABLE_SVG_ICONS - item.tooltip = GUI::L_str("Copy [Ctrl+C]"); + item.tooltip = GUI::L_str("Copy") + " [" + GUI::shortkey_ctrl_prefix() + "C]"; item.sprite_id = 4; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy(); }; @@ -3247,7 +3247,7 @@ bool GLCanvas3D::_init_toolbar() #if ENABLE_SVG_ICONS item.icon_filename = "paste.svg"; #endif // ENABLE_SVG_ICONS - item.tooltip = GUI::L_str("Paste [Ctrl+V]"); + item.tooltip = GUI::L_str("Paste") + " [" + GUI::shortkey_ctrl_prefix() + "V]"; item.sprite_id = 5; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste(); }; From a55022ae2f22ae347be961033bc0933afd2ad9f9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 12 Apr 2019 10:26:33 +0200 Subject: [PATCH 34/57] Fix for issue SPE-901 --- src/libslic3r/SLA/SLASupportTree.cpp | 30 +++++++++++++--------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 34dd80cee0..bb0e5e0071 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1572,10 +1572,8 @@ public: auto hit = bridge_mesh_intersect(headjp, n, r); if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i); - else { - if(m_cfg.ground_facing_only) head.invalidate(); - m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); - } + else if(m_cfg.ground_facing_only) head.invalidate(); + else m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); } // We want to search for clusters of points that are far enough @@ -1872,7 +1870,7 @@ public: } } - void cascade_pillars() { + void interconnect_pillars() { // Now comes the algorithm that connects pillars with each other. // Ideally every pillar should be connected with at least one of its // neighbors if that neighbor is within max_pillar_link_distance @@ -2121,7 +2119,7 @@ bool SLASupportTree::generate(const std::vector &support_points, std::bind(&Algorithm::routing_to_model, &alg), - std::bind(&Algorithm::cascade_pillars, &alg), + std::bind(&Algorithm::interconnect_pillars, &alg), std::bind(&Algorithm::routing_headless, &alg), @@ -2150,16 +2148,16 @@ bool SLASupportTree::generate(const std::vector &support_points, // Let's define a simple automaton that will run our program. auto progress = [&ctl, &pc] () { static const std::array stepstr { - L("Starting"), - L("Filtering"), - L("Generate pinheads"), - L("Classification"), - L("Routing to ground"), - L("Routing supports to model surface"), - L("Cascading pillars"), - L("Processing small holes"), - L("Done"), - L("Abort") + "Starting", + "Filtering", + "Generate pinheads", + "Classification", + "Routing to ground", + "Routing supports to model surface", + "Interconnecting pillars", + "Processing small holes", + "Done", + "Abort" }; static const std::array stepstate { From 566bb7041a9444a9b14616d172dee061a7db616f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 12 Apr 2019 11:01:53 +0200 Subject: [PATCH 35/57] Optimization of the OpenGL shaders for clipping with clipping planes. --- resources/shaders/gouraud.fs | 15 +++------------ resources/shaders/gouraud.vs | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index b2bc915ab6..09003f4079 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -2,30 +2,21 @@ const vec3 ZERO = vec3(0.0, 0.0, 0.0); +varying vec3 clipping_planes_dots; + // x = tainted, y = specular; varying vec2 intensity; varying vec3 delta_box_min; varying vec3 delta_box_max; -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_pos.z < z_range.x) || (z_range.y < world_pos.z)) + if (any(lessThan(clipping_planes_dots, ZERO))) 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 vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb; gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a); diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index a226cf312c..cc54c1c449 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -28,13 +28,18 @@ struct PrintBoxDetection uniform PrintBoxDetection print_box; +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + // x = tainted, y = specular; varying vec2 intensity; varying vec3 delta_box_min; varying vec3 delta_box_max; -varying vec3 world_pos; +varying vec3 clipping_planes_dots; void main() { @@ -66,8 +71,11 @@ void main() { delta_box_min = ZERO; delta_box_max = ZERO; - } + } gl_Position = ftransform(); - world_pos = vec3(print_box.volume_world_matrix * gl_Vertex); -} + // Point in homogenous coordinates. + vec4 world_pos = print_box.volume_world_matrix * gl_Vertex; + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); +} From 2fe63e0b045361584e9320039088c7634303017a Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 12 Apr 2019 10:57:30 +0200 Subject: [PATCH 36/57] Revert high DPI bitmaps in Preset and PresetBundle for now due to bugs in wxBitmapComboBox et al. --- src/slic3r/GUI/Preset.cpp | 8 ++++++-- src/slic3r/GUI/PresetBundle.cpp | 15 +++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 97c42d7fb9..bed960a626 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -801,12 +801,16 @@ bool PresetCollection::delete_current_preset() void PresetCollection::load_bitmap_default(wxWindow *window, const std::string &file_name) { - *m_bitmap_main_frame = create_scaled_bitmap(window, file_name); + // XXX: See note in PresetBundle::load_compatible_bitmaps() + (void)window; + *m_bitmap_main_frame = create_scaled_bitmap(nullptr, file_name); } void PresetCollection::load_bitmap_add(wxWindow *window, const std::string &file_name) { - *m_bitmap_add = create_scaled_bitmap(window, file_name); + // XXX: See note in PresetBundle::load_compatible_bitmaps() + (void)window; + *m_bitmap_add = create_scaled_bitmap(nullptr, file_name); } const Preset* PresetCollection::get_selected_preset_parent() const diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index ad7829b2c4..a8a63056cc 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -398,10 +398,17 @@ void PresetBundle::export_selections(AppConfig &config) void PresetBundle::load_compatible_bitmaps(wxWindow *window) { - *m_bitmapCompatible = create_scaled_bitmap(window, "flag_green"); - *m_bitmapIncompatible = create_scaled_bitmap(window, "flag_red"); - *m_bitmapLock = create_scaled_bitmap(window, "lock_closed"); - *m_bitmapLockOpen = create_scaled_bitmap(window, "sys_unlock.png"); + // We don't actually pass the window pointer here and instead generate + // a low DPI bitmap, because the wxBitmapComboBox and wxDataViewControl don't support + // high DPI bitmaps very well, they compute their dimensions wrong. + // TODO: Update this when fixed in wxWidgets + // See also PresetCollection::load_bitmap_default() and PresetCollection::load_bitmap_add() + + (void)window; + *m_bitmapCompatible = create_scaled_bitmap(nullptr, "flag_green"); + *m_bitmapIncompatible = create_scaled_bitmap(nullptr, "flag_red"); + *m_bitmapLock = create_scaled_bitmap(nullptr, "lock_closed"); + *m_bitmapLockOpen = create_scaled_bitmap(nullptr, "sys_unlock.png"); prints .set_bitmap_compatible(m_bitmapCompatible); filaments .set_bitmap_compatible(m_bitmapCompatible); From 5522ed95744ef82ff925421fe195880b35eab73a Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 12 Apr 2019 11:28:07 +0200 Subject: [PATCH 37/57] Also revert high DPI bitmaps in ObjectList for now --- src/slic3r/GUI/GUI_ObjectList.cpp | 41 +++++++++++++++++-------------- src/slic3r/GUI/PresetBundle.cpp | 2 +- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index dc39093513..87a00ea0f7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -61,19 +61,22 @@ ObjectList::ObjectList(wxWindow* parent) : { // Fill CATEGORY_ICON { + // Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget, + // see note in PresetBundle::load_compatible_bitmaps() + // ptFFF - CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(this, "layers"); - CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(this, "infill"); - CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(this, "support"); - CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(this, "time"); - CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(this, "funnel"); - CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(this, "funnel"); -// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(this, "skirt+brim"); -// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(this, "time"); - CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(this, "wrench"); - // ptSLA - CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(this, "sla_supports"); - CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(this, "brick.png"); + CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers"); + CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill"); + CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support"); + CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); + CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); + CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); +// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); +// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); + CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); + // ptSLA + CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "sla_supports"); + CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "brick.png"); } // create control @@ -392,10 +395,10 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const void ObjectList::init_icons() { - m_bmp_modifiermesh = create_scaled_bitmap(this, "lambda.png"); - m_bmp_solidmesh = create_scaled_bitmap(this, "object.png"); - m_bmp_support_enforcer = create_scaled_bitmap(this, "support_enforcer_.png"); - m_bmp_support_blocker = create_scaled_bitmap(this, "support_blocker_.png"); + m_bmp_modifiermesh = create_scaled_bitmap(nullptr, "lambda.png"); + m_bmp_solidmesh = create_scaled_bitmap(nullptr, "object.png"); + m_bmp_support_enforcer = create_scaled_bitmap(nullptr, "support_enforcer_.png"); + m_bmp_support_blocker = create_scaled_bitmap(nullptr, "support_blocker_.png"); m_bmp_vector.reserve(4); // bitmaps for different types of parts @@ -406,13 +409,13 @@ void ObjectList::init_icons() m_objects_model->SetVolumeBitmaps(m_bmp_vector); // init icon for manifold warning - m_bmp_manifold_warning = create_scaled_bitmap(this, "exclamation_mark_.png"); + m_bmp_manifold_warning = create_scaled_bitmap(nullptr, "exclamation_mark_.png"); // init bitmap for "Split to sub-objects" context menu - m_bmp_split = create_scaled_bitmap(this, "split_parts"); + m_bmp_split = create_scaled_bitmap(nullptr, "split_parts"); // init bitmap for "Add Settings" context menu - m_bmp_cog = create_scaled_bitmap(this, "cog"); + m_bmp_cog = create_scaled_bitmap(nullptr, "cog"); } diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index a8a63056cc..6e93d06705 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -399,7 +399,7 @@ void PresetBundle::export_selections(AppConfig &config) void PresetBundle::load_compatible_bitmaps(wxWindow *window) { // We don't actually pass the window pointer here and instead generate - // a low DPI bitmap, because the wxBitmapComboBox and wxDataViewControl don't support + // a low DPI bitmap, because the wxBitmapComboBox and wxDataViewCtrl don't support // high DPI bitmaps very well, they compute their dimensions wrong. // TODO: Update this when fixed in wxWidgets // See also PresetCollection::load_bitmap_default() and PresetCollection::load_bitmap_add() From defcd26b4ac03843611c5ec42938e8c232d1cb60 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 12 Apr 2019 11:28:24 +0200 Subject: [PATCH 38/57] Copy and paste -> Fixed paste for multivolumes copies --- src/slic3r/GUI/GUI_ObjectList.cpp | 6 ++++++ src/slic3r/GUI/Selection.cpp | 1 + 2 files changed, 7 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index fd2c43806b..b65b780437 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -463,6 +463,12 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol m_parts_changed = true; parts_changed(obj_idx); + if (items.size() > 1) + { + m_selection_mode = smVolume; + m_last_selected_item = wxDataViewItem(0); + } + select_items(items); #ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 22df9ed196..8ee449a216 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1795,6 +1795,7 @@ void Selection::paste_objects_from_clipboard() dst_object->translate(10.0, 10.0, 0.0); object_idxs.push_back(m_model->objects.size() - 1); } + wxGetApp().obj_list()->paste_objects_into_list(object_idxs); } From 9f53123204bfef4f444eafcedff3634d621dbeb7 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 12 Apr 2019 11:43:29 +0200 Subject: [PATCH 39/57] Windows specific: Added "--sw-renderer" to load the MESA SW rasterizer. --- src/libslic3r/PrintConfig.cpp | 7 +++++++ src/slic3r_app_msvc.cpp | 12 ++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 66641a7f54..8c031bcaa0 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3117,6 +3117,13 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->label = L("Logging level"); def->tooltip = L("Messages with severity lower or eqal to the loglevel will be printed out. 0:trace, 1:debug, 2:info, 3:warning, 4:error, 5:fatal"); def->min = 0; + +#ifdef _MSC_VER + def = this->add("sw_renderer", coBool); + def->label = L("Render with a software renderer"); + def->tooltip = L("Render with a software renderer. The bundled MESA software renderer is loaded instead of the default OpenGL driver."); + def->min = 0; +#endif /* _MSC_VER */ } const CLIActionsConfigDef cli_actions_config_def; diff --git a/src/slic3r_app_msvc.cpp b/src/slic3r_app_msvc.cpp index 380d30cf40..e2c6013a23 100644 --- a/src/slic3r_app_msvc.cpp +++ b/src/slic3r_app_msvc.cpp @@ -207,12 +207,20 @@ int wmain(int argc, wchar_t **argv) std::vector argv_extended; argv_extended.emplace_back(argv[0]); // Here one may push some additional parameters based on the wrapper type. - for (int i = 1; i < argc; ++ i) + bool force_mesa = false; + for (int i = 1; i < argc; ++ i) { + if (wcscmp(argv[i], L"--sw-renderer") == 0) + force_mesa = true; + else if (wcscmp(argv[i], L"--no-sw-renderer") == 0) + force_mesa = false; argv_extended.emplace_back(argv[i]); + } argv_extended.emplace_back(nullptr); OpenGLVersionCheck opengl_version_check; bool load_mesa = + // Forced from the command line. + force_mesa || // Running over a rempote desktop, and the RemoteFX is not enabled, therefore Windows will only provide SW OpenGL 1.1 context. // In that case, use Mesa. ::GetSystemMetrics(SM_REMOTESESSION) || @@ -267,5 +275,5 @@ int wmain(int argc, wchar_t **argv) return -1; } // argc minus the trailing nullptr of the argv - return slic3r_main(argv_extended.size() - 1, argv_extended.data()); + return slic3r_main((int)argv_extended.size() - 1, argv_extended.data()); } From edab2a056e26cddf877b83f1f0c7b8effe671952 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 12 Apr 2019 11:50:14 +0200 Subject: [PATCH 40/57] Added icons for Edit menu items: Delete selected, Delete all, Copy, Paste --- resources/icons/copy_menu.svg | 37 ++++++++++++++++++++++++ resources/icons/delete_all_menu.svg | 31 ++++++++++++++++++++ resources/icons/paste_menu.svg | 27 ++++++++++++++++++ resources/icons/remove_menu.svg | 44 +++++++++++++++++++++++++++++ src/slic3r/GUI/MainFrame.cpp | 8 +++--- 5 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 resources/icons/copy_menu.svg create mode 100644 resources/icons/delete_all_menu.svg create mode 100644 resources/icons/paste_menu.svg create mode 100644 resources/icons/remove_menu.svg diff --git a/resources/icons/copy_menu.svg b/resources/icons/copy_menu.svg new file mode 100644 index 0000000000..0d1af6a0a7 --- /dev/null +++ b/resources/icons/copy_menu.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/delete_all_menu.svg b/resources/icons/delete_all_menu.svg new file mode 100644 index 0000000000..5ee6d6ea6d --- /dev/null +++ b/resources/icons/delete_all_menu.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/paste_menu.svg b/resources/icons/paste_menu.svg new file mode 100644 index 0000000000..74dbbf8eef --- /dev/null +++ b/resources/icons/paste_menu.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/resources/icons/remove_menu.svg b/resources/icons/remove_menu.svg new file mode 100644 index 0000000000..a25ae965b4 --- /dev/null +++ b/resources/icons/remove_menu.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d24d41a48c..56c7ec0ed2 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -375,16 +375,16 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { m_plater->select_all(); }, ""); editMenu->AppendSeparator(); wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + sep + hotkey_delete, _(L("Deletes the current selection")), - [this](wxCommandEvent&) { m_plater->remove_selected(); }, ""); + [this](wxCommandEvent&) { m_plater->remove_selected(); }, "remove_menu"); wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete, _(L("Deletes all objects")), - [this](wxCommandEvent&) { m_plater->reset(); }, ""); + [this](wxCommandEvent&) { m_plater->reset(); }, "delete_all_menu"); editMenu->AppendSeparator(); wxMenuItem* item_copy = append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + "\tCtrl+C", _(L("Copy selection to clipboard")), - [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, ""); + [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, "copy_menu"); wxMenuItem* item_paste = append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + "\tCtrl+V", _(L("Paste clipboard")), - [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, ""); + [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, "paste_menu"); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_select()); }, item_select_all->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete_sel->GetId()); From 50f8f45e0a907ae722c2293928127470edbf0bef Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 12 Apr 2019 12:08:52 +0200 Subject: [PATCH 41/57] Added new icons --- resources/icons/add_copies.svg | 19 ++++++++++ resources/icons/add_modifier.svg | 13 +++++++ resources/icons/add_part.svg | 19 ++++++++++ resources/icons/advanced_plus.svg | 24 ++++++++++++ resources/icons/delete.svg | 22 +++++++++++ resources/icons/layers.svg | 8 ++-- resources/icons/number_of_copies.svg | 28 ++++++++++++++ resources/icons/open.svg | 11 ++++++ resources/icons/plater.svg | 12 ++++++ resources/icons/re_slice.svg | 19 ++++++++++ resources/icons/remove_copies.svg | 15 ++++++++ resources/icons/split_parts_SMALL.svg | 18 +++++++++ resources/icons/support_blocker.svg | 48 ++++++++++++++++++++++++ resources/icons/support_enforcer.svg | 19 ++++++++++ resources/icons/upload_queue.svg | 29 +++++++++++++++ resources/icons/wrench.svg | 53 ++++++++++++++++++--------- src/slic3r/GUI/GUI_ObjectList.cpp | 10 ++--- src/slic3r/GUI/MainFrame.cpp | 10 ++--- src/slic3r/GUI/Plater.cpp | 21 +++++------ src/slic3r/GUI/Tab.cpp | 4 +- src/slic3r/GUI/wxExtensions.cpp | 2 +- 21 files changed, 359 insertions(+), 45 deletions(-) create mode 100644 resources/icons/add_copies.svg create mode 100644 resources/icons/add_modifier.svg create mode 100644 resources/icons/add_part.svg create mode 100644 resources/icons/advanced_plus.svg create mode 100644 resources/icons/delete.svg create mode 100644 resources/icons/number_of_copies.svg create mode 100644 resources/icons/open.svg create mode 100644 resources/icons/plater.svg create mode 100644 resources/icons/re_slice.svg create mode 100644 resources/icons/remove_copies.svg create mode 100644 resources/icons/split_parts_SMALL.svg create mode 100644 resources/icons/support_blocker.svg create mode 100644 resources/icons/support_enforcer.svg create mode 100644 resources/icons/upload_queue.svg diff --git a/resources/icons/add_copies.svg b/resources/icons/add_copies.svg new file mode 100644 index 0000000000..45b1d27cf9 --- /dev/null +++ b/resources/icons/add_copies.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/add_modifier.svg b/resources/icons/add_modifier.svg new file mode 100644 index 0000000000..c3cfaabbb2 --- /dev/null +++ b/resources/icons/add_modifier.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/resources/icons/add_part.svg b/resources/icons/add_part.svg new file mode 100644 index 0000000000..5f0afdcc35 --- /dev/null +++ b/resources/icons/add_part.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/resources/icons/advanced_plus.svg b/resources/icons/advanced_plus.svg new file mode 100644 index 0000000000..48c4e08db0 --- /dev/null +++ b/resources/icons/advanced_plus.svg @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/resources/icons/delete.svg b/resources/icons/delete.svg new file mode 100644 index 0000000000..f0976a76bc --- /dev/null +++ b/resources/icons/delete.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/layers.svg b/resources/icons/layers.svg index cd71fab3a3..da5dec21d5 100644 --- a/resources/icons/layers.svg +++ b/resources/icons/layers.svg @@ -5,13 +5,13 @@ - + - + - + @@ -20,7 +20,7 @@ - + diff --git a/resources/icons/number_of_copies.svg b/resources/icons/number_of_copies.svg new file mode 100644 index 0000000000..a5c8affc7f --- /dev/null +++ b/resources/icons/number_of_copies.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/open.svg b/resources/icons/open.svg new file mode 100644 index 0000000000..8fcddc2a61 --- /dev/null +++ b/resources/icons/open.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/resources/icons/plater.svg b/resources/icons/plater.svg new file mode 100644 index 0000000000..6fd21215b6 --- /dev/null +++ b/resources/icons/plater.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/resources/icons/re_slice.svg b/resources/icons/re_slice.svg new file mode 100644 index 0000000000..54490ea428 --- /dev/null +++ b/resources/icons/re_slice.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/resources/icons/remove_copies.svg b/resources/icons/remove_copies.svg new file mode 100644 index 0000000000..d249cb5bcc --- /dev/null +++ b/resources/icons/remove_copies.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/resources/icons/split_parts_SMALL.svg b/resources/icons/split_parts_SMALL.svg new file mode 100644 index 0000000000..45f90bee06 --- /dev/null +++ b/resources/icons/split_parts_SMALL.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/resources/icons/support_blocker.svg b/resources/icons/support_blocker.svg new file mode 100644 index 0000000000..56086b2795 --- /dev/null +++ b/resources/icons/support_blocker.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/support_enforcer.svg b/resources/icons/support_enforcer.svg new file mode 100644 index 0000000000..0f0eb4907f --- /dev/null +++ b/resources/icons/support_enforcer.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/resources/icons/upload_queue.svg b/resources/icons/upload_queue.svg new file mode 100644 index 0000000000..7647178366 --- /dev/null +++ b/resources/icons/upload_queue.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/resources/icons/wrench.svg b/resources/icons/wrench.svg index 7966da8d8f..5266ec1ebc 100644 --- a/resources/icons/wrench.svg +++ b/resources/icons/wrench.svg @@ -3,22 +3,41 @@ - - - - - - + + + + + + + + + diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index dc39093513..d3003b2a2f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -392,10 +392,10 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const void ObjectList::init_icons() { - m_bmp_modifiermesh = create_scaled_bitmap(this, "lambda.png"); - m_bmp_solidmesh = create_scaled_bitmap(this, "object.png"); - m_bmp_support_enforcer = create_scaled_bitmap(this, "support_enforcer_.png"); - m_bmp_support_blocker = create_scaled_bitmap(this, "support_blocker_.png"); + m_bmp_modifiermesh = create_scaled_bitmap(this, "add_modifier"); + m_bmp_solidmesh = create_scaled_bitmap(this, "add_part"); + m_bmp_support_enforcer = create_scaled_bitmap(this, "support_enforcer"); + m_bmp_support_blocker = create_scaled_bitmap(this, "support_blocker"); m_bmp_vector.reserve(4); // bitmaps for different types of parts @@ -409,7 +409,7 @@ void ObjectList::init_icons() m_bmp_manifold_warning = create_scaled_bitmap(this, "exclamation_mark_.png"); // init bitmap for "Split to sub-objects" context menu - m_bmp_split = create_scaled_bitmap(this, "split_parts"); + m_bmp_split = create_scaled_bitmap(this, "split_parts_SMALL"); // init bitmap for "Add Settings" context menu m_bmp_cog = create_scaled_bitmap(this, "cog"); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b68027178a..87145511c4 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -271,7 +271,7 @@ void MainFrame::init_menubar() wxMenu* fileMenu = new wxMenu; { wxMenuItem* item_open = append_menu_item(fileMenu, wxID_ANY, _(L("&Open Project")) + dots + "\tCtrl+O", _(L("Open a project file")), - [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "brick_add.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "open"); wxMenuItem* item_save = append_menu_item(fileMenu, wxID_ANY, _(L("&Save Project")) + "\tCtrl+S", _(L("Save current project file")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename())); }, "save"); wxMenuItem* item_save_as = append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Alt+S", _(L("Save current project file as")), @@ -332,10 +332,10 @@ void MainFrame::init_menubar() fileMenu->AppendSeparator(); #endif m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _(L("(Re)Slice &Now")) + "\tCtrl+R", _(L("Start new slicing process")), - [this](wxCommandEvent&) { reslice_now(); }, "shape_handles.png"); + [this](wxCommandEvent&) { reslice_now(); }, "re_slice"); fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _(L("&Repair STL file")) + dots, _(L("Automatically repair an STL file")), - [this](wxCommandEvent&) { repair_stl(); }, "wrench.png"); + [this](wxCommandEvent&) { repair_stl(); }, "wrench"); fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), _(L("Quit Slic3r")), [this](wxCommandEvent&) { Close(false); }); @@ -390,7 +390,7 @@ void MainFrame::init_menubar() size_t tab_offset = 0; if (m_plater) { append_menu_item(windowMenu, wxID_HIGHEST + 1, _(L("&Plater Tab")) + "\tCtrl+1", _(L("Show the plater")), - [this](wxCommandEvent&) { select_tab(0); }, "application_view_tile.png"); + [this](wxCommandEvent&) { select_tab(0); }, "plater"); tab_offset += 1; } if (tab_offset > 0) { @@ -428,7 +428,7 @@ void MainFrame::init_menubar() windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _(L("Print &Host Upload Queue")) + "\tCtrl+J", _(L("Display the Print Host Upload Queue window")), - [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "arrow_up.png"); + [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue"); } // View menu diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5d7436250a..960d908013 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -143,8 +143,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : info_manifold_text->SetFont(wxGetApp().small_font()); info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold->SetFont(wxGetApp().small_font()); - wxBitmap bitmap(GUI::from_u8(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); - manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, bitmap); + manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(parent, "exclamation_mark_.png")/*bitmap*/); auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); sizer_manifold->Add(info_manifold_text, 0); sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); @@ -2820,17 +2819,17 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ wxMenuItem* item_delete = nullptr; if (is_part) { item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), - [this](wxCommandEvent&) { q->remove_selected(); }, "remove"); + [this](wxCommandEvent&) { q->remove_selected(); }, "delete"); sidebar->obj_list()->append_menu_item_export_stl(menu); } else { wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Increase copies")) + "\t+", _(L("Place one more copy of the selected object")), - [this](wxCommandEvent&) { q->increase_instances(); }, "instance_add"); + [this](wxCommandEvent&) { q->increase_instances(); }, "add_copies"); wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Decrease copies")) + "\t-", _(L("Remove one copy of the selected object")), - [this](wxCommandEvent&) { q->decrease_instances(); }, "instance_remove"); + [this](wxCommandEvent&) { q->decrease_instances(); }, "remove_copies"); wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _(L("Set number of copies")) + dots, _(L("Change the number of copies of the selected object")), - [this](wxCommandEvent&) { q->set_number_of_copies(); }, "textfield.png"); + [this](wxCommandEvent&) { q->set_number_of_copies(); }, "number_of_copies"); items_increase.push_back(item_increase); items_decrease.push_back(item_decrease); @@ -2838,7 +2837,7 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake. item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), - [this](wxCommandEvent&) { q->remove_selected(); }, "remove"); + [this](wxCommandEvent&) { q->remove_selected(); }, "delete"); menu->AppendSeparator(); wxMenuItem* item_instance_to_object = sidebar->obj_list()->append_menu_item_instance_to_object(menu); @@ -2893,9 +2892,9 @@ bool Plater::priv::complit_init_object_menu() return false; wxMenuItem* item_split_objects = append_menu_item(split_menu, wxID_ANY, _(L("To objects")), _(L("Split the selected object into individual objects")), - [this](wxCommandEvent&) { split_object(); }, "split_objects.png", &object_menu); + [this](wxCommandEvent&) { split_object(); }, "split_objects", &object_menu); wxMenuItem* item_split_volumes = append_menu_item(split_menu, wxID_ANY, _(L("To parts")), _(L("Split the selected object into individual sub-parts")), - [this](wxCommandEvent&) { split_volume(); }, "split_parts.png", &object_menu); + [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", &object_menu); wxMenuItem* item_split = append_submenu(&object_menu, split_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object"))/*, "shape_ungroup.png"*/); object_menu.AppendSeparator(); @@ -2915,7 +2914,7 @@ bool Plater::priv::complit_init_object_menu() bool Plater::priv::complit_init_sla_object_menu() { wxMenuItem* item_split = append_menu_item(&sla_object_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual objects")), - [this](wxCommandEvent&) { split_object(); }, "shape_ungroup_o.png"); + [this](wxCommandEvent&) { split_object(); }, "split_objects"); sla_object_menu.AppendSeparator(); @@ -2935,7 +2934,7 @@ bool Plater::priv::complit_init_sla_object_menu() bool Plater::priv::complit_init_part_menu() { wxMenuItem* item_split = append_menu_item(&part_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual sub-parts")), - [this](wxCommandEvent&) { split_volume(); }, "shape_ungroup_p.png"); + [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL"); part_menu.AppendSeparator(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 30e3bfe896..6457de1656 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1090,7 +1090,7 @@ void TabPrint::build() optgroup = page->new_optgroup(_(L("Advanced"))); optgroup->append_single_option_line("interface_shells"); - page = add_options_page(_(L("Advanced")), "wrench.png"); + page = add_options_page(_(L("Advanced")), "wrench"); optgroup = page->new_optgroup(_(L("Extrusion width"))); optgroup->append_single_option_line("extrusion_width"); optgroup->append_single_option_line("first_layer_extrusion_width"); @@ -1463,7 +1463,7 @@ void TabFilament::build() optgroup->append_single_option_line("slowdown_below_layer_time"); optgroup->append_single_option_line("min_print_speed"); - page = add_options_page(_(L("Advanced")), "wrench.png"); + page = add_options_page(_(L("Advanced")), "wrench"); optgroup = page->new_optgroup(_(L("Filament properties"))); optgroup->append_single_option_line("filament_type"); optgroup->append_single_option_line("filament_soluble"); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 7f5b5ad9c4..7683604822 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -461,7 +461,7 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, con // ---------------------------------------------------------------------------- void PrusaObjectDataViewModelNode::set_object_action_icon() { - m_action_icon = create_scaled_bitmap(nullptr, "add_object.png"); // FIXME: pass window ptr + m_action_icon = create_scaled_bitmap(nullptr, "advanced_plus"); // FIXME: pass window ptr } void PrusaObjectDataViewModelNode::set_part_action_icon() { m_action_icon = create_scaled_bitmap(nullptr, m_type == itVolume ? "cog.png" : "brick_go.png"); // FIXME: pass window ptr From 349e30a39c351fdedd4ec8e216442ccf71c906f1 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 12 Apr 2019 12:16:44 +0200 Subject: [PATCH 42/57] WIP: Restoring of the command line only builds (no GUI support). --- CMakeLists.txt | 4 ++++ src/slic3r.cpp | 24 ++++++++++++++++-------- src/slic3r_app_msvc.cpp | 27 ++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e6cf45bd4..8adea28c34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,10 @@ foreach (_cache_var ${_cache_vars}) endif () endforeach() +if (SLIC3R_GUI) + add_definitions(-DSLIC3R_GUI) +endif () + if (MSVC) if (SLIC3R_MSVC_COMPILE_PARALLEL) add_compile_options(/MP) diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 780efea7b3..f6a2282bb0 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -6,10 +6,12 @@ #define NOMINMAX #include #include - // Let the NVIDIA and AMD know we want to use their graphics card - // on a dual graphics card system. - __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; - __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + #ifdef SLIC3R_GUI + // Let the NVIDIA and AMD know we want to use their graphics card + // on a dual graphics card system. + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + #endif /* SLIC3R_GUI */ #endif /* WIN32 */ #include @@ -448,7 +450,7 @@ int CLI::run(int argc, char **argv) } if (start_gui) { -#if 1 +#ifdef SLIC3R_GUI // #ifdef USE_WX GUI::GUI_App *gui = new GUI::GUI_App(); // gui->autosave = m_config.opt_string("autosave"); @@ -477,12 +479,12 @@ int CLI::run(int argc, char **argv) gui->mainframe->load_config(m_extra_config); }); return wxEntry(argc, argv); -#else +#else /* SLIC3R_GUI */ // No GUI support. Just print out a help. this->print_help(false); // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). return (argc == 0) ? 0 : 1; -#endif +#endif /* SLIC3R_GUI */ } return 0; @@ -563,7 +565,13 @@ bool CLI::setup(int argc, char **argv) void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const { boost::nowide::cout - << "Slic3r Prusa Edition " << SLIC3R_BUILD << std::endl + << "Slic3r Prusa Edition " << SLIC3R_BUILD +#ifdef SLIC3R_GUI + << " (with GUI support)" +#else /* SLIC3R_GUI */ + << " (without GUI support)" +#endif /* SLIC3R_GUI */ + << std::endl << "https://github.com/prusa3d/Slic3r" << std::endl << std::endl << "Usage: slic3r [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl << std::endl diff --git a/src/slic3r_app_msvc.cpp b/src/slic3r_app_msvc.cpp index e2c6013a23..bf72ee1360 100644 --- a/src/slic3r_app_msvc.cpp +++ b/src/slic3r_app_msvc.cpp @@ -6,14 +6,20 @@ #include #include #include -// Let the NVIDIA and AMD know we want to use their graphics card -// on a dual graphics card system. -__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; -__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + +#ifdef SLIC3R_GUI + // Let the NVIDIA and AMD know we want to use their graphics card + // on a dual graphics card system. + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +#endif /* SLIC3R_GUI */ #include #include -#include + +#ifdef SLIC3R_GUI + #include +#endif /* SLIC3R_GUI */ #include #include @@ -23,6 +29,7 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #include +#ifdef SLIC3R_GUI class OpenGLVersionCheck { public: @@ -188,6 +195,7 @@ protected: }; bool OpenGLVersionCheck::message_pump_exit = false; +#endif /* SLIC3R_GUI */ extern "C" { typedef int (__stdcall *Slic3rMainFunc)(int argc, wchar_t **argv); @@ -206,17 +214,23 @@ int wmain(int argc, wchar_t **argv) std::vector argv_extended; argv_extended.emplace_back(argv[0]); + +#ifdef SLIC3R_GUI // Here one may push some additional parameters based on the wrapper type. bool force_mesa = false; +#endif /* SLIC3R_GUI */ for (int i = 1; i < argc; ++ i) { +#ifdef SLIC3R_GUI if (wcscmp(argv[i], L"--sw-renderer") == 0) force_mesa = true; else if (wcscmp(argv[i], L"--no-sw-renderer") == 0) force_mesa = false; +#endif /* SLIC3R_GUI */ argv_extended.emplace_back(argv[i]); } argv_extended.emplace_back(nullptr); +#ifdef SLIC3R_GUI OpenGLVersionCheck opengl_version_check; bool load_mesa = // Forced from the command line. @@ -226,6 +240,7 @@ int wmain(int argc, wchar_t **argv) ::GetSystemMetrics(SM_REMOTESESSION) || // Try to load the default OpenGL driver and test its context version. ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); +#endif /* SLIC3R_GUI */ wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; ::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH); @@ -236,6 +251,7 @@ int wmain(int argc, wchar_t **argv) _wsplitpath(path_to_exe, drive, dir, fname, ext); _wmakepath(path_to_exe, drive, dir, nullptr, nullptr); +#ifdef SLIC3R_GUI // https://wiki.qt.io/Cross_compiling_Mesa_for_Windows // http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/ if (load_mesa) { @@ -260,6 +276,7 @@ int wmain(int argc, wchar_t **argv) printf("slic3r.dll was not loaded\n"); return -1; } +#endif /* SLIC3R_GUI */ // resolve function address here slic3r_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r, From 33ef1173a7018f3a65d8f460866ea200a50019e8 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 12 Apr 2019 12:57:45 +0200 Subject: [PATCH 43/57] Moved the GLEW dependencies from libslic3r to the gui slic3r library. --- src/libslic3r/CMakeLists.txt | 1 - src/slic3r/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 5bd4ea2b69..ce93d95fab 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -189,7 +189,6 @@ target_link_libraries(libslic3r clipper nowide ${EXPAT_LIBRARIES} - ${GLEW_LIBRARIES} glu-libtess polypartition poly2tri diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 9d25318136..1d8a1e26e3 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -154,7 +154,7 @@ endif () add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES}) -target_link_libraries(libslic3r_gui libslic3r avrdude imgui) +target_link_libraries(libslic3r_gui libslic3r avrdude imgui ${GLEW_LIBRARIES}) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) endif () From 3ab1b69682b87db2bff6667245f0a4a3c4938f8e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 12 Apr 2019 13:13:31 +0200 Subject: [PATCH 44/57] Fixed a bug in command line only win32 slic3r build. --- src/slic3r_app_msvc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r_app_msvc.cpp b/src/slic3r_app_msvc.cpp index bf72ee1360..c100f8fd9b 100644 --- a/src/slic3r_app_msvc.cpp +++ b/src/slic3r_app_msvc.cpp @@ -266,6 +266,8 @@ int wmain(int argc, wchar_t **argv) } else printf("MESA OpenGL library was loaded sucessfully\n"); } +#endif /* SLIC3R_GUI */ + wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; wcscpy(path_to_slic3r, path_to_exe); @@ -276,7 +278,6 @@ int wmain(int argc, wchar_t **argv) printf("slic3r.dll was not loaded\n"); return -1; } -#endif /* SLIC3R_GUI */ // resolve function address here slic3r_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r, From 8614bb2eded6a59b0393224745cbc368039870a4 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 12 Apr 2019 13:29:06 +0200 Subject: [PATCH 45/57] One more command line only slic3r build fix. --- src/slic3r.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/slic3r.cpp b/src/slic3r.cpp index f6a2282bb0..ff87b3f6d9 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -40,8 +40,11 @@ #include "libslic3r/Utils.hpp" #include "slic3r.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/GUI_App.hpp" + +#ifdef SLIC3R_GUI + #include "slic3r/GUI/GUI.hpp" + #include "slic3r/GUI/GUI_App.hpp" +#endif /* SLIC3R_GUI */ using namespace Slic3r; From 17ad59c7e6cd2c96978682532e36e16052645541 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 12 Apr 2019 13:33:06 +0200 Subject: [PATCH 46/57] Only show the "--software-renderer" option if GUI is compiled in. --- src/libslic3r/PrintConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8c031bcaa0..b3061ca618 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3118,7 +3118,7 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->tooltip = L("Messages with severity lower or eqal to the loglevel will be printed out. 0:trace, 1:debug, 2:info, 3:warning, 4:error, 5:fatal"); def->min = 0; -#ifdef _MSC_VER +#if defined(_MSC_VER) && defined(SLIC3R_GUI) def = this->add("sw_renderer", coBool); def->label = L("Render with a software renderer"); def->tooltip = L("Render with a software renderer. The bundled MESA software renderer is loaded instead of the default OpenGL driver."); From 4abcf7bec41ac89b26b7bf0aaea969d2a475a94e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 12 Apr 2019 14:24:59 +0200 Subject: [PATCH 47/57] SLA gizmo dialog height increased so the new clipping plane slider fits --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 129db27150..04c7e2046f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -771,7 +771,7 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_l RENDER_AGAIN: m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - const ImVec2 window_size(m_imgui->scaled(17.f, 18.f)); + const ImVec2 window_size(m_imgui->scaled(17.f, 20.f)); ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); ImGui::SetNextWindowSize(ImVec2(window_size)); From e1debd3a4c43032489a6680a32541e423a854a12 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 12 Apr 2019 14:30:28 +0200 Subject: [PATCH 48/57] Added icons for 3D and preview menu items --- resources/icons/editor_menu.svg | 20 +++++++++++++ resources/icons/preview_menu.svg | 48 ++++++++++++++++++++++++++++++++ src/slic3r/GUI/MainFrame.cpp | 4 +-- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 resources/icons/editor_menu.svg create mode 100644 resources/icons/preview_menu.svg diff --git a/resources/icons/editor_menu.svg b/resources/icons/editor_menu.svg new file mode 100644 index 0000000000..3485f61876 --- /dev/null +++ b/resources/icons/editor_menu.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/resources/icons/preview_menu.svg b/resources/icons/preview_menu.svg new file mode 100644 index 0000000000..5e10c8357b --- /dev/null +++ b/resources/icons/preview_menu.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index a6b5ed6be8..afffebbb3a 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -414,9 +414,9 @@ void MainFrame::init_menubar() if (m_plater) { windowMenu->AppendSeparator(); wxMenuItem* item_3d = append_menu_item(windowMenu, wxID_HIGHEST + 5, _(L("3&D")) + "\tCtrl+5", _(L("Show the 3D editing view")), - [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, ""); + [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "editor_menu"); wxMenuItem* item_preview = append_menu_item(windowMenu, wxID_HIGHEST + 6, _(L("Pre&view")) + "\tCtrl+6", _(L("Show the 3D slices preview")), - [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, ""); + [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "preview_menu"); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_3d->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_preview->GetId()); From 7d15ee8fd943c23fef059d204adbe504d618430b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 12 Apr 2019 15:31:33 +0200 Subject: [PATCH 49/57] Offset used to place newly added volumes and instances proportional to the bed max size --- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ src/slic3r/GUI/GLCanvas3D.hpp | 2 ++ src/slic3r/GUI/GUI_ObjectList.cpp | 4 +--- src/slic3r/GUI/Plater.cpp | 5 +++-- src/slic3r/GUI/Selection.cpp | 6 ++++-- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 19ce28bed7..edfeb0f576 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3140,6 +3140,11 @@ Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)); } +double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const +{ + return factor * m_bed.get_bounding_box().max_size(); +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 2486753e7f..bea6b3e3bb 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -583,6 +583,8 @@ public: void refresh_camera_scene_box() { m_camera.set_scene_box(scene_bounding_box()); } bool is_mouse_dragging() const { return m_mouse.dragging; } + double get_size_proportional_to_max_bed_size(double factor) const; + private: bool _is_shown_on_screen() const; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2a1d328e16..a1adb55117 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1437,9 +1437,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode const wxString name = _(L("Generic")) + "-" + _(type_name); TriangleMesh mesh; - auto& bed_shape = printer_config().option("bed_shape")->values; - const auto& sz = BoundingBoxf(bed_shape).size(); - const auto side = 0.1 * std::max(sz(0), sz(1)); + double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1); if (type_name == "Box") // Sitting on the print bed, left front front corner at (0, 0). diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 001f7bb6dc..0b501b81ed 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3225,8 +3225,9 @@ void Plater::increase_instances(size_t num) bool was_one_instance = model_object->instances.size()==1; - float offset = 10.0; - for (size_t i = 0; i < num; i++, offset += 10.0) { + double offset_base = canvas3D()->get_size_proportional_to_max_bed_size(0.05); + double offset = offset_base; + for (size_t i = 0; i < num; i++, offset += offset_base) { Vec3d offset_vec = model_instance->get_offset() + Vec3d(offset, offset, 0.0); model_object->add_instance(offset_vec, model_instance->get_scaling_factor(), model_instance->get_rotation(), model_instance->get_mirror()); // p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec)); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 8ee449a216..8b80310dab 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1778,7 +1778,8 @@ void Selection::paste_volumes_from_clipboard() ModelVolume* dst_volume = dst_object->add_volume(*src_volume); dst_volume->config = src_volume->config; dst_volume->set_new_unique_id(); - dst_volume->translate(10.0, 10.0, 0.0); + double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); + dst_volume->translate(offset, offset, 0.0); volumes.push_back(dst_volume); } wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes); @@ -1792,7 +1793,8 @@ void Selection::paste_objects_from_clipboard() for (const ModelObject* src_object : src_objects) { ModelObject* dst_object = m_model->add_object(*src_object); - dst_object->translate(10.0, 10.0, 0.0); + double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); + dst_object->translate(offset, offset, 0.0); object_idxs.push_back(m_model->objects.size() - 1); } From 95eb98a103ab87bfb31b1638fdbefbf9b6135f6a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 12 Apr 2019 15:47:40 +0200 Subject: [PATCH 50/57] Darker icons for 3D and preview menu items --- resources/icons/editor_menu.svg | 6 +++--- resources/icons/preview_menu.svg | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/resources/icons/editor_menu.svg b/resources/icons/editor_menu.svg index 3485f61876..223efda0f5 100644 --- a/resources/icons/editor_menu.svg +++ b/resources/icons/editor_menu.svg @@ -4,15 +4,15 @@ viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> - - + - diff --git a/resources/icons/preview_menu.svg b/resources/icons/preview_menu.svg index 5e10c8357b..725caf7b8a 100644 --- a/resources/icons/preview_menu.svg +++ b/resources/icons/preview_menu.svg @@ -4,27 +4,27 @@ viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> - - - - - - @@ -32,12 +32,12 @@ c0.59-0.58,1.54-0.57,2.12,0.02c0.58,0.59,0.57,1.54-0.02,2.12L91.77,60.95C91.49,61.23,91.11,61.38,90.72,61.38z"/> - - + + + + + + diff --git a/resources/icons/dot.svg b/resources/icons/dot.svg new file mode 100644 index 0000000000..236db36786 --- /dev/null +++ b/resources/icons/dot.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/resources/icons/dot_white.svg b/resources/icons/dot_white.svg new file mode 100644 index 0000000000..90fbaf7fb1 --- /dev/null +++ b/resources/icons/dot_white.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/resources/icons/exclamation.svg b/resources/icons/exclamation.svg new file mode 100644 index 0000000000..4568026529 --- /dev/null +++ b/resources/icons/exclamation.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/export_config.svg b/resources/icons/export_config.svg new file mode 100644 index 0000000000..e70035dae5 --- /dev/null +++ b/resources/icons/export_config.svg @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/resources/icons/export_config_bundle.svg b/resources/icons/export_config_bundle.svg new file mode 100644 index 0000000000..1e587689d0 --- /dev/null +++ b/resources/icons/export_config_bundle.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/export_gcode.svg b/resources/icons/export_gcode.svg new file mode 100644 index 0000000000..317e01f7df --- /dev/null +++ b/resources/icons/export_gcode.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/resources/icons/export_plater.svg b/resources/icons/export_plater.svg new file mode 100644 index 0000000000..641d952ad6 --- /dev/null +++ b/resources/icons/export_plater.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/resources/icons/import_config.svg b/resources/icons/import_config.svg new file mode 100644 index 0000000000..636a311510 --- /dev/null +++ b/resources/icons/import_config.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/import_config_bundle.svg b/resources/icons/import_config_bundle.svg new file mode 100644 index 0000000000..b8342760a8 --- /dev/null +++ b/resources/icons/import_config_bundle.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/import_plater.svg b/resources/icons/import_plater.svg new file mode 100644 index 0000000000..a953122f26 --- /dev/null +++ b/resources/icons/import_plater.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/mark_X.svg b/resources/icons/mark_X.svg new file mode 100644 index 0000000000..1045debc51 --- /dev/null +++ b/resources/icons/mark_X.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/resources/icons/mark_Y.svg b/resources/icons/mark_Y.svg new file mode 100644 index 0000000000..26e01b2952 --- /dev/null +++ b/resources/icons/mark_Y.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/resources/icons/mark_Z.svg b/resources/icons/mark_Z.svg new file mode 100644 index 0000000000..cd2826ac00 --- /dev/null +++ b/resources/icons/mark_Z.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/resources/icons/pad.svg b/resources/icons/pad.svg new file mode 100644 index 0000000000..dc5907e37f --- /dev/null +++ b/resources/icons/pad.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/printer_white.svg b/resources/icons/printer_white.svg new file mode 100644 index 0000000000..d94f6fd5c4 --- /dev/null +++ b/resources/icons/printer_white.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/resources/icons/set_separate_obj.svg b/resources/icons/set_separate_obj.svg new file mode 100644 index 0000000000..c95149e2dd --- /dev/null +++ b/resources/icons/set_separate_obj.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/split_object_SMALL.svg b/resources/icons/split_object_SMALL.svg new file mode 100644 index 0000000000..7e362c2ba2 --- /dev/null +++ b/resources/icons/split_object_SMALL.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/resources/icons/test.svg b/resources/icons/test.svg new file mode 100644 index 0000000000..abf35d0ae7 --- /dev/null +++ b/resources/icons/test.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a1adb55117..23756a2e2b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -75,8 +75,8 @@ ObjectList::ObjectList(wxWindow* parent) : // CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); // ptSLA - CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "sla_supports"); - CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "brick.png"); + CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); + CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad"); } // create control @@ -409,7 +409,7 @@ void ObjectList::init_icons() m_objects_model->SetVolumeBitmaps(m_bmp_vector); // init icon for manifold warning - m_bmp_manifold_warning = create_scaled_bitmap(nullptr, "exclamation_mark_.png"); + m_bmp_manifold_warning = create_scaled_bitmap(nullptr, "exclamation"); // init bitmap for "Split to sub-objects" context menu m_bmp_split = create_scaled_bitmap(nullptr, "split_parts_SMALL"); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index afffebbb3a..6a09b5a94c 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -281,30 +281,30 @@ void MainFrame::init_menubar() wxMenu* import_menu = new wxMenu(); wxMenuItem* item_import_model = append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")), - [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "brick_add.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater"); import_menu->AppendSeparator(); append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")), - [this](wxCommandEvent&) { load_config_file(); }, "plugin_add.png"); + [this](wxCommandEvent&) { load_config_file(); }, "import_config"); append_menu_item(import_menu, wxID_ANY, _(L("Import Config from &project")) + dots +"\tCtrl+Alt+L", _(L("Load configuration from project file")), - [this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, "plugin_add.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, "import_config"); import_menu->AppendSeparator(); append_menu_item(import_menu, wxID_ANY, _(L("Import Config &Bundle")) + dots, _(L("Load presets from a bundle")), - [this](wxCommandEvent&) { load_configbundle(); }, "lorry_add.png"); + [this](wxCommandEvent&) { load_configbundle(); }, "import_config_bundle"); append_submenu(fileMenu, import_menu, wxID_ANY, _(L("&Import")), ""); wxMenu* export_menu = new wxMenu(); wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "cog_go.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "export_gcode"); export_menu->AppendSeparator(); wxMenuItem* item_export_stl = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "brick_go.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "export_plater"); wxMenuItem* item_export_amf = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &AMF")) + dots, _(L("Export current plate as AMF")), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "brick_go.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "export_plater"); export_menu->AppendSeparator(); append_menu_item(export_menu, wxID_ANY, _(L("Export &Config")) +dots +"\tCtrl+E", _(L("Export current configuration to file")), - [this](wxCommandEvent&) { export_config(); }, "plugin_go.png"); + [this](wxCommandEvent&) { export_config(); }, "export_config"); append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")), - [this](wxCommandEvent&) { export_configbundle(); }, "lorry_go.png"); + [this](wxCommandEvent&) { export_configbundle(); }, "export_config_bundle"); append_submenu(fileMenu, export_menu, wxID_ANY, _(L("&Export")), ""); fileMenu->AppendSeparator(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0b501b81ed..8512011d84 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -143,7 +143,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : info_manifold_text->SetFont(wxGetApp().small_font()); info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold->SetFont(wxGetApp().small_font()); - manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(parent, "exclamation_mark_.png")/*bitmap*/); + manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(parent, "exclamation")); auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); sizer_manifold->Add(info_manifold_text, 0); sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); @@ -2869,11 +2869,11 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ return false; append_menu_item(mirror_menu, wxID_ANY, _(L("Along X axis")), _(L("Mirror the selected object along the X axis")), - [this](wxCommandEvent&) { mirror(X); }, "bullet_red.png", menu); + [this](wxCommandEvent&) { mirror(X); }, "mark_X", menu); append_menu_item(mirror_menu, wxID_ANY, _(L("Along Y axis")), _(L("Mirror the selected object along the Y axis")), - [this](wxCommandEvent&) { mirror(Y); }, "bullet_green.png", menu); + [this](wxCommandEvent&) { mirror(Y); }, "mark_Y", menu); append_menu_item(mirror_menu, wxID_ANY, _(L("Along Z axis")), _(L("Mirror the selected object along the Z axis")), - [this](wxCommandEvent&) { mirror(Z); }, "bullet_blue.png", menu); + [this](wxCommandEvent&) { mirror(Z); }, "mark_Z", menu); wxMenuItem* item_mirror = append_submenu(menu, mirror_menu, wxID_ANY, _(L("Mirror")), _(L("Mirror the selected object"))); @@ -2894,7 +2894,7 @@ bool Plater::priv::complit_init_object_menu() return false; wxMenuItem* item_split_objects = append_menu_item(split_menu, wxID_ANY, _(L("To objects")), _(L("Split the selected object into individual objects")), - [this](wxCommandEvent&) { split_object(); }, "split_objects", &object_menu); + [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", &object_menu); wxMenuItem* item_split_volumes = append_menu_item(split_menu, wxID_ANY, _(L("To parts")), _(L("Split the selected object into individual sub-parts")), [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", &object_menu); @@ -2916,7 +2916,7 @@ bool Plater::priv::complit_init_object_menu() bool Plater::priv::complit_init_sla_object_menu() { wxMenuItem* item_split = append_menu_item(&sla_object_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual objects")), - [this](wxCommandEvent&) { split_object(); }, "split_objects"); + [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL"); sla_object_menu.AppendSeparator(); diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 6e93d06705..bcf76d9583 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -414,13 +414,11 @@ void PresetBundle::load_compatible_bitmaps(wxWindow *window) filaments .set_bitmap_compatible(m_bitmapCompatible); sla_prints .set_bitmap_compatible(m_bitmapCompatible); sla_materials.set_bitmap_compatible(m_bitmapCompatible); - printers .set_bitmap_compatible(m_bitmapCompatible); prints .set_bitmap_incompatible(m_bitmapIncompatible); filaments .set_bitmap_incompatible(m_bitmapIncompatible); sla_prints .set_bitmap_incompatible(m_bitmapIncompatible); sla_materials.set_bitmap_incompatible(m_bitmapIncompatible); - printers .set_bitmap_incompatible(m_bitmapIncompatible); prints .set_bitmap_lock(m_bitmapLock); filaments .set_bitmap_lock(m_bitmapLock); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6457de1656..a697c7a717 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -153,7 +153,7 @@ void Tab::create_preset_tab() m_bmp_non_system = &m_bmp_white_bullet; // Bitmaps to be shown on the "Undo user changes" button next to each input field. m_bmp_value_revert = create_scaled_bitmap(this, "undo"); - m_bmp_white_bullet = create_scaled_bitmap(this, "bullet_white.png"); + m_bmp_white_bullet = create_scaled_bitmap(this, luma >= 128 ? "dot" : "dot_white"/*"bullet_white.png"*/); m_bmp_question = create_scaled_bitmap(this, "question"); fill_icon_descriptions(); @@ -1632,7 +1632,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse ")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - btn->SetBitmap(create_scaled_bitmap(this, "zoom.png")); + btn->SetBitmap(create_scaled_bitmap(this, "browse")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1651,7 +1651,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - btn->SetBitmap(create_scaled_bitmap(this, "wrench.png")); + btn->SetBitmap(create_scaled_bitmap(this, "test")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1688,7 +1688,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - btn->SetBitmap(create_scaled_bitmap(this, "zoom.png")); + btn->SetBitmap(create_scaled_bitmap(this, "browse")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1766,7 +1766,7 @@ void TabPrinter::build_fff() line.widget = [this](wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(wxGetApp().small_font()); - btn->SetBitmap(create_scaled_bitmap(this, "printer")); + btn->SetBitmap(create_scaled_bitmap(this, "printer_white")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1967,7 +1967,7 @@ void TabPrinter::build_sla() line.widget = [this](wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Set ")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(wxGetApp().small_font()); - btn->SetBitmap(create_scaled_bitmap(this, "printer")); + btn->SetBitmap(create_scaled_bitmap(this, "printer_white")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -2912,7 +2912,7 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep deps.btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - deps.btn->SetBitmap(create_scaled_bitmap(this, "printer")); + deps.btn->SetBitmap(create_scaled_bitmap(this, "printer_white")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add((deps.checkbox), 0, wxALIGN_CENTER_VERTICAL); @@ -3308,7 +3308,7 @@ void TabSLAPrint::build() optgroup->append_single_option_line("layer_height"); optgroup->append_single_option_line("faded_layers"); - page = add_options_page(_(L("Supports")), "sla_supports"); + page = add_options_page(_(L("Supports")), "support"/*"sla_supports"*/); optgroup = page->new_optgroup(_(L("Supports"))); optgroup->append_single_option_line("supports_enable"); @@ -3336,7 +3336,7 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_points_density_relative"); optgroup->append_single_option_line("support_points_minimal_distance"); - page = add_options_page(_(L("Pad")), "brick.png"); + page = add_options_page(_(L("Pad")), "pad"); optgroup = page->new_optgroup(_(L("Pad"))); optgroup->append_single_option_line("pad_enable"); optgroup->append_single_option_line("pad_wall_thickness"); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 6e078617df..c0390d1a27 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -464,7 +464,7 @@ void PrusaObjectDataViewModelNode::set_object_action_icon() { m_action_icon = create_scaled_bitmap(nullptr, "advanced_plus"); // FIXME: pass window ptr } void PrusaObjectDataViewModelNode::set_part_action_icon() { - m_action_icon = create_scaled_bitmap(nullptr, m_type == itVolume ? "cog.png" : "brick_go.png"); // FIXME: pass window ptr + m_action_icon = create_scaled_bitmap(nullptr, m_type == itVolume ? "cog" : "set_separate_obj"); // FIXME: pass window ptr } Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; From b0c33a1fe9db68b7121bea9cb8729bcd14299418 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 12 Apr 2019 18:29:47 +0200 Subject: [PATCH 53/57] Fixed copying of some object's attributes into the clipboard (layer height profile, layer height table etc) Added public Plater::schedule_background_process() --- src/slic3r/GUI/Plater.cpp | 5 +++++ src/slic3r/GUI/Plater.hpp | 1 + src/slic3r/GUI/Selection.cpp | 16 +++++++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8512011d84..646a2ee64a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3698,6 +3698,11 @@ void Plater::changed_object(int obj_idx) this->p->schedule_background_process(); } +void Plater::schedule_background_process() +{ + this->p->schedule_background_process(); +} + void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); } void Plater::update_object_menu() { p->update_object_menu(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 708c07f395..30d780e7cd 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -162,6 +162,7 @@ public: void reslice(); void reslice_SLA_supports(const ModelObject &object); void changed_object(int obj_idx); + void schedule_background_process(); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void send_gcode(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 8b80310dab..957c04d690 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1033,9 +1033,14 @@ void Selection::copy_to_clipboard() { ModelObject* src_object = m_model->objects[object.first]; ModelObject* dst_object = m_clipboard.add_object(); - dst_object->name = src_object->name; - dst_object->input_file = src_object->input_file; - dst_object->config = src_object->config; + dst_object->name = src_object->name; + dst_object->input_file = src_object->input_file; + 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_height_profile = src_object->layer_height_profile; + dst_object->origin_translation = src_object->origin_translation; for (int i : object.second) { @@ -1044,6 +1049,7 @@ void Selection::copy_to_clipboard() for (unsigned int i : m_list) { + // Copy the ModelVolumes only for the selected GLVolumes of the 1st selected instance. const GLVolume* volume = (*m_volumes)[i]; if ((volume->object_idx() == object.first) && (volume->instance_idx() == *object.second.begin())) { @@ -1053,7 +1059,8 @@ void Selection::copy_to_clipboard() ModelVolume* src_volume = src_object->volumes[volume_idx]; ModelVolume* dst_volume = dst_object->add_volume(*src_volume); dst_volume->set_new_unique_id(); - dst_volume->config = src_volume->config; + } else { + assert(false); } } } @@ -1776,7 +1783,6 @@ void Selection::paste_volumes_from_clipboard() for (ModelVolume* src_volume : src_object->volumes) { ModelVolume* dst_volume = dst_object->add_volume(*src_volume); - dst_volume->config = src_volume->config; dst_volume->set_new_unique_id(); double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); dst_volume->translate(offset, offset, 0.0); From 1e455bc0654c4764e7231b9d41a919be065e7dcd Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sat, 13 Apr 2019 14:15:54 +0200 Subject: [PATCH 54/57] Fix of "Variable layer height feature breaks after rotating part #2073" There was an approximate bounding box used at the GUI, while a snug bounding box was used at the back end, causing invalidation of the variable layer height editing profile on rotated objects. A snug bounding box around the first instance is now cached. --- src/libslic3r/Model.cpp | 57 ++++++++++++++++++++--------------- src/libslic3r/Model.hpp | 10 +++--- src/libslic3r/Print.hpp | 2 +- src/libslic3r/PrintObject.cpp | 6 ++-- src/slic3r/GUI/GLCanvas3D.cpp | 17 +++++++---- 5 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ac4cd73efc..6b16855e8e 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -593,6 +593,8 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->origin_translation = rhs.origin_translation; m_bounding_box = rhs.m_bounding_box; m_bounding_box_valid = rhs.m_bounding_box_valid; + m_raw_bounding_box = rhs.m_raw_bounding_box; + m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid; m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box; m_raw_mesh_bounding_box_valid = rhs.m_raw_mesh_bounding_box_valid; @@ -627,6 +629,8 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs) this->origin_translation = std::move(rhs.origin_translation); m_bounding_box = std::move(rhs.m_bounding_box); m_bounding_box_valid = std::move(rhs.m_bounding_box_valid); + m_raw_bounding_box = rhs.m_raw_bounding_box; + m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid; m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box; m_raw_mesh_bounding_box_valid = rhs.m_raw_mesh_bounding_box_valid; @@ -859,7 +863,7 @@ TriangleMesh ModelObject::full_raw_mesh() const return mesh; } -BoundingBoxf3 ModelObject::raw_mesh_bounding_box() const +const BoundingBoxf3& ModelObject::raw_mesh_bounding_box() const { if (! m_raw_mesh_bounding_box_valid) { m_raw_mesh_bounding_box_valid = true; @@ -880,33 +884,36 @@ BoundingBoxf3 ModelObject::full_raw_mesh_bounding_box() const } // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. -// This bounding box is only used for the actual slicing. -BoundingBoxf3 ModelObject::raw_bounding_box() const +// This bounding box is only used for the actual slicing and for layer editing UI to calculate the layers. +const BoundingBoxf3& ModelObject::raw_bounding_box() const { - BoundingBoxf3 bb; -#if ENABLE_GENERIC_SUBPARTS_PLACEMENT - if (this->instances.empty()) - throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); + if (! m_raw_bounding_box_valid) { + m_raw_bounding_box_valid = true; + m_raw_bounding_box.reset(); + #if ENABLE_GENERIC_SUBPARTS_PLACEMENT + if (this->instances.empty()) + throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); - const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); -#endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT - for (const ModelVolume *v : this->volumes) - if (v->is_model_part()) { -#if !ENABLE_GENERIC_SUBPARTS_PLACEMENT - if (this->instances.empty()) - throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); -#endif // !ENABLE_GENERIC_SUBPARTS_PLACEMENT + const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); + #endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) { + #if !ENABLE_GENERIC_SUBPARTS_PLACEMENT + if (this->instances.empty()) + throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); + #endif // !ENABLE_GENERIC_SUBPARTS_PLACEMENT -#if ENABLE_GENERIC_SUBPARTS_PLACEMENT - bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); -#else - // 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; + #if ENABLE_GENERIC_SUBPARTS_PLACEMENT + m_raw_bounding_box.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); + #else + // unmaintaned + assert(false); + // vol_mesh.transform(v->get_matrix()); + // m_raw_bounding_box_valid.merge(this->instances.front()->transform_mesh_bounding_box(vol_mesh, true)); + #endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT + } + } + return m_raw_bounding_box; } // This returns an accurate snug bounding box of the transformed object instance, without the translation applied. diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 9514012434..1234102e0f 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -212,7 +212,7 @@ public: // This bounding box is approximate and not snug. // This bounding box is being cached. const BoundingBoxf3& bounding_box() const; - void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } + void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } // A mesh containing all transformed instances of this object. TriangleMesh mesh() const; @@ -223,11 +223,11 @@ public: TriangleMesh full_raw_mesh() const; // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. // This bounding box is only used for the actual slicing. - BoundingBoxf3 raw_bounding_box() const; + const BoundingBoxf3& raw_bounding_box() const; // A snug bounding box around the transformed non-modifier object volumes. BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. - BoundingBoxf3 raw_mesh_bounding_box() const; + const BoundingBoxf3& raw_mesh_bounding_box() const; // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. BoundingBoxf3 full_raw_mesh_bounding_box() const; @@ -285,7 +285,7 @@ protected: private: ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), - m_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} + m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} ~ModelObject(); /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ @@ -304,6 +304,8 @@ private: // Bounding box, cached. mutable BoundingBoxf3 m_bounding_box; mutable bool m_bounding_box_valid; + mutable BoundingBoxf3 m_raw_bounding_box; + mutable bool m_raw_bounding_box_valid; mutable BoundingBoxf3 m_raw_mesh_bounding_box; mutable bool m_raw_mesh_bounding_box_valid; }; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9e97ab20cb..b566eadeda 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -132,7 +132,7 @@ public: // The slicing parameters are dependent on various configuration values // (layer height, first layer height, raft settings, print nozzle diameter etc). const SlicingParameters& slicing_parameters() const { return m_slicing_params; } - static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object); + static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z); // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) std::vector object_extruders() const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 954e583f7c..0b51f36ec0 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1370,7 +1370,7 @@ void PrintObject::update_slicing_parameters() this->print()->config(), m_config, unscale(this->size(2)), this->object_extruders()); } -SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object) +SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z) { PrintConfig print_config; PrintObjectConfig object_config; @@ -1390,7 +1390,9 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full object_extruders); sort_remove_duplicates(object_extruders); - return SlicingParameters::create_from_config(print_config, object_config, model_object.bounding_box().max.z(), object_extruders); + if (object_max_z <= 0.f) + object_max_z = model_object.raw_bounding_box().size().z(); + return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders); } // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fe3f0765cb..0dc3ec83a8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -255,16 +255,21 @@ void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) { const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr; - if (model_object_new == nullptr || this->last_object_id != object_id || m_model_object != model_object_new || m_model_object->id() != model_object_new->id()) { + // Maximum height of an object changes when the object gets rotated or scaled. + // Changing maximum height of an object will invalidate the layer heigth editing profile. + // m_model_object->raw_bounding_box() is cached, therefore it is cheap even if this method is called frequently. + float new_max_z = (m_model_object == nullptr) ? 0.f : m_model_object->raw_bounding_box().size().z(); + if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || + (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { m_layer_height_profile.clear(); m_layer_height_profile_modified = false; delete m_slicing_parameters; - m_slicing_parameters = nullptr; + m_slicing_parameters = nullptr; m_layers_texture.valid = false; + this->last_object_id = object_id; + m_model_object = model_object_new; + m_object_max_z = new_max_z; } - this->last_object_id = object_id; - m_model_object = model_object_new; - m_object_max_z = (m_model_object == nullptr) ? 0.f : m_model_object->bounding_box().max.z(); } bool GLCanvas3D::LayersEditing::is_allowed() const @@ -623,7 +628,7 @@ void GLCanvas3D::LayersEditing::update_slicing_parameters() { if (m_slicing_parameters == nullptr) { m_slicing_parameters = new SlicingParameters(); - *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object); + *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); } } From 255a4e05dca6b227c7880ae369823e2c92990d1d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sat, 13 Apr 2019 14:45:35 +0200 Subject: [PATCH 55/57] Fix of "Repir from File menu doesn't save the file #2064" The file was saved, albeit using an "obj" format, but into a file with an ".stl" extension. The software was fixed to propose a file to save with a correct ".obj" extension. --- src/slic3r/GUI/MainFrame.cpp | 20 +++++++++++--------- src/slic3r/GUI/MainFrame.hpp | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6a09b5a94c..e867d2d579 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -549,7 +549,7 @@ void MainFrame::quick_slice(const int qs) dlg->ShowModal(); return; } - if (std::ifstream(m_qs_last_input_file.char_str())) { + if (std::ifstream(m_qs_last_input_file.ToUTF8().data())) { auto dlg = new wxMessageDialog(this, _(L("Previously sliced file ("))+m_qs_last_input_file+_(L(") not found.")), _(L("File Not Found")), wxICON_ERROR | wxOK); dlg->ShowModal(); @@ -664,24 +664,23 @@ void MainFrame::repair_stl() dlg->Destroy(); } - auto output_file = input_file; + wxString output_file = input_file; { -// output_file = ~s / \.[sS][tT][lL]$ / _fixed.obj / ; auto dlg = new wxFileDialog( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"), - get_dir_name(output_file), get_base_name(output_file), + get_dir_name(output_file), get_base_name(output_file, ".obj"), file_wildcards(FT_OBJ), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dlg->ShowModal() != wxID_OK) { dlg->Destroy(); - return /*undef*/; + return; } output_file = dlg->GetPath(); dlg->Destroy(); } auto tmesh = new Slic3r::TriangleMesh(); - tmesh->ReadSTLFile(input_file.char_str()); + tmesh->ReadSTLFile(input_file.ToUTF8().data()); tmesh->repair(); - tmesh->WriteOBJFile(output_file.char_str()); + tmesh->WriteOBJFile(output_file.ToUTF8().data()); Slic3r::GUI::show_info(this, L("Your file was repaired."), L("Repair")); } @@ -921,9 +920,12 @@ void MainFrame::update_ui_from_settings() tab->update_ui_from_settings(); } -std::string MainFrame::get_base_name(const wxString &full_name) const +std::string MainFrame::get_base_name(const wxString &full_name, const char *extension) const { - return boost::filesystem::path(full_name.wx_str()).filename().string(); + boost::filesystem::path filename = boost::filesystem::path(full_name.wx_str()).filename(); + if (extension != nullptr) + filename = filename.replace_extension(extension); + return filename.string(); } std::string MainFrame::get_dir_name(const wxString &full_name) const diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 625e70b83b..a5d3a1f6de 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -54,7 +54,7 @@ class MainFrame : public DPIFrame PrintHostQueueDialog *m_printhost_queue_dlg; - std::string get_base_name(const wxString &full_name) const; + std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const; std::string get_dir_name(const wxString &full_name) const; void on_presets_changed(SimpleEvent&); From e4162bbee90a872267a9da1554329708cbd824ca Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 14 Apr 2019 08:26:10 +0200 Subject: [PATCH 56/57] When loading a 3MF with advanced data, switch to an Advanced mode, not Export moe. --- src/slic3r/GUI/Plater.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 646a2ee64a..cb14d9fe53 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1649,11 +1649,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (advanced) { - wxMessageDialog dlg(q, _(L("This file cannot be loaded in simple mode. Do you want to switch to expert mode?\n")), + wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")), _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); if (dlg.ShowModal() == wxID_YES) { - Slic3r::GUI::wxGetApp().save_mode(comExpert); + Slic3r::GUI::wxGetApp().save_mode(comAdvanced); view3D->set_as_dirty(); } else From 44a611795b10a3773f31b688d7d9ba74095fd086 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 14 Apr 2019 10:24:31 +0200 Subject: [PATCH 57/57] Bumped up version number --- version.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.inc b/version.inc index 18ac508b2a..6a3f00e00e 100644 --- a/version.inc +++ b/version.inc @@ -2,7 +2,7 @@ # (the version numbers are generated by the build script from the git current label) set(SLIC3R_FORK_NAME "Slic3r Prusa Edition") -set(SLIC3R_VERSION "1.42.0-beta") +set(SLIC3R_VERSION "1.42.0-beta2") set(SLIC3R_BUILD "${SLIC3R_VERSION}+UNKNOWN") set(SLIC3R_BUILD_ID "${SLIC3R_BUILD_ID}") set(SLIC3R_RC_VERSION "1,42,0,0")