OrcaSlicer/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
Lukas Matena 5d4014a4a5 ObjectClipper implementation, hollowing gizmo reenabled
ObjectClipper tracks active mesh (incl. possible hollowing), remembers clipping plane position and can render the cut on demand
Hollowing gizmo uses the new infrastructure
2020-04-07 16:45:16 +02:00

1090 lines
40 KiB
C++

#include "GLGizmoHollow.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Gizmos/GLGizmos.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include <GL/glew.h>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
namespace Slic3r {
namespace GUI {
GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_quadric(nullptr)
{
m_quadric = ::gluNewQuadric();
if (m_quadric != nullptr)
// using GLU_FILL does not work when the instance's transformation
// contains mirroring (normals are reverted)
::gluQuadricDrawStyle(m_quadric, GLU_FILL);
}
GLGizmoHollow::~GLGizmoHollow()
{
if (m_quadric != nullptr)
::gluDeleteQuadric(m_quadric);
}
bool GLGizmoHollow::on_init()
{
m_shortcut_key = WXK_CONTROL_H;
m_desc["enable"] = _(L("Hollow this object"));
m_desc["preview"] = _(L("Preview hollowed and drilled model"));
m_desc["offset"] = _(L("Offset")) + ": ";
m_desc["quality"] = _(L("Quality")) + ": ";
m_desc["closing_distance"] = _(L("Closing distance")) + ": ";
m_desc["hole_diameter"] = _(L("Hole diameter")) + ": ";
m_desc["hole_depth"] = _(L("Hole depth")) + ": ";
m_desc["remove_selected"] = _(L("Remove selected holes"));
m_desc["remove_all"] = _(L("Remove all holes"));
m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": ";
m_desc["reset_direction"] = _(L("Reset direction"));
m_desc["show_supports"] = _(L("Show supports"));
return true;
}
void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&)
{
const ModelObject* mo = m_c->selection_info()->model_object();
if (mo) {
reload_cache();
if (m_c->hollowed_mesh()->get_hollowed_mesh())
m_holes_in_drilled_mesh = mo->sla_drain_holes;
}
/*if (m_c->recent_update) {
if (m_state == On)
m_c->build_AABB_if_needed();
update_clipping_plane(m_c->m_clipping_plane_was_moved);
// This is a temporary and not very nice hack, to make sure that
// if the cp was moved by the data returned by backend, it will
// remember its direction. FIXME: Refactor this mess and make
// the clipping plane itself part of the shared data.
if (! m_c->m_clipping_plane_was_moved && m_c->m_clipping_plane_distance == 0.25f)
m_c->m_clipping_plane_was_moved = true;
if (m_c->m_model_object) {
reload_cache();
if (m_c->has_drilled_mesh())
m_holes_in_drilled_mesh = m_c->m_model_object->sla_drain_holes;
}
}
if (m_state == On) {
m_parent.toggle_model_objects_visibility(false);
m_parent.toggle_model_objects_visibility(true, m_c->m_model_object, m_c->m_active_instance);
m_parent.toggle_sla_auxiliaries_visibility(m_show_supports, m_c->m_model_object, m_c->m_active_instance);
}*/
}
void GLGizmoHollow::on_render() const
{
const Selection& selection = m_parent.get_selection();
const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info();
// If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off
if (m_state == On
&& (sel_info->model_object() != selection.get_model()->objects[selection.get_object_idx()]
|| sel_info->get_active_instance() != selection.get_instance_idx())) {
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
return;
}
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
//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, false);
m_selection_rectangle.render(m_parent);
//render_clipping_plane(selection);
m_c->object_clipper()->render_cut();
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoHollow::render_clipping_plane(const Selection& selection) const
{
return;
/*
if (m_c->m_clipping_plane_distance == 0.f)
return;
// Get transformation of the instance
const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
Geometry::Transformation trafo = vol->get_instance_transformation();
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
// Get transformation of supports
Geometry::Transformation supports_trafo;
supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), vol->get_sla_shift_z()));
supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2)));
// I don't know why, but following seems to be correct.
supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2),
1,
1.));
// Now initialize the TMS for the object, perform the cut and save the result.
if (! m_c->m_object_clipper) {
m_c->m_object_clipper.reset(new MeshClipper);
m_c->m_object_clipper->set_mesh(*m_c->mesh());
}
m_c->m_object_clipper->set_plane(*m_c->m_clipping_plane);
m_c->m_object_clipper->set_transformation(trafo);
if (m_c->m_print_object_idx >= 0) {
const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_c->m_print_object_idx];
if (print_object->is_step_done(slaposSupportTree) && !print_object->get_mesh(slaposSupportTree).empty()) {
// If the supports are already calculated, save the timestamp of the respective step
// so we can later tell they were recalculated.
size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
if (! m_c->m_supports_clipper || (int)timestamp != m_c->m_old_timestamp) {
// The timestamp has changed.
m_c->m_supports_clipper.reset(new MeshClipper);
// The mesh should already have the shared vertices calculated.
m_c->m_supports_clipper->set_mesh(print_object->support_mesh());
m_c->m_old_timestamp = timestamp;
}
m_c->m_supports_clipper->set_plane(*m_c->m_clipping_plane);
m_c->m_supports_clipper->set_transformation(supports_trafo);
}
else
// The supports are not valid. We better dump the cached data.
m_c->m_supports_clipper.reset();
}
// At this point we have the triangulated cuts for both the object and supports - let's render.
if (! m_c->m_object_clipper->get_triangles().empty()) {
::glPushMatrix();
::glColor3f(1.0f, 0.37f, 0.0f);
::glBegin(GL_TRIANGLES);
for (const Vec3f& point : m_c->m_object_clipper->get_triangles())
::glVertex3f(point(0), point(1), point(2));
::glEnd();
::glPopMatrix();
}
if (m_show_supports && m_c->m_supports_clipper && ! m_c->m_supports_clipper->get_triangles().empty()) {
::glPushMatrix();
::glColor3f(1.0f, 0.f, 0.37f);
::glBegin(GL_TRIANGLES);
for (const Vec3f& point : m_c->m_supports_clipper->get_triangles())
::glVertex3f(point(0), point(1), point(2));
::glEnd();
::glPopMatrix();
}
*/
}
void GLGizmoHollow::on_render_for_picking() const
{
const Selection& selection = m_parent.get_selection();
#if ENABLE_RENDER_PICKING_PASS
m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
#endif
glsafe(::glEnable(GL_DEPTH_TEST));
render_points(selection, true);
}
void GLGizmoHollow::render_points(const Selection& selection, bool picking) const
{
if (!picking)
glsafe(::glEnable(GL_LIGHTING));
const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse();
const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix();
glsafe(::glPushMatrix());
glsafe(::glTranslated(0.0, 0.0, m_z_shift));
glsafe(::glMultMatrixd(instance_matrix.data()));
float render_color[4];
const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
size_t cache_size = drain_holes.size();
for (size_t i = 0; i < cache_size; ++i)
{
const sla::DrainHole& drain_hole = drain_holes[i];
const bool& point_selected = m_selected[i];
if (is_mesh_point_clipped((drain_hole.pos+HoleStickOutLength*drain_hole.normal).cast<double>()))
continue;
// First decide about the color of the point.
if (picking) {
std::array<float, 4> color = picking_color_component(i);
render_color[0] = color[0];
render_color[1] = color[1];
render_color[2] = color[2];
render_color[3] = color[3];
}
else {
render_color[3] = 1.f;
if (size_t(m_hover_id) == i) {
render_color[0] = 0.f;
render_color[1] = 1.0f;
render_color[2] = 1.0f;
}
else { // neigher hover nor picking
render_color[0] = point_selected ? 1.0f : 0.7f;
render_color[1] = point_selected ? 0.3f : 0.7f;
render_color[2] = point_selected ? 0.3f : 0.7f;
render_color[3] = 0.5f;
}
}
glsafe(::glColor4fv(render_color));
float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f};
glsafe(::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive));
// 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);
// 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<double>());
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());
}
{
// Reset emissive component to zero (the default value)
float render_color_emissive[4] = { 0.f, 0.f, 0.f, 1.f };
glsafe(::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive));
}
if (!picking)
glsafe(::glDisable(GL_LIGHTING));
glsafe(::glPopMatrix());
}
bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const
{
if (m_c->object_clipper()->get_position() == 0.)
return false;
auto sel_info = m_c->selection_info();
int active_inst = m_c->selection_info()->get_active_instance();
const ModelInstance* mi = sel_info->model_object()->instances[active_inst];
const Transform3d& trafo = mi->get_transformation().get_matrix();
Vec3d transformed_point = trafo * point;
transformed_point(2) += sel_info->get_sla_shift();
return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
}
// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
// Return false if no intersection was found, true otherwise.
bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
{
if (! m_c->raycaster()->raycaster())
return false;
#if ENABLE_NON_STATIC_CANVAS_MANAGER
const Camera& camera = wxGetApp().plater()->get_camera();
#else
const Camera& camera = m_parent.get_camera();
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
const Selection& selection = m_parent.get_selection();
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
Geometry::Transformation trafo = volume->get_instance_transformation();
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
double clp_dist = m_c->object_clipper()->get_position();
const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
// The raycaster query
Vec3f hit;
Vec3f normal;
if (m_c->raycaster()->raycaster()->unproject_on_mesh(
mouse_pos,
trafo.get_matrix(),
camera,
hit,
normal,
clp_dist != 0. ? clp : nullptr))
{
if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) {
// in this case the raycaster sees the hollowed and drilled mesh.
// if the point lies on the surface created by the hole, we want
// to ignore it.
for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) {
sla::DrainHole outer(hole);
outer.radius *= 1.001f;
outer.height *= 1.001f;
if (outer.is_inside(hit))
return false;
}
}
// Return both the point and the facet normal.
pos_and_normal = std::make_pair(hit, normal);
return true;
}
else
return false;
}
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
// concludes that the event was not intended for it, it should return false.
bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
{
ModelObject* mo = m_c->selection_info()->model_object();
int active_inst = m_c->selection_info()->get_active_instance();
// left down with shift - show the selection rectangle:
if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) {
if (m_hover_id == -1) {
if (shift_down || alt_down) {
m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
}
}
else {
if (m_selected[m_hover_id])
unselect_point(m_hover_id);
else {
if (!alt_down)
select_point(m_hover_id);
}
}
return true;
}
// left down without selection rectangle - place point on the mesh:
if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) {
// If any point is in hover state, this should initiate its move - return control back to GLCanvas:
if (m_hover_id != -1)
return false;
// If there is some selection, don't add new point and deselect everything instead.
if (m_selection_empty) {
std::pair<Vec3f, Vec3f> 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")));
Vec3d scaling = mo->instances[active_inst]->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));
mo->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);
m_selected.push_back(false);
assert(m_selected.size() == mo->sla_drain_holes.size());
m_parent.set_as_dirty();
m_wait_for_up_event = true;
}
else
return false;
}
else
select_point(NoPoints);
return true;
}
// left up with selection rectangle - select points inside the rectangle:
if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) {
// Is this a selection or deselection rectangle?
GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
// First collect positions of all the points in world coordinates.
Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation();
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
std::vector<Vec3d> points;
for (unsigned int i=0; i<mo->sla_drain_holes.size(); ++i)
points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast<double>());
// Now ask the rectangle which of the points are inside.
std::vector<Vec3f> points_inside;
std::vector<unsigned int> points_idxs = m_selection_rectangle.stop_dragging(m_parent, points);
for (size_t idx : points_idxs)
points_inside.push_back(points[idx].cast<float>());
// Only select/deselect points that are actually visible
#if ENABLE_NON_STATIC_CANVAS_MANAGER
for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs(
trafo, wxGetApp().plater()->get_camera(), points_inside,
m_c->object_clipper()->get_clipping_plane()))
#else
for (size_t idx : m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_c->m_clipping_plane.get()))
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
{
if (rectangle_status == GLSelectionRectangle::Deselect)
unselect_point(points_idxs[idx]);
else
select_point(points_idxs[idx]);
}
return true;
}
// left up with no selection rectangle
if (action == SLAGizmoEventType::LeftUp) {
if (m_wait_for_up_event) {
m_wait_for_up_event = false;
return true;
}
}
// dragging the selection rectangle:
if (action == SLAGizmoEventType::Dragging) {
if (m_wait_for_up_event)
return true; // point has been placed and the button not released yet
// this prevents GLCanvas from starting scene rotation
if (m_selection_rectangle.is_dragging()) {
m_selection_rectangle.dragging(mouse_position);
return true;
}
return false;
}
if (action == SLAGizmoEventType::Delete) {
// delete key pressed
delete_selected_points();
return true;
}
if (action == SLAGizmoEventType::RightDown) {
if (m_hover_id != -1) {
select_point(NoPoints);
select_point(m_hover_id);
delete_selected_points();
return true;
}
return false;
}
if (action == SLAGizmoEventType::SelectAll) {
select_point(AllPoints);
return true;
}
if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
double pos = m_c->object_clipper()->get_position();
pos = std::min(1., pos + 0.01);
m_c->object_clipper()->set_position(pos, true);
//update_clipping_plane(/*m_c->m_clipping_plane_was_moved*/);
//m_c->m_clipping_plane_was_moved = true;
return true;
}
if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
double pos = m_c->object_clipper()->get_position();
pos = std::max(0., pos - 0.01);
m_c->object_clipper()->set_position(pos, true);
//update_clipping_plane(true);
return true;
}
if (action == SLAGizmoEventType::ResetClippingPlane) {
m_c->object_clipper()->set_position(-1., false);
return true;
}
return false;
}
void GLGizmoHollow::delete_selected_points()
{
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete drainage hole")));
sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
for (unsigned int idx=0; idx<drain_holes.size(); ++idx) {
if (m_selected[idx]) {
m_selected.erase(m_selected.begin()+idx);
drain_holes.erase(drain_holes.begin() + (idx--));
}
}
select_point(NoPoints);
}
void GLGizmoHollow::on_update(const UpdateData& data)
{
sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
if (m_hover_id != -1) {
std::pair<Vec3f, Vec3f> pos_and_normal;
if (! unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
return;
drain_holes[m_hover_id].pos = pos_and_normal.first + HoleStickOutLength * pos_and_normal.second;
drain_holes[m_hover_id].normal = -pos_and_normal.second;
}
}
void GLGizmoHollow::hollow_mesh(bool postpone_error_messages)
{
wxGetApp().CallAfter([this, postpone_error_messages]() {
wxGetApp().plater()->reslice_SLA_hollowing(
*m_c->selection_info()->model_object(), postpone_error_messages);
});
}
std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>>
GLGizmoHollow::get_config_options(const std::vector<std::string>& keys) const
{
std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> out;
const ModelObject* mo = m_c->selection_info()->model_object();
if (! mo)
return out;
const DynamicPrintConfig& object_cfg = mo->config;
const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
for (const std::string& key : keys) {
if (object_cfg.has(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.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.emplace_back(default_cfg->option(key), &default_cfg->def()->options.at(key));
}
}
return out;
}
ClippingPlane GLGizmoHollow::get_sla_clipping_plane() const
{
if (! m_c->selection_info()->model_object()
|| m_state == Off
|| m_c->object_clipper()->get_position() == 0.)
return ClippingPlane::ClipsNothing();
else {
const ClippingPlane& clp = *m_c->object_clipper()->get_clipping_plane();
return ClippingPlane(-clp.get_normal(), clp.get_data()[3]);
}
}
void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit)
{
ModelObject* mo = m_c->selection_info()->model_object();
if (! mo)
return;
bool first_run = true; // This is a hack to redraw the button when all points are removed,
// so it is not delayed until the background process finishes.
ConfigOptionMode current_mode = wxGetApp().get_mode();
std::vector<std::string> opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"};
auto opts = get_config_options(opts_keys);
auto* offset_cfg = static_cast<const ConfigOptionFloat*>(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<const ConfigOptionFloat*>(opts[1].first);
float quality = quality_cfg->value;
double quality_min = opts[1].second->min;
double quality_max = opts[1].second->max;
ConfigOptionMode quality_mode = opts[1].second->mode;
auto* closing_d_cfg = static_cast<const ConfigOptionFloat*>(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;
ConfigOptionMode closing_d_mode = opts[2].second->mode;
m_desc["offset"] = _(opts[0].second->label) + ":";
m_desc["quality"] = _(opts[1].second->label) + ":";
m_desc["closing_distance"] = _(opts[2].second->label) + ":";
RENDER_AGAIN:
const float approx_height = m_imgui->scaled(20.0f);
y = std::min(y, bottom_limit - approx_height);
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float settings_sliders_left =
std::max({m_imgui->calc_text_size(m_desc.at("offset")).x,
m_imgui->calc_text_size(m_desc.at("quality")).x,
m_imgui->calc_text_size(m_desc.at("closing_distance")).x,
m_imgui->calc_text_size(m_desc.at("hole_diameter")).x,
m_imgui->calc_text_size(m_desc.at("hole_depth")).x})
+ m_imgui->scaled(1.f);
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
const float diameter_slider_left = settings_sliders_left; //m_imgui->calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f);
const float minimal_slider_width = m_imgui->scaled(4.f);
float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left});
window_width = std::max(window_width, m_imgui->calc_text_size(m_desc.at("preview")).x);
if (m_imgui->button(m_desc["preview"]))
hollow_mesh();
bool config_changed = false;
ImGui::Separator();
{
auto opts = get_config_options({"hollowing_enable"});
m_enable_hollowing = static_cast<const ConfigOptionBool*>(opts[0].first)->value;
if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) {
mo->config.opt<ConfigOptionBool>("hollowing_enable", true)->value = m_enable_hollowing;
wxGetApp().obj_list()->update_and_show_object_settings_item();
config_changed = true;
}
}
m_imgui->disabled_begin(! m_enable_hollowing);
float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
m_imgui->text(m_desc.at("offset"));
ImGui::SameLine(settings_sliders_left);
ImGui::PushItemWidth(window_width - settings_sliders_left);
ImGui::SliderFloat(" ", &offset, offset_min, offset_max, "%.1f mm");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted((_utf8(opts[0].second->tooltip)).c_str());
ImGui::PopTextWrapPos();
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
if (current_mode >= quality_mode) {
m_imgui->text(m_desc.at("quality"));
ImGui::SameLine(settings_sliders_left);
ImGui::SliderFloat(" ", &quality, quality_min, quality_max, "%.1f");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted((_utf8(opts[1].second->tooltip)).c_str());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
slider_clicked |= ImGui::IsItemClicked();
slider_edited |= ImGui::IsItemEdited();
slider_released |= ImGui::IsItemDeactivatedAfterEdit();
}
if (current_mode >= closing_d_mode) {
m_imgui->text(m_desc.at("closing_distance"));
ImGui::SameLine(settings_sliders_left);
ImGui::SliderFloat(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted((_utf8(opts[2].second->tooltip)).c_str());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
slider_clicked |= ImGui::IsItemClicked();
slider_edited |= ImGui::IsItemEdited();
slider_released |= ImGui::IsItemDeactivatedAfterEdit();
}
if (slider_clicked) {
m_offset_stash = offset;
m_quality_stash = quality;
m_closing_d_stash = closing_d;
}
if (slider_edited || slider_released) {
if (slider_released) {
mo->config.opt<ConfigOptionFloat>("hollowing_min_thickness", true)->value = m_offset_stash;
mo->config.opt<ConfigOptionFloat>("hollowing_quality", true)->value = m_quality_stash;
mo->config.opt<ConfigOptionFloat>("hollowing_closing_distance", true)->value = m_closing_d_stash;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change")));
}
mo->config.opt<ConfigOptionFloat>("hollowing_min_thickness", true)->value = offset;
mo->config.opt<ConfigOptionFloat>("hollowing_quality", true)->value = quality;
mo->config.opt<ConfigOptionFloat>("hollowing_closing_distance", true)->value = closing_d;
if (slider_released) {
wxGetApp().obj_list()->update_and_show_object_settings_item();
config_changed = true;
}
}
m_imgui->disabled_end();
bool force_refresh = false;
bool remove_selected = false;
bool remove_all = false;
ImGui::Separator();
float diameter_upper_cap = 15.;
if (m_new_hole_radius > diameter_upper_cap)
m_new_hole_radius = diameter_upper_cap;
m_imgui->text(m_desc.at("hole_diameter"));
ImGui::SameLine(diameter_slider_left);
ImGui::PushItemWidth(window_width - diameter_slider_left);
float diam = 2.f * m_new_hole_radius;
ImGui::SliderFloat("", &diam, 1.f, diameter_upper_cap, "%.1f mm");
m_new_hole_radius = diam / 2.f;
bool clicked = ImGui::IsItemClicked();
bool edited = ImGui::IsItemEdited();
bool deactivated = ImGui::IsItemDeactivatedAfterEdit();
m_imgui->text(m_desc["hole_depth"]);
ImGui::SameLine(diameter_slider_left);
m_new_hole_height -= HoleStickOutLength;
ImGui::SliderFloat(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm");
m_new_hole_height += HoleStickOutLength;
clicked |= ImGui::IsItemClicked();
edited |= ImGui::IsItemEdited();
deactivated |= ImGui::IsItemDeactivatedAfterEdit();
// Following is a nasty way to:
// - save the initial value of the slider before one starts messing with it
// - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
// - take correct undo/redo snapshot after the user is done with moving the slider
if (! m_selection_empty) {
if (clicked) {
m_holes_stash = mo->sla_drain_holes;
}
if (edited) {
for (size_t idx=0; idx<m_selected.size(); ++idx)
if (m_selected[idx]) {
mo->sla_drain_holes[idx].radius = m_new_hole_radius;
mo->sla_drain_holes[idx].height = m_new_hole_height;
}
}
if (deactivated) {
// momentarily restore the old value to take snapshot
sla::DrainHoles new_holes = mo->sla_drain_holes;
mo->sla_drain_holes = m_holes_stash;
float backup_rad = m_new_hole_radius;
float backup_hei = m_new_hole_height;
for (size_t i=0; i<m_holes_stash.size(); ++i) {
if (m_selected[i]) {
m_new_hole_radius = m_holes_stash[i].radius;
m_new_hole_height = m_holes_stash[i].height;
break;
}
}
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Change drainage hole diameter")));
m_new_hole_radius = backup_rad;
m_new_hole_height = backup_hei;
mo->sla_drain_holes = new_holes;
}
}
m_imgui->disabled_begin(m_selection_empty);
remove_selected = m_imgui->button(m_desc.at("remove_selected"));
m_imgui->disabled_end();
m_imgui->disabled_begin(mo->sla_drain_holes.empty());
remove_all = m_imgui->button(m_desc.at("remove_all"));
m_imgui->disabled_end();
// Following is rendered in both editing and non-editing mode:
// m_imgui->text("");
ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f)
m_imgui->text(m_desc.at("clipping_of_view"));
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){
m_c->object_clipper()->set_position(-1., false);
});
}
}
ImGui::SameLine(clipping_slider_left);
ImGui::PushItemWidth(window_width - clipping_slider_left);
float clp_dist = m_c->object_clipper()->get_position();
if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
// make sure supports are shown/hidden as appropriate
// DODELAT
//if (m_imgui->checkbox(m_desc["show_supports"], m_show_supports)) {
// m_parent.toggle_sla_auxiliaries_visibility(m_show_supports, mo, m_c->m_active_instance);
// force_refresh = true;
//}
m_imgui->end();
if (remove_selected || remove_all) {
force_refresh = false;
m_parent.set_as_dirty();
if (remove_all) {
select_point(AllPoints);
delete_selected_points();
}
if (remove_selected)
delete_selected_points();
if (first_run) {
first_run = false;
goto RENDER_AGAIN;
}
}
if (force_refresh)
m_parent.set_as_dirty();
if (config_changed)
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
}
bool GLGizmoHollow::on_is_activable() const
{
const Selection& selection = m_parent.get_selection();
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA
|| !selection.is_from_single_instance())
return false;
// Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
const Selection::IndicesList& list = selection.get_volume_idxs();
for (const auto& idx : list)
if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0)
return false;
return true;
}
bool GLGizmoHollow::on_is_selectable() const
{
return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA);
}
std::string GLGizmoHollow::on_get_name() const
{
return (_(L("Hollow and drill")) + " [H]").ToUTF8().data();
}
CommonGizmosDataID GLGizmoHollow::on_get_requirements() const
{
return CommonGizmosDataID(
int(CommonGizmosDataID::SelectionInfo)
| int(CommonGizmosDataID::InstancesHider)
| int(CommonGizmosDataID::Raycaster)
| int(CommonGizmosDataID::HollowedMesh)
| int(CommonGizmosDataID::ObjectClipper));
}
void GLGizmoHollow::on_set_state()
{
// m_c->m_model_object pointer can be invalid (for instance because of undo/redo action),
// we should recover it from the object id
/*m_c->m_model_object = nullptr;
for (const auto mo : wxGetApp().model().objects) {
if (mo->id() == m_c->m_model_object_id) {
m_c->m_model_object = mo;
break;
}
}*/
if (m_state == m_old_state)
return;
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
//Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on")));
//m_c->update_from_backend(m_parent, m_c->m_model_object);
//m_c->unstash_clipping_plane();
//update_clipping_plane(/*m_c->m_clipping_plane_was_moved*/);
//m_c->build_AABB_if_needed();
// we'll now reload support points:
if (m_c->selection_info()->model_object())
reload_cache();
/*m_parent.toggle_model_objects_visibility(false);
if (m_c->m_model_object) {
m_parent.toggle_model_objects_visibility(true, m_c->m_model_object, m_c->m_active_instance);
m_parent.toggle_sla_auxiliaries_visibility(m_show_supports, m_c->m_model_object, m_c->m_active_instance);
}*/
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
//Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off")));
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
m_parent.toggle_model_objects_visibility(true);
//m_c->stash_clipping_plane();
//m_c->m_clipping_plane_distance = 0.f;
//update_clipping_plane(true);
// Release clippers and the AABB raycaster.
//m_c->m_object_clipper.reset();
//m_c->m_supports_clipper.reset();
//m_c->m_mesh_raycaster.reset();
//m_c->m_cavity_mesh.reset();
//m_c->m_volume_with_cavity.reset();
}
m_old_state = m_state;
}
void GLGizmoHollow::on_start_dragging()
{
if (m_hover_id != -1) {
select_point(NoPoints);
select_point(m_hover_id);
m_hole_before_drag = m_c->selection_info()->model_object()->sla_drain_holes[m_hover_id].pos;
}
else
m_hole_before_drag = Vec3f::Zero();
}
void GLGizmoHollow::on_stop_dragging()
{
sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
if (m_hover_id != -1) {
Vec3f backup = drain_holes[m_hover_id].pos;
if (m_hole_before_drag != Vec3f::Zero() // some point was touched
&& backup != m_hole_before_drag) // and it was moved, not just selected
{
drain_holes[m_hover_id].pos = m_hole_before_drag;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move drainage hole")));
drain_holes[m_hover_id].pos = backup;
}
}
m_hole_before_drag = Vec3f::Zero();
}
void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar)
{
ar(m_new_hole_radius,
m_new_hole_height,
m_selected,
m_selection_empty
);
}
void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const
{
ar(m_new_hole_radius,
m_new_hole_height,
m_selected,
m_selection_empty
);
}
void GLGizmoHollow::select_point(int i)
{
const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
if (i == AllPoints || i == NoPoints) {
m_selected.assign(m_selected.size(), i == AllPoints);
m_selection_empty = (i == NoPoints);
if (i == AllPoints) {
m_new_hole_radius = drain_holes[0].radius;
m_new_hole_height = drain_holes[0].height;
}
}
else {
while (size_t(i) >= m_selected.size())
m_selected.push_back(false);
m_selected[i] = true;
m_selection_empty = false;
m_new_hole_radius = drain_holes[i].radius;
m_new_hole_height = drain_holes[i].height;
}
}
void GLGizmoHollow::unselect_point(int i)
{
m_selected[i] = false;
m_selection_empty = true;
for (const bool sel : m_selected) {
if (sel) {
m_selection_empty = false;
break;
}
}
}
void GLGizmoHollow::reload_cache()
{
m_selected.clear();
m_selected.assign(m_c->selection_info()->model_object()->sla_drain_holes.size(), false);
}
void GLGizmoHollow::on_set_hover_id()
{
if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id)
m_hover_id = -1;
}
} // namespace GUI
} // namespace Slic3r