diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 89d256a90..00c33dcfb 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2887,8 +2887,9 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Minimum wall thickness of a hollowed model."); def->sidetext = L("mm"); def->min = 1; + def->max = 10; def->mode = comSimple; - def->set_default_value(new ConfigOptionFloat(4)); + def->set_default_value(new ConfigOptionFloat(3.)); def = this->add("hollowing_quality", coFloat); def->label = L("Hollowing accuracy"); @@ -2904,6 +2905,7 @@ void PrintConfigDef::init_sla_params() def->category = L("Hollowing"); def->tooltip = L(""); def->min = 0; + def->max = 10; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(2.0)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 048392b45..efb110199 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -443,7 +443,13 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos std::pair pos_and_normal; if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole"))); - m_c->m_model_object->sla_drain_holes.emplace_back(pos_and_normal.first + HoleStickOutLength * pos_and_normal.second, + + Vec3d scaling = m_c->m_model_object->instances[m_c->m_active_instance]->get_scaling_factor(); + Vec3f normal_transformed(pos_and_normal.second(0)/scaling(0), + pos_and_normal.second(1)/scaling(1), + pos_and_normal.second(2)/scaling(2)); + + m_c->m_model_object->sla_drain_holes.emplace_back(pos_and_normal.first + HoleStickOutLength * pos_and_normal.second/* normal_transformed.normalized()*/, -pos_and_normal.second, m_new_hole_radius, m_new_hole_height+HoleStickOutLength); m_selected.push_back(false); assert(m_selected.size() == m_c->m_model_object->sla_drain_holes.size()); @@ -580,10 +586,10 @@ std::pair GLGizmoHollow::get_hollowi { // FIXME this function is probably obsolete, caller could // get the data from model config himself - std::vector opts = get_config_options({"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"}); - double offset = static_cast(opts[0])->value; - double quality = static_cast(opts[1])->value; - double closing_d = static_cast(opts[2])->value; + auto opts = get_config_options({"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"}); + double offset = static_cast(opts[0].first)->value; + double quality = static_cast(opts[1].first)->value; + double closing_d = static_cast(opts[2].first)->value; return std::make_pair(m_c->m_mesh, sla::HollowingConfig{offset, quality, closing_d}); } @@ -611,16 +617,19 @@ void GLGizmoHollow::update_hollowed_mesh(std::unique_ptr &&mesh) if (! m_c->m_model_object->sla_drain_holes.empty()) { TriangleMesh holes_mesh; for (const sla::DrainHole& hole : m_c->m_model_object->sla_drain_holes) { - TriangleMesh hole_mesh = make_cylinder(hole.radius, hole.height, 2*M_PI/8); + TriangleMesh hole_mesh = make_cylinder(hole.radius, hole.height, 2*M_PI/32); + + Vec3d scaling = m_c->m_model_object->instances[m_c->m_active_instance]->get_scaling_factor(); + Vec3d normal_transformed = Vec3d(hole.normal(0)/scaling(0), hole.normal(1)/scaling(1), hole.normal(2)/scaling(2)); + normal_transformed.normalize(); // Rotate the cylinder appropriately - Eigen::Quaternionf q; - Transform3f m = Transform3f::Identity(); - m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3f::UnitZ(), hole.normal).toRotationMatrix(); - hole_mesh.transform(m.cast()); + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), normal_transformed).toRotationMatrix(); + hole_mesh.transform(m); // If the instance is scaled, undo the scaling of the hole - Vec3d scaling = m_c->m_model_object->instances[m_c->m_active_instance]->get_scaling_factor(); hole_mesh.scale(Vec3d(1/scaling(0), 1/scaling(1), 1/scaling(2))); // Translate the hole into position and merge with the others @@ -647,9 +656,9 @@ void GLGizmoHollow::update_hollowed_mesh(std::unique_ptr &&mesh) } } -std::vector GLGizmoHollow::get_config_options(const std::vector& keys) const +std::vector> GLGizmoHollow::get_config_options(const std::vector& keys) const { - std::vector out; + std::vector> out; if (!m_c->m_model_object) return out; @@ -660,14 +669,14 @@ std::vector GLGizmoHollow::get_config_options(const std::ve for (const std::string& key : keys) { if (object_cfg.has(key)) - out.push_back(object_cfg.option(key)); + out.emplace_back(object_cfg.option(key), &object_cfg.def()->options.at(key)); // at() needed for const map else if (print_cfg.has(key)) - out.push_back(print_cfg.option(key)); + out.emplace_back(print_cfg.option(key), &print_cfg.def()->options.at(key)); else { // we must get it from defaults if (default_cfg == nullptr) default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); - out.push_back(default_cfg->option(key)); + out.emplace_back(default_cfg->option(key), &default_cfg->def()->options.at(key)); } } @@ -714,8 +723,8 @@ RENDER_AGAIN: window_width = std::max(std::max(window_width, /*buttons_width_approx*/0.f), 0.f); { - std::vector opts = get_config_options({"hollowing_enable"}); - m_enable_hollowing = static_cast(opts[0])->value; + auto opts = get_config_options({"hollowing_enable"}); + m_enable_hollowing = static_cast(opts[0].first)->value; if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) { m_c->m_model_object->config.opt("hollowing_enable", true)->value = m_enable_hollowing; wxGetApp().obj_list()->update_and_show_object_settings_item(); @@ -727,29 +736,57 @@ RENDER_AGAIN: if (m_imgui->button(m_desc["preview"])) hollow_mesh(); - std::vector opts = get_config_options({"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"}); - float offset = static_cast(opts[0])->value; - float quality = static_cast(opts[1])->value; - float closing_d = static_cast(opts[2])->value; + std::vector opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"}; + auto opts = get_config_options(opts_keys); + auto* offset_cfg = static_cast(opts[0].first); + float offset = offset_cfg->value; + double offset_min = opts[0].second->min; + double offset_max = opts[0].second->max; + + auto* quality_cfg = static_cast(opts[1].first); + float quality = quality_cfg->value; + double quality_min = opts[1].second->min; + double quality_max = opts[1].second->max; + + auto* closing_d_cfg = static_cast(opts[2].first); + float closing_d = closing_d_cfg->value; + double closing_d_min = opts[2].second->min; + double closing_d_max = opts[2].second->max; + m_imgui->text(m_desc.at("offset")); ImGui::SameLine(settings_sliders_left); ImGui::PushItemWidth(window_width - settings_sliders_left); - ImGui::SliderFloat(" ", &offset, 0.f, 5.f, "%.1f"); + ImGui::SliderFloat(" ", &offset, offset_min, offset_max, "%.1f"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(_(opts[0].second->tooltip).ToUTF8()); + ImGui::EndTooltip(); + } bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider m_imgui->text(m_desc.at("quality")); ImGui::SameLine(settings_sliders_left); - ImGui::SliderFloat(" ", &quality, 0.f, 1.f, "%.1f"); + ImGui::SliderFloat(" ", &quality, quality_min, quality_max, "%.1f"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(_(opts[1].second->tooltip).ToUTF8()); + ImGui::EndTooltip(); + } slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); m_imgui->text(m_desc.at("closing_distance")); ImGui::SameLine(settings_sliders_left); - ImGui::SliderFloat(" ", &closing_d, 0.f, 10.f, "%.1f"); + ImGui::SliderFloat(" ", &closing_d, closing_d_min, closing_d_max, "%.1f"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(_(opts[2].second->tooltip).ToUTF8()); + ImGui::EndTooltip(); + } slider_clicked |= ImGui::IsItemClicked(); slider_edited |= ImGui::IsItemEdited(); slider_released |= ImGui::IsItemDeactivatedAfterEdit(); @@ -955,8 +992,8 @@ void GLGizmoHollow::on_set_state() m_parent.toggle_model_objects_visibility(true, m_c->m_model_object, m_c->m_active_instance); // Set default head diameter from config. - const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - m_new_hole_radius = static_cast(cfg.option("support_head_front_diameter"))->value; + //const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + //m_new_hole_radius = static_cast(cfg.option("support_head_front_diameter"))->value; } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off //Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off"))); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index bea396097..ba2935a56 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -21,10 +21,6 @@ enum class SLAGizmoEventType : unsigned char; class GLGizmoHollow : public GLGizmoBase { private: - //ModelObject* m_model_object = nullptr; - //ObjectID m_model_object_id = 0; - //int m_active_instance = -1; - //float m_active_instance_bb_radius; // to cache the bb mutable double m_z_shift = 0.; bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); @@ -32,13 +28,6 @@ private: GLUquadricObj* m_quadric; - //std::unique_ptr m_mesh_raycaster; - //std::unique_ptr m_cavity_mesh; - //std::unique_ptr m_volume_with_cavity; - //const TriangleMesh* m_mesh; - //mutable int m_old_timestamp = -1; - //mutable int m_print_object_idx = -1; - //mutable int m_print_objects_count = -1; public: GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* cd); @@ -69,7 +58,7 @@ private: bool unsaved_changes() const; bool m_show_supports = true; - float m_new_hole_radius; // Size of a new hole. + float m_new_hole_radius = 4.f; // Size of a new hole. float m_new_hole_height = 5.f; mutable std::vector m_selected; // which holes are currently selected @@ -77,7 +66,7 @@ private: // Stashes to keep data for undo redo. Is taken after the editing // is done, the data are updated continuously. - float m_offset_stash = 2.0f; + float m_offset_stash = 3.0f; float m_quality_stash = 0.5f; float m_closing_d_stash = 2.f; Vec3f m_hole_before_drag = Vec3f::Zero(); @@ -100,10 +89,7 @@ private: bool m_selection_empty = true; EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) - //mutable std::unique_ptr m_object_clipper; - //mutable std::unique_ptr m_supports_clipper; - - std::vector get_config_options(const std::vector& keys) const; + std::vector> get_config_options(const std::vector& keys) const; bool is_mesh_point_clipped(const Vec3d& point) const; // Methods that do the model_object and editing cache synchronization, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 2dc41edca..4e00b0f77 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -68,19 +68,31 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S return; } - if (m_c->m_model_object != model_object || m_c->m_model_object_id != model_object->id()) { + bool something_changed = false; + + if (m_c->m_model_object != model_object + || m_c->m_model_object_id != model_object->id() + || m_c->m_active_instance != selection.get_instance_idx()) { m_c->m_model_object = model_object; m_c->m_print_object_idx = -1; + m_c->m_active_instance = selection.get_instance_idx(); + something_changed = true; } - m_c->m_active_instance = selection.get_instance_idx(); - if (model_object && selection.is_from_single_instance()) { // Cache the bb - it's needed for dealing with the clipping plane quite often // It could be done inside update_mesh but one has to account for scaling of the instance. - //FIXME calling ModelObject::instance_bounding_box() is expensive! - m_c->m_active_instance_bb_radius = m_c->m_model_object->instance_bounding_box(m_c->m_active_instance).radius(); + if (something_changed) { + m_c->m_active_instance_bb_radius = m_c->m_model_object->instance_bounding_box(m_c->m_active_instance).radius(); + if (m_state == On) { + m_parent.toggle_model_objects_visibility(false); + m_parent.toggle_model_objects_visibility(! m_c->m_cavity_mesh, m_c->m_model_object, m_c->m_active_instance); + m_parent.toggle_sla_auxiliaries_visibility(bool(m_c->m_cavity_mesh), m_c->m_model_object, m_c->m_active_instance); + } + else + m_parent.toggle_model_objects_visibility(true, nullptr, -1); + } if (is_mesh_update_necessary()) { update_mesh(); @@ -90,14 +102,6 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S // If we triggered autogeneration before, check backend and fetch results if they are there if (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating) get_data_from_backend(); - - if (m_state == On) { - m_parent.toggle_model_objects_visibility(false); - m_parent.toggle_model_objects_visibility(! m_c->m_cavity_mesh, m_c->m_model_object, m_c->m_active_instance); - m_parent.toggle_sla_auxiliaries_visibility(bool(m_c->m_cavity_mesh), m_c->m_model_object, m_c->m_active_instance); - } - else - m_parent.toggle_model_objects_visibility(true, nullptr, -1); } } @@ -350,41 +354,42 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) } // Now render the drain holes: -// render_color[0] = 0.7f; -// render_color[1] = 0.7f; -// render_color[2] = 0.7f; -// render_color[3] = 0.7f; -// glsafe(::glColor4fv(render_color)); -// for (const sla::DrainHole& drain_hole : m_c->m_model_object->sla_drain_holes) { -// // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. -// glsafe(::glPushMatrix()); -// glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2))); -// glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); + if (! m_c->m_cavity_mesh) { + render_color[0] = 0.7f; + render_color[1] = 0.7f; + render_color[2] = 0.7f; + render_color[3] = 0.7f; + glsafe(::glColor4fv(render_color)); + for (const sla::DrainHole& drain_hole : m_c->m_model_object->sla_drain_holes) { + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2))); + glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); -// if (vol->is_left_handed()) -// glFrontFace(GL_CW); + if (vol->is_left_handed()) + glFrontFace(GL_CW); -// // Matrices set, we can render the point mark now. + // Matrices set, we can render the point mark now. -// Eigen::Quaterniond q; -// q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); -// Eigen::AngleAxisd aa(q); -// glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); -// glsafe(::glPushMatrix()); -// glsafe(::glTranslated(0., 0., -drain_hole.height)); -// ::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height, 24, 1); -// glsafe(::glTranslated(0., 0., drain_hole.height)); -// ::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1); -// glsafe(::glTranslated(0., 0., -drain_hole.height)); -// glsafe(::glRotatef(180.f, 1.f, 0.f, 0.f)); -// ::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1); -// glsafe(::glPopMatrix()); + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); + Eigen::AngleAxisd aa(q); + glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(0., 0., -drain_hole.height)); + ::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height, 24, 1); + glsafe(::glTranslated(0., 0., drain_hole.height)); + ::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1); + glsafe(::glTranslated(0., 0., -drain_hole.height)); + glsafe(::glRotatef(180.f, 1.f, 0.f, 0.f)); + ::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1); + glsafe(::glPopMatrix()); -// if (vol->is_left_handed()) -// glFrontFace(GL_CCW); -// glsafe(::glPopMatrix()); - -// } + if (vol->is_left_handed()) + glFrontFace(GL_CCW); + glsafe(::glPopMatrix()); + } + } if (!picking) glsafe(::glDisable(GL_LIGHTING)); @@ -454,10 +459,15 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pairm_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get())) { // Check whether the hit is in a hole bool in_hole = false; - for (const sla::DrainHole& hole : m_c->m_model_object->sla_drain_holes) { - if (hole.is_inside(hit)) { - in_hole = true; - break; + // In case the hollowed and drilled mesh is available, we can allow + // placing points in holes, because they should never end up + // on surface that's been drilled away. + if (! m_c->m_cavity_mesh) { + for (const sla::DrainHole& hole : m_c->m_model_object->sla_drain_holes) { + if (hole.is_inside(hit)) { + in_hole = true; + break; + } } } if (! in_hole) {