mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-07-14 22:21:47 +08:00

Drain holes reference position was saved slightly above the mesh to avoid problem when the hole is placed on flat or nearly flat surface The depth of the hole was internally bigger than what the user has set to compensato for it However, this leads to problem with scaling and makes reprojection of the holes on the mesh complicated This commit changes the reference point to the point on the mesh and the extra elevation is handled when rendering and drilling the hole. The change is reflected in 3MF drain holes versioning so that old 3MFs are loaded correctly. Reprojection on the mesh after reload from disk/fix through netfabb has been enabled.
920 lines
33 KiB
C++
920 lines
33 KiB
C++
#include "GLGizmoHollow.hpp"
|
|
#include "slic3r/GUI/GLCanvas3D.hpp"
|
|
#include "slic3r/GUI/Camera.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 "libslic3r/PresetBundle.hpp"
|
|
|
|
#include "libslic3r/Model.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&)
|
|
{
|
|
if (! m_c->selection_info())
|
|
return;
|
|
|
|
const ModelObject* mo = m_c->selection_info()->model_object();
|
|
if (m_state == On && mo) {
|
|
if (m_old_mo_id != mo->id()) {
|
|
reload_cache();
|
|
m_old_mo_id = mo->id();
|
|
}
|
|
if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh())
|
|
m_holes_in_drilled_mesh = mo->sla_drain_holes;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
if (m_quadric != nullptr && selection.is_from_single_instance())
|
|
render_points(selection, false);
|
|
|
|
m_selection_rectangle.render(m_parent);
|
|
m_c->object_clipper()->render_cut();
|
|
m_c->supports_clipper()->render_cut();
|
|
|
|
glsafe(::glDisable(GL_BLEND));
|
|
}
|
|
|
|
|
|
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_c->selection_info()->get_sla_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.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 + sla::HoleStickOutLength, 24, 1);
|
|
glsafe(::glTranslated(0., 0., drain_hole.height + sla::HoleStickOutLength));
|
|
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
|
|
glsafe(::glTranslated(0., 0., -drain_hole.height - sla::HoleStickOutLength));
|
|
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;
|
|
|
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
|
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")));
|
|
|
|
mo->sla_drain_holes.emplace_back(pos_and_normal.first,
|
|
-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_c->selection_info()->get_sla_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
|
|
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()))
|
|
{
|
|
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);
|
|
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);
|
|
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;
|
|
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;
|
|
}
|
|
|
|
|
|
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);
|
|
ImGui::SliderFloat(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm");
|
|
|
|
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
|
|
bool show_sups = m_c->instances_hider()->are_supports_shown();
|
|
if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) {
|
|
m_c->instances_hider()->show_supports(show_sups);
|
|
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)
|
|
| int(CommonGizmosDataID::SupportsClipper));
|
|
}
|
|
|
|
|
|
void GLGizmoHollow::on_set_state()
|
|
{
|
|
if (m_state == m_old_state)
|
|
return;
|
|
|
|
if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off
|
|
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
|
|
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
|