mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-02-03 01:18:44 +03:00
QIDISlicer1.0.0
This commit is contained in:
345
src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
Normal file
345
src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
Normal file
@@ -0,0 +1,345 @@
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
// TODO: Display tooltips quicker on Linux
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
const float GLGizmoBase::Grabber::SizeFactor = 0.05f;
|
||||
const float GLGizmoBase::Grabber::MinHalfSize = 1.5f;
|
||||
const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f;
|
||||
|
||||
PickingModel GLGizmoBase::Grabber::s_cube;
|
||||
PickingModel GLGizmoBase::Grabber::s_cone;
|
||||
|
||||
GLGizmoBase::Grabber::~Grabber()
|
||||
{
|
||||
if (s_cube.model.is_initialized())
|
||||
s_cube.model.reset();
|
||||
|
||||
if (s_cone.model.is_initialized())
|
||||
s_cone.model.reset();
|
||||
}
|
||||
|
||||
float GLGizmoBase::Grabber::get_half_size(float size) const
|
||||
{
|
||||
return std::max(size * SizeFactor, MinHalfSize);
|
||||
}
|
||||
|
||||
float GLGizmoBase::Grabber::get_dragging_half_size(float size) const
|
||||
{
|
||||
return get_half_size(size) * DraggingScaleFactor;
|
||||
}
|
||||
|
||||
void GLGizmoBase::Grabber::register_raycasters_for_picking(int id)
|
||||
{
|
||||
picking_id = id;
|
||||
// registration will happen on next call to render()
|
||||
}
|
||||
|
||||
void GLGizmoBase::Grabber::unregister_raycasters_for_picking()
|
||||
{
|
||||
wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, picking_id);
|
||||
picking_id = -1;
|
||||
raycasters = { nullptr };
|
||||
}
|
||||
|
||||
void GLGizmoBase::Grabber::render(float size, const ColorRGBA& render_color)
|
||||
{
|
||||
GLShaderProgram* shader = wxGetApp().get_current_shader();
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
if (!s_cube.model.is_initialized()) {
|
||||
// This cannot be done in constructor, OpenGL is not yet
|
||||
// initialized at that point (on Linux at least).
|
||||
indexed_triangle_set its = its_make_cube(1.0, 1.0, 1.0);
|
||||
its_translate(its, -0.5f * Vec3f::Ones());
|
||||
s_cube.model.init_from(its);
|
||||
s_cube.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its)));
|
||||
}
|
||||
|
||||
if (!s_cone.model.is_initialized()) {
|
||||
indexed_triangle_set its = its_make_cone(0.375, 1.5, double(PI) / 18.0);
|
||||
s_cone.model.init_from(its);
|
||||
s_cone.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its)));
|
||||
}
|
||||
|
||||
const float half_size = dragging ? get_dragging_half_size(size) : get_half_size(size);
|
||||
|
||||
s_cube.model.set_color(render_color);
|
||||
s_cone.model.set_color(render_color);
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
const Transform3d& view_matrix = camera.get_view_matrix();
|
||||
const Matrix3d view_matrix_no_offset = view_matrix.matrix().block(0, 0, 3, 3);
|
||||
std::vector<Transform3d> elements_matrices(GRABBER_ELEMENTS_MAX_COUNT, Transform3d::Identity());
|
||||
elements_matrices[0] = matrix * Geometry::translation_transform(center) * Geometry::rotation_transform(angles) * Geometry::scale_transform(2.0 * half_size);
|
||||
Transform3d view_model_matrix = view_matrix * elements_matrices[0];
|
||||
|
||||
shader->set_uniform("view_model_matrix", view_model_matrix);
|
||||
Matrix3d view_normal_matrix = view_matrix_no_offset * elements_matrices[0].matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
shader->set_uniform("view_normal_matrix", view_normal_matrix);
|
||||
s_cube.model.render();
|
||||
|
||||
auto render_extension = [&view_matrix, &view_matrix_no_offset, shader](const Transform3d& matrix) {
|
||||
const Transform3d view_model_matrix = view_matrix * matrix;
|
||||
shader->set_uniform("view_model_matrix", view_model_matrix);
|
||||
const Matrix3d view_normal_matrix = view_matrix_no_offset * matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
shader->set_uniform("view_normal_matrix", view_normal_matrix);
|
||||
s_cone.model.render();
|
||||
};
|
||||
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosX)) != 0) {
|
||||
elements_matrices[1] = elements_matrices[0] * Geometry::translation_transform(Vec3d::UnitX()) * Geometry::rotation_transform({ 0.0, 0.5 * double(PI), 0.0 });
|
||||
render_extension(elements_matrices[1]);
|
||||
}
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegX)) != 0) {
|
||||
elements_matrices[2] = elements_matrices[0] * Geometry::translation_transform(-Vec3d::UnitX()) * Geometry::rotation_transform({ 0.0, -0.5 * double(PI), 0.0 });
|
||||
render_extension(elements_matrices[2]);
|
||||
}
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosY)) != 0) {
|
||||
elements_matrices[3] = elements_matrices[0] * Geometry::translation_transform(Vec3d::UnitY()) * Geometry::rotation_transform({ -0.5 * double(PI), 0.0, 0.0 });
|
||||
render_extension(elements_matrices[3]);
|
||||
}
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegY)) != 0) {
|
||||
elements_matrices[4] = elements_matrices[0] * Geometry::translation_transform(-Vec3d::UnitY()) * Geometry::rotation_transform({ 0.5 * double(PI), 0.0, 0.0 });
|
||||
render_extension(elements_matrices[4]);
|
||||
}
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosZ)) != 0) {
|
||||
elements_matrices[5] = elements_matrices[0] * Geometry::translation_transform(Vec3d::UnitZ());
|
||||
render_extension(elements_matrices[5]);
|
||||
}
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegZ)) != 0) {
|
||||
elements_matrices[6] = elements_matrices[0] * Geometry::translation_transform(-Vec3d::UnitZ()) * Geometry::rotation_transform({ double(PI), 0.0, 0.0 });
|
||||
render_extension(elements_matrices[6]);
|
||||
}
|
||||
|
||||
if (raycasters[0] == nullptr) {
|
||||
GLCanvas3D& canvas = *wxGetApp().plater()->canvas3D();
|
||||
raycasters[0] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cube.mesh_raycaster, elements_matrices[0]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosX)) != 0)
|
||||
raycasters[1] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[1]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegX)) != 0)
|
||||
raycasters[2] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[2]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosY)) != 0)
|
||||
raycasters[3] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[3]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegY)) != 0)
|
||||
raycasters[4] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[4]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosZ)) != 0)
|
||||
raycasters[5] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[5]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegZ)) != 0)
|
||||
raycasters[6] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[6]);
|
||||
}
|
||||
else {
|
||||
for (size_t i = 0; i < GRABBER_ELEMENTS_MAX_COUNT; ++i) {
|
||||
if (raycasters[i] != nullptr)
|
||||
raycasters[i]->set_transform(elements_matrices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: m_parent(parent)
|
||||
, m_group_id(-1)
|
||||
, m_state(Off)
|
||||
, m_shortcut_key(NO_SHORTCUT_KEY_VALUE)
|
||||
, m_icon_filename(icon_filename)
|
||||
, m_sprite_id(sprite_id)
|
||||
, m_imgui(wxGetApp().imgui())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
std::string GLGizmoBase::get_action_snapshot_name() const
|
||||
{
|
||||
return _u8L("Gizmo action");
|
||||
}
|
||||
|
||||
void GLGizmoBase::set_hover_id(int id)
|
||||
{
|
||||
// do not change hover id during dragging
|
||||
assert(!m_dragging);
|
||||
|
||||
// allow empty grabbers when not using grabbers but use hover_id - flatten, rotate
|
||||
// if (!m_grabbers.empty() && id >= (int) m_grabbers.size())
|
||||
// return;
|
||||
|
||||
m_hover_id = id;
|
||||
on_set_hover_id();
|
||||
}
|
||||
|
||||
bool GLGizmoBase::update_items_state()
|
||||
{
|
||||
bool res = m_dirty;
|
||||
m_dirty = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
void GLGizmoBase::register_grabbers_for_picking()
|
||||
{
|
||||
for (size_t i = 0; i < m_grabbers.size(); ++i) {
|
||||
m_grabbers[i].register_raycasters_for_picking((m_group_id >= 0) ? m_group_id : i);
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoBase::unregister_grabbers_for_picking()
|
||||
{
|
||||
for (size_t i = 0; i < m_grabbers.size(); ++i) {
|
||||
m_grabbers[i].unregister_raycasters_for_picking();
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const
|
||||
{
|
||||
render_grabbers((float)((box.size().x() + box.size().y() + box.size().z()) / 3.0));
|
||||
}
|
||||
|
||||
void GLGizmoBase::render_grabbers(float size) const
|
||||
{
|
||||
render_grabbers(0, m_grabbers.size() - 1, size, false);
|
||||
}
|
||||
|
||||
void GLGizmoBase::render_grabbers(size_t first, size_t last, float size, bool force_hover) const
|
||||
{
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.1f);
|
||||
glsafe(::glDisable(GL_CULL_FACE));
|
||||
for (size_t i = first; i <= last; ++i) {
|
||||
if (m_grabbers[i].enabled)
|
||||
m_grabbers[i].render(force_hover ? true : m_hover_id == (int)i, size);
|
||||
}
|
||||
glsafe(::glEnable(GL_CULL_FACE));
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
// help function to process grabbers
|
||||
// call start_dragging, stop_dragging, on_dragging
|
||||
bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
|
||||
bool is_dragging_finished = false;
|
||||
if (mouse_event.Moving()) {
|
||||
// it should not happen but for sure
|
||||
assert(!m_dragging);
|
||||
if (m_dragging) is_dragging_finished = true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
if (mouse_event.LeftDown()) {
|
||||
Selection &selection = m_parent.get_selection();
|
||||
if (!selection.is_empty() && m_hover_id != -1 /* &&
|
||||
(m_grabbers.empty() || m_hover_id < static_cast<int>(m_grabbers.size()))*/) {
|
||||
selection.setup_cache();
|
||||
|
||||
m_dragging = true;
|
||||
for (auto &grabber : m_grabbers) grabber.dragging = false;
|
||||
// if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size()))
|
||||
// m_grabbers[m_hover_id].dragging = true;
|
||||
|
||||
on_start_dragging();
|
||||
|
||||
// Let the plater know that the dragging started
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED));
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
}
|
||||
} else if (m_dragging) {
|
||||
// when mouse cursor leave window than finish actual dragging operation
|
||||
bool is_leaving = mouse_event.Leaving();
|
||||
if (mouse_event.Dragging()) {
|
||||
Point mouse_coord(mouse_event.GetX(), mouse_event.GetY());
|
||||
auto ray = m_parent.mouse_ray(mouse_coord);
|
||||
UpdateData data(ray, mouse_coord);
|
||||
|
||||
on_dragging(data);
|
||||
|
||||
wxGetApp().obj_manipul()->set_dirty();
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
}
|
||||
else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) {
|
||||
do_stop_dragging(is_leaving);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup)
|
||||
{
|
||||
for (auto& grabber : m_grabbers) grabber.dragging = false;
|
||||
m_dragging = false;
|
||||
|
||||
// NOTE: This should be part of GLCanvas3D
|
||||
// Reset hover_id when leave window
|
||||
if (perform_mouse_cleanup) m_parent.mouse_up_cleanup();
|
||||
|
||||
on_stop_dragging();
|
||||
|
||||
// There is prediction that after draggign, data are changed
|
||||
// Data are updated twice also by canvas3D::reload_scene.
|
||||
// Should be fixed.
|
||||
m_parent.get_gizmos_manager().update_data();
|
||||
|
||||
wxGetApp().obj_manipul()->set_dirty();
|
||||
|
||||
// Let the plater know that the dragging finished, so a delayed
|
||||
// refresh of the scene with the background processing data should
|
||||
// be performed.
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
|
||||
// updates camera target constraints
|
||||
m_parent.refresh_camera_scene_box();
|
||||
}
|
||||
|
||||
std::string GLGizmoBase::format(float value, unsigned int decimals) const
|
||||
{
|
||||
return Slic3r::string_printf("%.*f", decimals, value);
|
||||
}
|
||||
|
||||
void GLGizmoBase::set_dirty() {
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoBase::render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
on_render_input_window(x, y, bottom_limit);
|
||||
if (m_first_input_window_render) {
|
||||
// imgui windows that don't have an initial size needs to be processed once to get one
|
||||
// and are not rendered in the first frame
|
||||
// so, we forces to render another frame the first time the imgui window is shown
|
||||
// https://github.com/ocornut/imgui/issues/2949
|
||||
m_parent.set_as_dirty();
|
||||
m_parent.request_extra_frame();
|
||||
m_first_input_window_render = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string GLGizmoBase::get_name(bool include_shortcut) const
|
||||
{
|
||||
std::string out = on_get_name();
|
||||
if (!include_shortcut) return out;
|
||||
|
||||
int key = get_shortcut_key();
|
||||
assert(key==NO_SHORTCUT_KEY_VALUE || (key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z));
|
||||
out += std::string(" [") + char(int('A') + key - int(WXK_CONTROL_A)) + "]";
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
249
src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
Normal file
249
src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
Normal file
@@ -0,0 +1,249 @@
|
||||
#ifndef slic3r_GLGizmoBase_hpp_
|
||||
#define slic3r_GLGizmoBase_hpp_
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "slic3r/GUI/MeshUtils.hpp"
|
||||
#include "slic3r/GUI/SceneRaycaster.hpp"
|
||||
|
||||
#include <cereal/archives/binary.hpp>
|
||||
|
||||
class wxWindow;
|
||||
class wxMouseEvent;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class BoundingBoxf3;
|
||||
class Linef3;
|
||||
class ModelObject;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
static const ColorRGBA DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f };
|
||||
static const ColorRGBA DEFAULT_DRAG_COLOR = ColorRGBA::WHITE();
|
||||
//B18
|
||||
static const ColorRGBA DEFAULT_HIGHLIGHT_COLOR = ColorRGBA::WHITE();
|
||||
static const std::array<ColorRGBA, 3> AXES_COLOR = {{ ColorRGBA::X(), ColorRGBA::Y(), ColorRGBA::Z() }};
|
||||
static const ColorRGBA CONSTRAINED_COLOR = ColorRGBA::GRAY();
|
||||
|
||||
class ImGuiWrapper;
|
||||
class GLCanvas3D;
|
||||
enum class CommonGizmosDataID;
|
||||
class CommonGizmosDataPool;
|
||||
|
||||
class GLGizmoBase
|
||||
{
|
||||
public:
|
||||
// Starting value for ids to avoid clashing with ids used by GLVolumes
|
||||
// (254 is choosen to leave some space for forward compatibility)
|
||||
static const unsigned int BASE_ID = 255 * 255 * 254;
|
||||
static const unsigned int GRABBER_ELEMENTS_MAX_COUNT = 7;
|
||||
|
||||
enum class EGrabberExtension
|
||||
{
|
||||
None = 0,
|
||||
PosX = 1 << 0,
|
||||
NegX = 1 << 1,
|
||||
PosY = 1 << 2,
|
||||
NegY = 1 << 3,
|
||||
PosZ = 1 << 4,
|
||||
NegZ = 1 << 5,
|
||||
};
|
||||
|
||||
// Represents NO key(button on keyboard) value
|
||||
static const int NO_SHORTCUT_KEY_VALUE = 0;
|
||||
|
||||
protected:
|
||||
struct Grabber
|
||||
{
|
||||
static const float SizeFactor;
|
||||
static const float MinHalfSize;
|
||||
static const float DraggingScaleFactor;
|
||||
|
||||
bool enabled{ true };
|
||||
bool dragging{ false };
|
||||
Vec3d center{ Vec3d::Zero() };
|
||||
Vec3d angles{ Vec3d::Zero() };
|
||||
Transform3d matrix{ Transform3d::Identity() };
|
||||
ColorRGBA color{ ColorRGBA::WHITE() };
|
||||
EGrabberExtension extensions{ EGrabberExtension::None };
|
||||
// the picking id shared by all the elements
|
||||
int picking_id{ -1 };
|
||||
std::array<std::shared_ptr<SceneRaycasterItem>, GRABBER_ELEMENTS_MAX_COUNT> raycasters = { nullptr };
|
||||
|
||||
Grabber() = default;
|
||||
~Grabber();
|
||||
|
||||
void render(bool hover, float size) { render(size, hover ? complementary(color) : color); }
|
||||
|
||||
float get_half_size(float size) const;
|
||||
float get_dragging_half_size(float size) const;
|
||||
|
||||
void register_raycasters_for_picking(int id);
|
||||
void unregister_raycasters_for_picking();
|
||||
|
||||
private:
|
||||
void render(float size, const ColorRGBA& render_color);
|
||||
|
||||
static PickingModel s_cube;
|
||||
static PickingModel s_cone;
|
||||
};
|
||||
|
||||
public:
|
||||
enum EState
|
||||
{
|
||||
Off,
|
||||
On,
|
||||
Num_States
|
||||
};
|
||||
|
||||
struct UpdateData
|
||||
{
|
||||
const Linef3& mouse_ray;
|
||||
const Point& mouse_pos;
|
||||
|
||||
UpdateData(const Linef3& mouse_ray, const Point& mouse_pos)
|
||||
: mouse_ray(mouse_ray), mouse_pos(mouse_pos)
|
||||
{}
|
||||
};
|
||||
|
||||
protected:
|
||||
GLCanvas3D& m_parent;
|
||||
|
||||
int m_group_id; // TODO: remove only for rotate
|
||||
EState m_state;
|
||||
int m_shortcut_key;
|
||||
std::string m_icon_filename;
|
||||
unsigned int m_sprite_id;
|
||||
int m_hover_id{ -1 };
|
||||
bool m_dragging{ false };
|
||||
mutable std::vector<Grabber> m_grabbers;
|
||||
ImGuiWrapper* m_imgui;
|
||||
bool m_first_input_window_render{ true };
|
||||
CommonGizmosDataPool* m_c{ nullptr };
|
||||
|
||||
public:
|
||||
GLGizmoBase(GLCanvas3D& parent,
|
||||
const std::string& icon_filename,
|
||||
unsigned int sprite_id);
|
||||
virtual ~GLGizmoBase() = default;
|
||||
|
||||
bool init() { return on_init(); }
|
||||
|
||||
void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); }
|
||||
void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); }
|
||||
|
||||
std::string get_name(bool include_shortcut = true) const;
|
||||
|
||||
EState get_state() const { return m_state; }
|
||||
void set_state(EState state) { m_state = state; on_set_state(); }
|
||||
|
||||
int get_shortcut_key() const { return m_shortcut_key; }
|
||||
|
||||
const std::string& get_icon_filename() const { return m_icon_filename; }
|
||||
|
||||
bool is_activable() const { return on_is_activable(); }
|
||||
bool is_selectable() const { return on_is_selectable(); }
|
||||
CommonGizmosDataID get_requirements() const { return on_get_requirements(); }
|
||||
virtual bool wants_enter_leave_snapshots() const { return false; }
|
||||
virtual std::string get_gizmo_entering_text() const { assert(false); return ""; }
|
||||
virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; }
|
||||
virtual std::string get_action_snapshot_name() const;
|
||||
void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; }
|
||||
|
||||
unsigned int get_sprite_id() const { return m_sprite_id; }
|
||||
|
||||
int get_hover_id() const { return m_hover_id; }
|
||||
void set_hover_id(int id);
|
||||
|
||||
bool is_dragging() const { return m_dragging; }
|
||||
|
||||
// returns True when Gizmo changed its state
|
||||
bool update_items_state();
|
||||
|
||||
void render() { on_render(); }
|
||||
void render_input_window(float x, float y, float bottom_limit);
|
||||
|
||||
/// <summary>
|
||||
/// Mouse tooltip text
|
||||
/// </summary>
|
||||
/// <returns>Text to be visible in mouse tooltip</returns>
|
||||
virtual std::string get_tooltip() const { return ""; }
|
||||
|
||||
/// <summary>
|
||||
/// Is called when data (Selection) is changed
|
||||
/// </summary>
|
||||
virtual void data_changed(bool is_serializing){};
|
||||
|
||||
/// <summary>
|
||||
/// Implement when want to process mouse events in gizmo
|
||||
/// Click, Right click, move, drag, ...
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information and don't want to propagate it otherwise False.</returns>
|
||||
virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; }
|
||||
|
||||
void register_raycasters_for_picking() { register_grabbers_for_picking(); on_register_raycasters_for_picking(); }
|
||||
void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); }
|
||||
|
||||
virtual bool is_in_editing_mode() const { return false; }
|
||||
virtual bool is_selection_rectangle_dragging() const { return false; }
|
||||
|
||||
protected:
|
||||
virtual bool on_init() = 0;
|
||||
virtual void on_load(cereal::BinaryInputArchive& ar) {}
|
||||
virtual void on_save(cereal::BinaryOutputArchive& ar) const {}
|
||||
virtual std::string on_get_name() const = 0;
|
||||
virtual void on_set_state() {}
|
||||
virtual void on_set_hover_id() {}
|
||||
virtual bool on_is_activable() const { return true; }
|
||||
virtual bool on_is_selectable() const { return true; }
|
||||
virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); }
|
||||
virtual void on_enable_grabber(unsigned int id) {}
|
||||
virtual void on_disable_grabber(unsigned int id) {}
|
||||
|
||||
// called inside use_grabbers
|
||||
virtual void on_start_dragging() {}
|
||||
virtual void on_stop_dragging() {}
|
||||
virtual void on_dragging(const UpdateData& data) {}
|
||||
|
||||
virtual void on_render() = 0;
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) {}
|
||||
|
||||
void register_grabbers_for_picking();
|
||||
void unregister_grabbers_for_picking();
|
||||
virtual void on_register_raycasters_for_picking() {}
|
||||
virtual void on_unregister_raycasters_for_picking() {}
|
||||
|
||||
void render_grabbers(const BoundingBoxf3& box) const;
|
||||
void render_grabbers(float size) const;
|
||||
void render_grabbers(size_t first, size_t last, float size, bool force_hover) const;
|
||||
|
||||
std::string format(float value, unsigned int decimals) const;
|
||||
|
||||
// Mark gizmo as dirty to Re-Render when idle()
|
||||
void set_dirty();
|
||||
|
||||
/// <summary>
|
||||
/// function which
|
||||
/// Set up m_dragging and call functions
|
||||
/// on_start_dragging / on_dragging / on_stop_dragging
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>same as on_mouse</returns>
|
||||
bool use_grabbers(const wxMouseEvent &mouse_event);
|
||||
|
||||
void do_stop_dragging(bool perform_mouse_cleanup);
|
||||
|
||||
private:
|
||||
// Flag for dirty visible state of Gizmo
|
||||
// When True then need new rendering
|
||||
bool m_dirty{ false };
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoBase_hpp_
|
||||
3005
src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
Normal file
3005
src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
Normal file
File diff suppressed because it is too large
Load Diff
347
src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
Normal file
347
src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
Normal file
@@ -0,0 +1,347 @@
|
||||
#ifndef slic3r_GLGizmoCut_hpp_
|
||||
#define slic3r_GLGizmoCut_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class CutConnectorType : int;
|
||||
class ModelVolume;
|
||||
struct CutConnectorAttributes;
|
||||
|
||||
namespace GUI {
|
||||
class Selection;
|
||||
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
|
||||
namespace CommonGizmosDataObjects { class ObjectClipper; }
|
||||
|
||||
class GLGizmoCut3D : public GLGizmoBase
|
||||
{
|
||||
enum GrabberID {
|
||||
X = 0,
|
||||
Y,
|
||||
Z,
|
||||
CutPlane,
|
||||
Count,
|
||||
};
|
||||
|
||||
Transform3d m_rotation_m{ Transform3d::Identity() };
|
||||
double m_snap_step{ 1.0 };
|
||||
int m_connectors_group_id;
|
||||
|
||||
// archived values
|
||||
Vec3d m_ar_plane_center { Vec3d::Zero() };
|
||||
Transform3d m_start_dragging_m{ Transform3d::Identity() };
|
||||
|
||||
Vec3d m_plane_center{ Vec3d::Zero() };
|
||||
// data to check position of the cut palne center on gizmo activation
|
||||
Vec3d m_min_pos{ Vec3d::Zero() };
|
||||
Vec3d m_max_pos{ Vec3d::Zero() };
|
||||
Vec3d m_bb_center{ Vec3d::Zero() };
|
||||
Vec3d m_center_offset{ Vec3d::Zero() };
|
||||
|
||||
BoundingBoxf3 m_bounding_box;
|
||||
BoundingBoxf3 m_transformed_bounding_box;
|
||||
|
||||
// values from RotationGizmo
|
||||
double m_radius{ 0.0 };
|
||||
double m_grabber_radius{ 0.0 };
|
||||
double m_grabber_connection_len{ 0.0 };
|
||||
|
||||
double m_snap_coarse_in_radius{ 0.0 };
|
||||
double m_snap_coarse_out_radius{ 0.0 };
|
||||
double m_snap_fine_in_radius{ 0.0 };
|
||||
double m_snap_fine_out_radius{ 0.0 };
|
||||
|
||||
// dragging angel in hovered axes
|
||||
double m_angle{ 0.0 };
|
||||
|
||||
TriangleMesh m_connector_mesh;
|
||||
// workaround for using of the clipping plane normal
|
||||
Vec3d m_clp_normal{ Vec3d::Ones() };
|
||||
|
||||
Vec3d m_line_beg{ Vec3d::Zero() };
|
||||
Vec3d m_line_end{ Vec3d::Zero() };
|
||||
|
||||
Vec2d m_ldown_mouse_position{ Vec2d::Zero() };
|
||||
|
||||
GLModel m_grabber_connection;
|
||||
GLModel m_cut_line;
|
||||
|
||||
PickingModel m_plane;
|
||||
PickingModel m_sphere;
|
||||
PickingModel m_cone;
|
||||
std::map<CutConnectorAttributes, PickingModel> m_shapes;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_raycasters;
|
||||
|
||||
GLModel m_circle;
|
||||
GLModel m_scale;
|
||||
GLModel m_snap_radii;
|
||||
GLModel m_reference_radius;
|
||||
GLModel m_angle_arc;
|
||||
|
||||
Vec3d m_old_center;
|
||||
Vec3d m_cut_normal;
|
||||
|
||||
struct InvalidConnectorsStatistics
|
||||
{
|
||||
unsigned int outside_cut_contour;
|
||||
unsigned int outside_bb;
|
||||
bool is_overlap;
|
||||
|
||||
void invalidate() {
|
||||
outside_cut_contour = 0;
|
||||
outside_bb = 0;
|
||||
is_overlap = false;
|
||||
}
|
||||
} m_info_stats;
|
||||
|
||||
bool m_keep_upper{ true };
|
||||
bool m_keep_lower{ true };
|
||||
bool m_keep_as_parts{ false };
|
||||
bool m_place_on_cut_upper{ true };
|
||||
bool m_place_on_cut_lower{ false };
|
||||
bool m_rotate_upper{ false };
|
||||
bool m_rotate_lower{ false };
|
||||
|
||||
bool m_hide_cut_plane{ false };
|
||||
bool m_connectors_editing{ false };
|
||||
bool m_cut_plane_as_circle{ false };
|
||||
|
||||
float m_connector_depth_ratio{ 3.f };
|
||||
float m_connector_size{ 2.5f };
|
||||
|
||||
float m_connector_depth_ratio_tolerance{ 0.1f };
|
||||
float m_connector_size_tolerance{ 0.f };
|
||||
|
||||
float m_label_width{ 0.f };
|
||||
float m_control_width{ 200.f };
|
||||
bool m_imperial_units{ false };
|
||||
|
||||
float m_contour_width{ 0.4f };
|
||||
float m_cut_plane_radius_koef{ 1.5f };
|
||||
bool m_is_contour_changed{ false };
|
||||
float m_shortcut_label_width{ -1.f };
|
||||
|
||||
mutable std::vector<bool> m_selected; // which pins are currently selected
|
||||
int m_selected_count{ 0 };
|
||||
|
||||
GLSelectionRectangle m_selection_rectangle;
|
||||
|
||||
std::vector<size_t> m_invalid_connectors_idxs;
|
||||
bool m_was_cut_plane_dragged { false };
|
||||
bool m_was_contour_selected { false };
|
||||
|
||||
class PartSelection {
|
||||
public:
|
||||
PartSelection() = default;
|
||||
PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc);
|
||||
~PartSelection() { m_model.clear_objects(); }
|
||||
|
||||
struct Part {
|
||||
GLModel glmodel;
|
||||
MeshRaycaster raycaster;
|
||||
bool selected;
|
||||
bool is_modifier;
|
||||
};
|
||||
|
||||
void render(const Vec3d* normal, GLModel& sphere_model);
|
||||
void toggle_selection(const Vec2d& mouse_pos);
|
||||
void turn_over_selection();
|
||||
ModelObject* model_object() { return m_model.objects.front(); }
|
||||
bool valid() const { return m_valid; }
|
||||
bool is_one_object() const;
|
||||
const std::vector<Part>& parts() const { return m_parts; }
|
||||
const std::vector<size_t>* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); }
|
||||
|
||||
private:
|
||||
Model m_model;
|
||||
int m_instance_idx;
|
||||
std::vector<Part> m_parts;
|
||||
bool m_valid = false;
|
||||
std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>> m_contour_to_parts; // for each contour, there is a vector of parts above and a vector of parts below
|
||||
std::vector<size_t> m_ignored_contours; // contour that should not be rendered (the parts on both sides will both be parts of the same object)
|
||||
|
||||
std::vector<Vec3d> m_contour_points; // Debugging
|
||||
std::vector<std::vector<Vec3d>> m_debug_pts; // Debugging
|
||||
};
|
||||
|
||||
PartSelection m_part_selection;
|
||||
|
||||
bool m_show_shortcuts{ false };
|
||||
std::vector<std::pair<wxString, wxString>> m_shortcuts;
|
||||
|
||||
enum class CutMode {
|
||||
cutPlanar
|
||||
, cutGrig
|
||||
//,cutRadial
|
||||
//,cutModular
|
||||
};
|
||||
|
||||
enum class CutConnectorMode {
|
||||
Auto
|
||||
, Manual
|
||||
};
|
||||
|
||||
// std::vector<std::string> m_modes;
|
||||
size_t m_mode{ size_t(CutMode::cutPlanar) };
|
||||
|
||||
std::vector<std::string> m_connector_modes;
|
||||
CutConnectorMode m_connector_mode{ CutConnectorMode::Manual };
|
||||
|
||||
std::vector<std::string> m_connector_types;
|
||||
CutConnectorType m_connector_type;
|
||||
|
||||
std::vector<std::string> m_connector_styles;
|
||||
int m_connector_style;
|
||||
|
||||
std::vector<std::string> m_connector_shapes;
|
||||
int m_connector_shape_id;
|
||||
|
||||
std::vector<std::string> m_axis_names;
|
||||
|
||||
std::map<std::string, wxString> m_part_orientation_names;
|
||||
|
||||
std::map<std::string, std::string> m_labels_map;
|
||||
|
||||
public:
|
||||
GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
||||
std::string get_tooltip() const override;
|
||||
bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour = true);
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
||||
bool is_in_editing_mode() const override { return m_connectors_editing; }
|
||||
bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); }
|
||||
bool is_looking_forward() const;
|
||||
|
||||
/// <summary>
|
||||
/// Drag of plane
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
void shift_cut(double delta);
|
||||
void rotate_vec3d_around_plane_center(Vec3d&vec);
|
||||
void put_connectors_on_cut_plane(const Vec3d& cp_normal, double cp_offset);
|
||||
void update_clipper();
|
||||
void invalidate_cut_plane();
|
||||
|
||||
BoundingBoxf3 bounding_box() const;
|
||||
BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center, const Transform3d& rotation_m = Transform3d::Identity()) const;
|
||||
|
||||
protected:
|
||||
bool on_init() override;
|
||||
void on_load(cereal::BinaryInputArchive&ar) override;
|
||||
void on_save(cereal::BinaryOutputArchive&ar) const override;
|
||||
std::string on_get_name() const override;
|
||||
void on_set_state() override;
|
||||
CommonGizmosDataID on_get_requirements() const override;
|
||||
void on_set_hover_id() override;
|
||||
bool on_is_activable() const override;
|
||||
bool on_is_selectable() const override;
|
||||
Vec3d mouse_position_in_local_plane(GrabberID axis, const Linef3&mouse_ray) const;
|
||||
void dragging_grabber_z(const GLGizmoBase::UpdateData &data);
|
||||
void dragging_grabber_xy(const GLGizmoBase::UpdateData &data);
|
||||
void dragging_connector(const GLGizmoBase::UpdateData &data);
|
||||
void on_dragging(const UpdateData&data) override;
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
void on_render() override;
|
||||
|
||||
void render_debug_input_window(float x);
|
||||
void adjust_window_position(float x, float y, float bottom_limit);
|
||||
void unselect_all_connectors();
|
||||
void select_all_connectors();
|
||||
void render_shortcuts();
|
||||
void apply_selected_connectors(std::function<void(size_t idx)> apply_fn);
|
||||
void render_connectors_input_window(CutConnectors &connectors);
|
||||
void render_build_size();
|
||||
void reset_cut_plane();
|
||||
void set_connectors_editing(bool connectors_editing);
|
||||
void flip_cut_plane();
|
||||
void process_contours();
|
||||
void reset_cut_by_contours();
|
||||
void render_flip_plane_button(bool disable_pred = false);
|
||||
void add_vertical_scaled_interval(float interval);
|
||||
void add_horizontal_scaled_interval(float interval);
|
||||
void add_horizontal_shift(float shift);
|
||||
void render_color_marker(float size, const ImU32& color);
|
||||
void render_cut_plane_input_window(CutConnectors &connectors);
|
||||
void init_input_window_data(CutConnectors &connectors);
|
||||
void render_input_window_warning() const;
|
||||
bool add_connector(CutConnectors&connectors, const Vec2d&mouse_position);
|
||||
bool delete_selected_connectors(CutConnectors&connectors);
|
||||
void select_connector(int idx, bool select);
|
||||
bool is_selection_changed(bool alt_down, bool shift_down);
|
||||
void process_selection_rectangle(CutConnectors &connectors);
|
||||
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
void update_raycasters_for_picking();
|
||||
void set_volumes_picking_state(bool state);
|
||||
void update_raycasters_for_picking_transform();
|
||||
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
|
||||
bool wants_enter_leave_snapshots() const override { return true; }
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Cut gizmo"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Cut gizmo"); }
|
||||
std::string get_action_snapshot_name() const override { return _u8L("Cut gizmo editing"); }
|
||||
|
||||
void data_changed(bool is_serializing) override;
|
||||
Transform3d get_cut_matrix(const Selection& selection);
|
||||
|
||||
private:
|
||||
void set_center(const Vec3d& center, bool update_tbb = false);
|
||||
bool render_combo(const std::string& label, const std::vector<std::string>& lines, int& selection_idx);
|
||||
bool render_double_input(const std::string& label, double& value_in);
|
||||
bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in);
|
||||
void render_move_center_input(int axis);
|
||||
void render_connect_mode_radio_button(CutConnectorMode mode);
|
||||
bool render_reset_button(const std::string& label_id, const std::string& tooltip) const;
|
||||
bool render_connect_type_radio_button(CutConnectorType type);
|
||||
bool is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
|
||||
bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
|
||||
void render_connectors();
|
||||
|
||||
bool can_perform_cut() const;
|
||||
bool has_valid_contour() const;
|
||||
void apply_connectors_in_model(ModelObject* mo, bool &create_dowels_as_separate_object);
|
||||
bool cut_line_processing() const;
|
||||
void discard_cut_line_processing();
|
||||
|
||||
void render_cut_plane();
|
||||
static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix);
|
||||
void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width);
|
||||
void render_rotation_snapping(GrabberID axis, const ColorRGBA& color);
|
||||
void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix);
|
||||
void render_cut_plane_grabbers();
|
||||
void render_cut_line();
|
||||
void perform_cut(const Selection&selection);
|
||||
void set_center_pos(const Vec3d¢er_pos, bool update_tbb = false);
|
||||
void update_bb();
|
||||
void init_picking_models();
|
||||
void init_rendering_items();
|
||||
void render_clipper_cut();
|
||||
void clear_selection();
|
||||
void reset_connectors();
|
||||
void init_connector_shapes();
|
||||
void update_connector_shape();
|
||||
void validate_connector_settings();
|
||||
bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position);
|
||||
void check_and_update_connectors_state();
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoCut_hpp_
|
||||
3691
src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
Normal file
3691
src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
Normal file
File diff suppressed because it is too large
Load Diff
342
src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp
Normal file
342
src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp
Normal file
@@ -0,0 +1,342 @@
|
||||
#ifndef slic3r_GLGizmoEmboss_hpp_
|
||||
#define slic3r_GLGizmoEmboss_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "GLGizmoRotate.hpp"
|
||||
#include "slic3r/GUI/IconManager.hpp"
|
||||
#include "slic3r/GUI/SurfaceDrag.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/Utils/RaycastManager.hpp"
|
||||
#include "slic3r/Utils/EmbossStyleManager.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
#include "libslic3r/Emboss.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/TextConfiguration.hpp"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <GL/glew.h>
|
||||
|
||||
class wxFont;
|
||||
namespace Slic3r{
|
||||
class AppConfig;
|
||||
class GLVolume;
|
||||
enum class ModelVolumeType : int;
|
||||
}
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
class GLGizmoEmboss : public GLGizmoBase
|
||||
{
|
||||
public:
|
||||
GLGizmoEmboss(GLCanvas3D& parent);
|
||||
|
||||
/// <summary>
|
||||
/// Create new embossed text volume by type on position of mouse
|
||||
/// </summary>
|
||||
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
|
||||
/// <param name="mouse_pos">Define position of new volume</param>
|
||||
void create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos);
|
||||
|
||||
/// <summary>
|
||||
/// Create new text without given position
|
||||
/// </summary>
|
||||
/// <param name="volume_type">Object part / Negative volume / Modifier</param>
|
||||
void create_volume(ModelVolumeType volume_type);
|
||||
|
||||
/// <summary>
|
||||
/// Handle pressing of shortcut
|
||||
/// </summary>
|
||||
void on_shortcut_key();
|
||||
protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
void on_render() override;
|
||||
void on_register_raycasters_for_picking() override;
|
||||
void on_unregister_raycasters_for_picking() override;
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
bool on_is_selectable() const override { return false; }
|
||||
bool on_is_activable() const override { return true; };
|
||||
void on_set_state() override;
|
||||
void data_changed(bool is_serializing) override; // selection changed
|
||||
void on_set_hover_id() override{ m_rotate_gizmo.set_hover_id(m_hover_id); }
|
||||
void on_enable_grabber(unsigned int id) override { m_rotate_gizmo.enable_grabber(); }
|
||||
void on_disable_grabber(unsigned int id) override { m_rotate_gizmo.disable_grabber(); }
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
void on_dragging(const UpdateData &data) override;
|
||||
|
||||
/// <summary>
|
||||
/// Rotate by text on dragging rotate grabers
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Information about mouse</param>
|
||||
/// <returns>Propagete normaly return false.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
bool wants_enter_leave_snapshots() const override { return true; }
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Enter emboss gizmo"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leave emboss gizmo"); }
|
||||
std::string get_action_snapshot_name() const override { return _u8L("Embossing actions"); }
|
||||
private:
|
||||
static EmbossStyles create_default_styles();
|
||||
// localized default text
|
||||
bool init_create(ModelVolumeType volume_type);
|
||||
|
||||
void set_volume_by_selection();
|
||||
void reset_volume();
|
||||
|
||||
// create volume from text - main functionality
|
||||
bool process();
|
||||
void close();
|
||||
void draw_window();
|
||||
void draw_text_input();
|
||||
void draw_model_type();
|
||||
void fix_transformation(const FontProp &from, const FontProp &to);
|
||||
void draw_style_list();
|
||||
void draw_delete_style_button();
|
||||
void draw_style_rename_popup();
|
||||
void draw_style_rename_button();
|
||||
void draw_style_save_button(bool is_modified);
|
||||
void draw_style_save_as_popup();
|
||||
void draw_style_add_button();
|
||||
void init_font_name_texture();
|
||||
struct FaceName;
|
||||
void draw_font_preview(FaceName &face, bool is_visible);
|
||||
void draw_font_list_line();
|
||||
void draw_font_list();
|
||||
void draw_height(bool use_inch);
|
||||
void draw_depth(bool use_inch);
|
||||
|
||||
// call after set m_style_manager.get_style().prop.size_in_mm
|
||||
bool set_height();
|
||||
// call after set m_style_manager.get_style().prop.emboss
|
||||
bool set_depth();
|
||||
|
||||
bool draw_italic_button();
|
||||
bool draw_bold_button();
|
||||
void draw_advanced();
|
||||
|
||||
bool select_facename(const wxString& facename);
|
||||
|
||||
void do_translate(const Vec3d& relative_move);
|
||||
void do_rotate(float relative_z_angle);
|
||||
|
||||
bool rev_input_mm(const std::string &name, float &value, const float *default_value,
|
||||
const std::string &undo_tooltip, float step, float step_fast, const char *format,
|
||||
bool use_inch, const std::optional<float>& scale);
|
||||
|
||||
/// <summary>
|
||||
/// Reversible input float with option to restor default value
|
||||
/// TODO: make more general, static and move to ImGuiWrapper
|
||||
/// </summary>
|
||||
/// <returns>True when value changed otherwise FALSE.</returns>
|
||||
bool rev_input(const std::string &name, float &value, const float *default_value,
|
||||
const std::string &undo_tooltip, float step, float step_fast, const char *format,
|
||||
ImGuiInputTextFlags flags = 0);
|
||||
bool rev_checkbox(const std::string &name, bool &value, const bool* default_value, const std::string &undo_tooltip);
|
||||
bool rev_slider(const std::string &name, std::optional<int>& value, const std::optional<int> *default_value,
|
||||
const std::string &undo_tooltip, int v_min, int v_max, const std::string &format, const wxString &tooltip);
|
||||
bool rev_slider(const std::string &name, std::optional<float>& value, const std::optional<float> *default_value,
|
||||
const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip);
|
||||
bool rev_slider(const std::string &name, float &value, const float *default_value,
|
||||
const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip);
|
||||
template<typename T, typename Draw>
|
||||
bool revertible(const std::string &name, T &value, const T *default_value, const std::string &undo_tooltip, float undo_offset, Draw draw);
|
||||
|
||||
bool m_should_set_minimal_windows_size = false;
|
||||
void set_minimal_window_size(bool is_advance_edit_style);
|
||||
ImVec2 get_minimal_window_size() const;
|
||||
|
||||
// process mouse event
|
||||
bool on_mouse_for_rotation(const wxMouseEvent &mouse_event);
|
||||
bool on_mouse_for_translate(const wxMouseEvent &mouse_event);
|
||||
void on_mouse_change_selection(const wxMouseEvent &mouse_event);
|
||||
|
||||
// When open text loaded from .3mf it could be written with unknown font
|
||||
bool m_is_unknown_font;
|
||||
void create_notification_not_valid_font(const TextConfiguration& tc);
|
||||
void create_notification_not_valid_font(const std::string& text);
|
||||
void remove_notification_not_valid_font();
|
||||
|
||||
// This configs holds GUI layout size given by translated texts.
|
||||
// etc. When language changes, GUI is recreated and this class constructed again,
|
||||
// so the change takes effect. (info by GLGizmoFdmSupports.hpp)
|
||||
struct GuiCfg
|
||||
{
|
||||
// Detect invalid config values when change monitor DPI
|
||||
double screen_scale;
|
||||
float main_toolbar_height;
|
||||
|
||||
// Zero means it is calculated in init function
|
||||
ImVec2 minimal_window_size = ImVec2(0, 0);
|
||||
ImVec2 minimal_window_size_with_advance = ImVec2(0, 0);
|
||||
ImVec2 minimal_window_size_with_collections = ImVec2(0, 0);
|
||||
float height_of_volume_type_selector = 0.f;
|
||||
float input_width = 0.f;
|
||||
float delete_pos_x = 0.f;
|
||||
float max_style_name_width = 0.f;
|
||||
unsigned int icon_width = 0;
|
||||
|
||||
// maximal width and height of style image
|
||||
Vec2i max_style_image_size = Vec2i(0, 0);
|
||||
|
||||
float indent = 0.f;
|
||||
float input_offset = 0.f;
|
||||
float advanced_input_offset = 0.f;
|
||||
|
||||
float lock_offset = 0.f;
|
||||
|
||||
ImVec2 text_size;
|
||||
|
||||
// maximal size of face name image
|
||||
Vec2i face_name_size = Vec2i(100, 0);
|
||||
float face_name_max_width = 100.f;
|
||||
float face_name_texture_offset_x = 105.f;
|
||||
|
||||
// maximal texture generate jobs running at once
|
||||
unsigned int max_count_opened_font_files = 10;
|
||||
|
||||
// Only translations needed for calc GUI size
|
||||
struct Translations
|
||||
{
|
||||
std::string font;
|
||||
std::string height;
|
||||
std::string depth;
|
||||
std::string use_surface;
|
||||
|
||||
// advanced
|
||||
std::string char_gap;
|
||||
std::string line_gap;
|
||||
std::string boldness;
|
||||
std::string skew_ration;
|
||||
std::string from_surface;
|
||||
std::string rotation;
|
||||
std::string keep_up;
|
||||
std::string collection;
|
||||
};
|
||||
Translations translations;
|
||||
};
|
||||
std::optional<const GuiCfg> m_gui_cfg;
|
||||
static GuiCfg create_gui_configuration();
|
||||
|
||||
// Is open tree with advanced options
|
||||
bool m_is_advanced_edit_style = false;
|
||||
|
||||
// when true window will appear near to text volume when open
|
||||
// When false it opens on last position
|
||||
bool m_allow_open_near_volume = false;
|
||||
// setted only when wanted to use - not all the time
|
||||
std::optional<ImVec2> m_set_window_offset;
|
||||
|
||||
// Keep information about stored styles and loaded actual style to compare with
|
||||
Emboss::StyleManager m_style_manager;
|
||||
|
||||
struct FaceName{
|
||||
wxString wx_name;
|
||||
std::string name_truncated = "";
|
||||
size_t texture_index = 0;
|
||||
// State for generation of texture
|
||||
// when start generate create share pointers
|
||||
std::shared_ptr<std::atomic<bool>> cancel = nullptr;
|
||||
// R/W only on main thread - finalize of job
|
||||
std::shared_ptr<bool> is_created = nullptr;
|
||||
};
|
||||
|
||||
// Keep sorted list of loadable face names
|
||||
struct Facenames
|
||||
{
|
||||
// flag to keep need of enumeration fonts from OS
|
||||
// false .. wants new enumeration check by Hash
|
||||
// true .. already enumerated(During opened combo box)
|
||||
bool is_init = false;
|
||||
|
||||
bool has_truncated_names = false;
|
||||
|
||||
// data of can_load() faces
|
||||
std::vector<FaceName> faces = {};
|
||||
// Sorter set of Non valid face names in OS
|
||||
std::vector<wxString> bad = {};
|
||||
|
||||
// Configuration of font encoding
|
||||
static constexpr wxFontEncoding encoding = wxFontEncoding::wxFONTENCODING_SYSTEM;
|
||||
|
||||
// Identify if preview texture exists
|
||||
GLuint texture_id = 0;
|
||||
|
||||
// protection for open too much font files together
|
||||
// Gtk:ERROR:../../../../gtk/gtkiconhelper.c:494:ensure_surface_for_gicon: assertion failed (error == NULL): Failed to load /usr/share/icons/Yaru/48x48/status/image-missing.png: Error opening file /usr/share/icons/Yaru/48x48/status/image-missing.png: Too many open files (g-io-error-quark, 31)
|
||||
// This variable must exist until no CreateFontImageJob is running
|
||||
unsigned int count_opened_font_files = 0;
|
||||
|
||||
// Configuration for texture height
|
||||
const int count_cached_textures = 32;
|
||||
|
||||
// index for new generated texture index(must be lower than count_cached_textures)
|
||||
size_t texture_index = 0;
|
||||
|
||||
// hash created from enumerated font from OS
|
||||
// check when new font was installed
|
||||
size_t hash = 0;
|
||||
|
||||
// filtration pattern
|
||||
std::string search = "";
|
||||
std::vector<bool> hide; // result of filtration
|
||||
} m_face_names;
|
||||
static bool store(const Facenames &facenames);
|
||||
static bool load(Facenames &facenames);
|
||||
|
||||
static void init_face_names(Facenames &facenames);
|
||||
static void init_truncated_names(Facenames &face_names, float max_width);
|
||||
|
||||
// Text to emboss
|
||||
std::string m_text; // Sequence of Unicode UTF8 symbols
|
||||
|
||||
// When true keep up vector otherwise relative rotation
|
||||
bool m_keep_up = true;
|
||||
|
||||
// current selected volume
|
||||
// NOTE: Be carefull could be uninitialized (removed from Model)
|
||||
ModelVolume *m_volume;
|
||||
|
||||
// When work with undo redo stack there could be situation that
|
||||
// m_volume point to unexisting volume so One need also objectID
|
||||
ObjectID m_volume_id;
|
||||
|
||||
// True when m_text contain character unknown by selected font
|
||||
bool m_text_contain_unknown_glyph = false;
|
||||
|
||||
// cancel for previous update of volume to cancel finalize part
|
||||
std::shared_ptr<std::atomic<bool>> m_job_cancel;
|
||||
|
||||
// Rotation gizmo
|
||||
GLGizmoRotate m_rotate_gizmo;
|
||||
// Value is set only when dragging rotation to calculate actual angle
|
||||
std::optional<float> m_rotate_start_angle;
|
||||
|
||||
// Keep data about dragging only during drag&drop
|
||||
std::optional<SurfaceDrag> m_surface_drag;
|
||||
|
||||
// TODO: it should be accessible by other gizmo too.
|
||||
// May be move to plater?
|
||||
RaycastManager m_raycast_manager;
|
||||
|
||||
// For text on scaled objects
|
||||
std::optional<float> m_scale_height;
|
||||
std::optional<float> m_scale_depth;
|
||||
void calculate_scale();
|
||||
|
||||
// drawing icons
|
||||
IconManager m_icon_manager;
|
||||
IconManager::VIcons m_icons;
|
||||
void init_icons();
|
||||
|
||||
// only temporary solution
|
||||
static const std::string M_ICON_FILENAME;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_GLGizmoEmboss_hpp_
|
||||
581
src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
Normal file
581
src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
Normal file
@@ -0,0 +1,581 @@
|
||||
#include "GLGizmoFdmSupports.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
//#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "libslic3r/SupportSpotsGenerator.hpp"
|
||||
#include "libslic3r/TriangleSelectorWrapper.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
|
||||
|
||||
void GLGizmoFdmSupports::on_shutdown()
|
||||
{
|
||||
m_highlight_by_angle_threshold_deg = 0.f;
|
||||
m_parent.use_slope(false);
|
||||
m_parent.toggle_model_objects_visibility(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string GLGizmoFdmSupports::on_get_name() const
|
||||
{
|
||||
return _u8L("Paint-on supports");
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool GLGizmoFdmSupports::on_init()
|
||||
{
|
||||
m_shortcut_key = WXK_CONTROL_L;
|
||||
|
||||
m_desc["autopaint"] = _L("Automatic painting");
|
||||
// TRN GizmoFdmSupports : message line during the waiting for autogenerated supports
|
||||
m_desc["painting"] = _L("painting") + dots;
|
||||
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
||||
m_desc["reset_direction"] = _L("Reset direction");
|
||||
m_desc["cursor_size"] = _L("Brush size") + ": ";
|
||||
m_desc["cursor_type"] = _L("Brush shape") + ": ";
|
||||
m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
|
||||
m_desc["enforce"] = _L("Enforce supports");
|
||||
m_desc["block_caption"] = _L("Right mouse button") + ": ";
|
||||
m_desc["block"] = _L("Block supports");
|
||||
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
|
||||
m_desc["remove"] = _L("Remove selection");
|
||||
m_desc["remove_all"] = _L("Remove all selection");
|
||||
m_desc["circle"] = _L("Circle");
|
||||
m_desc["sphere"] = _L("Sphere");
|
||||
m_desc["pointer"] = _L("Triangles");
|
||||
m_desc["highlight_by_angle"] = _L("Highlight overhang by angle");
|
||||
m_desc["enforce_button"] = _L("Enforce");
|
||||
m_desc["cancel"] = _L("Cancel");
|
||||
|
||||
m_desc["tool_type"] = _L("Tool type") + ": ";
|
||||
m_desc["tool_brush"] = _L("Brush");
|
||||
m_desc["tool_smart_fill"] = _L("Smart fill");
|
||||
|
||||
m_desc["smart_fill_angle"] = _L("Smart fill angle");
|
||||
|
||||
m_desc["split_triangles"] = _L("Split triangles");
|
||||
m_desc["on_overhangs_only"] = _L("On overhangs only");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLGizmoFdmSupports::render_painter_gizmo()
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
render_triangles(selection);
|
||||
m_c->object_clipper()->render_cut();
|
||||
m_c->instances_hider()->render_cut();
|
||||
render_cursor();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
if (! m_c->selection_info()->model_object())
|
||||
return;
|
||||
|
||||
const float approx_height = m_imgui->scaled(25.f);
|
||||
y = std::min(y, bottom_limit - approx_height);
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
|
||||
m_imgui->begin(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 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 cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
||||
const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f);
|
||||
const float autoset_slider_label_max_width = m_imgui->scaled(7.5f);
|
||||
const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f);
|
||||
|
||||
const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
|
||||
const float button_enforce_width = m_imgui->calc_text_size(m_desc.at("enforce_button")).x;
|
||||
const float button_cancel_width = m_imgui->calc_text_size(m_desc.at("cancel")).x;
|
||||
const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f);
|
||||
const float minimal_slider_width = m_imgui->scaled(4.f);
|
||||
|
||||
const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f);
|
||||
const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f);
|
||||
const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
float caption_max = 0.f;
|
||||
float total_text_max = 0.f;
|
||||
for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"}) {
|
||||
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
|
||||
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
|
||||
}
|
||||
total_text_max += caption_max + m_imgui->scaled(1.f);
|
||||
caption_max += m_imgui->scaled(1.f);
|
||||
|
||||
const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left));
|
||||
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
|
||||
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
|
||||
window_width = std::max(window_width, total_text_max);
|
||||
window_width = std::max(window_width, button_width);
|
||||
window_width = std::max(window_width, split_triangles_checkbox_width);
|
||||
window_width = std::max(window_width, on_overhangs_only_checkbox_width);
|
||||
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
|
||||
window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill);
|
||||
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
|
||||
|
||||
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
|
||||
//B18
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, caption);
|
||||
ImGui::SameLine(caption_max);
|
||||
m_imgui->text(text);
|
||||
};
|
||||
|
||||
for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"})
|
||||
draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (waiting_for_autogenerated_supports) {
|
||||
m_imgui->text(m_desc.at("painting"));
|
||||
} else {
|
||||
bool generate = m_imgui->button(m_desc.at("autopaint"));
|
||||
if (generate)
|
||||
auto_generate();
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
float position_before_text_y = ImGui::GetCursorPos().y;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
float position_after_text_y = ImGui::GetCursorPos().y;
|
||||
std::string format_str = std::string("%.f") + I18N::translate_utf8("°",
|
||||
"Degree sign to use in the respective slider in FDM supports gizmo,"
|
||||
"placed after the number with no whitespace in between.");
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
|
||||
float slider_height = m_imgui->get_slider_float_height();
|
||||
// Makes slider to be aligned to bottom of the multi-line text.
|
||||
float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height);
|
||||
ImGui::SetCursorPosY(slider_start_position_y);
|
||||
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||
wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when "
|
||||
"the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]);
|
||||
if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) {
|
||||
m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg);
|
||||
if (! m_parent.is_using_slope()) {
|
||||
m_parent.use_slope(true);
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Restores the cursor position to be below the multi-line text.
|
||||
ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y));
|
||||
|
||||
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
||||
|
||||
m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f);
|
||||
ImGui::NewLine();
|
||||
ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f));
|
||||
if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) {
|
||||
select_facets_by_angle(m_highlight_by_angle_threshold_deg, false);
|
||||
m_highlight_by_angle_threshold_deg = 0.f;
|
||||
m_parent.use_slope(false);
|
||||
}
|
||||
ImGui::SameLine(window_width - buttons_width);
|
||||
if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) {
|
||||
m_highlight_by_angle_threshold_deg = 0.f;
|
||||
m_parent.use_slope(false);
|
||||
}
|
||||
m_imgui->disabled_end();
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc["tool_type"]);
|
||||
|
||||
float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f;
|
||||
ImGui::SameLine(tool_type_offset);
|
||||
ImGui::PushItemWidth(tool_type_radio_brush);
|
||||
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH))
|
||||
m_tool_type = ToolType::BRUSH;
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
|
||||
ImGui::PushItemWidth(tool_type_radio_smart_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL))
|
||||
m_tool_type = ToolType::SMART_FILL;
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
|
||||
|
||||
m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only);
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (m_tool_type == ToolType::BRUSH) {
|
||||
m_imgui->text(m_desc.at("cursor_type"));
|
||||
ImGui::NewLine();
|
||||
|
||||
float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f;
|
||||
ImGui::SameLine(cursor_type_offset);
|
||||
ImGui::PushItemWidth(cursor_type_radio_sphere);
|
||||
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
|
||||
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
|
||||
ImGui::PushItemWidth(cursor_type_radio_circle);
|
||||
|
||||
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
|
||||
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle);
|
||||
ImGui::PushItemWidth(cursor_type_radio_pointer);
|
||||
|
||||
if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
|
||||
m_cursor_type = TriangleSelector::CursorType::POINTER;
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width);
|
||||
|
||||
m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_size"));
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
|
||||
|
||||
m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width);
|
||||
|
||||
m_imgui->disabled_end();
|
||||
} else {
|
||||
assert(m_tool_type == ToolType::SMART_FILL);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc["smart_fill_angle"] + ":");
|
||||
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_c->object_clipper()->get_position() == 0.f) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
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_by_ratio(-1., false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
auto clp_dist = float(m_c->object_clipper()->get_position());
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
|
||||
m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction);
|
||||
ModelObject *mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume *mv : mo->volumes)
|
||||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
m_triangle_selectors[idx]->request_update_render_data();
|
||||
}
|
||||
|
||||
update_model_object();
|
||||
this->waiting_for_autogenerated_supports = false;
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
m_imgui->end();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
|
||||
{
|
||||
float threshold = (float(M_PI)/180.f)*threshold_deg;
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
|
||||
|
||||
int mesh_id = -1;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
|
||||
++mesh_id;
|
||||
|
||||
const Transform3d trafo_matrix = mi->get_matrix_no_offset() * mv->get_matrix_no_offset();
|
||||
Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized();
|
||||
Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized();
|
||||
|
||||
float dot_limit = limit.dot(down);
|
||||
|
||||
// Now calculate dot product of vert_direction and facets' normals.
|
||||
int idx = 0;
|
||||
const indexed_triangle_set &its = mv->mesh().its;
|
||||
for (const stl_triangle_vertex_indices &face : its.indices) {
|
||||
if (its_face_normal(its, face).dot(down) > dot_limit) {
|
||||
m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER);
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
++ idx;
|
||||
}
|
||||
}
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
|
||||
: _L("Add supports by angle"));
|
||||
update_model_object();
|
||||
this->waiting_for_autogenerated_supports = false;
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
void GLGizmoFdmSupports::data_changed(bool is_serializing)
|
||||
{
|
||||
GLGizmoPainterBase::data_changed(is_serializing);
|
||||
if (! m_c->selection_info())
|
||||
return;
|
||||
|
||||
ModelObject* mo = m_c->selection_info()->model_object();
|
||||
if (mo && this->waiting_for_autogenerated_supports) {
|
||||
apply_data_from_backend();
|
||||
} else {
|
||||
this->waiting_for_autogenerated_supports = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoFdmSupports::apply_data_from_backend()
|
||||
{
|
||||
if (!has_backend_supports())
|
||||
return;
|
||||
ModelObject *mo = m_c->selection_info()->model_object();
|
||||
if (!mo) {
|
||||
this->waiting_for_autogenerated_supports = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// find the respective PrintObject, we need a pointer to it
|
||||
for (const PrintObject *po : m_parent.fff_print()->objects()) {
|
||||
if (po->model_object()->id() == mo->id()) {
|
||||
std::unordered_map<size_t, TriangleSelectorWrapper> selectors;
|
||||
SupportSpotsGenerator::SupportPoints support_points = po->shared_regions()->generated_support_points->support_points;
|
||||
auto obj_transform = po->shared_regions()->generated_support_points->object_transform;
|
||||
for (ModelVolume *model_volume : po->model_object()->volumes) {
|
||||
if (model_volume->is_model_part()) {
|
||||
Transform3d mesh_transformation = obj_transform * model_volume->get_matrix();
|
||||
Transform3d inv_transform = mesh_transformation.inverse();
|
||||
selectors.emplace(model_volume->id().id, TriangleSelectorWrapper{model_volume->mesh(), mesh_transformation});
|
||||
|
||||
for (const SupportSpotsGenerator::SupportPoint &support_point : support_points) {
|
||||
Vec3f point = Vec3f(inv_transform.cast<float>() * support_point.position);
|
||||
Vec3f origin = Vec3f(inv_transform.cast<float>() *
|
||||
Vec3f(support_point.position.x(), support_point.position.y(), 0.0f));
|
||||
selectors.at(model_volume->id().id).enforce_spot(point, origin, support_point.spot_radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int mesh_id = -1.0f;
|
||||
for (ModelVolume *mv : mo->volumes) {
|
||||
if (mv->is_model_part()) {
|
||||
mesh_id++;
|
||||
auto selector = selectors.find(mv->id().id);
|
||||
if (selector != selectors.end()) {
|
||||
m_triangle_selectors[mesh_id]->deserialize(selector->second.selector.serialize(), true);
|
||||
m_triangle_selectors[mesh_id]->request_update_render_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this->waiting_for_autogenerated_supports = false;
|
||||
update_model_object();
|
||||
}
|
||||
|
||||
void GLGizmoFdmSupports::update_model_object() const
|
||||
{
|
||||
bool updated = false;
|
||||
ModelObject* mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
++idx;
|
||||
updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get());
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
const ModelObjectPtrs& mos = wxGetApp().model().objects;
|
||||
wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
|
||||
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoFdmSupports::update_from_model_object()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
m_triangle_selectors.clear();
|
||||
|
||||
int volume_id = -1;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
|
||||
++volume_id;
|
||||
|
||||
// This mesh does not account for the possible Z up SLA offset.
|
||||
const TriangleMesh* mesh = &mv->mesh();
|
||||
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
|
||||
// Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize().
|
||||
m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data(), false);
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
bool GLGizmoFdmSupports::has_backend_supports()
|
||||
{
|
||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
||||
if (!mo) {
|
||||
waiting_for_autogenerated_supports = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// find PrintObject with this ID
|
||||
bool done = false;
|
||||
for (const PrintObject *po : m_parent.fff_print()->objects()) {
|
||||
if (po->model_object()->id() == mo->id())
|
||||
done = done || po->is_step_done(posSupportSpotsSearch);
|
||||
}
|
||||
|
||||
if (!done && !wxGetApp().plater()->is_background_process_update_scheduled()) {
|
||||
waiting_for_autogenerated_supports = false;
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
void GLGizmoFdmSupports::auto_generate()
|
||||
{
|
||||
std::string err = wxGetApp().plater()->fff_print().validate();
|
||||
if (!err.empty()) {
|
||||
MessageDialog dlg(GUI::wxGetApp().plater(), _L("Automatic painting requires valid print setup.") + " \n" + from_u8(err), _L("Warning"), wxOK);
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
|
||||
ModelObject *mo = m_c->selection_info()->model_object();
|
||||
bool printable = std::any_of(mo->instances.begin(), mo->instances.end(), [](const ModelInstance *p) { return p->is_printable(); });
|
||||
if (!printable) {
|
||||
MessageDialog dlg(GUI::wxGetApp().plater(), _L("Automatic painting requires printable object."), _L("Warning"), wxOK);
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
|
||||
bool not_painted = std::all_of(mo->volumes.begin(), mo->volumes.end(), [](const ModelVolume* vol){
|
||||
return vol->type() != ModelVolumeType::MODEL_PART || vol->supported_facets.empty();
|
||||
});
|
||||
|
||||
MessageDialog dlg(GUI::wxGetApp().plater(),
|
||||
_L("Automatic painting will erase all currently painted areas.") + "\n\n" +
|
||||
_L("Are you sure you want to do it?") + "\n",
|
||||
_L("Warning"), wxICON_WARNING | wxYES | wxNO);
|
||||
|
||||
if (not_painted || dlg.ShowModal() == wxID_YES) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Automatic painting support points"));
|
||||
int mesh_id = -1.0f;
|
||||
for (ModelVolume *mv : mo->volumes) {
|
||||
if (mv->is_model_part()) {
|
||||
mesh_id++;
|
||||
mv->supported_facets.reset();
|
||||
m_triangle_selectors[mesh_id]->reset();
|
||||
m_triangle_selectors[mesh_id]->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
wxGetApp().CallAfter([this]() {
|
||||
wxGetApp().plater()->reslice_FFF_until_step(posSupportSpotsSearch, *m_c->selection_info()->model_object(), false);
|
||||
this->waiting_for_autogenerated_supports = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PainterGizmoType GLGizmoFdmSupports::get_painter_type() const
|
||||
{
|
||||
return PainterGizmoType::FDM_SUPPORTS;
|
||||
}
|
||||
|
||||
wxString GLGizmoFdmSupports::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
|
||||
{
|
||||
wxString action_name;
|
||||
if (shift_down)
|
||||
action_name = _L("Remove selection");
|
||||
else {
|
||||
if (button_down == Button::Left)
|
||||
action_name = _L("Add supports");
|
||||
else
|
||||
action_name = _L("Block supports");
|
||||
}
|
||||
return action_name;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
58
src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
Normal file
58
src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef slic3r_GLGizmoFdmSupports_hpp_
|
||||
#define slic3r_GLGizmoFdmSupports_hpp_
|
||||
|
||||
#include "GLGizmoPainterBase.hpp"
|
||||
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
class GLGizmoFdmSupports : public GLGizmoPainterBase
|
||||
{
|
||||
public:
|
||||
GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoPainterBase(parent, icon_filename, sprite_id) {}
|
||||
|
||||
void render_painter_gizmo() override;
|
||||
|
||||
protected:
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
std::string on_get_name() const override;
|
||||
|
||||
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
||||
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Paint-on supports"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Paint-on supports"); }
|
||||
std::string get_action_snapshot_name() const override { return _u8L("Paint-on supports editing"); }
|
||||
|
||||
|
||||
private:
|
||||
bool on_init() override;
|
||||
void data_changed(bool is_serializing) override;
|
||||
|
||||
void update_model_object() const override;
|
||||
void update_from_model_object() override;
|
||||
|
||||
void on_opening() override {}
|
||||
void on_shutdown() override;
|
||||
PainterGizmoType get_painter_type() const override;
|
||||
|
||||
void select_facets_by_angle(float threshold, bool block);
|
||||
|
||||
// This map holds all translated description texts, so they can be easily referenced during layout calculations
|
||||
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||
std::map<std::string, wxString> m_desc;
|
||||
|
||||
|
||||
bool waiting_for_autogenerated_supports = false;
|
||||
bool has_backend_supports();
|
||||
void auto_generate();
|
||||
void apply_data_from_backend();
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
|
||||
#endif // slic3r_GLGizmoFdmSupports_hpp_
|
||||
410
src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
Normal file
410
src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
Normal file
@@ -0,0 +1,410 @@
|
||||
#include "GLGizmoFlatten.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
|
||||
|
||||
#include "libslic3r/Geometry/ConvexHull.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f };
|
||||
static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f };
|
||||
|
||||
GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||
{}
|
||||
|
||||
bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event)
|
||||
{
|
||||
if (mouse_event.LeftDown()) {
|
||||
if (m_hover_id != -1) {
|
||||
Selection &selection = m_parent.get_selection();
|
||||
if (selection.is_single_full_instance()) {
|
||||
// Rotate the object so the normal points downward:
|
||||
selection.flattening_rotate(m_planes[m_hover_id].normal);
|
||||
m_parent.do_rotate(L("Gizmo-Place on Face"));
|
||||
wxGetApp().obj_manipul()->set_dirty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (mouse_event.LeftUp())
|
||||
return m_hover_id != -1;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLGizmoFlatten::data_changed(bool is_serializing)
|
||||
{
|
||||
const Selection & selection = m_parent.get_selection();
|
||||
const ModelObject *model_object = nullptr;
|
||||
int instance_id = -1;
|
||||
if (selection.is_single_full_instance() ||
|
||||
selection.is_from_single_object() ) {
|
||||
model_object = selection.get_model()->objects[selection.get_object_idx()];
|
||||
instance_id = selection.get_instance_idx();
|
||||
}
|
||||
set_flattening_data(model_object, instance_id);
|
||||
}
|
||||
|
||||
bool GLGizmoFlatten::on_init()
|
||||
{
|
||||
m_shortcut_key = WXK_CONTROL_F;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLGizmoFlatten::on_set_state()
|
||||
{
|
||||
}
|
||||
|
||||
CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const
|
||||
{
|
||||
return CommonGizmosDataID::SelectionInfo;
|
||||
}
|
||||
|
||||
std::string GLGizmoFlatten::on_get_name() const
|
||||
{
|
||||
return _u8L("Place on face");
|
||||
}
|
||||
|
||||
bool GLGizmoFlatten::on_is_activable() const
|
||||
{
|
||||
// This is assumed in GLCanvas3D::do_rotate, do not change this
|
||||
// without updating that function too.
|
||||
return m_parent.get_selection().is_single_full_instance();
|
||||
}
|
||||
|
||||
void GLGizmoFlatten::on_render()
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
shader->start_using();
|
||||
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
|
||||
if (selection.is_single_full_instance()) {
|
||||
const Transform3d& inst_matrix = selection.get_first_volume()->get_instance_transformation().get_matrix();
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Transform3d model_matrix = Geometry::translation_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * inst_matrix;
|
||||
const Transform3d view_model_matrix = camera.get_view_matrix() * model_matrix;
|
||||
|
||||
shader->set_uniform("view_model_matrix", view_model_matrix);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
if (this->is_plane_update_necessary())
|
||||
update_planes();
|
||||
for (int i = 0; i < (int)m_planes.size(); ++i) {
|
||||
m_planes[i].vbo.model.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR);
|
||||
m_planes[i].vbo.model.render();
|
||||
}
|
||||
}
|
||||
|
||||
glsafe(::glEnable(GL_CULL_FACE));
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
void GLGizmoFlatten::on_register_raycasters_for_picking()
|
||||
{
|
||||
// the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account
|
||||
m_parent.set_raycaster_gizmos_on_top(true);
|
||||
|
||||
assert(m_planes_casters.empty());
|
||||
|
||||
if (!m_planes.empty()) {
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const Transform3d matrix = Geometry::translation_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) *
|
||||
selection.get_first_volume()->get_instance_transformation().get_matrix();
|
||||
|
||||
for (int i = 0; i < (int)m_planes.size(); ++i) {
|
||||
m_planes_casters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_planes[i].vbo.mesh_raycaster, matrix));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoFlatten::on_unregister_raycasters_for_picking()
|
||||
{
|
||||
m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo);
|
||||
m_parent.set_raycaster_gizmos_on_top(false);
|
||||
m_planes_casters.clear();
|
||||
}
|
||||
|
||||
void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object, int instance_id)
|
||||
{
|
||||
if (model_object != m_old_model_object || instance_id != m_old_instance_id) {
|
||||
m_planes.clear();
|
||||
on_unregister_raycasters_for_picking();
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoFlatten::update_planes()
|
||||
{
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
TriangleMesh ch;
|
||||
for (const ModelVolume* vol : mo->volumes) {
|
||||
if (vol->type() != ModelVolumeType::MODEL_PART)
|
||||
continue;
|
||||
TriangleMesh vol_ch = vol->get_convex_hull();
|
||||
vol_ch.transform(vol->get_matrix());
|
||||
ch.merge(vol_ch);
|
||||
}
|
||||
ch = ch.convex_hull_3d();
|
||||
m_planes.clear();
|
||||
on_unregister_raycasters_for_picking();
|
||||
const Transform3d inst_matrix = mo->instances.front()->get_matrix_no_offset();
|
||||
|
||||
// Following constants are used for discarding too small polygons.
|
||||
const float minimal_area = 5.f; // in square mm (world coordinates)
|
||||
const float minimal_side = 1.f; // mm
|
||||
|
||||
// Now we'll go through all the facets and append Points of facets sharing the same normal.
|
||||
// This part is still performed in mesh coordinate system.
|
||||
const int num_of_facets = ch.facets_count();
|
||||
const std::vector<Vec3f> face_normals = its_face_normals(ch.its);
|
||||
const std::vector<Vec3i> face_neighbors = its_face_neighbors(ch.its);
|
||||
std::vector<int> facet_queue(num_of_facets, 0);
|
||||
std::vector<bool> facet_visited(num_of_facets, false);
|
||||
int facet_queue_cnt = 0;
|
||||
const stl_normal* normal_ptr = nullptr;
|
||||
int facet_idx = 0;
|
||||
while (1) {
|
||||
// Find next unvisited triangle:
|
||||
for (; facet_idx < num_of_facets; ++ facet_idx)
|
||||
if (!facet_visited[facet_idx]) {
|
||||
facet_queue[facet_queue_cnt ++] = facet_idx;
|
||||
facet_visited[facet_idx] = true;
|
||||
normal_ptr = &face_normals[facet_idx];
|
||||
m_planes.emplace_back();
|
||||
break;
|
||||
}
|
||||
if (facet_idx == num_of_facets)
|
||||
break; // Everything was visited already
|
||||
|
||||
while (facet_queue_cnt > 0) {
|
||||
int facet_idx = facet_queue[-- facet_queue_cnt];
|
||||
const stl_normal& this_normal = face_normals[facet_idx];
|
||||
if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) {
|
||||
const Vec3i face = ch.its.indices[facet_idx];
|
||||
for (int j=0; j<3; ++j)
|
||||
m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast<double>());
|
||||
|
||||
facet_visited[facet_idx] = true;
|
||||
for (int j = 0; j < 3; ++ j)
|
||||
if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx])
|
||||
facet_queue[facet_queue_cnt ++] = neighbor_idx;
|
||||
}
|
||||
}
|
||||
m_planes.back().normal = normal_ptr->cast<double>();
|
||||
|
||||
Pointf3s& verts = m_planes.back().vertices;
|
||||
// Now we'll transform all the points into world coordinates, so that the areas, angles and distances
|
||||
// make real sense.
|
||||
verts = transform(verts, inst_matrix);
|
||||
|
||||
// if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway):
|
||||
if (verts.size() == 3 &&
|
||||
((verts[0] - verts[1]).norm() < minimal_side
|
||||
|| (verts[0] - verts[2]).norm() < minimal_side
|
||||
|| (verts[1] - verts[2]).norm() < minimal_side))
|
||||
m_planes.pop_back();
|
||||
}
|
||||
|
||||
// Let's prepare transformation of the normal vector from mesh to instance coordinates.
|
||||
const Matrix3d normal_matrix = inst_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
|
||||
// Now we'll go through all the polygons, transform the points into xy plane to process them:
|
||||
for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) {
|
||||
Pointf3s& polygon = m_planes[polygon_id].vertices;
|
||||
const Vec3d& normal = m_planes[polygon_id].normal;
|
||||
|
||||
// transform the normal according to the instance matrix:
|
||||
const Vec3d normal_transformed = normal_matrix * normal;
|
||||
|
||||
// We are going to rotate about z and y to flatten the plane
|
||||
Eigen::Quaterniond q;
|
||||
Transform3d m = Transform3d::Identity();
|
||||
m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix();
|
||||
polygon = transform(polygon, m);
|
||||
|
||||
// Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since
|
||||
// it works in fixed point representation, we will rescale the polygon to avoid overflows.
|
||||
// And yes, it is a nasty thing to do. Whoever has time is free to refactor.
|
||||
Vec3d bb_size = BoundingBoxf3(polygon).size();
|
||||
float sf = std::min(1./bb_size(0), 1./bb_size(1));
|
||||
Transform3d tr = Geometry::scale_transform({ sf, sf, 1.f });
|
||||
polygon = transform(polygon, tr);
|
||||
polygon = Slic3r::Geometry::convex_hull(polygon);
|
||||
polygon = transform(polygon, tr.inverse());
|
||||
|
||||
// Calculate area of the polygons and discard ones that are too small
|
||||
float& area = m_planes[polygon_id].area;
|
||||
area = 0.f;
|
||||
for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula
|
||||
area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1);
|
||||
area = 0.5f * std::abs(area);
|
||||
|
||||
bool discard = false;
|
||||
if (area < minimal_area)
|
||||
discard = true;
|
||||
else {
|
||||
// We also check the inner angles and discard polygons with angles smaller than the following threshold
|
||||
const double angle_threshold = ::cos(10.0 * (double)PI / 180.0);
|
||||
|
||||
for (unsigned int i = 0; i < polygon.size(); ++i) {
|
||||
const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1];
|
||||
const Vec3d& curr = polygon[i];
|
||||
const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1];
|
||||
|
||||
if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) {
|
||||
discard = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (discard) {
|
||||
m_planes[polygon_id--] = std::move(m_planes.back());
|
||||
m_planes.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
// We will shrink the polygon a little bit so it does not touch the object edges:
|
||||
Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0));
|
||||
centroid /= (double)polygon.size();
|
||||
for (auto& vertex : polygon)
|
||||
vertex = 0.9f*vertex + 0.1f*centroid;
|
||||
|
||||
// Polygon is now simple and convex, we'll round the corners to make them look nicer.
|
||||
// The algorithm takes a vertex, calculates middles of respective sides and moves the vertex
|
||||
// towards their average (controlled by 'aggressivity'). This is repeated k times.
|
||||
// In next iterations, the neighbours are not always taken at the middle (to increase the
|
||||
// rounding effect at the corners, where we need it most).
|
||||
const unsigned int k = 10; // number of iterations
|
||||
const float aggressivity = 0.2f; // agressivity
|
||||
const unsigned int N = polygon.size();
|
||||
std::vector<std::pair<unsigned int, unsigned int>> neighbours;
|
||||
if (k != 0) {
|
||||
Pointf3s points_out(2*k*N); // vector long enough to store the future vertices
|
||||
for (unsigned int j=0; j<N; ++j) {
|
||||
points_out[j*2*k] = polygon[j];
|
||||
neighbours.push_back(std::make_pair((int)(j*2*k-k) < 0 ? (N-1)*2*k+k : j*2*k-k, j*2*k+k));
|
||||
}
|
||||
|
||||
for (unsigned int i=0; i<k; ++i) {
|
||||
// Calculate middle of each edge so that neighbours points to something useful:
|
||||
for (unsigned int j=0; j<N; ++j)
|
||||
if (i==0)
|
||||
points_out[j*2*k+k] = 0.5f * (points_out[j*2*k] + points_out[j==N-1 ? 0 : (j+1)*2*k]);
|
||||
else {
|
||||
float r = 0.2+0.3/(k-1)*i; // the neighbours are not always taken in the middle
|
||||
points_out[neighbours[j].first] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].first-1];
|
||||
points_out[neighbours[j].second] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].second+1];
|
||||
}
|
||||
// Now we have a triangle and valid neighbours, we can do an iteration:
|
||||
for (unsigned int j=0; j<N; ++j)
|
||||
points_out[2*k*j] = (1-aggressivity) * points_out[2*k*j] +
|
||||
aggressivity*0.5f*(points_out[neighbours[j].first] + points_out[neighbours[j].second]);
|
||||
|
||||
for (auto& n : neighbours) {
|
||||
++n.first;
|
||||
--n.second;
|
||||
}
|
||||
}
|
||||
polygon = points_out; // replace the coarse polygon with the smooth one that we just created
|
||||
}
|
||||
|
||||
|
||||
// Raise a bit above the object surface to avoid flickering:
|
||||
for (auto& b : polygon)
|
||||
b(2) += 0.1f;
|
||||
|
||||
// Transform back to 3D (and also back to mesh coordinates)
|
||||
polygon = transform(polygon, inst_matrix.inverse() * m.inverse());
|
||||
}
|
||||
|
||||
// We'll sort the planes by area and only keep the 254 largest ones (because of the picking pass limitations):
|
||||
std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData& a, const PlaneData& b) { return a.area < b.area; });
|
||||
m_planes.resize(std::min((int)m_planes.size(), 254));
|
||||
|
||||
// Planes are finished - let's save what we calculated it from:
|
||||
m_volumes_matrices.clear();
|
||||
m_volumes_types.clear();
|
||||
for (const ModelVolume* vol : mo->volumes) {
|
||||
m_volumes_matrices.push_back(vol->get_matrix());
|
||||
m_volumes_types.push_back(vol->type());
|
||||
}
|
||||
m_first_instance_scale = mo->instances.front()->get_scaling_factor();
|
||||
m_first_instance_mirror = mo->instances.front()->get_mirror();
|
||||
m_old_model_object = mo;
|
||||
m_old_instance_id = m_c->selection_info()->get_active_instance();
|
||||
|
||||
// And finally create respective VBOs. The polygon is convex with
|
||||
// the vertices in order, so triangulation is trivial.
|
||||
for (auto& plane : m_planes) {
|
||||
indexed_triangle_set its;
|
||||
its.vertices.reserve(plane.vertices.size());
|
||||
its.indices.reserve(plane.vertices.size() / 3);
|
||||
for (size_t i = 0; i < plane.vertices.size(); ++i) {
|
||||
its.vertices.emplace_back((Vec3f)plane.vertices[i].cast<float>());
|
||||
}
|
||||
for (size_t i = 1; i < plane.vertices.size() - 1; ++i) {
|
||||
its.indices.emplace_back(0, i, i + 1); // triangle fan
|
||||
}
|
||||
|
||||
plane.vbo.model.init_from(its);
|
||||
if (Geometry::Transformation(inst_matrix).is_left_handed()) {
|
||||
// we need to swap face normals in case the object is mirrored
|
||||
// for the raycaster to work properly
|
||||
for (stl_triangle_vertex_indices& face : its.indices) {
|
||||
if (its_face_normal(its, face).cast<double>().dot(plane.normal) < 0.0)
|
||||
std::swap(face[1], face[2]);
|
||||
}
|
||||
}
|
||||
plane.vbo.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its)));
|
||||
// vertices are no more needed, clear memory
|
||||
plane.vertices = std::vector<Vec3d>();
|
||||
}
|
||||
|
||||
on_register_raycasters_for_picking();
|
||||
}
|
||||
|
||||
bool GLGizmoFlatten::is_plane_update_necessary() const
|
||||
{
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
if (m_state != On || ! mo || mo->instances.empty())
|
||||
return false;
|
||||
|
||||
if (m_planes.empty() || mo != m_old_model_object
|
||||
|| mo->volumes.size() != m_volumes_matrices.size())
|
||||
return true;
|
||||
|
||||
// We want to recalculate when the scale changes - some planes could (dis)appear.
|
||||
if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale)
|
||||
|| ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror))
|
||||
return true;
|
||||
|
||||
for (unsigned int i=0; i < mo->volumes.size(); ++i)
|
||||
if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i])
|
||||
|| mo->volumes[i]->type() != m_volumes_types[i])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
73
src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp
Normal file
73
src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef slic3r_GLGizmoFlatten_hpp_
|
||||
#define slic3r_GLGizmoFlatten_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "slic3r/GUI/MeshUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class ModelVolumeType : int;
|
||||
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
class GLGizmoFlatten : public GLGizmoBase
|
||||
{
|
||||
// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself.
|
||||
|
||||
private:
|
||||
|
||||
GLModel arrow;
|
||||
|
||||
struct PlaneData {
|
||||
std::vector<Vec3d> vertices; // should be in fact local in update_planes()
|
||||
PickingModel vbo;
|
||||
Vec3d normal;
|
||||
float area;
|
||||
int picking_id{ -1 };
|
||||
};
|
||||
|
||||
// This holds information to decide whether recalculation is necessary:
|
||||
std::vector<Transform3d> m_volumes_matrices;
|
||||
std::vector<ModelVolumeType> m_volumes_types;
|
||||
Vec3d m_first_instance_scale;
|
||||
Vec3d m_first_instance_mirror;
|
||||
|
||||
std::vector<PlaneData> m_planes;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_planes_casters;
|
||||
const ModelObject* m_old_model_object = nullptr;
|
||||
int m_old_instance_id{ -1 };
|
||||
|
||||
void update_planes();
|
||||
bool is_plane_update_necessary() const;
|
||||
|
||||
public:
|
||||
GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
||||
void set_flattening_data(const ModelObject* model_object, int instance_id);
|
||||
|
||||
/// <summary>
|
||||
/// Apply rotation on select plane
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
void data_changed(bool is_serializing) override;
|
||||
protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
void on_render() override;
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
void on_set_state() override;
|
||||
CommonGizmosDataID on_get_requirements() const override;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoFlatten_hpp_
|
||||
987
src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
Normal file
987
src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
Normal file
@@ -0,0 +1,987 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "GLGizmoHollow.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.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/SLAPrint.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoSlaBase(parent, icon_filename, sprite_id, slaposAssembly)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
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::data_changed(bool is_serializing)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
const SLAPrintObject* po = m_c->selection_info()->print_object();
|
||||
if (po != nullptr) {
|
||||
std::shared_ptr<const indexed_triangle_set> preview_mesh_ptr = po->get_mesh_to_print();
|
||||
if (!preview_mesh_ptr || preview_mesh_ptr->empty())
|
||||
reslice_until_step(slaposAssembly);
|
||||
}
|
||||
|
||||
update_volumes();
|
||||
|
||||
if (m_hole_raycasters.empty())
|
||||
register_hole_raycasters_for_picking();
|
||||
else
|
||||
update_hole_raycasters_for_picking_transform();
|
||||
|
||||
m_c->instances_hider()->set_hide_full_scene(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoHollow::on_render()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (m_state == On) {
|
||||
// This gizmo is showing the object elevated. Tell the common
|
||||
// SelectionInfo object to lie about the actual shift.
|
||||
m_c->selection_info()->set_use_shift(true);
|
||||
}
|
||||
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
render_volumes();
|
||||
render_points(selection);
|
||||
|
||||
m_selection_rectangle.render(m_parent);
|
||||
m_c->object_clipper()->render_cut();
|
||||
if (are_sla_supports_shown())
|
||||
m_c->supports_clipper()->render_cut();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
}
|
||||
|
||||
void GLGizmoHollow::on_register_raycasters_for_picking()
|
||||
{
|
||||
register_hole_raycasters_for_picking();
|
||||
register_volume_raycasters_for_picking();
|
||||
}
|
||||
|
||||
void GLGizmoHollow::on_unregister_raycasters_for_picking()
|
||||
{
|
||||
unregister_hole_raycasters_for_picking();
|
||||
unregister_volume_raycasters_for_picking();
|
||||
}
|
||||
|
||||
void GLGizmoHollow::render_points(const Selection& selection)
|
||||
{
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
shader->start_using();
|
||||
ScopeGuard guard([shader]() { shader->stop_using(); });
|
||||
|
||||
auto *inst = m_c->selection_info()->model_instance();
|
||||
if (!inst)
|
||||
return;
|
||||
|
||||
double shift_z = m_c->selection_info()->print_object()->get_current_elevation();
|
||||
Transform3d trafo(inst->get_transformation().get_matrix());
|
||||
trafo.translation()(2) += shift_z;
|
||||
const Geometry::Transformation transformation{trafo};
|
||||
|
||||
const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse();
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Transform3d& view_matrix = camera.get_view_matrix();
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
|
||||
ColorRGBA render_color;
|
||||
const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
|
||||
const 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];
|
||||
|
||||
const bool clipped = is_mesh_point_clipped(drain_hole.pos.cast<double>());
|
||||
m_hole_raycasters[i]->set_active(!clipped);
|
||||
if (clipped)
|
||||
continue;
|
||||
|
||||
// First decide about the color of the point.
|
||||
if (size_t(m_hover_id) == i)
|
||||
render_color = ColorRGBA::CYAN();
|
||||
else
|
||||
render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f);
|
||||
|
||||
m_cylinder.model.set_color(render_color);
|
||||
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
|
||||
const Transform3d hole_matrix = Geometry::translation_transform(drain_hole.pos.cast<double>()) * instance_scaling_matrix_inverse;
|
||||
|
||||
if (transformation.is_left_handed())
|
||||
glsafe(::glFrontFace(GL_CW));
|
||||
|
||||
// Matrices set, we can render the point mark now.
|
||||
Eigen::Quaterniond q;
|
||||
q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast<double>());
|
||||
const Eigen::AngleAxisd aa(q);
|
||||
const Transform3d model_matrix = trafo * hole_matrix * Transform3d(aa.toRotationMatrix()) *
|
||||
Geometry::translation_transform(-drain_hole.height * Vec3d::UnitZ()) * Geometry::scale_transform(Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength));
|
||||
shader->set_uniform("view_model_matrix", view_matrix * model_matrix);
|
||||
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
shader->set_uniform("view_normal_matrix", view_normal_matrix);
|
||||
m_cylinder.model.render();
|
||||
|
||||
if (transformation.is_left_handed())
|
||||
glsafe(::glFrontFace(GL_CCW));
|
||||
}
|
||||
}
|
||||
|
||||
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() * sel_info->model_object()->volumes.front()->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);
|
||||
}
|
||||
|
||||
// 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::EState::Select : GLSelectionRectangle::EState::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;
|
||||
unregister_hole_raycasters_for_picking();
|
||||
register_hole_raycasters_for_picking();
|
||||
}
|
||||
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.contains(points);
|
||||
m_selection_rectangle.stop_dragging();
|
||||
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::EState::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_by_ratio(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_by_ratio(pos, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == SLAGizmoEventType::ResetClippingPlane) {
|
||||
m_c->object_clipper()->set_position_by_ratio(-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--));
|
||||
}
|
||||
}
|
||||
|
||||
unregister_hole_raycasters_for_picking();
|
||||
register_hole_raycasters_for_picking();
|
||||
select_point(NoPoints);
|
||||
}
|
||||
|
||||
bool GLGizmoHollow::on_mouse(const wxMouseEvent &mouse_event)
|
||||
{
|
||||
if (!is_input_enabled()) return true;
|
||||
if (mouse_event.Moving()) return false;
|
||||
if (use_grabbers(mouse_event)) return true;
|
||||
|
||||
// wxCoord == int --> wx/types.h
|
||||
Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
|
||||
Vec2d mouse_pos = mouse_coord.cast<double>();
|
||||
|
||||
static bool pending_right_up = false;
|
||||
if (mouse_event.LeftDown()) {
|
||||
bool control_down = mouse_event.CmdDown();
|
||||
bool grabber_contains_mouse = (get_hover_id() != -1);
|
||||
if ((!control_down || grabber_contains_mouse) &&
|
||||
gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false))
|
||||
// the gizmo got the event and took some action, there is no need
|
||||
// to do anything more
|
||||
return true;
|
||||
} else if (mouse_event.Dragging()) {
|
||||
if (m_parent.get_move_volume_id() != -1)
|
||||
// don't allow dragging objects with the Sla gizmo on
|
||||
return true;
|
||||
|
||||
bool control_down = mouse_event.CmdDown();
|
||||
if (control_down) {
|
||||
// CTRL has been pressed while already dragging -> stop current action
|
||||
if (mouse_event.LeftIsDown())
|
||||
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
|
||||
else if (mouse_event.RightIsDown()) {
|
||||
pending_right_up = false;
|
||||
}
|
||||
} else if(gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) {
|
||||
// the gizmo got the event and took some action, no need to do
|
||||
// anything more here
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
}
|
||||
} else if (mouse_event.LeftUp()) {
|
||||
if (!m_parent.is_mouse_dragging()) {
|
||||
bool control_down = mouse_event.CmdDown();
|
||||
// in case gizmo is selected, we just pass the LeftUp event
|
||||
// and stop processing - neither object moving or selecting is
|
||||
// suppressed in that case
|
||||
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down);
|
||||
return true;
|
||||
}
|
||||
} else if (mouse_event.RightDown()) {
|
||||
if (m_parent.get_selection().get_object_idx() != -1 &&
|
||||
gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) {
|
||||
// we need to set the following right up as processed to avoid showing
|
||||
// the context menu if the user release the mouse over the object
|
||||
pending_right_up = true;
|
||||
// event was taken care of by the SlaSupports gizmo
|
||||
return true;
|
||||
}
|
||||
} else if (mouse_event.RightUp()) {
|
||||
if (pending_right_up) {
|
||||
pending_right_up = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLGizmoHollow::register_hole_raycasters_for_picking()
|
||||
{
|
||||
assert(m_hole_raycasters.empty());
|
||||
|
||||
init_cylinder_model();
|
||||
|
||||
const CommonGizmosDataObjects::SelectionInfo* info = m_c->selection_info();
|
||||
if (info != nullptr && !info->model_object()->sla_drain_holes.empty()) {
|
||||
const sla::DrainHoles& drain_holes = info->model_object()->sla_drain_holes;
|
||||
for (int i = 0; i < (int)drain_holes.size(); ++i) {
|
||||
m_hole_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_cylinder.mesh_raycaster, Transform3d::Identity()));
|
||||
}
|
||||
update_hole_raycasters_for_picking_transform();
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoHollow::unregister_hole_raycasters_for_picking()
|
||||
{
|
||||
for (size_t i = 0; i < m_hole_raycasters.size(); ++i) {
|
||||
m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, i);
|
||||
}
|
||||
m_hole_raycasters.clear();
|
||||
}
|
||||
|
||||
void GLGizmoHollow::update_hole_raycasters_for_picking_transform()
|
||||
{
|
||||
const CommonGizmosDataObjects::SelectionInfo* info = m_c->selection_info();
|
||||
if (info != nullptr) {
|
||||
const sla::DrainHoles& drain_holes = info->model_object()->sla_drain_holes;
|
||||
if (!drain_holes.empty()) {
|
||||
assert(!m_hole_raycasters.empty());
|
||||
|
||||
const GLVolume* vol = m_parent.get_selection().get_first_volume();
|
||||
Geometry::Transformation transformation(vol->get_instance_transformation());
|
||||
|
||||
auto *inst = m_c->selection_info()->model_instance();
|
||||
if (inst && m_c->selection_info() && m_c->selection_info()->print_object()) {
|
||||
double shift_z = m_c->selection_info()->print_object()->get_current_elevation();
|
||||
auto trafo = inst->get_transformation().get_matrix();
|
||||
trafo.translation()(2) += shift_z;
|
||||
transformation.set_matrix(trafo);
|
||||
}
|
||||
const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse();
|
||||
|
||||
for (size_t i = 0; i < drain_holes.size(); ++i) {
|
||||
const sla::DrainHole& drain_hole = drain_holes[i];
|
||||
const Transform3d hole_matrix = Geometry::translation_transform(drain_hole.pos.cast<double>()) * instance_scaling_matrix_inverse;
|
||||
Eigen::Quaterniond q;
|
||||
q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast<double>());
|
||||
const Eigen::AngleAxisd aa(q);
|
||||
const Transform3d matrix = transformation.get_matrix() * hole_matrix * Transform3d(aa.toRotationMatrix()) *
|
||||
Geometry::translation_transform(-drain_hole.height * Vec3d::UnitZ()) * Geometry::scale_transform(Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength));
|
||||
m_hole_raycasters[i]->set_transform(matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.get();
|
||||
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.option_def(key));
|
||||
else
|
||||
if (print_cfg.has(key))
|
||||
out.emplace_back(print_cfg.option(key), print_cfg.option_def(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->option_def(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(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 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(0.5f);
|
||||
|
||||
const float settings_sliders_left =
|
||||
std::max(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(0.5f), clipping_slider_left);
|
||||
|
||||
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);
|
||||
|
||||
const float button_preview_width = m_imgui->calc_button_size(m_desc.at("preview")).x;
|
||||
|
||||
float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left});
|
||||
window_width = std::max(window_width, button_preview_width);
|
||||
|
||||
m_imgui->disabled_begin(!is_input_enabled());
|
||||
|
||||
if (m_imgui->button(m_desc["preview"]))
|
||||
reslice_until_step(slaposDrillHoles);
|
||||
|
||||
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.set("hollowing_enable", m_enable_hollowing);
|
||||
wxGetApp().obj_list()->update_and_show_object_settings_item();
|
||||
config_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_imgui->disabled_end();
|
||||
|
||||
m_imgui->disabled_begin(!is_input_enabled() || !m_enable_hollowing);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("offset"));
|
||||
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
|
||||
ImGui::PushItemWidth(window_width - settings_sliders_left);
|
||||
m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip));
|
||||
|
||||
bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider
|
||||
bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider
|
||||
bool slider_released =m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider
|
||||
|
||||
if (current_mode >= quality_mode) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("quality"));
|
||||
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
|
||||
m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip));
|
||||
|
||||
slider_clicked |= m_imgui->get_last_slider_status().clicked;
|
||||
slider_edited |= m_imgui->get_last_slider_status().edited;
|
||||
slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
|
||||
}
|
||||
|
||||
if (current_mode >= closing_d_mode) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("closing_distance"));
|
||||
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
|
||||
m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm", 1.0f, true, _L(opts[2].second->tooltip));
|
||||
|
||||
slider_clicked |= m_imgui->get_last_slider_status().clicked;
|
||||
slider_edited |= m_imgui->get_last_slider_status().edited;
|
||||
slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
|
||||
}
|
||||
|
||||
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.set("hollowing_min_thickness", m_offset_stash);
|
||||
mo->config.set("hollowing_quality", m_quality_stash);
|
||||
mo->config.set("hollowing_closing_distance", m_closing_d_stash);
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Hollowing parameter change"));
|
||||
}
|
||||
mo->config.set("hollowing_min_thickness", offset);
|
||||
mo->config.set("hollowing_quality", quality);
|
||||
mo->config.set("hollowing_closing_distance", 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 = 60.;
|
||||
if (m_new_hole_radius * 2.f > diameter_upper_cap)
|
||||
m_new_hole_radius = diameter_upper_cap / 2.f;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
|
||||
m_imgui->disabled_begin(!is_input_enabled());
|
||||
|
||||
m_imgui->text(m_desc.at("hole_diameter"));
|
||||
ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x);
|
||||
ImGui::PushItemWidth(window_width - diameter_slider_left);
|
||||
float diam = 2.f * m_new_hole_radius;
|
||||
m_imgui->slider_float("##hole_diameter", &diam, 1.f, 25.f, "%.1f mm", 1.f, false);
|
||||
|
||||
// Let's clamp the value (which could have been entered by keyboard) to a larger range
|
||||
// than the slider. This allows entering off-scale values and still protects against
|
||||
//complete non-sense.
|
||||
diam = std::clamp(diam, 0.1f, diameter_upper_cap);
|
||||
m_new_hole_radius = diam / 2.f;
|
||||
bool clicked = m_imgui->get_last_slider_status().clicked;
|
||||
bool edited = m_imgui->get_last_slider_status().edited;
|
||||
bool deactivated = m_imgui->get_last_slider_status().deactivated_after_edit;
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
|
||||
m_imgui->text(m_desc["hole_depth"]);
|
||||
ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x);
|
||||
m_imgui->slider_float("##hole_depth", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false);
|
||||
|
||||
m_imgui->disabled_end();
|
||||
|
||||
// Same as above:
|
||||
m_new_hole_height = std::clamp(m_new_hole_height, 0.f, 100.f);
|
||||
|
||||
clicked |= m_imgui->get_last_slider_status().clicked;
|
||||
edited |= m_imgui->get_last_slider_status().edited;
|
||||
deactivated |= m_imgui->get_last_slider_status().deactivated_after_edit;;
|
||||
|
||||
// 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(!is_input_enabled() || m_selection_empty);
|
||||
remove_selected = m_imgui->button(m_desc.at("remove_selected"));
|
||||
m_imgui->disabled_end();
|
||||
|
||||
m_imgui->disabled_begin(!is_input_enabled() || 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:
|
||||
ImGui::Separator();
|
||||
m_imgui->disabled_begin(!is_input_enabled());
|
||||
if (m_c->object_clipper()->get_position() == 0.f) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
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_by_ratio(-1., false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
|
||||
ImGui::PushItemWidth(window_width - settings_sliders_left);
|
||||
float clp_dist = m_c->object_clipper()->get_position();
|
||||
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
|
||||
m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
|
||||
|
||||
// make sure supports are shown/hidden as appropriate
|
||||
ImGui::Separator();
|
||||
bool show_sups = are_sla_supports_shown();
|
||||
if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) {
|
||||
show_sla_supports(show_sups);
|
||||
force_refresh = true;
|
||||
}
|
||||
|
||||
m_imgui->disabled_end();
|
||||
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_single_full_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;
|
||||
|
||||
// Check that none of the selected volumes is marked as non-pritable.
|
||||
for (const auto& idx : list) {
|
||||
if (!selection.get_volume(idx)->printable)
|
||||
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 _u8L("Hollow and drill");
|
||||
}
|
||||
|
||||
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_c->instances_hider()->set_hide_full_scene(false);
|
||||
m_c->selection_info()->set_use_shift(false); // see top of on_render for details
|
||||
}
|
||||
|
||||
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_dragging(const UpdateData &data)
|
||||
{
|
||||
assert(m_hover_id != -1);
|
||||
std::pair<Vec3f, Vec3f> pos_and_normal;
|
||||
if (!unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
|
||||
return;
|
||||
sla::DrainHoles &drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
|
||||
drain_holes[m_hover_id].pos = pos_and_normal.first;
|
||||
drain_holes[m_hover_id].normal = -pos_and_normal.second;
|
||||
}
|
||||
|
||||
|
||||
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 && !drain_holes.empty()) {
|
||||
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 (m_c->selection_info()->model_object() == nullptr)
|
||||
return;
|
||||
|
||||
if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id)
|
||||
m_hover_id = -1;
|
||||
}
|
||||
|
||||
void GLGizmoHollow::init_cylinder_model()
|
||||
{
|
||||
if (!m_cylinder.model.is_initialized()) {
|
||||
indexed_triangle_set its = its_make_cylinder(1.0, 1.0);
|
||||
m_cylinder.model.init_from(its);
|
||||
m_cylinder.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
120
src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp
Normal file
120
src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef slic3r_GLGizmoHollow_hpp_
|
||||
#define slic3r_GLGizmoHollow_hpp_
|
||||
|
||||
#include "GLGizmoSlaBase.hpp"
|
||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <libslic3r/ObjectID.hpp>
|
||||
#include <wx/dialog.h>
|
||||
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ConfigOption;
|
||||
class ConfigOptionDef;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
class Selection;
|
||||
class GLGizmoHollow : public GLGizmoSlaBase
|
||||
{
|
||||
public:
|
||||
GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
void data_changed(bool is_serializing) override;
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
void delete_selected_points();
|
||||
bool is_selection_rectangle_dragging() const override {
|
||||
return m_selection_rectangle.is_dragging();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Postpone to Grabber for move
|
||||
/// Detect move of object by dragging
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
protected:
|
||||
bool on_init() override;
|
||||
void on_render() override;
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
|
||||
private:
|
||||
void render_points(const Selection& selection);
|
||||
void register_hole_raycasters_for_picking();
|
||||
void unregister_hole_raycasters_for_picking();
|
||||
void update_hole_raycasters_for_picking_transform();
|
||||
|
||||
ObjectID m_old_mo_id = -1;
|
||||
|
||||
PickingModel m_cylinder;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_hole_raycasters;
|
||||
|
||||
float m_new_hole_radius = 2.f; // Size of a new hole.
|
||||
float m_new_hole_height = 6.f;
|
||||
mutable std::vector<bool> m_selected; // which holes are currently selected
|
||||
|
||||
bool m_enable_hollowing = true;
|
||||
|
||||
// Stashes to keep data for undo redo. Is taken after the editing
|
||||
// is done, the data are updated continuously.
|
||||
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();
|
||||
sla::DrainHoles m_holes_in_drilled_mesh;
|
||||
|
||||
sla::DrainHoles m_holes_stash;
|
||||
|
||||
// This map holds all translated description texts, so they can be easily referenced during layout calculations
|
||||
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||
std::map<std::string, wxString> m_desc;
|
||||
|
||||
GLSelectionRectangle m_selection_rectangle;
|
||||
|
||||
bool m_wait_for_up_event = false;
|
||||
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)
|
||||
|
||||
std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> get_config_options(const std::vector<std::string>& keys) const;
|
||||
bool is_mesh_point_clipped(const Vec3d& point) const;
|
||||
|
||||
// Methods that do the model_object and editing cache synchronization,
|
||||
// editing mode selection, etc:
|
||||
enum {
|
||||
AllPoints = -2,
|
||||
NoPoints,
|
||||
};
|
||||
void select_point(int i);
|
||||
void unselect_point(int i);
|
||||
void reload_cache();
|
||||
|
||||
protected:
|
||||
void on_set_state() override;
|
||||
void on_set_hover_id() override;
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
void on_dragging(const UpdateData &data) override;
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
bool on_is_selectable() const override;
|
||||
void on_load(cereal::BinaryInputArchive& ar) override;
|
||||
void on_save(cereal::BinaryOutputArchive& ar) const override;
|
||||
|
||||
void init_cylinder_model();
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoHollow_hpp_
|
||||
2138
src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp
Normal file
2138
src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp
Normal file
File diff suppressed because it is too large
Load Diff
186
src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp
Normal file
186
src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#ifndef slic3r_GLGizmoMeasure_hpp_
|
||||
#define slic3r_GLGizmoMeasure_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "slic3r/GUI/GUI_Utils.hpp"
|
||||
#include "slic3r/GUI/MeshUtils.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "libslic3r/Measure.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class ModelVolumeType : int;
|
||||
|
||||
namespace Measure { class Measuring; }
|
||||
|
||||
|
||||
namespace GUI {
|
||||
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
|
||||
class GLGizmoMeasure : public GLGizmoBase
|
||||
{
|
||||
enum class EMode : unsigned char
|
||||
{
|
||||
FeatureSelection,
|
||||
PointSelection
|
||||
};
|
||||
|
||||
struct SelectedFeatures
|
||||
{
|
||||
struct Item
|
||||
{
|
||||
bool is_center{ false };
|
||||
std::optional<Measure::SurfaceFeature> source;
|
||||
std::optional<Measure::SurfaceFeature> feature;
|
||||
|
||||
bool operator == (const Item& other) const {
|
||||
return this->is_center == other.is_center && this->source == other.source && this->feature == other.feature;
|
||||
}
|
||||
|
||||
bool operator != (const Item& other) const {
|
||||
return !operator == (other);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
is_center = false;
|
||||
source.reset();
|
||||
feature.reset();
|
||||
}
|
||||
};
|
||||
|
||||
Item first;
|
||||
Item second;
|
||||
|
||||
void reset() {
|
||||
first.reset();
|
||||
second.reset();
|
||||
}
|
||||
|
||||
bool operator == (const SelectedFeatures & other) const {
|
||||
if (this->first != other.first) return false;
|
||||
return this->second == other.second;
|
||||
}
|
||||
|
||||
bool operator != (const SelectedFeatures & other) const {
|
||||
return !operator == (other);
|
||||
}
|
||||
};
|
||||
|
||||
struct VolumeCacheItem
|
||||
{
|
||||
const ModelObject* object{ nullptr };
|
||||
const ModelInstance* instance{ nullptr };
|
||||
const ModelVolume* volume{ nullptr };
|
||||
Transform3d world_trafo;
|
||||
|
||||
bool operator == (const VolumeCacheItem& other) const {
|
||||
return this->object == other.object && this->instance == other.instance && this->volume == other.volume &&
|
||||
this->world_trafo.isApprox(other.world_trafo);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<VolumeCacheItem> m_volumes_cache;
|
||||
|
||||
EMode m_mode{ EMode::FeatureSelection };
|
||||
Measure::MeasurementResult m_measurement_result;
|
||||
|
||||
std::unique_ptr<Measure::Measuring> m_measuring; // PIMPL
|
||||
|
||||
PickingModel m_sphere;
|
||||
PickingModel m_cylinder;
|
||||
PickingModel m_circle;
|
||||
PickingModel m_plane;
|
||||
struct Dimensioning
|
||||
{
|
||||
GLModel line;
|
||||
GLModel triangle;
|
||||
GLModel arc;
|
||||
};
|
||||
Dimensioning m_dimensioning;
|
||||
|
||||
// Uses a standalone raycaster and not the shared one because of the
|
||||
// difference in how the mesh is updated
|
||||
std::unique_ptr<MeshRaycaster> m_raycaster;
|
||||
|
||||
std::vector<GLModel> m_plane_models_cache;
|
||||
std::map<int, std::shared_ptr<SceneRaycasterItem>> m_raycasters;
|
||||
// used to keep the raycasters for point/center spheres
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_selected_sphere_raycasters;
|
||||
std::optional<Measure::SurfaceFeature> m_curr_feature;
|
||||
std::optional<Vec3d> m_curr_point_on_feature_position;
|
||||
struct SceneRaycasterState
|
||||
{
|
||||
std::shared_ptr<SceneRaycasterItem> raycaster{ nullptr };
|
||||
bool state{true};
|
||||
|
||||
};
|
||||
std::vector<SceneRaycasterState> m_scene_raycasters;
|
||||
|
||||
// These hold information to decide whether recalculation is necessary:
|
||||
float m_last_inv_zoom{ 0.0f };
|
||||
std::optional<Measure::SurfaceFeature> m_last_circle;
|
||||
int m_last_plane_idx{ -1 };
|
||||
|
||||
bool m_mouse_left_down{ false }; // for detection left_up of this gizmo
|
||||
|
||||
Vec2d m_mouse_pos{ Vec2d::Zero() };
|
||||
|
||||
KeyAutoRepeatFilter m_shift_kar_filter;
|
||||
|
||||
SelectedFeatures m_selected_features;
|
||||
bool m_pending_scale{ false };
|
||||
bool m_editing_distance{ false };
|
||||
bool m_is_editing_distance_first_frame{ true };
|
||||
|
||||
void update_if_needed();
|
||||
|
||||
void disable_scene_raycasters();
|
||||
void restore_scene_raycasters_state();
|
||||
|
||||
void render_dimensioning();
|
||||
|
||||
#if ENABLE_MEASURE_GIZMO_DEBUG
|
||||
void render_debug_dialog();
|
||||
#endif // ENABLE_MEASURE_GIZMO_DEBUG
|
||||
|
||||
public:
|
||||
GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
||||
/// <summary>
|
||||
/// Apply rotation on select plane
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
void data_changed(bool is_serializing) override;
|
||||
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
||||
bool wants_enter_leave_snapshots() const override { return true; }
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Measure gizmo"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Measure gizmo"); }
|
||||
std::string get_action_snapshot_name() const override { return _u8L("Measure gizmo editing"); }
|
||||
|
||||
protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
void on_render() override;
|
||||
void on_set_state() override;
|
||||
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
|
||||
void remove_selected_sphere_raycaster(int id);
|
||||
void update_measurement_result();
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoMeasure_hpp_
|
||||
763
src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp
Normal file
763
src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp
Normal file
@@ -0,0 +1,763 @@
|
||||
#include "GLGizmoMmuSegmentation.hpp"
|
||||
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/BitmapCache.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
#include "slic3r/GUI/OpenGLManager.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
static inline void show_notification_extruders_limit_exceeded()
|
||||
{
|
||||
wxGetApp()
|
||||
.plater()
|
||||
->get_notification_manager()
|
||||
->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::PrintInfoNotificationLevel,
|
||||
GUI::format(_L("Your printer has more extruders than the multi-material painting gizmo supports. For this reason, only the "
|
||||
"first %1% extruders will be able to be used for painting."), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT));
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::on_opening()
|
||||
{
|
||||
if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
|
||||
show_notification_extruders_limit_exceeded();
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::on_shutdown()
|
||||
{
|
||||
m_parent.use_slope(false);
|
||||
m_parent.toggle_model_objects_visibility(true);
|
||||
}
|
||||
|
||||
std::string GLGizmoMmuSegmentation::on_get_name() const
|
||||
{
|
||||
return _u8L("Multimaterial painting");
|
||||
}
|
||||
|
||||
bool GLGizmoMmuSegmentation::on_is_selectable() const
|
||||
{
|
||||
return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF
|
||||
&& wxGetApp().get_mode() != comSimple && wxGetApp().extruders_edited_cnt() > 1);
|
||||
}
|
||||
|
||||
bool GLGizmoMmuSegmentation::on_is_activable() const
|
||||
{
|
||||
return GLGizmoPainterBase::on_is_activable() && wxGetApp().extruders_edited_cnt() > 1;
|
||||
}
|
||||
|
||||
static std::vector<ColorRGBA> get_extruders_colors()
|
||||
{
|
||||
std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
std::vector<ColorRGBA> ret;
|
||||
decode_colors(colors, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static std::vector<std::string> get_extruders_names()
|
||||
{
|
||||
size_t extruders_count = wxGetApp().extruders_edited_cnt();
|
||||
std::vector<std::string> extruders_out;
|
||||
extruders_out.reserve(extruders_count);
|
||||
for (size_t extruder_idx = 1; extruder_idx <= extruders_count; ++extruder_idx)
|
||||
extruders_out.emplace_back(_u8L("Extruder") + " " + std::to_string(extruder_idx));
|
||||
|
||||
return extruders_out;
|
||||
}
|
||||
|
||||
static std::vector<int> get_extruder_id_for_volumes(const ModelObject &model_object)
|
||||
{
|
||||
std::vector<int> extruders_idx;
|
||||
extruders_idx.reserve(model_object.volumes.size());
|
||||
for (const ModelVolume *model_volume : model_object.volumes) {
|
||||
if (!model_volume->is_model_part())
|
||||
continue;
|
||||
|
||||
extruders_idx.emplace_back(model_volume->extruder_id());
|
||||
}
|
||||
|
||||
return extruders_idx;
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::init_extruders_data()
|
||||
{
|
||||
m_original_extruders_names = get_extruders_names();
|
||||
m_original_extruders_colors = get_extruders_colors();
|
||||
m_modified_extruders_colors = m_original_extruders_colors;
|
||||
m_first_selected_extruder_idx = 0;
|
||||
m_second_selected_extruder_idx = 1;
|
||||
}
|
||||
|
||||
bool GLGizmoMmuSegmentation::on_init()
|
||||
{
|
||||
m_shortcut_key = WXK_CONTROL_N;
|
||||
|
||||
m_desc["reset_direction"] = _L("Reset direction");
|
||||
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
||||
m_desc["cursor_size"] = _L("Brush size") + ": ";
|
||||
m_desc["cursor_type"] = _L("Brush shape");
|
||||
m_desc["first_color_caption"] = _L("Left mouse button") + ": ";
|
||||
m_desc["first_color"] = _L("First color");
|
||||
m_desc["second_color_caption"] = _L("Right mouse button") + ": ";
|
||||
m_desc["second_color"] = _L("Second color");
|
||||
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
|
||||
m_desc["remove"] = _L("Remove painted color");
|
||||
m_desc["remove_all"] = _L("Clear all");
|
||||
m_desc["circle"] = _L("Circle");
|
||||
m_desc["sphere"] = _L("Sphere");
|
||||
m_desc["pointer"] = _L("Triangles");
|
||||
|
||||
m_desc["tool_type"] = _L("Tool type");
|
||||
m_desc["tool_brush"] = _L("Brush");
|
||||
m_desc["tool_smart_fill"] = _L("Smart fill");
|
||||
m_desc["tool_bucket_fill"] = _L("Bucket fill");
|
||||
|
||||
m_desc["smart_fill_angle"] = _L("Smart fill angle");
|
||||
m_desc["split_triangles"] = _L("Split triangles");
|
||||
|
||||
init_extruders_data();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::render_painter_gizmo()
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
render_triangles(selection);
|
||||
|
||||
m_c->object_clipper()->render_cut();
|
||||
m_c->instances_hider()->render_cut();
|
||||
render_cursor();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::data_changed(bool is_serializing)
|
||||
{
|
||||
GLGizmoPainterBase::data_changed(is_serializing);
|
||||
if (m_state != On || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF || wxGetApp().extruders_edited_cnt() <= 1)
|
||||
return;
|
||||
|
||||
ModelObject *model_object = m_c->selection_info()->model_object();
|
||||
if (int prev_extruders_count = int(m_original_extruders_colors.size());
|
||||
prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) {
|
||||
if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
|
||||
show_notification_extruders_limit_exceeded();
|
||||
|
||||
this->init_extruders_data();
|
||||
// Reinitialize triangle selectors because of change of extruder count need also change the size of GLIndexedVertexArray
|
||||
if (prev_extruders_count != wxGetApp().extruders_edited_cnt())
|
||||
this->init_model_triangle_selectors();
|
||||
} else if (model_object != nullptr && get_extruder_id_for_volumes(*model_object) != m_original_volumes_extruder_idxs) {
|
||||
this->init_model_triangle_selectors();
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const
|
||||
{
|
||||
ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
|
||||
auto *shader = wxGetApp().get_shader("mm_gouraud");
|
||||
if (!shader)
|
||||
return;
|
||||
shader->start_using();
|
||||
shader->set_uniform("clipping_plane", clp_data.clp_dataf);
|
||||
shader->set_uniform("z_range", clp_data.z_range);
|
||||
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
|
||||
|
||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
||||
int mesh_id = -1;
|
||||
for (const ModelVolume *mv : mo->volumes) {
|
||||
if (!mv->is_model_part())
|
||||
continue;
|
||||
|
||||
++mesh_id;
|
||||
|
||||
const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix();
|
||||
|
||||
const bool is_left_handed = trafo_matrix.matrix().determinant() < 0.0;
|
||||
if (is_left_handed)
|
||||
glsafe(::glFrontFace(GL_CW));
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Transform3d& view_matrix = camera.get_view_matrix();
|
||||
shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
shader->set_uniform("view_normal_matrix", view_normal_matrix);
|
||||
|
||||
shader->set_uniform("volume_world_matrix", trafo_matrix);
|
||||
shader->set_uniform("volume_mirrored", is_left_handed);
|
||||
m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix);
|
||||
|
||||
if (is_left_handed)
|
||||
glsafe(::glFrontFace(GL_CCW));
|
||||
}
|
||||
}
|
||||
|
||||
static void render_extruders_combo(const std::string& label,
|
||||
const std::vector<std::string>& extruders,
|
||||
const std::vector<ColorRGBA>& extruders_colors,
|
||||
size_t& selection_idx)
|
||||
{
|
||||
assert(!extruders_colors.empty());
|
||||
assert(extruders_colors.size() == extruders_colors.size());
|
||||
|
||||
size_t selection_out = selection_idx;
|
||||
// It is necessary to use BeginGroup(). Otherwise, when using SameLine() is called, then other items will be drawn inside the combobox.
|
||||
ImGui::BeginGroup();
|
||||
ImVec2 combo_pos = ImGui::GetCursorScreenPos();
|
||||
if (ImGui::BeginCombo(label.c_str(), "")) {
|
||||
for (size_t extruder_idx = 0; extruder_idx < std::min(extruders.size(), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT); ++extruder_idx) {
|
||||
ImGui::PushID(int(extruder_idx));
|
||||
ImVec2 start_position = ImGui::GetCursorScreenPos();
|
||||
|
||||
if (ImGui::Selectable("", extruder_idx == selection_idx))
|
||||
selection_out = extruder_idx;
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
float height = ImGui::GetTextLineHeight();
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), ImGuiWrapper::to_ImU32(extruders_colors[extruder_idx]));
|
||||
ImGui::GetWindowDrawList()->AddRect(start_position, ImVec2(start_position.x + height + height / 2, start_position.y + height), IM_COL32_BLACK);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(start_position.x + height + height / 2 + style.FramePadding.x, start_position.y));
|
||||
ImGui::Text("%s", extruders[extruder_idx].c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImVec2 backup_pos = ImGui::GetCursorScreenPos();
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(combo_pos.x + style.FramePadding.x, combo_pos.y + style.FramePadding.y));
|
||||
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||
float height = ImGui::GetTextLineHeight();
|
||||
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + height + height / 2, p.y + height), ImGuiWrapper::to_ImU32(extruders_colors[selection_idx]));
|
||||
ImGui::GetWindowDrawList()->AddRect(p, ImVec2(p.x + height + height / 2, p.y + height), IM_COL32_BLACK);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(p.x + height + height / 2 + style.FramePadding.x, p.y));
|
||||
ImGui::Text("%s", extruders[selection_out].c_str());
|
||||
ImGui::SetCursorScreenPos(backup_pos);
|
||||
ImGui::EndGroup();
|
||||
|
||||
selection_idx = selection_out;
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
if (!m_c->selection_info()->model_object())
|
||||
return;
|
||||
|
||||
const float approx_height = m_imgui->scaled(22.0f);
|
||||
y = std::min(y, bottom_limit - approx_height);
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
|
||||
m_imgui->begin(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 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 cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
||||
const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f);
|
||||
|
||||
const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
|
||||
const float buttons_width = m_imgui->scaled(0.5f);
|
||||
const float minimal_slider_width = m_imgui->scaled(4.f);
|
||||
const float color_button_width = m_imgui->scaled(1.75f);
|
||||
const float combo_label_width = std::max(m_imgui->calc_text_size(m_desc.at("first_color")).x,
|
||||
m_imgui->calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f);
|
||||
|
||||
const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
float caption_max = 0.f;
|
||||
float total_text_max = 0.f;
|
||||
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"}) {
|
||||
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
|
||||
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
|
||||
}
|
||||
total_text_max += caption_max + m_imgui->scaled(1.f);
|
||||
caption_max += m_imgui->scaled(1.f);
|
||||
|
||||
const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
|
||||
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
|
||||
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
|
||||
window_width = std::max(window_width, total_text_max);
|
||||
window_width = std::max(window_width, button_width);
|
||||
window_width = std::max(window_width, split_triangles_checkbox_width);
|
||||
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
|
||||
window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill);
|
||||
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
|
||||
|
||||
auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) {
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, caption);
|
||||
ImGui::SameLine(caption_max);
|
||||
m_imgui->text(text);
|
||||
};
|
||||
|
||||
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"})
|
||||
draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("first_color"));
|
||||
ImGui::SameLine(combo_label_width);
|
||||
ImGui::PushItemWidth(window_width - combo_label_width - color_button_width);
|
||||
render_extruders_combo("##first_color_combo", m_original_extruders_names, m_original_extruders_colors, m_first_selected_extruder_idx);
|
||||
ImGui::SameLine();
|
||||
|
||||
const ColorRGBA& select_first_color = m_modified_extruders_colors[m_first_selected_extruder_idx];
|
||||
ImVec4 first_color = ImGuiWrapper::to_ImVec4(select_first_color);
|
||||
const std::string first_label = into_u8(m_desc.at("first_color")) + "##color_picker";
|
||||
if (ImGui::ColorEdit4(first_label.c_str(), (float*)&first_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel,
|
||||
// TRN Means "current color"
|
||||
_u8L("Current").c_str(),
|
||||
// TRN Means "original color"
|
||||
_u8L("Original").c_str()))
|
||||
m_modified_extruders_colors[m_first_selected_extruder_idx] = ImGuiWrapper::from_ImVec4(first_color);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("second_color"));
|
||||
ImGui::SameLine(combo_label_width);
|
||||
ImGui::PushItemWidth(window_width - combo_label_width - color_button_width);
|
||||
render_extruders_combo("##second_color_combo", m_original_extruders_names, m_original_extruders_colors, m_second_selected_extruder_idx);
|
||||
ImGui::SameLine();
|
||||
|
||||
const ColorRGBA& select_second_color = m_modified_extruders_colors[m_second_selected_extruder_idx];
|
||||
ImVec4 second_color = ImGuiWrapper::to_ImVec4(select_second_color);
|
||||
const std::string second_label = into_u8(m_desc.at("second_color")) + "##color_picker";
|
||||
if (ImGui::ColorEdit4(second_label.c_str(), (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel,
|
||||
_u8L("Current").c_str(), _u8L("Original").c_str()))
|
||||
m_modified_extruders_colors[m_second_selected_extruder_idx] = ImGuiWrapper::from_ImVec4(second_color);
|
||||
|
||||
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
m_imgui->text(m_desc.at("tool_type"));
|
||||
ImGui::NewLine();
|
||||
|
||||
float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_smart_fill + m_imgui->scaled(1.5f)) / 2.f;
|
||||
ImGui::SameLine(tool_type_offset);
|
||||
ImGui::PushItemWidth(tool_type_radio_brush);
|
||||
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) {
|
||||
m_tool_type = ToolType::BRUSH;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
|
||||
ImGui::PushItemWidth(tool_type_radio_smart_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) {
|
||||
m_tool_type = ToolType::SMART_FILL;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill);
|
||||
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == ToolType::BUCKET_FILL)) {
|
||||
m_tool_type = ToolType::BUCKET_FILL;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints neighboring facets that have the same color."), max_tooltip_width);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if(m_tool_type == ToolType::BRUSH) {
|
||||
m_imgui->text(m_desc.at("cursor_type"));
|
||||
ImGui::NewLine();
|
||||
|
||||
float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f;
|
||||
ImGui::SameLine(cursor_type_offset);
|
||||
ImGui::PushItemWidth(cursor_type_radio_sphere);
|
||||
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
|
||||
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
|
||||
ImGui::PushItemWidth(cursor_type_radio_circle);
|
||||
|
||||
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
|
||||
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle);
|
||||
ImGui::PushItemWidth(cursor_type_radio_pointer);
|
||||
|
||||
if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
|
||||
m_cursor_type = TriangleSelector::CursorType::POINTER;
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width);
|
||||
|
||||
m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_size"));
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
|
||||
|
||||
m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width);
|
||||
|
||||
m_imgui->disabled_end();
|
||||
|
||||
ImGui::Separator();
|
||||
} else if(m_tool_type == ToolType::SMART_FILL) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc["smart_fill_angle"] + ":");
|
||||
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
|
||||
"placed after the number with no whitespace in between.");
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
if (m_c->object_clipper()->get_position() == 0.f) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
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_by_ratio(-1., false); });
|
||||
}
|
||||
}
|
||||
|
||||
auto clp_dist = float(m_c->object_clipper()->get_position());
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
|
||||
m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"),
|
||||
UndoRedo::SnapshotType::GizmoAction);
|
||||
ModelObject * mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume *mv : mo->volumes)
|
||||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
m_triangle_selectors[idx]->request_update_render_data();
|
||||
}
|
||||
|
||||
update_model_object();
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
m_imgui->end();
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::update_model_object() const
|
||||
{
|
||||
bool updated = false;
|
||||
ModelObject* mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
++idx;
|
||||
updated |= mv->mmu_segmentation_facets.set(*m_triangle_selectors[idx].get());
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
const ModelObjectPtrs &mos = wxGetApp().model().objects;
|
||||
wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::init_model_triangle_selectors()
|
||||
{
|
||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
||||
m_triangle_selectors.clear();
|
||||
|
||||
// Don't continue when extruders colors are not initialized
|
||||
if(m_original_extruders_colors.empty())
|
||||
return;
|
||||
|
||||
for (const ModelVolume *mv : mo->volumes) {
|
||||
if (!mv->is_model_part())
|
||||
continue;
|
||||
|
||||
// This mesh does not account for the possible Z up SLA offset.
|
||||
const TriangleMesh *mesh = &mv->mesh();
|
||||
|
||||
int extruder_idx = (mv->extruder_id() > 0) ? mv->extruder_id() - 1 : 0;
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorMmGui>(*mesh, m_modified_extruders_colors, m_original_extruders_colors[size_t(extruder_idx)]));
|
||||
// Reset of TriangleSelector is done inside TriangleSelectorMmGUI's constructor, so we don't need it to perform it again in deserialize().
|
||||
m_triangle_selectors.back()->deserialize(mv->mmu_segmentation_facets.get_data(), false);
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
m_original_volumes_extruder_idxs = get_extruder_id_for_volumes(*mo);
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::update_from_model_object()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
|
||||
// Extruder colors need to be reloaded before calling init_model_triangle_selectors to render painted triangles
|
||||
// using colors from loaded 3MF and not from printer profile in Slicer.
|
||||
if (int prev_extruders_count = int(m_original_extruders_colors.size());
|
||||
prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors)
|
||||
this->init_extruders_data();
|
||||
|
||||
this->init_model_triangle_selectors();
|
||||
}
|
||||
|
||||
PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const
|
||||
{
|
||||
return PainterGizmoType::MMU_SEGMENTATION;
|
||||
}
|
||||
|
||||
ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const
|
||||
{
|
||||
ColorRGBA color = m_modified_extruders_colors[m_first_selected_extruder_idx];
|
||||
color.a(0.25f);
|
||||
return color;
|
||||
}
|
||||
|
||||
ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_right_button_color() const
|
||||
{
|
||||
ColorRGBA color = m_modified_extruders_colors[m_second_selected_extruder_idx];
|
||||
color.a(0.25f);
|
||||
return color;
|
||||
}
|
||||
|
||||
void TriangleSelectorMmGui::render(ImGuiWrapper* imgui, const Transform3d& matrix)
|
||||
{
|
||||
if (m_update_render_data)
|
||||
update_render_data();
|
||||
|
||||
auto *shader = wxGetApp().get_current_shader();
|
||||
if (!shader)
|
||||
return;
|
||||
assert(shader->get_name() == "mm_gouraud");
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Transform3d& view_matrix = camera.get_view_matrix();
|
||||
shader->set_uniform("view_model_matrix", view_matrix * matrix);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
shader->set_uniform("view_normal_matrix", view_normal_matrix);
|
||||
|
||||
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx) {
|
||||
if (m_gizmo_scene.has_VBOs(color_idx)) {
|
||||
if (color_idx > m_colors.size()) // Seed fill VBO
|
||||
shader->set_uniform("uniform_color", TriangleSelectorGUI::get_seed_fill_color(color_idx == (m_colors.size() + 1) ? m_default_volume_color : m_colors[color_idx - (m_colors.size() + 1) - 1]));
|
||||
else // Normal VBO
|
||||
shader->set_uniform("uniform_color", color_idx == 0 ? m_default_volume_color : m_colors[color_idx - 1]);
|
||||
|
||||
m_gizmo_scene.render(color_idx);
|
||||
}
|
||||
}
|
||||
|
||||
render_paint_contour(matrix);
|
||||
m_update_render_data = false;
|
||||
}
|
||||
|
||||
void TriangleSelectorMmGui::update_render_data()
|
||||
{
|
||||
m_gizmo_scene.release_geometry();
|
||||
m_vertices.reserve(m_vertices.size() * 3);
|
||||
for (const Vertex &vr : m_vertices) {
|
||||
m_gizmo_scene.vertices.emplace_back(vr.v.x());
|
||||
m_gizmo_scene.vertices.emplace_back(vr.v.y());
|
||||
m_gizmo_scene.vertices.emplace_back(vr.v.z());
|
||||
}
|
||||
m_gizmo_scene.finalize_vertices();
|
||||
|
||||
for (const Triangle &tr : m_triangles)
|
||||
if (tr.valid() && !tr.is_split()) {
|
||||
int color = int(tr.get_state()) <= int(m_colors.size()) ? int(tr.get_state()) : 0;
|
||||
assert(m_colors.size() + 1 + color < m_gizmo_scene.triangle_indices.size());
|
||||
std::vector<int> &iva = m_gizmo_scene.triangle_indices[color + tr.is_selected_by_seed_fill() * (m_colors.size() + 1)];
|
||||
|
||||
if (iva.size() + 3 > iva.capacity())
|
||||
iva.reserve(next_highest_power_of_2(iva.size() + 3));
|
||||
|
||||
iva.emplace_back(tr.verts_idxs[0]);
|
||||
iva.emplace_back(tr.verts_idxs[1]);
|
||||
iva.emplace_back(tr.verts_idxs[2]);
|
||||
}
|
||||
|
||||
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx)
|
||||
m_gizmo_scene.triangle_indices_sizes[color_idx] = m_gizmo_scene.triangle_indices[color_idx].size();
|
||||
|
||||
m_gizmo_scene.finalize_triangle_indices();
|
||||
update_paint_contour();
|
||||
}
|
||||
|
||||
wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
|
||||
{
|
||||
wxString action_name;
|
||||
if (shift_down)
|
||||
action_name = _L("Remove painted color");
|
||||
else {
|
||||
size_t extruder_id = (button_down == Button::Left ? m_first_selected_extruder_idx : m_second_selected_extruder_idx) + 1;
|
||||
action_name = GUI::format(_L("Painted using: Extruder %1%"), extruder_id);
|
||||
}
|
||||
return action_name;
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::release_geometry() {
|
||||
if (this->vertices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->vertices_VBO_id));
|
||||
this->vertices_VBO_id = 0;
|
||||
}
|
||||
for(auto &triangle_indices_VBO_id : triangle_indices_VBO_ids) {
|
||||
glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id));
|
||||
triangle_indices_VBO_id = 0;
|
||||
}
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (this->vertices_VAO_id > 0) {
|
||||
glsafe(::glDeleteVertexArrays(1, &this->vertices_VAO_id));
|
||||
this->vertices_VAO_id = 0;
|
||||
}
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const
|
||||
{
|
||||
assert(triangle_indices_idx < this->triangle_indices_VBO_ids.size());
|
||||
assert(this->triangle_indices_sizes.size() == this->triangle_indices_VBO_ids.size());
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
|
||||
assert(this->vertices_VAO_id != 0);
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
assert(this->vertices_VBO_id != 0);
|
||||
assert(this->triangle_indices_VBO_ids[triangle_indices_idx] != 0);
|
||||
|
||||
GLShaderProgram* shader = wxGetApp().get_current_shader();
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
|
||||
glsafe(::glBindVertexArray(this->vertices_VAO_id));
|
||||
// the following binding is needed to set the vertex attributes
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
|
||||
const GLint position_id = shader->get_attrib_location("v_position");
|
||||
if (position_id != -1) {
|
||||
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)0));
|
||||
glsafe(::glEnableVertexAttribArray(position_id));
|
||||
}
|
||||
|
||||
// Render using the Vertex Buffer Objects.
|
||||
if (this->triangle_indices_VBO_ids[triangle_indices_idx] != 0 &&
|
||||
this->triangle_indices_sizes[triangle_indices_idx] > 0) {
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[triangle_indices_idx]));
|
||||
glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_sizes[triangle_indices_idx]), GL_UNSIGNED_INT, nullptr));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
if (position_id != -1)
|
||||
glsafe(::glDisableVertexAttribArray(position_id));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
|
||||
glsafe(::glBindVertexArray(0));
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_vertices()
|
||||
{
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
assert(this->vertices_VAO_id == 0);
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
assert(this->vertices_VBO_id == 0);
|
||||
if (!this->vertices.empty()) {
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) {
|
||||
glsafe(::glGenVertexArrays(1, &this->vertices_VAO_id));
|
||||
glsafe(::glBindVertexArray(this->vertices_VAO_id));
|
||||
}
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
|
||||
glsafe(::glGenBuffers(1, &this->vertices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(float), this->vertices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->vertices.clear();
|
||||
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
|
||||
glsafe(::glBindVertexArray(0));
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
}
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_triangle_indices()
|
||||
{
|
||||
assert(std::all_of(triangle_indices_VBO_ids.cbegin(), triangle_indices_VBO_ids.cend(), [](const auto &ti_VBO_id) { return ti_VBO_id == 0; }));
|
||||
|
||||
assert(this->triangle_indices.size() == this->triangle_indices_VBO_ids.size());
|
||||
for (size_t buffer_idx = 0; buffer_idx < this->triangle_indices.size(); ++buffer_idx)
|
||||
if (!this->triangle_indices[buffer_idx].empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_ids[buffer_idx]));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[buffer_idx]));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices[buffer_idx].size() * sizeof(int), this->triangle_indices[buffer_idx].data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
this->triangle_indices[buffer_idx].clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
154
src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp
Normal file
154
src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp
Normal file
@@ -0,0 +1,154 @@
|
||||
#ifndef slic3r_GLGizmoMmuSegmentation_hpp_
|
||||
#define slic3r_GLGizmoMmuSegmentation_hpp_
|
||||
|
||||
#include "GLGizmoPainterBase.hpp"
|
||||
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
class GLMmSegmentationGizmo3DScene
|
||||
{
|
||||
public:
|
||||
GLMmSegmentationGizmo3DScene() = delete;
|
||||
|
||||
explicit GLMmSegmentationGizmo3DScene(size_t triangle_indices_buffers_count)
|
||||
{
|
||||
this->triangle_indices = std::vector<std::vector<int>>(triangle_indices_buffers_count);
|
||||
this->triangle_indices_sizes = std::vector<size_t>(triangle_indices_buffers_count);
|
||||
this->triangle_indices_VBO_ids = std::vector<unsigned int>(triangle_indices_buffers_count);
|
||||
}
|
||||
|
||||
virtual ~GLMmSegmentationGizmo3DScene() { release_geometry(); }
|
||||
|
||||
[[nodiscard]] inline bool has_VBOs(size_t triangle_indices_idx) const
|
||||
{
|
||||
assert(triangle_indices_idx < this->triangle_indices.size());
|
||||
return this->triangle_indices_VBO_ids[triangle_indices_idx] != 0;
|
||||
}
|
||||
|
||||
// Release the geometry data, release OpenGL VBOs.
|
||||
void release_geometry();
|
||||
// Finalize the initialization of the geometry, upload the geometry to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_vertices();
|
||||
// Finalize the initialization of the indices, upload the indices to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_triangle_indices();
|
||||
|
||||
void clear()
|
||||
{
|
||||
this->vertices.clear();
|
||||
for (std::vector<int> &ti : this->triangle_indices)
|
||||
ti.clear();
|
||||
|
||||
for (size_t &triangle_indices_size : this->triangle_indices_sizes)
|
||||
triangle_indices_size = 0;
|
||||
}
|
||||
|
||||
void render(size_t triangle_indices_idx) const;
|
||||
|
||||
std::vector<float> vertices;
|
||||
std::vector<std::vector<int>> triangle_indices;
|
||||
|
||||
// When the triangle indices are loaded into the graphics card as Vertex Buffer Objects,
|
||||
// the above mentioned std::vectors are cleared and the following variables keep their original length.
|
||||
std::vector<size_t> triangle_indices_sizes;
|
||||
|
||||
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
|
||||
// Zero if the VBOs are not sent to GPU yet.
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
unsigned int vertices_VAO_id{ 0 };
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
unsigned int vertices_VBO_id{ 0 };
|
||||
std::vector<unsigned int> triangle_indices_VBO_ids;
|
||||
};
|
||||
|
||||
class TriangleSelectorMmGui : public TriangleSelectorGUI {
|
||||
public:
|
||||
// Plus 1 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the indices above colors.size() are allocated for seed fill.
|
||||
TriangleSelectorMmGui(const TriangleMesh& mesh, const std::vector<ColorRGBA>& colors, const ColorRGBA& default_volume_color)
|
||||
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(2 * (colors.size() + 1)) {}
|
||||
~TriangleSelectorMmGui() override = default;
|
||||
|
||||
void render(ImGuiWrapper* imgui, const Transform3d& matrix) override;
|
||||
|
||||
private:
|
||||
void update_render_data();
|
||||
|
||||
const std::vector<ColorRGBA>& m_colors;
|
||||
const ColorRGBA m_default_volume_color;
|
||||
GLMmSegmentationGizmo3DScene m_gizmo_scene;
|
||||
};
|
||||
|
||||
class GLGizmoMmuSegmentation : public GLGizmoPainterBase
|
||||
{
|
||||
public:
|
||||
GLGizmoMmuSegmentation(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoPainterBase(parent, icon_filename, sprite_id) {}
|
||||
~GLGizmoMmuSegmentation() override = default;
|
||||
|
||||
void render_painter_gizmo() override;
|
||||
|
||||
void data_changed(bool is_serializing) override;
|
||||
|
||||
void render_triangles(const Selection& selection) const override;
|
||||
|
||||
// TriangleSelector::serialization/deserialization has a limit to store 19 different states.
|
||||
// EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored.
|
||||
// When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization
|
||||
// will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
|
||||
static const constexpr size_t EXTRUDERS_LIMIT = 16;
|
||||
|
||||
const float get_cursor_radius_min() const override { return CursorRadiusMin; }
|
||||
|
||||
protected:
|
||||
ColorRGBA get_cursor_sphere_left_button_color() const override;
|
||||
ColorRGBA get_cursor_sphere_right_button_color() const override;
|
||||
|
||||
EnforcerBlockerType get_left_button_state_type() const override { return EnforcerBlockerType(m_first_selected_extruder_idx + 1); }
|
||||
EnforcerBlockerType get_right_button_state_type() const override { return EnforcerBlockerType(m_second_selected_extruder_idx + 1); }
|
||||
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
std::string on_get_name() const override;
|
||||
|
||||
bool on_is_selectable() const override;
|
||||
bool on_is_activable() const override;
|
||||
|
||||
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
||||
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Multimaterial painting"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Multimaterial painting"); }
|
||||
std::string get_action_snapshot_name() const override { return _u8L("Multimaterial painting editing"); }
|
||||
|
||||
size_t m_first_selected_extruder_idx = 0;
|
||||
size_t m_second_selected_extruder_idx = 1;
|
||||
std::vector<std::string> m_original_extruders_names;
|
||||
std::vector<ColorRGBA> m_original_extruders_colors;
|
||||
std::vector<ColorRGBA> m_modified_extruders_colors;
|
||||
std::vector<int> m_original_volumes_extruder_idxs;
|
||||
|
||||
static const constexpr float CursorRadiusMin = 0.1f; // cannot be zero
|
||||
|
||||
private:
|
||||
bool on_init() override;
|
||||
|
||||
void update_model_object() const override;
|
||||
void update_from_model_object() override;
|
||||
|
||||
void on_opening() override;
|
||||
void on_shutdown() override;
|
||||
PainterGizmoType get_painter_type() const override;
|
||||
|
||||
void init_model_triangle_selectors();
|
||||
void init_extruders_data();
|
||||
|
||||
// This map holds all translated description texts, so they can be easily referenced during layout calculations
|
||||
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||
std::map<std::string, wxString> m_desc;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
#endif // slic3r_GLGizmoMmuSegmentation_hpp_
|
||||
283
src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
Normal file
283
src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
#include "GLGizmoMove.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <wx/utils.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
const double GLGizmoMove3D::Offset = 10.0;
|
||||
|
||||
GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||
{}
|
||||
|
||||
std::string GLGizmoMove3D::get_tooltip() const
|
||||
{
|
||||
if (m_hover_id == 0)
|
||||
return "X: " + format(m_displacement.x(), 2);
|
||||
else if (m_hover_id == 1)
|
||||
return "Y: " + format(m_displacement.y(), 2);
|
||||
else if (m_hover_id == 2)
|
||||
return "Z: " + format(m_displacement.z(), 2);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) {
|
||||
return use_grabbers(mouse_event);
|
||||
}
|
||||
|
||||
void GLGizmoMove3D::data_changed(bool is_serializing) {
|
||||
m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower();
|
||||
}
|
||||
|
||||
bool GLGizmoMove3D::on_init()
|
||||
{
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
m_grabbers.push_back(Grabber());
|
||||
m_grabbers.back().extensions = GLGizmoBase::EGrabberExtension::PosZ;
|
||||
}
|
||||
|
||||
m_grabbers[0].angles = { 0.0, 0.5 * double(PI), 0.0 };
|
||||
m_grabbers[1].angles = { -0.5 * double(PI), 0.0, 0.0 };
|
||||
|
||||
m_shortcut_key = WXK_CONTROL_M;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GLGizmoMove3D::on_get_name() const
|
||||
{
|
||||
return _u8L("Move");
|
||||
}
|
||||
|
||||
bool GLGizmoMove3D::on_is_activable() const
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
return !selection.is_any_cut_volume() && !selection.is_any_connector() && !selection.is_empty();
|
||||
}
|
||||
|
||||
void GLGizmoMove3D::on_start_dragging()
|
||||
{
|
||||
assert(m_hover_id != -1);
|
||||
m_displacement = Vec3d::Zero();
|
||||
m_starting_drag_position = m_grabbers[m_hover_id].matrix * m_grabbers[m_hover_id].center;
|
||||
m_starting_box_center = m_center;
|
||||
m_starting_box_bottom_center = Vec3d(m_center.x(), m_center.y(), m_bounding_box.min.z());
|
||||
}
|
||||
|
||||
void GLGizmoMove3D::on_stop_dragging()
|
||||
{
|
||||
m_parent.do_move(L("Gizmo-Move"));
|
||||
m_displacement = Vec3d::Zero();
|
||||
}
|
||||
|
||||
void GLGizmoMove3D::on_dragging(const UpdateData& data)
|
||||
{
|
||||
if (m_hover_id == 0)
|
||||
m_displacement.x() = calc_projection(data);
|
||||
else if (m_hover_id == 1)
|
||||
m_displacement.y() = calc_projection(data);
|
||||
else if (m_hover_id == 2)
|
||||
m_displacement.z() = calc_projection(data);
|
||||
|
||||
Selection &selection = m_parent.get_selection();
|
||||
TransformationType trafo_type;
|
||||
trafo_type.set_relative();
|
||||
switch (wxGetApp().obj_manipul()->get_coordinates_type())
|
||||
{
|
||||
case ECoordinatesType::Instance: { trafo_type.set_instance(); break; }
|
||||
case ECoordinatesType::Local: { trafo_type.set_local(); break; }
|
||||
default: { break; }
|
||||
}
|
||||
selection.translate(m_displacement, trafo_type);
|
||||
}
|
||||
|
||||
void GLGizmoMove3D::on_render()
|
||||
{
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const auto& [box, box_trafo] = selection.get_bounding_box_in_current_reference_system();
|
||||
m_bounding_box = box;
|
||||
m_center = box_trafo.translation();
|
||||
const Transform3d base_matrix = box_trafo;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
m_grabbers[i].matrix = base_matrix;
|
||||
}
|
||||
|
||||
const Vec3d zero = Vec3d::Zero();
|
||||
const Vec3d half_box_size = 0.5 * m_bounding_box.size();
|
||||
|
||||
// x axis
|
||||
m_grabbers[0].center = { half_box_size.x() + Offset, 0.0, 0.0 };
|
||||
m_grabbers[0].color = AXES_COLOR[0];
|
||||
|
||||
// y axis
|
||||
m_grabbers[1].center = { 0.0, half_box_size.y() + Offset, 0.0 };
|
||||
m_grabbers[1].color = AXES_COLOR[1];
|
||||
|
||||
// z axis
|
||||
m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset };
|
||||
m_grabbers[2].color = AXES_COLOR[2];
|
||||
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (!OpenGLManager::get_gl_info().is_core_profile())
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
|
||||
|
||||
auto render_grabber_connection = [this, &zero](unsigned int id) {
|
||||
if (m_grabbers[id].enabled) {
|
||||
if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(m_grabbers[id].center)) {
|
||||
m_grabber_connections[id].old_center = m_grabbers[id].center;
|
||||
m_grabber_connections[id].model.reset();
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
|
||||
init_data.color = AXES_COLOR[id];
|
||||
init_data.vertices.reserve(2);
|
||||
init_data.indices.reserve(2);
|
||||
|
||||
// vertices
|
||||
init_data.add_vertex((Vec3f)zero.cast<float>());
|
||||
init_data.add_vertex((Vec3f)m_grabbers[id].center.cast<float>());
|
||||
|
||||
// indices
|
||||
init_data.add_line(0, 1);
|
||||
|
||||
m_grabber_connections[id].model.init_from(std::move(init_data));
|
||||
}
|
||||
|
||||
m_grabber_connections[id].model.render();
|
||||
}
|
||||
};
|
||||
|
||||
if (m_hover_id == -1) {
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
|
||||
#else
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
|
||||
shader->set_uniform("width", 0.25f);
|
||||
shader->set_uniform("gap_size", 0.0f);
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
|
||||
// draw axes
|
||||
for (unsigned int i = 0; i < 3; ++i) {
|
||||
render_grabber_connection(i);
|
||||
}
|
||||
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
// draw grabbers
|
||||
render_grabbers(m_bounding_box);
|
||||
}
|
||||
else {
|
||||
// draw axis
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
|
||||
#else
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
|
||||
shader->set_uniform("width", 0.5f);
|
||||
shader->set_uniform("gap_size", 0.0f);
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
|
||||
render_grabber_connection(m_hover_id);
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.1f);
|
||||
glsafe(::glDisable(GL_CULL_FACE));
|
||||
// draw grabber
|
||||
const Vec3d box_size = m_bounding_box.size();
|
||||
const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0);
|
||||
m_grabbers[m_hover_id].render(true, mean_size);
|
||||
glsafe(::glEnable(GL_CULL_FACE));
|
||||
shader->stop_using();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoMove3D::on_register_raycasters_for_picking()
|
||||
{
|
||||
// the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account
|
||||
m_parent.set_raycaster_gizmos_on_top(true);
|
||||
}
|
||||
|
||||
void GLGizmoMove3D::on_unregister_raycasters_for_picking()
|
||||
{
|
||||
m_parent.set_raycaster_gizmos_on_top(false);
|
||||
}
|
||||
|
||||
double GLGizmoMove3D::calc_projection(const UpdateData& data) const
|
||||
{
|
||||
double projection = 0.0;
|
||||
|
||||
const Vec3d starting_vec = m_starting_drag_position - m_starting_box_center;
|
||||
const double len_starting_vec = starting_vec.norm();
|
||||
if (len_starting_vec != 0.0) {
|
||||
const Vec3d mouse_dir = data.mouse_ray.unit_vector();
|
||||
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
|
||||
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
|
||||
// in our case plane normal and ray direction are the same (orthogonal view)
|
||||
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
|
||||
const Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) * mouse_dir;
|
||||
// vector from the starting position to the found intersection
|
||||
const Vec3d inters_vec = inters - m_starting_drag_position;
|
||||
|
||||
// finds projection of the vector along the staring direction
|
||||
projection = inters_vec.dot(starting_vec.normalized());
|
||||
}
|
||||
|
||||
if (wxGetKeyState(WXK_SHIFT))
|
||||
projection = m_snap_step * (double)std::round(projection / m_snap_step);
|
||||
|
||||
return projection;
|
||||
}
|
||||
|
||||
Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const
|
||||
{
|
||||
Transform3d ret = Geometry::translation_transform(m_center);
|
||||
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
|
||||
const GLVolume& v = *selection.get_first_volume();
|
||||
Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix();
|
||||
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
|
||||
orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix();
|
||||
ret = ret * orient_matrix;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
71
src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
Normal file
71
src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef slic3r_GLGizmoMove_hpp_
|
||||
#define slic3r_GLGizmoMove_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class Selection;
|
||||
|
||||
class GLGizmoMove3D : public GLGizmoBase
|
||||
{
|
||||
static const double Offset;
|
||||
|
||||
Vec3d m_displacement{ Vec3d::Zero() };
|
||||
Vec3d m_center{ Vec3d::Zero() };
|
||||
BoundingBoxf3 m_bounding_box;
|
||||
double m_snap_step{ 1.0 };
|
||||
Vec3d m_starting_drag_position{ Vec3d::Zero() };
|
||||
Vec3d m_starting_box_center{ Vec3d::Zero() };
|
||||
Vec3d m_starting_box_bottom_center{ Vec3d::Zero() };
|
||||
|
||||
struct GrabberConnection
|
||||
{
|
||||
GLModel model;
|
||||
Vec3d old_center{ Vec3d::Zero() };
|
||||
};
|
||||
std::array<GrabberConnection, 3> m_grabber_connections;
|
||||
|
||||
public:
|
||||
GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
virtual ~GLGizmoMove3D() = default;
|
||||
|
||||
double get_snap_step(double step) const { return m_snap_step; }
|
||||
void set_snap_step(double step) { m_snap_step = step; }
|
||||
|
||||
std::string get_tooltip() const override;
|
||||
|
||||
/// <summary>
|
||||
/// Postpone to Grabber for move
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
/// <summary>
|
||||
/// Detect reduction of move for wipetover on selection change
|
||||
/// </summary>
|
||||
void data_changed(bool is_serializing) override;
|
||||
protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
void on_dragging(const UpdateData& data) override;
|
||||
void on_render() override;
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
|
||||
private:
|
||||
double calc_projection(const UpdateData& data) const;
|
||||
Transform3d local_transform(const Selection& selection) const;
|
||||
};
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoMove_hpp_
|
||||
1117
src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
Normal file
1117
src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
Normal file
File diff suppressed because it is too large
Load Diff
231
src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp
Normal file
231
src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp
Normal file
@@ -0,0 +1,231 @@
|
||||
#ifndef slic3r_GLGizmoPainterBase_hpp_
|
||||
#define slic3r_GLGizmoPainterBase_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "libslic3r/TriangleSelector.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <cereal/types/vector.hpp>
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <memory>
|
||||
#include <wx/string.h>
|
||||
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
class ClippingPlane;
|
||||
struct Camera;
|
||||
class GLGizmoMmuSegmentation;
|
||||
class Selection;
|
||||
|
||||
enum class PainterGizmoType {
|
||||
FDM_SUPPORTS,
|
||||
SEAM,
|
||||
MMU_SEGMENTATION
|
||||
};
|
||||
|
||||
class TriangleSelectorGUI : public TriangleSelector {
|
||||
public:
|
||||
explicit TriangleSelectorGUI(const TriangleMesh& mesh)
|
||||
: TriangleSelector(mesh) {}
|
||||
virtual ~TriangleSelectorGUI() = default;
|
||||
|
||||
virtual void render(ImGuiWrapper* imgui, const Transform3d& matrix);
|
||||
void render(const Transform3d& matrix) { this->render(nullptr, matrix); }
|
||||
|
||||
void request_update_render_data() { m_update_render_data = true; }
|
||||
|
||||
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
void render_debug(ImGuiWrapper* imgui);
|
||||
bool m_show_triangles{false};
|
||||
bool m_show_invalid{false};
|
||||
#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
|
||||
protected:
|
||||
bool m_update_render_data = false;
|
||||
|
||||
static ColorRGBA get_seed_fill_color(const ColorRGBA& base_color);
|
||||
|
||||
private:
|
||||
void update_render_data();
|
||||
|
||||
GLModel m_iva_enforcers;
|
||||
GLModel m_iva_blockers;
|
||||
std::array<GLModel, 3> m_iva_seed_fills;
|
||||
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
std::array<GLModel, 3> m_varrays;
|
||||
#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
|
||||
protected:
|
||||
GLModel m_paint_contour;
|
||||
|
||||
void update_paint_contour();
|
||||
void render_paint_contour(const Transform3d& matrix);
|
||||
};
|
||||
|
||||
|
||||
// Following class is a base class for a gizmo with ability to paint on mesh
|
||||
// using circular blush (such as FDM supports gizmo and seam painting gizmo).
|
||||
// The purpose is not to duplicate code related to mesh painting.
|
||||
class GLGizmoPainterBase : public GLGizmoBase
|
||||
{
|
||||
private:
|
||||
ObjectID m_old_mo_id;
|
||||
size_t m_old_volumes_size = 0;
|
||||
void on_render() override {}
|
||||
|
||||
public:
|
||||
GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
~GLGizmoPainterBase() override;
|
||||
void data_changed(bool is_serializing) override;
|
||||
virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
||||
// Following function renders the triangles and cursor. Having this separated
|
||||
// from usual on_render method allows to render them before transparent
|
||||
// objects, so they can be seen inside them. The usual on_render is called
|
||||
// after all volumes (including transparent ones) are rendered.
|
||||
virtual void render_painter_gizmo() = 0;
|
||||
|
||||
virtual const float get_cursor_radius_min() const { return CursorRadiusMin; }
|
||||
virtual const float get_cursor_radius_max() const { return CursorRadiusMax; }
|
||||
virtual const float get_cursor_radius_step() const { return CursorRadiusStep; }
|
||||
|
||||
/// <summary>
|
||||
/// Implement when want to process mouse events in gizmo
|
||||
/// Click, Right click, move, drag, ...
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information and don't want to
|
||||
/// propagate it otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
protected:
|
||||
virtual void render_triangles(const Selection& selection) const;
|
||||
void render_cursor();
|
||||
void render_cursor_circle();
|
||||
void render_cursor_sphere(const Transform3d& trafo) const;
|
||||
virtual void update_model_object() const = 0;
|
||||
virtual void update_from_model_object() = 0;
|
||||
|
||||
virtual ColorRGBA get_cursor_sphere_left_button_color() const { return { 0.0f, 0.0f, 1.0f, 0.25f }; }
|
||||
virtual ColorRGBA get_cursor_sphere_right_button_color() const { return { 1.0f, 0.0f, 0.0f, 0.25f }; }
|
||||
|
||||
virtual EnforcerBlockerType get_left_button_state_type() const { return EnforcerBlockerType::ENFORCER; }
|
||||
virtual EnforcerBlockerType get_right_button_state_type() const { return EnforcerBlockerType::BLOCKER; }
|
||||
|
||||
float m_cursor_radius = 2.f;
|
||||
static constexpr float CursorRadiusMin = 0.4f; // cannot be zero
|
||||
static constexpr float CursorRadiusMax = 8.f;
|
||||
static constexpr float CursorRadiusStep = 0.2f;
|
||||
|
||||
// For each model-part volume, store status and division of the triangles.
|
||||
std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors;
|
||||
|
||||
TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE;
|
||||
|
||||
enum class ToolType {
|
||||
BRUSH,
|
||||
BUCKET_FILL,
|
||||
SMART_FILL
|
||||
};
|
||||
|
||||
struct ProjectedMousePosition
|
||||
{
|
||||
Vec3f mesh_hit;
|
||||
int mesh_idx;
|
||||
size_t facet_idx;
|
||||
};
|
||||
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
float m_smart_fill_angle = 30.f;
|
||||
|
||||
bool m_paint_on_overhangs_only = false;
|
||||
float m_highlight_by_angle_threshold_deg = 0.f;
|
||||
|
||||
GLModel m_circle;
|
||||
#if !ENABLE_GL_CORE_PROFILE
|
||||
Vec2d m_old_center{ Vec2d::Zero() };
|
||||
#endif // !ENABLE_GL_CORE_PROFILE
|
||||
float m_old_cursor_radius{ 0.0f };
|
||||
|
||||
static constexpr float SmartFillAngleMin = 0.0f;
|
||||
static constexpr float SmartFillAngleMax = 90.f;
|
||||
static constexpr float SmartFillAngleStep = 1.f;
|
||||
|
||||
// It stores the value of the previous mesh_id to which the seed fill was applied.
|
||||
// It is used to detect when the mouse has moved from one volume to another one.
|
||||
int m_seed_fill_last_mesh_id = -1;
|
||||
|
||||
enum class Button {
|
||||
None,
|
||||
Left,
|
||||
Right
|
||||
};
|
||||
|
||||
struct ClippingPlaneDataWrapper
|
||||
{
|
||||
std::array<float, 4> clp_dataf;
|
||||
std::array<float, 2> z_range;
|
||||
};
|
||||
|
||||
ClippingPlaneDataWrapper get_clipping_plane_data() const;
|
||||
|
||||
TriangleSelector::ClippingPlane get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<ProjectedMousePosition>> get_projected_mouse_positions(const Vec2d &mouse_position, double resolution, const std::vector<Transform3d> &trafo_matrices) const;
|
||||
|
||||
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
|
||||
void update_raycast_cache(const Vec2d& mouse_position,
|
||||
const Camera& camera,
|
||||
const std::vector<Transform3d>& trafo_matrices) const;
|
||||
|
||||
static std::shared_ptr<GLModel> s_sphere;
|
||||
|
||||
bool m_internal_stack_active = false;
|
||||
bool m_schedule_update = false;
|
||||
Vec2d m_last_mouse_click = Vec2d::Zero();
|
||||
|
||||
Button m_button_down = Button::None;
|
||||
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
|
||||
|
||||
// Following cache holds result of a raycast query. The queries are asked
|
||||
// during rendering the sphere cursor and painting, this saves repeated
|
||||
// raycasts when the mouse position is the same as before.
|
||||
struct RaycastResult {
|
||||
Vec2d mouse_position;
|
||||
int mesh_id;
|
||||
Vec3f hit;
|
||||
size_t facet;
|
||||
};
|
||||
mutable RaycastResult m_rr = {Vec2d::Zero(), -1, Vec3f::Zero(), 0};
|
||||
|
||||
protected:
|
||||
void on_set_state() override;
|
||||
virtual void on_opening() = 0;
|
||||
virtual void on_shutdown() = 0;
|
||||
virtual PainterGizmoType get_painter_type() const = 0;
|
||||
|
||||
bool on_is_activable() const override;
|
||||
bool on_is_selectable() const override;
|
||||
void on_load(cereal::BinaryInputArchive& ar) override;
|
||||
void on_save(cereal::BinaryOutputArchive& ar) const override {}
|
||||
CommonGizmosDataID on_get_requirements() const override;
|
||||
bool wants_enter_leave_snapshots() const override { return true; }
|
||||
|
||||
virtual wxString handle_snapshot_action_name(bool shift_down, Button button_down) const = 0;
|
||||
|
||||
friend class ::Slic3r::GUI::GLGizmoMmuSegmentation;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_GLGizmoPainterBase_hpp_
|
||||
698
src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
Normal file
698
src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
Normal file
@@ -0,0 +1,698 @@
|
||||
#include "GLGizmoRotate.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
|
||||
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
const float GLGizmoRotate::Offset = 5.0f;
|
||||
const unsigned int GLGizmoRotate::AngleResolution = 64;
|
||||
const unsigned int GLGizmoRotate::ScaleStepsCount = 72;
|
||||
const float GLGizmoRotate::ScaleStepRad = 2.0f * float(PI) / GLGizmoRotate::ScaleStepsCount;
|
||||
const unsigned int GLGizmoRotate::ScaleLongEvery = 2;
|
||||
const float GLGizmoRotate::ScaleLongTooth = 0.1f; // in percent of radius
|
||||
const unsigned int GLGizmoRotate::SnapRegionsCount = 8;
|
||||
const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius
|
||||
|
||||
GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis)
|
||||
: GLGizmoBase(parent, "", -1)
|
||||
, m_axis(axis)
|
||||
, m_angle(0.0)
|
||||
, m_center(0.0, 0.0, 0.0)
|
||||
, m_radius(0.0f)
|
||||
, m_snap_coarse_in_radius(0.0f)
|
||||
, m_snap_coarse_out_radius(0.0f)
|
||||
, m_snap_fine_in_radius(0.0f)
|
||||
, m_snap_fine_out_radius(0.0f)
|
||||
, m_drag_color(DEFAULT_DRAG_COLOR)
|
||||
, m_highlight_color(DEFAULT_HIGHLIGHT_COLOR)
|
||||
{
|
||||
m_group_id = static_cast<int>(axis);
|
||||
}
|
||||
|
||||
void GLGizmoRotate::set_highlight_color(const ColorRGBA &color)
|
||||
{
|
||||
m_highlight_color = color;
|
||||
}
|
||||
|
||||
void GLGizmoRotate::set_angle(double angle)
|
||||
{
|
||||
if (std::abs(angle - 2.0 * double(PI)) < EPSILON)
|
||||
angle = 0.0;
|
||||
|
||||
m_angle = angle;
|
||||
}
|
||||
|
||||
std::string GLGizmoRotate::get_tooltip() const
|
||||
{
|
||||
std::string axis;
|
||||
switch (m_axis)
|
||||
{
|
||||
case X: { axis = "X"; break; }
|
||||
case Y: { axis = "Y"; break; }
|
||||
case Z: { axis = "Z"; break; }
|
||||
}
|
||||
return (m_hover_id == 0 || m_grabbers.front().dragging) ? axis + ": " + format(float(Geometry::rad2deg(m_angle)), 4) : "";
|
||||
}
|
||||
|
||||
bool GLGizmoRotate::on_mouse(const wxMouseEvent &mouse_event)
|
||||
{
|
||||
return use_grabbers(mouse_event);
|
||||
}
|
||||
|
||||
void GLGizmoRotate::dragging(const UpdateData &data) { on_dragging(data); }
|
||||
|
||||
void GLGizmoRotate::start_dragging()
|
||||
{
|
||||
m_grabbers[0].dragging = true;
|
||||
on_start_dragging();
|
||||
}
|
||||
|
||||
void GLGizmoRotate::stop_dragging()
|
||||
{
|
||||
m_grabbers[0].dragging = false;
|
||||
on_stop_dragging();
|
||||
}
|
||||
|
||||
void GLGizmoRotate::enable_grabber() { m_grabbers[0].enabled = true; }
|
||||
void GLGizmoRotate::disable_grabber() { m_grabbers[0].enabled = false; }
|
||||
|
||||
bool GLGizmoRotate::on_init()
|
||||
{
|
||||
m_grabbers.push_back(Grabber());
|
||||
m_grabbers.back().extensions = (GLGizmoBase::EGrabberExtension)(int(GLGizmoBase::EGrabberExtension::PosY) | int(GLGizmoBase::EGrabberExtension::NegY));
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLGizmoRotate::on_start_dragging()
|
||||
{
|
||||
init_data_from_selection(m_parent.get_selection());
|
||||
}
|
||||
|
||||
void GLGizmoRotate::on_dragging(const UpdateData &data)
|
||||
{
|
||||
const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray));
|
||||
const Vec2d orig_dir = Vec2d::UnitX();
|
||||
const Vec2d new_dir = mouse_pos.normalized();
|
||||
|
||||
double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0));
|
||||
if (cross2(orig_dir, new_dir) < 0.0)
|
||||
theta = 2.0 * (double)PI - theta;
|
||||
|
||||
const double len = mouse_pos.norm();
|
||||
|
||||
// snap to coarse snap region
|
||||
if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) {
|
||||
const double step = 2.0 * double(PI) / double(SnapRegionsCount);
|
||||
theta = step * std::round(theta / step);
|
||||
}
|
||||
else {
|
||||
// snap to fine snap region (scale)
|
||||
if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) {
|
||||
const double step = 2.0 * double(PI) / double(ScaleStepsCount);
|
||||
theta = step * std::round(theta / step);
|
||||
}
|
||||
}
|
||||
|
||||
if (theta == 2.0 * double(PI))
|
||||
theta = 0.0;
|
||||
|
||||
m_angle = theta;
|
||||
}
|
||||
|
||||
void GLGizmoRotate::on_render()
|
||||
{
|
||||
if (!m_grabbers.front().enabled)
|
||||
return;
|
||||
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
if (m_hover_id != 0 && !m_grabbers.front().dragging)
|
||||
init_data_from_selection(selection);
|
||||
|
||||
const double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset);
|
||||
m_grabbers.front().center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0);
|
||||
m_grabbers.front().angles.z() = m_angle;
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
m_grabbers.front().matrix = local_transform(selection);
|
||||
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (!OpenGLManager::get_gl_info().is_core_profile())
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
|
||||
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
|
||||
#else
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Transform3d view_model_matrix = camera.get_view_matrix() * m_grabbers.front().matrix;
|
||||
shader->set_uniform("view_model_matrix", view_model_matrix);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
|
||||
shader->set_uniform("width", 0.25f);
|
||||
shader->set_uniform("gap_size", 0.0f);
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
|
||||
const bool radius_changed = std::abs(m_old_radius - m_radius) > EPSILON;
|
||||
m_old_radius = m_radius;
|
||||
|
||||
const ColorRGBA color = (m_hover_id != -1) ? m_drag_color : m_highlight_color;
|
||||
render_circle(color, radius_changed);
|
||||
if (m_hover_id != -1) {
|
||||
const bool hover_radius_changed = std::abs(m_old_hover_radius - m_radius) > EPSILON;
|
||||
m_old_hover_radius = m_radius;
|
||||
|
||||
render_scale(color, hover_radius_changed);
|
||||
render_snap_radii(color, hover_radius_changed);
|
||||
render_reference_radius(color, hover_radius_changed);
|
||||
render_angle_arc(m_highlight_color, hover_radius_changed);
|
||||
}
|
||||
|
||||
render_grabber_connection(color, radius_changed);
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
render_grabber(m_bounding_box);
|
||||
}
|
||||
|
||||
void GLGizmoRotate::init_data_from_selection(const Selection& selection)
|
||||
{
|
||||
const auto [box, box_trafo] = m_force_local_coordinate ?
|
||||
selection.get_bounding_box_in_reference_system(ECoordinatesType::Local) : selection.get_bounding_box_in_current_reference_system();
|
||||
m_bounding_box = box;
|
||||
m_center = box_trafo.translation();
|
||||
m_orient_matrix = box_trafo;
|
||||
|
||||
m_radius = Offset + m_bounding_box.radius();
|
||||
m_snap_coarse_in_radius = m_radius / 3.0f;
|
||||
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
|
||||
m_snap_fine_in_radius = m_radius;
|
||||
m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth;
|
||||
}
|
||||
|
||||
void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
|
||||
return;
|
||||
|
||||
RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}};
|
||||
}
|
||||
|
||||
void GLGizmoRotate3D::load_rotoptimize_state()
|
||||
{
|
||||
std::string accuracy_str =
|
||||
wxGetApp().app_config->get("sla_auto_rotate", "accuracy");
|
||||
|
||||
std::string method_str =
|
||||
wxGetApp().app_config->get("sla_auto_rotate", "method_id");
|
||||
|
||||
if (!accuracy_str.empty()) {
|
||||
float accuracy = std::stof(accuracy_str);
|
||||
accuracy = std::max(0.f, std::min(accuracy, 1.f));
|
||||
|
||||
m_rotoptimizewin_state.accuracy = accuracy;
|
||||
}
|
||||
|
||||
if (!method_str.empty()) {
|
||||
int method_id = std::stoi(method_str);
|
||||
if (method_id < int(RotoptimizeJob::get_methods_count()))
|
||||
m_rotoptimizewin_state.method_id = method_id;
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoRotate::render_circle(const ColorRGBA& color, bool radius_changed)
|
||||
{
|
||||
if (!m_circle.is_initialized() || radius_changed) {
|
||||
m_circle.reset();
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3 };
|
||||
init_data.reserve_vertices(ScaleStepsCount);
|
||||
init_data.reserve_indices(ScaleStepsCount);
|
||||
|
||||
// vertices + indices
|
||||
for (unsigned int i = 0; i < ScaleStepsCount; ++i) {
|
||||
const float angle = float(i * ScaleStepRad);
|
||||
init_data.add_vertex(Vec3f(::cos(angle) * m_radius, ::sin(angle) * m_radius, 0.0f));
|
||||
init_data.add_index(i);
|
||||
}
|
||||
|
||||
m_circle.init_from(std::move(init_data));
|
||||
}
|
||||
|
||||
m_circle.set_color(color);
|
||||
m_circle.render();
|
||||
}
|
||||
|
||||
void GLGizmoRotate::render_scale(const ColorRGBA& color, bool radius_changed)
|
||||
{
|
||||
const float out_radius_long = m_snap_fine_out_radius;
|
||||
const float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth);
|
||||
|
||||
if (!m_scale.is_initialized() || radius_changed) {
|
||||
m_scale.reset();
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
|
||||
init_data.reserve_vertices(2 * ScaleStepsCount);
|
||||
init_data.reserve_indices(2 * ScaleStepsCount);
|
||||
|
||||
// vertices + indices
|
||||
for (unsigned int i = 0; i < ScaleStepsCount; ++i) {
|
||||
const float angle = float(i * ScaleStepRad);
|
||||
const float cosa = ::cos(angle);
|
||||
const float sina = ::sin(angle);
|
||||
const float in_x = cosa * m_radius;
|
||||
const float in_y = sina * m_radius;
|
||||
const float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short;
|
||||
const float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short;
|
||||
|
||||
// vertices
|
||||
init_data.add_vertex(Vec3f(in_x, in_y, 0.0f));
|
||||
init_data.add_vertex(Vec3f(out_x, out_y, 0.0f));
|
||||
|
||||
// indices
|
||||
init_data.add_line(i * 2, i * 2 + 1);
|
||||
}
|
||||
|
||||
m_scale.init_from(std::move(init_data));
|
||||
}
|
||||
|
||||
m_scale.set_color(color);
|
||||
m_scale.render();
|
||||
}
|
||||
|
||||
void GLGizmoRotate::render_snap_radii(const ColorRGBA& color, bool radius_changed)
|
||||
{
|
||||
const float step = 2.0f * float(PI) / float(SnapRegionsCount);
|
||||
const float in_radius = m_radius / 3.0f;
|
||||
const float out_radius = 2.0f * in_radius;
|
||||
|
||||
if (!m_snap_radii.is_initialized() || radius_changed) {
|
||||
m_snap_radii.reset();
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
|
||||
init_data.reserve_vertices(2 * ScaleStepsCount);
|
||||
init_data.reserve_indices(2 * ScaleStepsCount);
|
||||
|
||||
// vertices + indices
|
||||
for (unsigned int i = 0; i < ScaleStepsCount; ++i) {
|
||||
const float angle = float(i * step);
|
||||
const float cosa = ::cos(angle);
|
||||
const float sina = ::sin(angle);
|
||||
const float in_x = cosa * in_radius;
|
||||
const float in_y = sina * in_radius;
|
||||
const float out_x = cosa * out_radius;
|
||||
const float out_y = sina * out_radius;
|
||||
|
||||
// vertices
|
||||
init_data.add_vertex(Vec3f(in_x, in_y, 0.0f));
|
||||
init_data.add_vertex(Vec3f(out_x, out_y, 0.0f));
|
||||
|
||||
// indices
|
||||
init_data.add_line(i * 2, i * 2 + 1);
|
||||
}
|
||||
|
||||
m_snap_radii.init_from(std::move(init_data));
|
||||
}
|
||||
|
||||
m_snap_radii.set_color(color);
|
||||
m_snap_radii.render();
|
||||
}
|
||||
|
||||
void GLGizmoRotate::render_reference_radius(const ColorRGBA& color, bool radius_changed)
|
||||
{
|
||||
if (!m_reference_radius.is_initialized() || radius_changed) {
|
||||
m_reference_radius.reset();
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
|
||||
init_data.reserve_vertices(2);
|
||||
init_data.reserve_indices(2);
|
||||
|
||||
// vertices
|
||||
init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f));
|
||||
init_data.add_vertex(Vec3f(m_radius * (1.0f + GrabberOffset), 0.0f, 0.0f));
|
||||
|
||||
// indices
|
||||
init_data.add_line(0, 1);
|
||||
|
||||
m_reference_radius.init_from(std::move(init_data));
|
||||
}
|
||||
|
||||
m_reference_radius.set_color(color);
|
||||
m_reference_radius.render();
|
||||
}
|
||||
|
||||
void GLGizmoRotate::render_angle_arc(const ColorRGBA& color, bool radius_changed)
|
||||
{
|
||||
const float step_angle = float(m_angle) / float(AngleResolution);
|
||||
const float ex_radius = m_radius * (1.0f + GrabberOffset);
|
||||
|
||||
const bool angle_changed = std::abs(m_old_angle - m_angle) > EPSILON;
|
||||
m_old_angle = m_angle;
|
||||
|
||||
if (!m_angle_arc.is_initialized() || radius_changed || angle_changed) {
|
||||
m_angle_arc.reset();
|
||||
if (m_angle > 0.0f) {
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3 };
|
||||
init_data.reserve_vertices(1 + AngleResolution);
|
||||
init_data.reserve_indices(1 + AngleResolution);
|
||||
|
||||
// vertices + indices
|
||||
for (unsigned int i = 0; i <= AngleResolution; ++i) {
|
||||
const float angle = float(i) * step_angle;
|
||||
init_data.add_vertex(Vec3f(::cos(angle) * ex_radius, ::sin(angle) * ex_radius, 0.0f));
|
||||
init_data.add_index(i);
|
||||
}
|
||||
|
||||
m_angle_arc.init_from(std::move(init_data));
|
||||
}
|
||||
}
|
||||
|
||||
m_angle_arc.set_color(color);
|
||||
m_angle_arc.render();
|
||||
}
|
||||
|
||||
void GLGizmoRotate::render_grabber_connection(const ColorRGBA& color, bool radius_changed)
|
||||
{
|
||||
if (!m_grabber_connection.model.is_initialized() || radius_changed || !m_grabber_connection.old_center.isApprox(m_grabbers.front().center)) {
|
||||
m_grabber_connection.model.reset();
|
||||
m_grabber_connection.old_center = m_grabbers.front().center;
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
|
||||
init_data.reserve_vertices(2);
|
||||
init_data.reserve_indices(2);
|
||||
|
||||
// vertices
|
||||
init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f));
|
||||
init_data.add_vertex((Vec3f)m_grabbers.front().center.cast<float>());
|
||||
|
||||
// indices
|
||||
init_data.add_line(0, 1);
|
||||
|
||||
m_grabber_connection.model.init_from(std::move(init_data));
|
||||
}
|
||||
|
||||
m_grabber_connection.model.set_color(color);
|
||||
m_grabber_connection.model.render();
|
||||
}
|
||||
|
||||
void GLGizmoRotate::render_grabber(const BoundingBoxf3& box)
|
||||
{
|
||||
m_grabbers.front().color = m_highlight_color;
|
||||
render_grabbers(box);
|
||||
}
|
||||
|
||||
Transform3d GLGizmoRotate::local_transform(const Selection& selection) const
|
||||
{
|
||||
Transform3d ret;
|
||||
|
||||
switch (m_axis)
|
||||
{
|
||||
case X:
|
||||
{
|
||||
ret = Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ());
|
||||
break;
|
||||
}
|
||||
case Y:
|
||||
{
|
||||
ret = Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case Z:
|
||||
{
|
||||
ret = Transform3d::Identity();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return m_orient_matrix * ret;
|
||||
}
|
||||
|
||||
Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray) const
|
||||
{
|
||||
const double half_pi = 0.5 * double(PI);
|
||||
|
||||
Transform3d m = Transform3d::Identity();
|
||||
|
||||
switch (m_axis)
|
||||
{
|
||||
case X:
|
||||
{
|
||||
m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ()));
|
||||
m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitY()));
|
||||
break;
|
||||
}
|
||||
case Y:
|
||||
{
|
||||
m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitY()));
|
||||
m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case Z:
|
||||
{
|
||||
// no rotation applied
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m = m * Geometry::Transformation(m_orient_matrix).get_matrix_no_offset().inverse();
|
||||
|
||||
m.translate(-m_center);
|
||||
|
||||
const Linef3 local_mouse_ray = transform(mouse_ray, m);
|
||||
if (std::abs(local_mouse_ray.vector().dot(Vec3d::UnitZ())) < EPSILON) {
|
||||
// if the ray is parallel to the plane containing the circle
|
||||
if (std::abs(local_mouse_ray.vector().dot(Vec3d::UnitY())) > 1.0 - EPSILON)
|
||||
// if the ray is parallel to grabber direction
|
||||
return Vec3d::UnitX();
|
||||
else {
|
||||
const Vec3d world_pos = (local_mouse_ray.a.x() >= 0.0) ? mouse_ray.a - m_center : mouse_ray.b - m_center;
|
||||
m.translate(m_center);
|
||||
return m * world_pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
return local_mouse_ray.intersect_plane(0.0);
|
||||
}
|
||||
|
||||
GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||
, m_gizmos({
|
||||
GLGizmoRotate(parent, GLGizmoRotate::X),
|
||||
GLGizmoRotate(parent, GLGizmoRotate::Y),
|
||||
GLGizmoRotate(parent, GLGizmoRotate::Z) })
|
||||
{
|
||||
load_rotoptimize_state();
|
||||
}
|
||||
|
||||
bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event)
|
||||
{
|
||||
if (mouse_event.Dragging() && m_dragging) {
|
||||
// Apply new temporary rotations
|
||||
TransformationType transformation_type;
|
||||
if (m_parent.get_selection().is_wipe_tower())
|
||||
transformation_type = TransformationType::World_Relative_Joint;
|
||||
else {
|
||||
switch (wxGetApp().obj_manipul()->get_coordinates_type())
|
||||
{
|
||||
default:
|
||||
case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; }
|
||||
case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; }
|
||||
case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; }
|
||||
}
|
||||
}
|
||||
if (mouse_event.AltDown())
|
||||
transformation_type.set_independent();
|
||||
m_parent.get_selection().rotate(get_rotation(), transformation_type);
|
||||
}
|
||||
return use_grabbers(mouse_event);
|
||||
}
|
||||
|
||||
void GLGizmoRotate3D::data_changed(bool is_serializing) {
|
||||
if (m_parent.get_selection().is_wipe_tower()) {
|
||||
m_gizmos[0].disable_grabber();
|
||||
m_gizmos[1].disable_grabber();
|
||||
}
|
||||
else {
|
||||
m_gizmos[0].enable_grabber();
|
||||
m_gizmos[1].enable_grabber();
|
||||
}
|
||||
set_rotation(Vec3d::Zero());
|
||||
}
|
||||
|
||||
bool GLGizmoRotate3D::on_init()
|
||||
{
|
||||
for (GLGizmoRotate& g : m_gizmos)
|
||||
if (!g.init()) return false;
|
||||
|
||||
for (unsigned int i = 0; i < 3; ++i)
|
||||
m_gizmos[i].set_highlight_color(AXES_COLOR[i]);
|
||||
|
||||
m_shortcut_key = WXK_CONTROL_R;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GLGizmoRotate3D::on_get_name() const
|
||||
{
|
||||
return _u8L("Rotate");
|
||||
}
|
||||
|
||||
bool GLGizmoRotate3D::on_is_activable() const
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
return !selection.is_any_cut_volume() && !selection.is_any_connector() && !selection.is_empty();
|
||||
}
|
||||
|
||||
void GLGizmoRotate3D::on_start_dragging()
|
||||
{
|
||||
assert(0 <= m_hover_id && m_hover_id < 3);
|
||||
m_gizmos[m_hover_id].start_dragging();
|
||||
}
|
||||
|
||||
void GLGizmoRotate3D::on_stop_dragging()
|
||||
{
|
||||
assert(0 <= m_hover_id && m_hover_id < 3);
|
||||
m_parent.do_rotate(L("Gizmo-Rotate"));
|
||||
m_gizmos[m_hover_id].stop_dragging();
|
||||
}
|
||||
|
||||
void GLGizmoRotate3D::on_dragging(const UpdateData &data)
|
||||
{
|
||||
assert(0 <= m_hover_id && m_hover_id < 3);
|
||||
m_gizmos[m_hover_id].dragging(data);
|
||||
}
|
||||
|
||||
void GLGizmoRotate3D::on_render()
|
||||
{
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
|
||||
if (m_hover_id == -1 || m_hover_id == 0)
|
||||
m_gizmos[X].render();
|
||||
|
||||
if (m_hover_id == -1 || m_hover_id == 1)
|
||||
m_gizmos[Y].render();
|
||||
|
||||
if (m_hover_id == -1 || m_hover_id == 2)
|
||||
m_gizmos[Z].render();
|
||||
}
|
||||
|
||||
void GLGizmoRotate3D::on_register_raycasters_for_picking()
|
||||
{
|
||||
// the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account
|
||||
m_parent.set_raycaster_gizmos_on_top(true);
|
||||
for (GLGizmoRotate& g : m_gizmos) {
|
||||
g.register_raycasters_for_picking();
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoRotate3D::on_unregister_raycasters_for_picking()
|
||||
{
|
||||
for (GLGizmoRotate& g : m_gizmos) {
|
||||
g.unregister_raycasters_for_picking();
|
||||
}
|
||||
m_parent.set_raycaster_gizmos_on_top(false);
|
||||
}
|
||||
|
||||
GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
|
||||
State & state,
|
||||
const Alignment &alignment)
|
||||
: m_imgui{imgui}
|
||||
{
|
||||
imgui->begin(_L("Optimize orientation"), ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
// adjust window position to avoid overlap the view toolbar
|
||||
float win_h = ImGui::GetWindowHeight();
|
||||
float x = alignment.x, y = alignment.y;
|
||||
y = std::min(y, alignment.bottom_limit - win_h);
|
||||
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
|
||||
|
||||
float max_text_w = 0.;
|
||||
auto padding = ImGui::GetStyle().FramePadding;
|
||||
padding.x *= 2.f;
|
||||
padding.y *= 2.f;
|
||||
|
||||
for (size_t i = 0; i < RotoptimizeJob::get_methods_count(); ++i) {
|
||||
float w =
|
||||
ImGui::CalcTextSize(RotoptimizeJob::get_method_name(i).c_str()).x +
|
||||
padding.x + ImGui::GetFrameHeight();
|
||||
max_text_w = std::max(w, max_text_w);
|
||||
}
|
||||
|
||||
ImGui::PushItemWidth(max_text_w);
|
||||
|
||||
if (ImGui::BeginCombo("", RotoptimizeJob::get_method_name(state.method_id).c_str())) {
|
||||
for (size_t i = 0; i < RotoptimizeJob::get_methods_count(); ++i) {
|
||||
if (ImGui::Selectable(RotoptimizeJob::get_method_name(i).c_str())) {
|
||||
state.method_id = i;
|
||||
wxGetApp().app_config->set("sla_auto_rotate",
|
||||
"method_id",
|
||||
std::to_string(state.method_id));
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", RotoptimizeJob::get_method_description(i).c_str());
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImVec2 sz = ImGui::GetItemRectSize();
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", RotoptimizeJob::get_method_description(state.method_id).c_str());
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
auto btn_txt = _L("Apply");
|
||||
auto btn_txt_sz = ImGui::CalcTextSize(btn_txt.c_str());
|
||||
ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y};
|
||||
ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x);
|
||||
|
||||
if (!wxGetApp().plater()->get_ui_job_worker().is_idle())
|
||||
imgui->disabled_begin(true);
|
||||
|
||||
if ( imgui->button(btn_txt) ) {
|
||||
replace_job(wxGetApp().plater()->get_ui_job_worker(),
|
||||
std::make_unique<RotoptimizeJob>());
|
||||
}
|
||||
|
||||
imgui->disabled_end();
|
||||
}
|
||||
|
||||
GLGizmoRotate3D::RotoptimzeWindow::~RotoptimzeWindow()
|
||||
{
|
||||
m_imgui->end();
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
206
src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
Normal file
206
src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
Normal file
@@ -0,0 +1,206 @@
|
||||
#ifndef slic3r_GLGizmoRotate_hpp_
|
||||
#define slic3r_GLGizmoRotate_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
class Selection;
|
||||
|
||||
class GLGizmoRotate : public GLGizmoBase
|
||||
{
|
||||
static const float Offset;
|
||||
static const unsigned int AngleResolution;
|
||||
static const unsigned int ScaleStepsCount;
|
||||
static const float ScaleStepRad;
|
||||
static const unsigned int ScaleLongEvery;
|
||||
static const float ScaleLongTooth;
|
||||
static const unsigned int SnapRegionsCount;
|
||||
static const float GrabberOffset;
|
||||
|
||||
public:
|
||||
enum Axis : unsigned char
|
||||
{
|
||||
X=0,
|
||||
Y=1,
|
||||
Z=2
|
||||
};
|
||||
|
||||
private:
|
||||
Axis m_axis;
|
||||
double m_angle{ 0.0 };
|
||||
Vec3d m_center{ Vec3d::Zero() };
|
||||
float m_radius{ 0.0f };
|
||||
float m_snap_coarse_in_radius{ 0.0f };
|
||||
float m_snap_coarse_out_radius{ 0.0f };
|
||||
float m_snap_fine_in_radius{ 0.0f };
|
||||
float m_snap_fine_out_radius{ 0.0f };
|
||||
BoundingBoxf3 m_bounding_box;
|
||||
Transform3d m_orient_matrix{ Transform3d::Identity() };
|
||||
|
||||
GLModel m_circle;
|
||||
GLModel m_scale;
|
||||
GLModel m_snap_radii;
|
||||
GLModel m_reference_radius;
|
||||
GLModel m_angle_arc;
|
||||
struct GrabberConnection
|
||||
{
|
||||
GLModel model;
|
||||
Vec3d old_center{ Vec3d::Zero() };
|
||||
};
|
||||
GrabberConnection m_grabber_connection;
|
||||
float m_old_radius{ 0.0f };
|
||||
float m_old_hover_radius{ 0.0f };
|
||||
float m_old_angle{ 0.0f };
|
||||
|
||||
// emboss need to draw rotation gizmo in local coordinate systems
|
||||
bool m_force_local_coordinate{ false };
|
||||
|
||||
ColorRGBA m_drag_color;
|
||||
ColorRGBA m_highlight_color;
|
||||
|
||||
public:
|
||||
GLGizmoRotate(GLCanvas3D& parent, Axis axis);
|
||||
virtual ~GLGizmoRotate() = default;
|
||||
|
||||
double get_angle() const { return m_angle; }
|
||||
void set_angle(double angle);
|
||||
|
||||
std::string get_tooltip() const override;
|
||||
|
||||
void set_group_id(int group_id) { m_group_id = group_id; }
|
||||
void set_force_local_coordinate(bool use) { m_force_local_coordinate = use; }
|
||||
|
||||
void start_dragging();
|
||||
void stop_dragging();
|
||||
|
||||
void enable_grabber();
|
||||
void disable_grabber();
|
||||
|
||||
void set_highlight_color(const ColorRGBA &color);
|
||||
|
||||
/// <summary>
|
||||
/// Postpone to Grabber for move
|
||||
/// Detect move of object by dragging
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
void dragging(const UpdateData &data);
|
||||
|
||||
protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override { return ""; }
|
||||
void on_start_dragging() override;
|
||||
void on_dragging(const UpdateData &data) override;
|
||||
void on_render() override;
|
||||
|
||||
private:
|
||||
void render_circle(const ColorRGBA& color, bool radius_changed);
|
||||
void render_scale(const ColorRGBA& color, bool radius_changed);
|
||||
void render_snap_radii(const ColorRGBA& color, bool radius_changed);
|
||||
void render_reference_radius(const ColorRGBA& color, bool radius_changed);
|
||||
void render_angle_arc(const ColorRGBA& color, bool radius_changed);
|
||||
void render_grabber_connection(const ColorRGBA& color, bool radius_changed);
|
||||
void render_grabber(const BoundingBoxf3& box);
|
||||
|
||||
Transform3d local_transform(const Selection& selection) const;
|
||||
|
||||
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
|
||||
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray) const;
|
||||
|
||||
void init_data_from_selection(const Selection& selection);
|
||||
};
|
||||
|
||||
class GLGizmoRotate3D : public GLGizmoBase
|
||||
{
|
||||
std::array<GLGizmoRotate, 3> m_gizmos;
|
||||
|
||||
public:
|
||||
GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
||||
Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); }
|
||||
void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation.x()); m_gizmos[Y].set_angle(rotation.y()); m_gizmos[Z].set_angle(rotation.z()); }
|
||||
|
||||
std::string get_tooltip() const override {
|
||||
std::string tooltip = m_gizmos[X].get_tooltip();
|
||||
if (tooltip.empty())
|
||||
tooltip = m_gizmos[Y].get_tooltip();
|
||||
if (tooltip.empty())
|
||||
tooltip = m_gizmos[Z].get_tooltip();
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Postpone to Rotation
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
void data_changed(bool is_serializing) override;
|
||||
protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
void on_set_state() override {
|
||||
for (GLGizmoRotate& g : m_gizmos)
|
||||
g.set_state(m_state);
|
||||
}
|
||||
void on_set_hover_id() override {
|
||||
for (int i = 0; i < 3; ++i)
|
||||
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
|
||||
}
|
||||
void on_enable_grabber(unsigned int id) override {
|
||||
if (id < 3)
|
||||
m_gizmos[id].enable_grabber();
|
||||
}
|
||||
void on_disable_grabber(unsigned int id) override {
|
||||
if (id < 3)
|
||||
m_gizmos[id].disable_grabber();
|
||||
}
|
||||
bool on_is_activable() const override;
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
void on_dragging(const UpdateData &data) override;
|
||||
|
||||
void on_render() override;
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
|
||||
private:
|
||||
|
||||
class RotoptimzeWindow
|
||||
{
|
||||
ImGuiWrapper *m_imgui = nullptr;
|
||||
|
||||
public:
|
||||
struct State {
|
||||
float accuracy = 1.f;
|
||||
int method_id = 0;
|
||||
};
|
||||
|
||||
struct Alignment { float x, y, bottom_limit; };
|
||||
|
||||
RotoptimzeWindow(ImGuiWrapper * imgui,
|
||||
State & state,
|
||||
const Alignment &bottom_limit);
|
||||
|
||||
~RotoptimzeWindow();
|
||||
|
||||
RotoptimzeWindow(const RotoptimzeWindow&) = delete;
|
||||
RotoptimzeWindow(RotoptimzeWindow &&) = delete;
|
||||
RotoptimzeWindow& operator=(const RotoptimzeWindow &) = delete;
|
||||
RotoptimzeWindow& operator=(RotoptimzeWindow &&) = delete;
|
||||
};
|
||||
|
||||
RotoptimzeWindow::State m_rotoptimizewin_state = {};
|
||||
|
||||
void load_rotoptimize_state();
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoRotate_hpp_
|
||||
476
src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
Normal file
476
src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
Normal file
@@ -0,0 +1,476 @@
|
||||
#include "GLGizmoScale.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <wx/utils.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
const double GLGizmoScale3D::Offset = 5.0;
|
||||
|
||||
GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||
, m_scale(Vec3d::Ones())
|
||||
, m_snap_step(0.05)
|
||||
, m_base_color(DEFAULT_BASE_COLOR)
|
||||
, m_drag_color(DEFAULT_DRAG_COLOR)
|
||||
, m_highlight_color(DEFAULT_HIGHLIGHT_COLOR)
|
||||
{
|
||||
m_grabber_connections[0].grabber_indices = { 0, 1 };
|
||||
m_grabber_connections[1].grabber_indices = { 2, 3 };
|
||||
m_grabber_connections[2].grabber_indices = { 4, 5 };
|
||||
m_grabber_connections[3].grabber_indices = { 6, 7 };
|
||||
m_grabber_connections[4].grabber_indices = { 7, 8 };
|
||||
m_grabber_connections[5].grabber_indices = { 8, 9 };
|
||||
m_grabber_connections[6].grabber_indices = { 9, 6 };
|
||||
}
|
||||
|
||||
std::string GLGizmoScale3D::get_tooltip() const
|
||||
{
|
||||
const Vec3d scale = 100.0 * m_scale;
|
||||
|
||||
if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging)
|
||||
return "X: " + format(scale.x(), 4) + "%";
|
||||
else if (m_hover_id == 2 || m_hover_id == 3 || m_grabbers[2].dragging || m_grabbers[3].dragging)
|
||||
return "Y: " + format(scale.y(), 4) + "%";
|
||||
else if (m_hover_id == 4 || m_hover_id == 5 || m_grabbers[4].dragging || m_grabbers[5].dragging)
|
||||
return "Z: " + format(scale.z(), 4) + "%";
|
||||
else if (m_hover_id == 6 || m_hover_id == 7 || m_hover_id == 8 || m_hover_id == 9 ||
|
||||
m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging)
|
||||
{
|
||||
std::string tooltip = "X: " + format(scale.x(), 4) + "%\n";
|
||||
tooltip += "Y: " + format(scale.y(), 4) + "%\n";
|
||||
tooltip += "Z: " + format(scale.z(), 4) + "%";
|
||||
return tooltip;
|
||||
}
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
static int constraint_id(int grabber_id)
|
||||
{
|
||||
static const std::vector<int> id_map = { 1, 0, 3, 2, 5, 4, 8, 9, 6, 7 };
|
||||
return (0 <= grabber_id && grabber_id < (int)id_map.size()) ? id_map[grabber_id] : -1;
|
||||
}
|
||||
|
||||
bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event)
|
||||
{
|
||||
if (mouse_event.Dragging()) {
|
||||
if (m_dragging) {
|
||||
// Apply new temporary scale factors
|
||||
TransformationType transformation_type;
|
||||
if (wxGetApp().obj_manipul()->is_local_coordinates())
|
||||
transformation_type.set_local();
|
||||
else if (wxGetApp().obj_manipul()->is_instance_coordinates())
|
||||
transformation_type.set_instance();
|
||||
|
||||
transformation_type.set_relative();
|
||||
|
||||
if (mouse_event.AltDown())
|
||||
transformation_type.set_independent();
|
||||
|
||||
Selection& selection = m_parent.get_selection();
|
||||
selection.scale(m_scale, transformation_type);
|
||||
if (m_starting.ctrl_down) {
|
||||
// constrained scale:
|
||||
// uses the performed scale to calculate the new position of the constrained grabber
|
||||
// and from that calculates the offset (in world coordinates) to be applied to fullfill the constraint
|
||||
update_render_data();
|
||||
const Vec3d constraint_position = m_grabbers_transform * m_grabbers[constraint_id(m_hover_id)].center;
|
||||
// re-apply the scale because the selection always applies the transformations with respect to the initial state
|
||||
// set into on_start_dragging() with the call to selection.setup_cache()
|
||||
m_parent.get_selection().scale_and_translate(m_scale, m_starting.constraint_position - constraint_position, transformation_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
return use_grabbers(mouse_event);
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::enable_ununiversal_scale(bool enable)
|
||||
{
|
||||
for (unsigned int i = 0; i < 6; ++i)
|
||||
m_grabbers[i].enabled = enable;
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::data_changed(bool is_serializing) {
|
||||
set_scale(Vec3d::Ones());
|
||||
}
|
||||
|
||||
bool GLGizmoScale3D::on_init()
|
||||
{
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
m_grabbers.push_back(Grabber());
|
||||
}
|
||||
|
||||
m_shortcut_key = WXK_CONTROL_S;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GLGizmoScale3D::on_get_name() const
|
||||
{
|
||||
return _u8L("Scale");
|
||||
}
|
||||
|
||||
bool GLGizmoScale3D::on_is_activable() const
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
return !selection.is_any_cut_volume() && !selection.is_any_connector() && !selection.is_empty() && !selection.is_wipe_tower();
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::on_start_dragging()
|
||||
{
|
||||
assert(m_hover_id != -1);
|
||||
m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL);
|
||||
m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center;
|
||||
m_starting.box = m_bounding_box;
|
||||
m_starting.center = m_center;
|
||||
m_starting.instance_center = m_instance_center;
|
||||
m_starting.constraint_position = m_grabbers_transform * m_grabbers[constraint_id(m_hover_id)].center;
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::on_stop_dragging()
|
||||
{
|
||||
m_parent.do_scale(L("Gizmo-Scale"));
|
||||
m_starting.ctrl_down = false;
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::on_dragging(const UpdateData& data)
|
||||
{
|
||||
if (m_hover_id == 0 || m_hover_id == 1)
|
||||
do_scale_along_axis(X, data);
|
||||
else if (m_hover_id == 2 || m_hover_id == 3)
|
||||
do_scale_along_axis(Y, data);
|
||||
else if (m_hover_id == 4 || m_hover_id == 5)
|
||||
do_scale_along_axis(Z, data);
|
||||
else if (m_hover_id >= 6)
|
||||
do_scale_uniform(data);
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::on_render()
|
||||
{
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
update_render_data();
|
||||
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (!OpenGLManager::get_gl_info().is_core_profile())
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
|
||||
|
||||
const float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0);
|
||||
|
||||
if (m_hover_id == -1) {
|
||||
// draw connections
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
|
||||
#else
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
|
||||
shader->set_uniform("width", 0.25f);
|
||||
shader->set_uniform("gap_size", 0.0f);
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
if (m_grabbers[0].enabled && m_grabbers[1].enabled)
|
||||
render_grabbers_connection(0, 1, m_grabbers[0].color);
|
||||
if (m_grabbers[2].enabled && m_grabbers[3].enabled)
|
||||
render_grabbers_connection(2, 3, m_grabbers[2].color);
|
||||
if (m_grabbers[4].enabled && m_grabbers[5].enabled)
|
||||
render_grabbers_connection(4, 5, m_grabbers[4].color);
|
||||
render_grabbers_connection(6, 7, m_base_color);
|
||||
render_grabbers_connection(7, 8, m_base_color);
|
||||
render_grabbers_connection(8, 9, m_base_color);
|
||||
render_grabbers_connection(9, 6, m_base_color);
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
// draw grabbers
|
||||
render_grabbers(grabber_mean_size);
|
||||
}
|
||||
else if ((m_hover_id == 0 || m_hover_id == 1) && m_grabbers[0].enabled && m_grabbers[1].enabled) {
|
||||
// draw connections
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
|
||||
#else
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
|
||||
shader->set_uniform("width", 0.25f);
|
||||
shader->set_uniform("gap_size", 0.0f);
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
render_grabbers_connection(0, 1, m_grabbers[0].color);
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
// draw grabbers
|
||||
shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.1f);
|
||||
render_grabbers(0, 1, grabber_mean_size, true);
|
||||
shader->stop_using();
|
||||
}
|
||||
}
|
||||
else if ((m_hover_id == 2 || m_hover_id == 3) && m_grabbers[2].enabled && m_grabbers[3].enabled) {
|
||||
// draw connections
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
|
||||
#else
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
|
||||
shader->set_uniform("width", 0.25f);
|
||||
shader->set_uniform("gap_size", 0.0f);
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
render_grabbers_connection(2, 3, m_grabbers[2].color);
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
// draw grabbers
|
||||
shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.1f);
|
||||
render_grabbers(2, 3, grabber_mean_size, true);
|
||||
shader->stop_using();
|
||||
}
|
||||
}
|
||||
else if ((m_hover_id == 4 || m_hover_id == 5) && m_grabbers[4].enabled && m_grabbers[5].enabled) {
|
||||
// draw connections
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
|
||||
#else
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
|
||||
shader->set_uniform("width", 0.25f);
|
||||
shader->set_uniform("gap_size", 0.0f);
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
render_grabbers_connection(4, 5, m_grabbers[4].color);
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
// draw grabbers
|
||||
shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.1f);
|
||||
render_grabbers(4, 5, grabber_mean_size, true);
|
||||
shader->stop_using();
|
||||
}
|
||||
}
|
||||
else if (m_hover_id >= 6) {
|
||||
// draw connections
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
|
||||
#else
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * m_grabbers_transform);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
|
||||
shader->set_uniform("width", 0.25f);
|
||||
shader->set_uniform("gap_size", 0.0f);
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
render_grabbers_connection(6, 7, m_drag_color);
|
||||
render_grabbers_connection(7, 8, m_drag_color);
|
||||
render_grabbers_connection(8, 9, m_drag_color);
|
||||
render_grabbers_connection(9, 6, m_drag_color);
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
// draw grabbers
|
||||
shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.1f);
|
||||
render_grabbers(6, 9, grabber_mean_size, true);
|
||||
shader->stop_using();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::on_register_raycasters_for_picking()
|
||||
{
|
||||
// the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account
|
||||
m_parent.set_raycaster_gizmos_on_top(true);
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::on_unregister_raycasters_for_picking()
|
||||
{
|
||||
m_parent.set_raycaster_gizmos_on_top(false);
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color)
|
||||
{
|
||||
auto grabber_connection = [this](unsigned int id_1, unsigned int id_2) {
|
||||
for (int i = 0; i < int(m_grabber_connections.size()); ++i) {
|
||||
if (m_grabber_connections[i].grabber_indices.first == id_1 && m_grabber_connections[i].grabber_indices.second == id_2)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
const int id = grabber_connection(id_1, id_2);
|
||||
if (id == -1)
|
||||
return;
|
||||
|
||||
if (!m_grabber_connections[id].model.is_initialized() ||
|
||||
!m_grabber_connections[id].old_v1.isApprox(m_grabbers[id_1].center) ||
|
||||
!m_grabber_connections[id].old_v2.isApprox(m_grabbers[id_2].center)) {
|
||||
m_grabber_connections[id].old_v1 = m_grabbers[id_1].center;
|
||||
m_grabber_connections[id].old_v2 = m_grabbers[id_2].center;
|
||||
m_grabber_connections[id].model.reset();
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
|
||||
init_data.reserve_vertices(2);
|
||||
init_data.reserve_indices(2);
|
||||
|
||||
// vertices
|
||||
init_data.add_vertex((Vec3f)m_grabbers[id_1].center.cast<float>());
|
||||
init_data.add_vertex((Vec3f)m_grabbers[id_2].center.cast<float>());
|
||||
|
||||
// indices
|
||||
init_data.add_line(0, 1);
|
||||
|
||||
m_grabber_connections[id].model.init_from(std::move(init_data));
|
||||
}
|
||||
|
||||
m_grabber_connections[id].model.set_color(color);
|
||||
m_grabber_connections[id].model.render();
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
|
||||
{
|
||||
double ratio = calc_ratio(data);
|
||||
if (ratio > 0.0) {
|
||||
Vec3d curr_scale = m_scale;
|
||||
curr_scale(axis) = m_starting.scale(axis) * ratio;
|
||||
m_scale = curr_scale;
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::do_scale_uniform(const UpdateData & data)
|
||||
{
|
||||
const double ratio = calc_ratio(data);
|
||||
if (ratio > 0.0)
|
||||
m_scale = m_starting.scale * ratio;
|
||||
}
|
||||
|
||||
double GLGizmoScale3D::calc_ratio(const UpdateData& data) const
|
||||
{
|
||||
double ratio = 0.0;
|
||||
const Vec3d starting_vec = m_starting.drag_position - m_starting.center;
|
||||
|
||||
const double len_starting_vec = starting_vec.norm();
|
||||
|
||||
if (len_starting_vec != 0.0) {
|
||||
const Vec3d mouse_dir = data.mouse_ray.unit_vector();
|
||||
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
|
||||
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
|
||||
// in our case plane normal and ray direction are the same (orthogonal view)
|
||||
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
|
||||
const Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) * mouse_dir;
|
||||
// vector from the starting position to the found intersection
|
||||
const Vec3d inters_vec = inters - m_starting.drag_position;
|
||||
|
||||
// finds projection of the vector along the staring direction
|
||||
const double proj = inters_vec.dot(starting_vec.normalized());
|
||||
|
||||
ratio = (len_starting_vec + proj) / len_starting_vec;
|
||||
}
|
||||
|
||||
if (wxGetKeyState(WXK_SHIFT))
|
||||
ratio = m_snap_step * (double)std::round(ratio / m_snap_step);
|
||||
|
||||
return ratio;
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::update_render_data()
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const auto& [box, box_trafo] = selection.get_bounding_box_in_current_reference_system();
|
||||
m_bounding_box = box;
|
||||
m_center = box_trafo.translation();
|
||||
m_grabbers_transform = box_trafo;
|
||||
m_instance_center = (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) ? selection.get_first_volume()->get_instance_offset() : m_center;
|
||||
|
||||
const Vec3d box_half_size = 0.5 * m_bounding_box.size();
|
||||
bool use_constrain = wxGetKeyState(WXK_CONTROL);
|
||||
|
||||
// x axis
|
||||
m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 };
|
||||
m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0];
|
||||
m_grabbers[1].center = { box_half_size.x() + Offset, 0.0, 0.0 };
|
||||
m_grabbers[1].color = (use_constrain && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0];
|
||||
|
||||
// y axis
|
||||
m_grabbers[2].center = { 0.0, -(box_half_size.y() + Offset), 0.0 };
|
||||
m_grabbers[2].color = (use_constrain && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1];
|
||||
m_grabbers[3].center = { 0.0, box_half_size.y() + Offset, 0.0 };
|
||||
m_grabbers[3].color = (use_constrain && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1];
|
||||
|
||||
// z axis
|
||||
m_grabbers[4].center = { 0.0, 0.0, -(box_half_size.z() + Offset) };
|
||||
m_grabbers[4].color = (use_constrain && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2];
|
||||
m_grabbers[5].center = { 0.0, 0.0, box_half_size.z() + Offset };
|
||||
m_grabbers[5].color = (use_constrain && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2];
|
||||
|
||||
// uniform
|
||||
m_grabbers[6].center = { -(box_half_size.x() + Offset), -(box_half_size.y() + Offset), 0.0 };
|
||||
m_grabbers[6].color = (use_constrain && m_hover_id == 8) ? CONSTRAINED_COLOR : m_highlight_color;
|
||||
m_grabbers[7].center = { box_half_size.x() + Offset, -(box_half_size.y() + Offset), 0.0 };
|
||||
m_grabbers[7].color = (use_constrain && m_hover_id == 9) ? CONSTRAINED_COLOR : m_highlight_color;
|
||||
m_grabbers[8].center = { box_half_size.x() + Offset, box_half_size.y() + Offset, 0.0 };
|
||||
m_grabbers[8].color = (use_constrain && m_hover_id == 6) ? CONSTRAINED_COLOR : m_highlight_color;
|
||||
m_grabbers[9].center = { -(box_half_size.x() + Offset), box_half_size.y() + Offset, 0.0 };
|
||||
m_grabbers[9].color = (use_constrain && m_hover_id == 7) ? CONSTRAINED_COLOR : m_highlight_color;
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
m_grabbers[i].matrix = m_grabbers_transform;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
91
src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
Normal file
91
src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#ifndef slic3r_GLGizmoScale_hpp_
|
||||
#define slic3r_GLGizmoScale_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class Selection;
|
||||
|
||||
class GLGizmoScale3D : public GLGizmoBase
|
||||
{
|
||||
static const double Offset;
|
||||
|
||||
struct StartingData
|
||||
{
|
||||
bool ctrl_down{ false };
|
||||
Vec3d scale{ Vec3d::Ones() };
|
||||
Vec3d drag_position{ Vec3d::Zero() };
|
||||
Vec3d center{ Vec3d::Zero() };
|
||||
Vec3d instance_center{ Vec3d::Zero() };
|
||||
Vec3d constraint_position{ Vec3d::Zero() };
|
||||
BoundingBoxf3 box;
|
||||
};
|
||||
|
||||
BoundingBoxf3 m_bounding_box;
|
||||
Transform3d m_grabbers_transform;
|
||||
Vec3d m_center{ Vec3d::Zero() };
|
||||
Vec3d m_instance_center{ Vec3d::Zero() };
|
||||
Vec3d m_scale{ Vec3d::Ones() };
|
||||
double m_snap_step{ 0.05 };
|
||||
StartingData m_starting;
|
||||
|
||||
struct GrabberConnection
|
||||
{
|
||||
GLModel model;
|
||||
std::pair<unsigned int, unsigned int> grabber_indices;
|
||||
Vec3d old_v1{ Vec3d::Zero() };
|
||||
Vec3d old_v2{ Vec3d::Zero() };
|
||||
};
|
||||
std::array<GrabberConnection, 7> m_grabber_connections;
|
||||
|
||||
ColorRGBA m_base_color;
|
||||
ColorRGBA m_drag_color;
|
||||
ColorRGBA m_highlight_color;
|
||||
public:
|
||||
GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
||||
double get_snap_step(double step) const { return m_snap_step; }
|
||||
void set_snap_step(double step) { m_snap_step = step; }
|
||||
|
||||
const Vec3d& get_scale() const { return m_scale; }
|
||||
void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; }
|
||||
|
||||
std::string get_tooltip() const override;
|
||||
|
||||
/// <summary>
|
||||
/// Postpone to Grabber for scale
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
void data_changed(bool is_serializing) override;
|
||||
void enable_ununiversal_scale(bool enable);
|
||||
protected:
|
||||
virtual bool on_init() override;
|
||||
virtual std::string on_get_name() const override;
|
||||
virtual bool on_is_activable() const override;
|
||||
virtual void on_start_dragging() override;
|
||||
virtual void on_stop_dragging() override;
|
||||
virtual void on_dragging(const UpdateData& data) override;
|
||||
virtual void on_render() override;
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
|
||||
private:
|
||||
void render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color);
|
||||
|
||||
void do_scale_along_axis(Axis axis, const UpdateData& data);
|
||||
void do_scale_uniform(const UpdateData& data);
|
||||
|
||||
double calc_ratio(const UpdateData& data) const;
|
||||
void update_render_data();
|
||||
};
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoScale_hpp_
|
||||
257
src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
Normal file
257
src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "GLGizmoSeam.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
//#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
|
||||
|
||||
void GLGizmoSeam::on_shutdown()
|
||||
{
|
||||
m_parent.toggle_model_objects_visibility(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool GLGizmoSeam::on_init()
|
||||
{
|
||||
m_shortcut_key = WXK_CONTROL_P;
|
||||
|
||||
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
||||
m_desc["reset_direction"] = _L("Reset direction");
|
||||
m_desc["cursor_size"] = _L("Brush size") + ": ";
|
||||
m_desc["cursor_type"] = _L("Brush shape") + ": ";
|
||||
m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
|
||||
m_desc["enforce"] = _L("Enforce seam");
|
||||
m_desc["block_caption"] = _L("Right mouse button") + ": ";
|
||||
m_desc["block"] = _L("Block seam");
|
||||
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
|
||||
m_desc["remove"] = _L("Remove selection");
|
||||
m_desc["remove_all"] = _L("Remove all selection");
|
||||
m_desc["circle"] = _L("Circle");
|
||||
m_desc["sphere"] = _L("Sphere");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string GLGizmoSeam::on_get_name() const
|
||||
{
|
||||
return _u8L("Seam painting");
|
||||
}
|
||||
|
||||
void GLGizmoSeam::render_painter_gizmo()
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
render_triangles(selection);
|
||||
|
||||
m_c->object_clipper()->render_cut();
|
||||
m_c->instances_hider()->render_cut();
|
||||
render_cursor();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
if (! m_c->selection_info()->model_object())
|
||||
return;
|
||||
|
||||
const float approx_height = m_imgui->scaled(12.5f);
|
||||
y = std::min(y, bottom_limit - approx_height);
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
m_imgui->begin(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 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 cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
||||
|
||||
const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc["cursor_type"]).x + m_imgui->scaled(1.f);
|
||||
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
|
||||
const float minimal_slider_width = m_imgui->scaled(4.f);
|
||||
|
||||
float caption_max = 0.f;
|
||||
float total_text_max = 0.f;
|
||||
for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"}) {
|
||||
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
|
||||
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
|
||||
}
|
||||
total_text_max += caption_max + m_imgui->scaled(1.f);
|
||||
caption_max += m_imgui->scaled(1.f);
|
||||
|
||||
const float sliders_left_width = std::max(cursor_size_slider_left, clipping_slider_left);
|
||||
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
|
||||
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
|
||||
window_width = std::max(window_width, total_text_max);
|
||||
window_width = std::max(window_width, button_width);
|
||||
window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_sphere + cursor_type_radio_circle);
|
||||
//B18
|
||||
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, caption);
|
||||
ImGui::SameLine(caption_max);
|
||||
m_imgui->text(text);
|
||||
};
|
||||
|
||||
for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"})
|
||||
draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_size"));
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_type"));
|
||||
|
||||
float cursor_type_offset = cursor_type_radio_left + (window_width - cursor_type_radio_left - cursor_type_radio_sphere - cursor_type_radio_circle + m_imgui->scaled(0.5f)) / 2.f;
|
||||
ImGui::SameLine(cursor_type_offset);
|
||||
ImGui::PushItemWidth(cursor_type_radio_sphere);
|
||||
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
|
||||
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
|
||||
ImGui::PushItemWidth(cursor_type_radio_circle);
|
||||
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
|
||||
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_c->object_clipper()->get_position() == 0.f) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
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_by_ratio(-1., false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
auto clp_dist = float(m_c->object_clipper()->get_position());
|
||||
ImGui::SameLine(sliders_left_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
|
||||
m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction);
|
||||
ModelObject *mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume *mv : mo->volumes)
|
||||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
m_triangle_selectors[idx]->request_update_render_data();
|
||||
}
|
||||
|
||||
update_model_object();
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
m_imgui->end();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoSeam::update_model_object() const
|
||||
{
|
||||
bool updated = false;
|
||||
ModelObject* mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
++idx;
|
||||
updated |= mv->seam_facets.set(*m_triangle_selectors[idx].get());
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
const ModelObjectPtrs& mos = wxGetApp().model().objects;
|
||||
wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
|
||||
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoSeam::update_from_model_object()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
m_triangle_selectors.clear();
|
||||
|
||||
int volume_id = -1;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
|
||||
++volume_id;
|
||||
|
||||
// This mesh does not account for the possible Z up SLA offset.
|
||||
const TriangleMesh* mesh = &mv->mesh();
|
||||
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
|
||||
// Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize().
|
||||
m_triangle_selectors.back()->deserialize(mv->seam_facets.get_data(), false);
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PainterGizmoType GLGizmoSeam::get_painter_type() const
|
||||
{
|
||||
return PainterGizmoType::SEAM;
|
||||
}
|
||||
|
||||
wxString GLGizmoSeam::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
|
||||
{
|
||||
wxString action_name;
|
||||
if (shift_down)
|
||||
action_name = _L("Remove selection");
|
||||
else {
|
||||
if (button_down == Button::Left)
|
||||
action_name = _L("Enforce seam");
|
||||
else
|
||||
action_name = _L("Block seam");
|
||||
}
|
||||
return action_name;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
48
src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
Normal file
48
src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef slic3r_GLGizmoSeam_hpp_
|
||||
#define slic3r_GLGizmoSeam_hpp_
|
||||
|
||||
#include "GLGizmoPainterBase.hpp"
|
||||
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
class GLGizmoSeam : public GLGizmoPainterBase
|
||||
{
|
||||
public:
|
||||
GLGizmoSeam(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoPainterBase(parent, icon_filename, sprite_id) {}
|
||||
|
||||
void render_painter_gizmo() override;
|
||||
|
||||
protected:
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
std::string on_get_name() const override;
|
||||
PainterGizmoType get_painter_type() const override;
|
||||
|
||||
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
||||
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Seam painting"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Seam painting"); }
|
||||
std::string get_action_snapshot_name() const override { return _u8L("Paint-on seam editing"); }
|
||||
|
||||
private:
|
||||
bool on_init() override;
|
||||
|
||||
void update_model_object() const override;
|
||||
void update_from_model_object() override;
|
||||
|
||||
void on_opening() override {}
|
||||
void on_shutdown() override;
|
||||
|
||||
// This map holds all translated description texts, so they can be easily referenced during layout calculations
|
||||
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||
std::map<std::string, wxString> m_desc;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
|
||||
#endif // slic3r_GLGizmoSeam_hpp_
|
||||
823
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
Normal file
823
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
Normal file
@@ -0,0 +1,823 @@
|
||||
#include "GLGizmoSimplify.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/OpenGLManager.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/QuadricEdgeCollapse.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
// Extend call after only when Simplify gizmo is still alive
|
||||
static void call_after_if_active(std::function<void()> fn, GUI_App* app = &wxGetApp())
|
||||
{
|
||||
// check application GUI
|
||||
if (app == nullptr) return;
|
||||
app->CallAfter([fn, app]() {
|
||||
// app must exist because it call this
|
||||
// if (app == nullptr) return;
|
||||
const Plater *plater = app->plater();
|
||||
if (plater == nullptr) return;
|
||||
const GLCanvas3D *canvas = plater->canvas3D();
|
||||
if (canvas == nullptr) return;
|
||||
const GLGizmosManager &mng = canvas->get_gizmos_manager();
|
||||
// check if simplify is still activ gizmo
|
||||
if (mng.get_current_type() != GLGizmosManager::Simplify) return;
|
||||
fn();
|
||||
});
|
||||
}
|
||||
|
||||
static std::set<ObjectID> get_volume_ids(const Selection &selection)
|
||||
{
|
||||
const Selection::IndicesList &volume_ids = selection.get_volume_idxs();
|
||||
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
|
||||
std::set<ObjectID> result;
|
||||
for (auto volume_id : volume_ids) {
|
||||
const GLVolume *selected_volume = selection.get_volume(volume_id);
|
||||
assert(selected_volume != nullptr);
|
||||
|
||||
const GLVolume::CompositeID &cid = selected_volume->composite_id;
|
||||
|
||||
assert(cid.object_id >= 0);
|
||||
assert(model_objects.size() > static_cast<size_t>(cid.object_id));
|
||||
|
||||
const ModelObject *obj = model_objects[cid.object_id];
|
||||
const ModelVolume *volume = obj->volumes[cid.volume_id];
|
||||
const ObjectID & id = volume->id();
|
||||
|
||||
// prevent selection of volume without inidces
|
||||
if (volume->mesh().its.indices.empty()) continue;
|
||||
|
||||
assert(result.find(id) == result.end());
|
||||
result.insert(id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// return ModelVolume from selection by object id
|
||||
static ModelVolume *get_volume(const ObjectID &id, const Selection &selection) {
|
||||
const Selection::IndicesList &volume_ids = selection.get_volume_idxs();
|
||||
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
|
||||
for (auto volume_id : volume_ids) {
|
||||
const GLVolume *selected_volume = selection.get_volume(volume_id);
|
||||
const GLVolume::CompositeID &cid = selected_volume->composite_id;
|
||||
ModelObject *obj = model_objects[cid.object_id];
|
||||
ModelVolume *volume = obj->volumes[cid.volume_id];
|
||||
if (id == volume->id()) return volume;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::string create_volumes_name(const std::set<ObjectID>& ids, const Selection &selection){
|
||||
assert(!ids.empty());
|
||||
std::string name;
|
||||
bool is_first = true;
|
||||
for (const ObjectID &id : ids) {
|
||||
if (is_first)
|
||||
is_first = false;
|
||||
else
|
||||
name += " + ";
|
||||
|
||||
const ModelVolume *volume = get_volume(id, selection);
|
||||
assert(volume != nullptr);
|
||||
name += volume->name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D &parent)
|
||||
: GLGizmoBase(parent, M_ICON_FILENAME, -1)
|
||||
, m_show_wireframe(false)
|
||||
, m_move_to_center(false)
|
||||
, m_original_triangle_count(0)
|
||||
, m_triangle_count(0)
|
||||
// translation for GUI size
|
||||
, tr_mesh_name(_u8L("Mesh name"))
|
||||
, tr_triangles(_u8L("Triangles"))
|
||||
, tr_detail_level(_u8L("Level of detail"))
|
||||
, tr_decimate_ratio(_u8L("Decimate ratio"))
|
||||
{}
|
||||
|
||||
GLGizmoSimplify::~GLGizmoSimplify()
|
||||
{
|
||||
stop_worker_thread_request();
|
||||
if (m_worker.joinable())
|
||||
m_worker.join();
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::on_esc_key_down() {
|
||||
//close();
|
||||
return stop_worker_thread_request();
|
||||
}
|
||||
|
||||
// while opening needs GLGizmoSimplify to set window position
|
||||
void GLGizmoSimplify::add_simplify_suggestion_notification(
|
||||
const std::vector<size_t> &object_ids,
|
||||
const std::vector<ModelObject*>& objects,
|
||||
NotificationManager & manager)
|
||||
{
|
||||
std::vector<size_t> big_ids;
|
||||
big_ids.reserve(object_ids.size());
|
||||
auto is_big_object = [&objects](size_t object_id) {
|
||||
const uint32_t triangles_to_suggest_simplify = 1000000;
|
||||
if (object_id >= objects.size()) return false; // out of object index
|
||||
ModelVolumePtrs &volumes = objects[object_id]->volumes;
|
||||
if (volumes.size() != 1) return false; // not only one volume
|
||||
size_t triangle_count = volumes.front()->mesh().its.indices.size();
|
||||
if (triangle_count < triangles_to_suggest_simplify)
|
||||
return false; // small volume
|
||||
return true;
|
||||
};
|
||||
std::copy_if(object_ids.begin(), object_ids.end(),
|
||||
std::back_inserter(big_ids), is_big_object);
|
||||
if (big_ids.empty()) return;
|
||||
|
||||
for (size_t object_id : big_ids) {
|
||||
std::string t = GUI::format(_L(
|
||||
"Processing model \"%1%\" with more than 1M triangles "
|
||||
"could be slow. It is highly recommended to reduce "
|
||||
"amount of triangles."), objects[object_id]->name);
|
||||
std::string hypertext = _u8L("Simplify model");
|
||||
|
||||
std::function<bool(wxEvtHandler *)> open_simplify =
|
||||
[object_id](wxEvtHandler *) {
|
||||
auto plater = wxGetApp().plater();
|
||||
if (object_id >= plater->model().objects.size()) return true;
|
||||
|
||||
Selection &selection = plater->canvas3D()->get_selection();
|
||||
selection.clear();
|
||||
selection.add_object((unsigned int) object_id);
|
||||
|
||||
auto &manager = plater->canvas3D()->get_gizmos_manager();
|
||||
bool close_notification = true;
|
||||
if(!manager.open_gizmo(GLGizmosManager::Simplify))
|
||||
return close_notification;
|
||||
GLGizmoSimplify* simplify = dynamic_cast<GLGizmoSimplify*>(manager.get_current());
|
||||
if (simplify == nullptr) return close_notification;
|
||||
simplify->set_center_position();
|
||||
return close_notification;
|
||||
};
|
||||
manager.push_simplify_suggestion_notification(
|
||||
t, objects[object_id]->id(), hypertext, open_simplify);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GLGizmoSimplify::on_get_name() const
|
||||
{
|
||||
return _u8L("Simplify");
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
create_gui_cfg();
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
auto act_volume_ids = get_volume_ids(selection);
|
||||
if (act_volume_ids.empty()) {
|
||||
stop_worker_thread_request();
|
||||
close();
|
||||
if (! m_parent.get_selection().is_single_volume()) {
|
||||
MessageDialog msg((wxWindow*)wxGetApp().mainframe,
|
||||
_L("Simplification is currently only allowed when a single part is selected"),
|
||||
_L("Error"));
|
||||
msg.ShowModal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_cancelling = false;
|
||||
bool is_worker_running = false;
|
||||
bool is_result_ready = false;
|
||||
int progress = 0;
|
||||
{
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
is_cancelling = m_state.status == State::cancelling;
|
||||
is_worker_running = m_state.status == State::running;
|
||||
is_result_ready = !m_state.result.empty();
|
||||
progress = m_state.progress;
|
||||
}
|
||||
|
||||
// Whether to trigger calculation after rendering is done.
|
||||
bool start_process = false;
|
||||
|
||||
// Check selection of new volume (or change)
|
||||
// Do not reselect object when processing
|
||||
if (m_volume_ids != act_volume_ids) {
|
||||
bool change_window_position = m_volume_ids.empty();
|
||||
// select different model
|
||||
|
||||
// close suggestion notification
|
||||
auto notification_manager = wxGetApp().plater()->get_notification_manager();
|
||||
for (const auto &id : act_volume_ids)
|
||||
notification_manager->remove_simplify_suggestion_with_id(id);
|
||||
|
||||
m_volume_ids = std::move(act_volume_ids);
|
||||
init_model();
|
||||
|
||||
// triangle count is calculated in init model
|
||||
m_original_triangle_count = m_triangle_count;
|
||||
|
||||
// Default value of configuration
|
||||
m_configuration.decimate_ratio = 50.; // default value
|
||||
m_configuration.fix_count_by_ratio(m_original_triangle_count);
|
||||
m_configuration.use_count = false;
|
||||
|
||||
// Create volumes name to describe what will be simplified
|
||||
std::string name = create_volumes_name(m_volume_ids, selection);
|
||||
if (name.length() > m_gui_cfg->max_char_in_name)
|
||||
name = name.substr(0, m_gui_cfg->max_char_in_name - 3) + "...";
|
||||
m_volumes_name = name;
|
||||
|
||||
// Start processing. If we switched from another object, process will
|
||||
// stop the background thread and it will restart itself later.
|
||||
start_process = true;
|
||||
|
||||
// set window position
|
||||
if (change_window_position) {
|
||||
ImVec2 pos;
|
||||
Size parent_size = m_parent.get_canvas_size();
|
||||
if (m_move_to_center) {
|
||||
m_move_to_center = false;
|
||||
pos = ImVec2(parent_size.get_width() / 2 - m_gui_cfg->window_offset_x,
|
||||
parent_size.get_height() / 2 - m_gui_cfg->window_offset_y);
|
||||
} else {
|
||||
// keep window wisible on canvas and close to mouse click
|
||||
pos = ImGui::GetMousePos();
|
||||
pos.x -= m_gui_cfg->window_offset_x;
|
||||
pos.y -= m_gui_cfg->window_offset_y;
|
||||
// minimal top left value
|
||||
ImVec2 tl(m_gui_cfg->window_padding,
|
||||
m_gui_cfg->window_padding + m_parent.get_main_toolbar_height());
|
||||
if (pos.x < tl.x) pos.x = tl.x;
|
||||
if (pos.y < tl.y) pos.y = tl.y;
|
||||
// maximal bottom right value
|
||||
ImVec2 br(parent_size.get_width() - (2 * m_gui_cfg->window_offset_x + m_gui_cfg->window_padding),
|
||||
parent_size.get_height() -(2 * m_gui_cfg->window_offset_y + m_gui_cfg->window_padding));
|
||||
if (pos.x > br.x) pos.x = br.x;
|
||||
if (pos.y > br.y) pos.y = br.y;
|
||||
}
|
||||
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_multipart = (m_volume_ids.size() > 1);
|
||||
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoCollapse;
|
||||
m_imgui->begin(on_get_name(), flag);
|
||||
//B18
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, tr_mesh_name + ":");
|
||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
||||
m_imgui->text(m_volumes_name);
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_BLUE_LIGHT, tr_triangles + ":");
|
||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
||||
|
||||
m_imgui->text(std::to_string(m_original_triangle_count));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if(ImGui::RadioButton("##use_error", !m_configuration.use_count) && !is_multipart) {
|
||||
m_configuration.use_count = !m_configuration.use_count;
|
||||
start_process = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
m_imgui->disabled_begin(m_configuration.use_count);
|
||||
ImGui::Text("%s", tr_detail_level.c_str());
|
||||
std::vector<std::string> reduce_captions = {
|
||||
static_cast<std::string>(_u8L("Extra high")),
|
||||
static_cast<std::string>(_u8L("High")),
|
||||
static_cast<std::string>(_u8L("Medium")),
|
||||
static_cast<std::string>(_u8L("Low")),
|
||||
static_cast<std::string>(_u8L("Extra low"))
|
||||
};
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
||||
static int reduction = 2;
|
||||
if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) {
|
||||
if (reduction < 0) reduction = 0;
|
||||
if (reduction > 4) reduction = 4;
|
||||
switch (reduction) {
|
||||
case 0: m_configuration.max_error = 1e-3f; break;
|
||||
case 1: m_configuration.max_error = 1e-2f; break;
|
||||
case 2: m_configuration.max_error = 0.1f; break;
|
||||
case 3: m_configuration.max_error = 0.5f; break;
|
||||
case 4: m_configuration.max_error = 1.f; break;
|
||||
}
|
||||
start_process = true;
|
||||
}
|
||||
m_imgui->disabled_end(); // !use_count
|
||||
|
||||
if (ImGui::RadioButton("##use_count", m_configuration.use_count) && !is_multipart) {
|
||||
m_configuration.use_count = !m_configuration.use_count;
|
||||
start_process = true;
|
||||
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_multipart)
|
||||
ImGui::SetTooltip("%s", _u8L("A multipart object can be simplified using only a Level of detail. "
|
||||
"If you want to enter a Decimate ratio, do the simplification separately.").c_str());
|
||||
ImGui::SameLine();
|
||||
|
||||
// show preview result triangle count (percent)
|
||||
if (!m_configuration.use_count) {
|
||||
m_configuration.wanted_count = static_cast<uint32_t>(m_triangle_count);
|
||||
m_configuration.decimate_ratio =
|
||||
(1.0f - (m_configuration.wanted_count / (float) m_original_triangle_count)) * 100.f;
|
||||
}
|
||||
|
||||
m_imgui->disabled_begin(!m_configuration.use_count);
|
||||
ImGui::Text("%s", tr_decimate_ratio.c_str());
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
||||
const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%":
|
||||
((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%");
|
||||
|
||||
if(m_imgui->slider_float("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)){
|
||||
if (m_configuration.decimate_ratio < 0.f)
|
||||
m_configuration.decimate_ratio = 0.01f;
|
||||
if (m_configuration.decimate_ratio > 100.f)
|
||||
m_configuration.decimate_ratio = 100.f;
|
||||
m_configuration.fix_count_by_ratio(m_original_triangle_count);
|
||||
start_process = true;
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
ImGui::Text(_u8L("%d triangles").c_str(), m_configuration.wanted_count);
|
||||
m_imgui->disabled_end(); // use_count
|
||||
|
||||
ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe);
|
||||
|
||||
m_imgui->disabled_begin(is_cancelling);
|
||||
if (m_imgui->button(_L("Close"))) {
|
||||
close();
|
||||
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_cancelling)
|
||||
ImGui::SetTooltip("%s", _u8L("Operation already cancelling. Please wait few seconds.").c_str());
|
||||
m_imgui->disabled_end(); // state cancelling
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
m_imgui->disabled_begin(is_worker_running || ! is_result_ready);
|
||||
if (m_imgui->button(_L("Apply"))) {
|
||||
apply_simplify();
|
||||
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_worker_running)
|
||||
ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str());
|
||||
m_imgui->disabled_end(); // state !settings
|
||||
|
||||
// draw progress bar
|
||||
if (is_worker_running) { // apply or preview
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
// draw progress bar
|
||||
std::string progress_text = GUI::format(_L("Process %1% / 100"), std::to_string(progress));
|
||||
ImVec2 progress_size(m_gui_cfg->input_width, 0.f);
|
||||
ImGui::ProgressBar(progress / 100., progress_size, progress_text.c_str());
|
||||
}
|
||||
m_imgui->end();
|
||||
if (start_process)
|
||||
process();
|
||||
}
|
||||
|
||||
|
||||
void GLGizmoSimplify::close() {
|
||||
// close gizmo == open it again
|
||||
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
|
||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::stop_worker_thread_request()
|
||||
{
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
if (m_state.status != State::running) return false;
|
||||
|
||||
m_state.status = State::Status::cancelling;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Following is called from a UI thread when the worker terminates
|
||||
// worker calls it through a CallAfter.
|
||||
void GLGizmoSimplify::worker_finished()
|
||||
{
|
||||
{
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
if (m_state.status == State::running) {
|
||||
// Someone started the worker again, before this callback
|
||||
// was called. Do nothing.
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (m_worker.joinable())
|
||||
m_worker.join();
|
||||
if (GLGizmoBase::m_state == Off)
|
||||
return;
|
||||
const auto &result = m_state.result;
|
||||
if (!result.empty())
|
||||
update_model(result);
|
||||
|
||||
if (m_state.config != m_configuration || m_state.volume_ids != m_volume_ids) {
|
||||
// Settings were changed, restart the worker immediately.
|
||||
process();
|
||||
}
|
||||
request_rerender(true);
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::process()
|
||||
{
|
||||
if (m_volume_ids.empty()) return;
|
||||
|
||||
// m_volume->mesh().its.indices.empty()
|
||||
bool configs_match = false;
|
||||
bool result_valid = false;
|
||||
bool is_worker_running = false;
|
||||
{
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
configs_match = (m_volume_ids == m_state.volume_ids && m_state.config == m_configuration);
|
||||
result_valid = !m_state.result.empty();
|
||||
is_worker_running = m_state.status == State::running;
|
||||
}
|
||||
|
||||
if ((result_valid || is_worker_running) && configs_match) {
|
||||
// Either finished or waiting for result already. Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_worker_running && ! configs_match) {
|
||||
// Worker is running with outdated config. Stop it. It will
|
||||
// restart itself when cancellation is done.
|
||||
stop_worker_thread_request();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_worker.joinable()) {
|
||||
// This can happen when process() is called after previous worker terminated,
|
||||
// but before the worker_finished callback was called. In this case, just join the thread,
|
||||
// the callback will check this and do nothing.
|
||||
m_worker.join();
|
||||
}
|
||||
|
||||
// Copy configuration that will be used.
|
||||
m_state.config = m_configuration;
|
||||
m_state.volume_ids = m_volume_ids;
|
||||
m_state.status = State::running;
|
||||
|
||||
// Create a copy of current meshes to pass to the worker thread.
|
||||
// Using unique_ptr instead of pass-by-value to avoid an extra
|
||||
// copy (which would happen when passing to std::thread).
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
State::Data its;
|
||||
for (const auto &id : m_volume_ids) {
|
||||
const ModelVolume *volume = get_volume(id, selection);
|
||||
its[id] = std::make_unique<indexed_triangle_set>(volume->mesh().its); // copy
|
||||
}
|
||||
|
||||
m_worker = std::thread([this](State::Data its) {
|
||||
|
||||
// Checks that the UI thread did not request cancellation, throws if so.
|
||||
std::function<void(void)> throw_on_cancel = [this]() {
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
if (m_state.status == State::cancelling)
|
||||
throw SimplifyCanceledException();
|
||||
};
|
||||
|
||||
// Called by worker thread, updates progress bar.
|
||||
// Using CallAfter so the rerequest function is run in UI thread.
|
||||
std::function<void(int)> statusfn = [this](int percent) {
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
m_state.progress = percent;
|
||||
call_after_if_active([this]() { request_rerender(); });
|
||||
};
|
||||
|
||||
// Initialize.
|
||||
uint32_t triangle_count = 0;
|
||||
float max_error = std::numeric_limits<float>::max();
|
||||
{
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
if (m_state.config.use_count)
|
||||
triangle_count = m_state.config.wanted_count;
|
||||
if (! m_state.config.use_count)
|
||||
max_error = m_state.config.max_error;
|
||||
m_state.progress = 0;
|
||||
m_state.result.clear();
|
||||
m_state.status = State::Status::running;
|
||||
}
|
||||
|
||||
// Start the actual calculation.
|
||||
try {
|
||||
for (const auto& it : its) {
|
||||
float me = max_error;
|
||||
its_quadric_edge_collapse(*it.second, triangle_count, &me, throw_on_cancel, statusfn);
|
||||
}
|
||||
} catch (SimplifyCanceledException &) {
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
m_state.status = State::idle;
|
||||
}
|
||||
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
if (m_state.status == State::Status::running) {
|
||||
// We were not cancelled, the result is valid.
|
||||
m_state.status = State::Status::idle;
|
||||
m_state.result = std::move(its);
|
||||
}
|
||||
|
||||
// Update UI. Use CallAfter so the function is run on UI thread.
|
||||
call_after_if_active([this]() { worker_finished(); });
|
||||
}, std::move(its));
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::apply_simplify() {
|
||||
// worker must be stopped
|
||||
assert(m_state.status == State::Status::idle);
|
||||
|
||||
// check that there is NO change of volume
|
||||
assert(m_state.volume_ids == m_volume_ids);
|
||||
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
auto plater = wxGetApp().plater();
|
||||
// TRN %1% = volumes name
|
||||
plater->take_snapshot(Slic3r::format(_u8L("Simplify %1%"), create_volumes_name(m_volume_ids, selection)));
|
||||
plater->clear_before_change_mesh(selection.get_object_idx(), _u8L("Custom supports, seams and multimaterial painting were "
|
||||
"removed after simplifying the mesh."));
|
||||
// After removing custom supports, seams, and multimaterial painting, we have to update info about the object to remove information about
|
||||
// custom supports, seams, and multimaterial painting in the right panel.
|
||||
wxGetApp().obj_list()->update_info_items(selection.get_object_idx());
|
||||
|
||||
for (const auto &item: m_state.result) {
|
||||
const ObjectID &id = item.first;
|
||||
const indexed_triangle_set &its = *item.second;
|
||||
ModelVolume *volume = get_volume(id, selection);
|
||||
assert(volume != nullptr);
|
||||
ModelObject *obj = volume->get_object();
|
||||
|
||||
volume->set_mesh(std::move(its));
|
||||
volume->calculate_convex_hull();
|
||||
volume->set_new_unique_id();
|
||||
obj->invalidate_bounding_box();
|
||||
obj->ensure_on_bed(true); // allow negative z
|
||||
}
|
||||
m_state.result.clear();
|
||||
// fix hollowing, sla support points, modifiers, ...
|
||||
int object_idx = selection.get_object_idx();
|
||||
plater->changed_mesh(object_idx);
|
||||
// Fix warning icon in object list
|
||||
wxGetApp().obj_list()->update_item_error_icon(object_idx, -1);
|
||||
close();
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::on_is_activable() const
|
||||
{
|
||||
return !m_parent.get_selection().is_empty();
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::on_set_state()
|
||||
{
|
||||
// Closing gizmo. e.g. selecting another one
|
||||
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
|
||||
m_parent.toggle_model_objects_visibility(true);
|
||||
|
||||
stop_worker_thread_request();
|
||||
m_volume_ids.clear(); // invalidate selected model
|
||||
m_glmodels.clear(); // free gpu memory
|
||||
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
|
||||
// when open by hyperlink it needs to show up
|
||||
request_rerender();
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::create_gui_cfg() {
|
||||
if (m_gui_cfg.has_value()) return;
|
||||
int space_size = m_imgui->calc_text_size(std::string_view{":MM"}).x;
|
||||
GuiCfg cfg;
|
||||
cfg.top_left_width = std::max(m_imgui->calc_text_size(tr_mesh_name).x,
|
||||
m_imgui->calc_text_size(tr_triangles).x)
|
||||
+ space_size;
|
||||
|
||||
const float radio_size = ImGui::GetFrameHeight();
|
||||
cfg.bottom_left_width =
|
||||
std::max(m_imgui->calc_text_size(tr_detail_level).x,
|
||||
m_imgui->calc_text_size(tr_decimate_ratio).x) +
|
||||
space_size + radio_size;
|
||||
|
||||
cfg.input_width = cfg.bottom_left_width * 1.5;
|
||||
cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2;
|
||||
cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5;
|
||||
|
||||
m_gui_cfg = cfg;
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::request_rerender(bool force) {
|
||||
int64_t now = m_parent.timestamp_now();
|
||||
if (force || now > m_last_rerender_timestamp + 250) { // 250 ms
|
||||
set_dirty();
|
||||
m_parent.schedule_extra_frame(0);
|
||||
m_last_rerender_timestamp = now;
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::set_center_position() {
|
||||
m_move_to_center = true;
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::init_model()
|
||||
{
|
||||
// volume ids must be set before init model
|
||||
assert(!m_volume_ids.empty());
|
||||
|
||||
m_parent.toggle_model_objects_visibility(true); // selected volume may have changed
|
||||
const auto info = m_c->selection_info();
|
||||
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
Model & model = *selection.get_model();
|
||||
const Selection::IndicesList &volume_ids = selection.get_volume_idxs();
|
||||
const ModelObjectPtrs &model_objects = model.objects;
|
||||
|
||||
m_glmodels.clear();
|
||||
//m_glmodels.reserve(volume_ids.size());
|
||||
m_triangle_count = 0;
|
||||
for (const ObjectID& id: m_volume_ids) {
|
||||
|
||||
const GLVolume *selected_volume;
|
||||
const ModelVolume *volume = nullptr;
|
||||
for (auto volume_id : volume_ids) {
|
||||
selected_volume = selection.get_volume(volume_id);
|
||||
const GLVolume::CompositeID &cid = selected_volume->composite_id;
|
||||
ModelObject * obj = model_objects[cid.object_id];
|
||||
ModelVolume * act_volume = obj->volumes[cid.volume_id];
|
||||
if (id == act_volume->id()) {
|
||||
volume = act_volume;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(volume != nullptr);
|
||||
|
||||
// set actual triangle count
|
||||
m_triangle_count += volume->mesh().its.indices.size();
|
||||
|
||||
assert(m_glmodels.find(id) == m_glmodels.end());
|
||||
GLModel &glmodel = m_glmodels[id]; // create new glmodel
|
||||
glmodel.init_from(volume->mesh());
|
||||
glmodel.set_color(selected_volume->color);
|
||||
|
||||
m_parent.toggle_model_objects_visibility(false, info->model_object(),
|
||||
info->get_active_instance(),
|
||||
volume);
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::update_model(const State::Data &data)
|
||||
{
|
||||
// check that model exist
|
||||
if (m_glmodels.empty()) return;
|
||||
|
||||
// check that result is for actual gl models
|
||||
size_t model_count = m_glmodels.size();
|
||||
if (data.size() != model_count) return;
|
||||
|
||||
m_triangle_count = 0;
|
||||
for (const auto &item : data) {
|
||||
const indexed_triangle_set &its = *item.second;
|
||||
|
||||
auto it = m_glmodels.find(item.first);
|
||||
assert(it != m_glmodels.end());
|
||||
|
||||
GLModel &glmodel = it->second;
|
||||
auto color = glmodel.get_color();
|
||||
// when not reset it keeps old shape
|
||||
glmodel.reset();
|
||||
#if ENABLE_OPENGL_ES
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3E3 };
|
||||
init_data.reserve_vertices(3 * its.indices.size());
|
||||
init_data.reserve_indices(3 * its.indices.size());
|
||||
|
||||
// vertices + indices
|
||||
std::array<Vec3f, 3> barycentric_coords = { Vec3f::UnitX(), Vec3f::UnitY(), Vec3f::UnitZ() };
|
||||
unsigned int vertices_counter = 0;
|
||||
for (uint32_t i = 0; i < its.indices.size(); ++i) {
|
||||
const stl_triangle_vertex_indices face = its.indices[i];
|
||||
const stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] };
|
||||
const stl_vertex n = face_normal_normalized(vertex);
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
init_data.add_vertex(vertex[j], n, barycentric_coords[j]);
|
||||
}
|
||||
vertices_counter += 3;
|
||||
init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
|
||||
}
|
||||
|
||||
glmodel.init_from(std::move(init_data));
|
||||
#else
|
||||
glmodel.init_from(its);
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
glmodel.set_color(color);
|
||||
|
||||
m_triangle_count += its.indices.size();
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::on_render()
|
||||
{
|
||||
if (m_glmodels.empty()) return;
|
||||
|
||||
const Selection & selection = m_parent.get_selection();
|
||||
|
||||
// Check that the GLVolume still belongs to the ModelObject we work on.
|
||||
if (m_volume_ids != get_volume_ids(selection)) return;
|
||||
|
||||
const ModelObjectPtrs &model_objects = selection.get_model()->objects;
|
||||
const Selection::IndicesList &volume_idxs = selection.get_volume_idxs();
|
||||
|
||||
// no need to render nothing
|
||||
if (volume_idxs.empty()) return;
|
||||
|
||||
// Iteration over selection because of world transformation matrix of object
|
||||
for (auto volume_id : volume_idxs) {
|
||||
const GLVolume *selected_volume = selection.get_volume(volume_id);
|
||||
const GLVolume::CompositeID &cid = selected_volume->composite_id;
|
||||
|
||||
ModelObject *obj = model_objects[cid.object_id];
|
||||
ModelVolume *volume = obj->volumes[cid.volume_id];
|
||||
|
||||
auto it = m_glmodels.find(volume->id());
|
||||
assert(it != m_glmodels.end());
|
||||
|
||||
GLModel &glmodel = it->second;
|
||||
|
||||
const Transform3d trafo_matrix = selected_volume->world_matrix();
|
||||
auto* gouraud_shader = wxGetApp().get_shader("gouraud_light");
|
||||
#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
bool depth_test_enabled = ::glIsEnabled(GL_DEPTH_TEST);
|
||||
#else
|
||||
glsafe(::glPushAttrib(GL_DEPTH_TEST));
|
||||
#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
gouraud_shader->start_using();
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Transform3d& view_matrix = camera.get_view_matrix();
|
||||
const Transform3d view_model_matrix = view_matrix * trafo_matrix;
|
||||
gouraud_shader->set_uniform("view_model_matrix", view_model_matrix);
|
||||
gouraud_shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
gouraud_shader->set_uniform("view_normal_matrix", view_normal_matrix);
|
||||
glmodel.render();
|
||||
gouraud_shader->stop_using();
|
||||
|
||||
if (m_show_wireframe) {
|
||||
#if ENABLE_OPENGL_ES
|
||||
auto* contour_shader = wxGetApp().get_shader("wireframe");
|
||||
#else
|
||||
auto *contour_shader = wxGetApp().get_shader("mm_contour");
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
contour_shader->start_using();
|
||||
contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001);
|
||||
contour_shader->set_uniform("view_model_matrix", view_model_matrix);
|
||||
contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
const ColorRGBA color = glmodel.get_color();
|
||||
glmodel.set_color(ColorRGBA::WHITE());
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (!OpenGLManager::get_gl_info().is_core_profile())
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
glsafe(::glLineWidth(1.0f));
|
||||
#if !ENABLE_OPENGL_ES
|
||||
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE));
|
||||
#endif // !ENABLE_OPENGL_ES
|
||||
glmodel.render();
|
||||
#if !ENABLE_OPENGL_ES
|
||||
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL));
|
||||
#endif // !ENABLE_OPENGL_ES
|
||||
glmodel.set_color(color);
|
||||
contour_shader->stop_using();
|
||||
}
|
||||
#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
if (depth_test_enabled)
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
#else
|
||||
glsafe(::glPopAttrib());
|
||||
#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
}
|
||||
}
|
||||
|
||||
CommonGizmosDataID GLGizmoSimplify::on_get_requirements() const
|
||||
{
|
||||
return CommonGizmosDataID(
|
||||
int(CommonGizmosDataID::SelectionInfo));
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::Configuration::fix_count_by_ratio(size_t triangle_count)
|
||||
{
|
||||
if (decimate_ratio <= 0.f)
|
||||
wanted_count = static_cast<uint32_t>(triangle_count);
|
||||
else if (decimate_ratio >= 100.f)
|
||||
wanted_count = 0;
|
||||
else
|
||||
wanted_count = static_cast<uint32_t>(std::round(
|
||||
triangle_count * (100.f - decimate_ratio) / 100.f));
|
||||
}
|
||||
|
||||
// any existing icon filename to not influence GUI
|
||||
const std::string GLGizmoSimplify::M_ICON_FILENAME = "cut.svg";
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
162
src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp
Normal file
162
src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#ifndef slic3r_GLGizmoSimplify_hpp_
|
||||
#define slic3r_GLGizmoSimplify_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "admesh/stl.h" // indexed_triangle_set
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace Slic3r {
|
||||
class ModelObject;
|
||||
class Model;
|
||||
class ObjectID;
|
||||
|
||||
namespace GUI {
|
||||
class NotificationManager; // for simplify suggestion
|
||||
|
||||
class GLGizmoSimplify: public GLGizmoBase
|
||||
{
|
||||
public:
|
||||
GLGizmoSimplify(GLCanvas3D& parent);
|
||||
virtual ~GLGizmoSimplify();
|
||||
bool on_esc_key_down();
|
||||
static void add_simplify_suggestion_notification(
|
||||
const std::vector<size_t> &object_ids,
|
||||
const std::vector<ModelObject*> & objects,
|
||||
NotificationManager & manager);
|
||||
|
||||
protected:
|
||||
virtual std::string on_get_name() const override;
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
virtual bool on_is_activable() const override;
|
||||
virtual bool on_is_selectable() const override { return false; }
|
||||
virtual void on_set_state() override;
|
||||
|
||||
// must implement
|
||||
virtual bool on_init() override { return true;};
|
||||
virtual void on_render() override;
|
||||
|
||||
CommonGizmosDataID on_get_requirements() const override;
|
||||
|
||||
private:
|
||||
void apply_simplify();
|
||||
void close();
|
||||
|
||||
void process();
|
||||
bool stop_worker_thread_request();
|
||||
void worker_finished();
|
||||
|
||||
void create_gui_cfg();
|
||||
void request_rerender(bool force = false);
|
||||
|
||||
void set_center_position();
|
||||
|
||||
struct Configuration
|
||||
{
|
||||
bool use_count = false;
|
||||
float decimate_ratio = 50.f; // in percent
|
||||
uint32_t wanted_count = 0; // initialize by percents
|
||||
float max_error = 1.; // maximal quadric error
|
||||
|
||||
void fix_count_by_ratio(size_t triangle_count);
|
||||
bool operator==(const Configuration& rhs) {
|
||||
return (use_count == rhs.use_count && decimate_ratio == rhs.decimate_ratio
|
||||
&& wanted_count == rhs.wanted_count && max_error == rhs.max_error);
|
||||
}
|
||||
bool operator!=(const Configuration& rhs) {
|
||||
return ! (*this == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
Configuration m_configuration;
|
||||
|
||||
bool m_move_to_center; // opening gizmo
|
||||
|
||||
std::set<ObjectID> m_volume_ids; // keep pointers to actual working volumes
|
||||
std::string m_volumes_name;
|
||||
size_t m_original_triangle_count;
|
||||
|
||||
bool m_show_wireframe;
|
||||
std::map<ObjectID, GLModel> m_glmodels;
|
||||
|
||||
|
||||
size_t m_triangle_count; // triangle count of the model currently shown
|
||||
|
||||
// Timestamp of the last rerender request. Only accessed from UI thread.
|
||||
int64_t m_last_rerender_timestamp = std::numeric_limits<int64_t>::min();
|
||||
|
||||
// Following struct is accessed by both UI and worker thread.
|
||||
// Accesses protected by a mutex.
|
||||
struct State {
|
||||
//using Data = std::vector<std::unique_ptr<indexed_triangle_set> >;
|
||||
using Data = std::map<ObjectID, std::unique_ptr<indexed_triangle_set> >;
|
||||
enum Status {
|
||||
idle,
|
||||
running,
|
||||
cancelling
|
||||
};
|
||||
|
||||
Status status = idle;
|
||||
int progress = 0; // percent of done work
|
||||
Configuration config; // Configuration we started with.
|
||||
const ModelObject* mo = nullptr;
|
||||
|
||||
Data result;
|
||||
std::set<ObjectID> volume_ids; // is same as result keys - store separate for faster check
|
||||
};
|
||||
|
||||
void init_model(); // initialize glModels from selection
|
||||
void update_model(const State::Data &data);
|
||||
|
||||
std::thread m_worker;
|
||||
std::mutex m_state_mutex; // guards m_state
|
||||
State m_state; // accessed by both threads
|
||||
|
||||
|
||||
// This configs holds GUI layout size given by translated texts.
|
||||
// etc. When language changes, GUI is recreated and this class constructed again,
|
||||
// so the change takes effect. (info by GLGizmoFdmSupports.hpp)
|
||||
struct GuiCfg
|
||||
{
|
||||
int top_left_width = 100;
|
||||
int bottom_left_width = 100;
|
||||
int input_width = 100;
|
||||
int window_offset_x = 100;
|
||||
int window_offset_y = 100;
|
||||
int window_padding = 0;
|
||||
|
||||
// trunc model name when longer
|
||||
size_t max_char_in_name = 30;
|
||||
|
||||
// to prevent freezing when move in gui
|
||||
// delay before process in [ms]
|
||||
std::chrono::duration<long int, std::milli> prcess_delay = std::chrono::milliseconds(250);
|
||||
};
|
||||
std::optional<GuiCfg> m_gui_cfg;
|
||||
|
||||
// translations used for calc window size
|
||||
const std::string tr_mesh_name;
|
||||
const std::string tr_triangles;
|
||||
const std::string tr_detail_level;
|
||||
const std::string tr_decimate_ratio;
|
||||
|
||||
// cancel exception
|
||||
class SimplifyCanceledException: public std::exception
|
||||
{
|
||||
public:
|
||||
const char *what() const throw()
|
||||
{
|
||||
return L("Model simplification has been canceled");
|
||||
}
|
||||
};
|
||||
|
||||
// only temporary solution
|
||||
static const std::string M_ICON_FILENAME;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoSimplify_hpp_
|
||||
217
src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp
Normal file
217
src/slic3r/GUI/Gizmos/GLGizmoSlaBase.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "GLGizmoSlaBase.hpp"
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
static const ColorRGBA DISABLED_COLOR = ColorRGBA::DARK_GRAY();
|
||||
static const int VOLUME_RAYCASTERS_BASE_ID = (int)SceneRaycaster::EIdBase::Gizmo;
|
||||
|
||||
GLGizmoSlaBase::GLGizmoSlaBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, SLAPrintObjectStep min_step)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||
, m_min_sla_print_object_step((int)min_step)
|
||||
{}
|
||||
|
||||
void GLGizmoSlaBase::reslice_until_step(SLAPrintObjectStep step, bool postpone_error_messages)
|
||||
{
|
||||
wxGetApp().CallAfter([this, step, postpone_error_messages]() {
|
||||
if (m_c->selection_info())
|
||||
wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages);
|
||||
else {
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const int object_idx = selection.get_object_idx();
|
||||
if (object_idx >= 0 && !selection.is_wipe_tower())
|
||||
wxGetApp().plater()->reslice_SLA_until_step(step, *wxGetApp().plater()->model().objects[object_idx], postpone_error_messages);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CommonGizmosDataID GLGizmoSlaBase::on_get_requirements() const
|
||||
{
|
||||
return CommonGizmosDataID(
|
||||
int(CommonGizmosDataID::SelectionInfo)
|
||||
| int(CommonGizmosDataID::InstancesHider)
|
||||
| int(CommonGizmosDataID::Raycaster)
|
||||
| int(CommonGizmosDataID::ObjectClipper)
|
||||
| int(CommonGizmosDataID::SupportsClipper));
|
||||
}
|
||||
|
||||
void GLGizmoSlaBase::update_volumes()
|
||||
{
|
||||
m_volumes.clear();
|
||||
unregister_volume_raycasters_for_picking();
|
||||
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
if (mo == nullptr)
|
||||
return;
|
||||
|
||||
const SLAPrintObject* po = m_c->selection_info()->print_object();
|
||||
if (po == nullptr)
|
||||
return;
|
||||
|
||||
m_input_enabled = false;
|
||||
|
||||
TriangleMesh backend_mesh;
|
||||
std::shared_ptr<const indexed_triangle_set> preview_mesh_ptr = po->get_mesh_to_print();
|
||||
if (preview_mesh_ptr != nullptr)
|
||||
backend_mesh = TriangleMesh(*preview_mesh_ptr);
|
||||
|
||||
if (!backend_mesh.empty()) {
|
||||
auto last_comp_step = static_cast<int>(po->last_completed_step());
|
||||
if (last_comp_step == slaposCount)
|
||||
last_comp_step = -1;
|
||||
|
||||
m_input_enabled = last_comp_step >= m_min_sla_print_object_step || po->model_object()->sla_points_status == sla::PointsStatus::UserModified;
|
||||
|
||||
const int object_idx = m_parent.get_selection().get_object_idx();
|
||||
const int instance_idx = m_parent.get_selection().get_instance_idx();
|
||||
const Geometry::Transformation& inst_trafo = po->model_object()->instances[instance_idx]->get_transformation();
|
||||
const double current_elevation = po->get_current_elevation();
|
||||
|
||||
auto add_volume = [this, object_idx, instance_idx, &inst_trafo, current_elevation](const TriangleMesh& mesh, int volume_id, bool add_mesh_raycaster = false) {
|
||||
GLVolume* volume = m_volumes.volumes.emplace_back(new GLVolume());
|
||||
volume->model.init_from(mesh);
|
||||
volume->set_instance_transformation(inst_trafo);
|
||||
volume->set_sla_shift_z(current_elevation);
|
||||
if (add_mesh_raycaster)
|
||||
volume->mesh_raycaster = std::make_unique<GUI::MeshRaycaster>(mesh);
|
||||
if (m_input_enabled)
|
||||
volume->selected = true; // to set the proper color
|
||||
else
|
||||
volume->set_color(DISABLED_COLOR);
|
||||
volume->composite_id = GLVolume::CompositeID(object_idx, volume_id, instance_idx);
|
||||
};
|
||||
|
||||
const Transform3d po_trafo_inverse = po->trafo().inverse();
|
||||
|
||||
// main mesh
|
||||
backend_mesh.transform(po_trafo_inverse);
|
||||
add_volume(backend_mesh, 0, true);
|
||||
|
||||
// supports mesh
|
||||
TriangleMesh supports_mesh = po->support_mesh();
|
||||
if (!supports_mesh.empty()) {
|
||||
supports_mesh.transform(po_trafo_inverse);
|
||||
add_volume(supports_mesh, -int(slaposSupportTree));
|
||||
}
|
||||
|
||||
// pad mesh
|
||||
TriangleMesh pad_mesh = po->pad_mesh();
|
||||
if (!pad_mesh.empty()) {
|
||||
pad_mesh.transform(po_trafo_inverse);
|
||||
add_volume(pad_mesh, -int(slaposPad));
|
||||
}
|
||||
}
|
||||
|
||||
if (m_volumes.volumes.empty()) {
|
||||
// No valid mesh found in the backend. Use the selection to duplicate the volumes
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const Selection::IndicesList& idxs = selection.get_volume_idxs();
|
||||
for (unsigned int idx : idxs) {
|
||||
const GLVolume* v = selection.get_volume(idx);
|
||||
if (!v->is_modifier) {
|
||||
m_volumes.volumes.emplace_back(new GLVolume());
|
||||
GLVolume* new_volume = m_volumes.volumes.back();
|
||||
const TriangleMesh& mesh = mo->volumes[v->volume_idx()]->mesh();
|
||||
new_volume->model.init_from(mesh);
|
||||
new_volume->set_instance_transformation(v->get_instance_transformation());
|
||||
new_volume->set_volume_transformation(v->get_volume_transformation());
|
||||
new_volume->set_sla_shift_z(v->get_sla_shift_z());
|
||||
new_volume->set_color(DISABLED_COLOR);
|
||||
new_volume->mesh_raycaster = std::make_unique<GUI::MeshRaycaster>(mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register_volume_raycasters_for_picking();
|
||||
}
|
||||
|
||||
void GLGizmoSlaBase::render_volumes()
|
||||
{
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light_clip");
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.0f);
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
|
||||
ClippingPlane clipping_plane = (m_c->object_clipper()->get_position() == 0.0) ? ClippingPlane::ClipsNothing() : *m_c->object_clipper()->get_clipping_plane();
|
||||
if (m_c->object_clipper()->get_position() != 0.0)
|
||||
clipping_plane.set_normal(-clipping_plane.get_normal());
|
||||
else
|
||||
// on Linux the clipping plane does not work when using DBL_MAX
|
||||
clipping_plane.set_offset(FLT_MAX);
|
||||
m_volumes.set_clipping_plane(clipping_plane.get_data());
|
||||
|
||||
for (GLVolume* v : m_volumes.volumes) {
|
||||
v->is_active = m_show_sla_supports || (!v->is_sla_pad() && !v->is_sla_support());
|
||||
}
|
||||
|
||||
m_volumes.render(GLVolumeCollection::ERenderType::Opaque, true, camera.get_view_matrix(), camera.get_projection_matrix());
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
void GLGizmoSlaBase::register_volume_raycasters_for_picking()
|
||||
{
|
||||
for (size_t i = 0; i < m_volumes.volumes.size(); ++i) {
|
||||
const GLVolume* v = m_volumes.volumes[i];
|
||||
if (!v->is_sla_pad() && !v->is_sla_support())
|
||||
m_volume_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, VOLUME_RAYCASTERS_BASE_ID + (int)i, *v->mesh_raycaster, v->world_matrix()));
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoSlaBase::unregister_volume_raycasters_for_picking()
|
||||
{
|
||||
for (size_t i = 0; i < m_volume_raycasters.size(); ++i) {
|
||||
m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, VOLUME_RAYCASTERS_BASE_ID + (int)i);
|
||||
}
|
||||
m_volume_raycasters.clear();
|
||||
}
|
||||
|
||||
// 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 GLGizmoSlaBase::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
|
||||
{
|
||||
if (m_c->raycaster()->raycasters().size() != 1)
|
||||
return false;
|
||||
if (!m_c->raycaster()->raycaster())
|
||||
return false;
|
||||
if (m_volumes.volumes.empty())
|
||||
return false;
|
||||
|
||||
auto *inst = m_c->selection_info()->model_instance();
|
||||
if (!inst)
|
||||
return false;
|
||||
|
||||
Transform3d trafo = m_volumes.volumes.front()->world_matrix();
|
||||
if (m_c->selection_info() && m_c->selection_info()->print_object()) {
|
||||
double shift_z = m_c->selection_info()->print_object()->get_current_elevation();
|
||||
trafo = inst->get_transformation().get_matrix();
|
||||
trafo.translation()(2) += shift_z;
|
||||
}
|
||||
|
||||
// The raycaster query
|
||||
Vec3f hit;
|
||||
Vec3f normal;
|
||||
if (m_c->raycaster()->raycaster()->unproject_on_mesh(
|
||||
mouse_pos,
|
||||
trafo/*m_volumes.volumes.front()->world_matrix()*/,
|
||||
wxGetApp().plater()->get_camera(),
|
||||
hit,
|
||||
normal,
|
||||
m_c->object_clipper()->get_position() != 0.0 ? m_c->object_clipper()->get_clipping_plane() : nullptr)) {
|
||||
// Return both the point and the facet normal.
|
||||
pos_and_normal = std::make_pair(hit, normal);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
59
src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp
Normal file
59
src/slic3r/GUI/Gizmos/GLGizmoSlaBase.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef slic3r_GLGizmoSlaBase_hpp_
|
||||
#define slic3r_GLGizmoSlaBase_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "slic3r/GUI/SceneRaycaster.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SLAPrint;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class GLCanvas3D;
|
||||
|
||||
class GLGizmoSlaBase : public GLGizmoBase
|
||||
{
|
||||
public:
|
||||
GLGizmoSlaBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, SLAPrintObjectStep min_step);
|
||||
|
||||
void reslice_until_step(SLAPrintObjectStep step, bool postpone_error_messages = false);
|
||||
|
||||
protected:
|
||||
virtual CommonGizmosDataID on_get_requirements() const override;
|
||||
|
||||
void update_volumes();
|
||||
void render_volumes();
|
||||
|
||||
void register_volume_raycasters_for_picking();
|
||||
void unregister_volume_raycasters_for_picking();
|
||||
|
||||
bool is_input_enabled() const { return m_input_enabled; }
|
||||
int get_min_sla_print_object_step() const { return m_min_sla_print_object_step; }
|
||||
|
||||
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
|
||||
|
||||
bool are_sla_supports_shown() const { return m_show_sla_supports; }
|
||||
void show_sla_supports(bool show) { m_show_sla_supports = show; }
|
||||
|
||||
const GLVolumeCollection &volumes() const { return m_volumes; }
|
||||
|
||||
private:
|
||||
GLVolumeCollection m_volumes;
|
||||
bool m_input_enabled{ false };
|
||||
bool m_show_sla_supports{ false };
|
||||
int m_min_sla_print_object_step{ -1 };
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_volume_raycasters;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoSlaBase_hpp_
|
||||
1297
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
Normal file
1297
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
Normal file
File diff suppressed because it is too large
Load Diff
165
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
Normal file
165
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#ifndef slic3r_GLGizmoSlaSupports_hpp_
|
||||
#define slic3r_GLGizmoSlaSupports_hpp_
|
||||
|
||||
#include "GLGizmoSlaBase.hpp"
|
||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
#include "libslic3r/SLA/SupportPoint.hpp"
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include <wx/dialog.h>
|
||||
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ConfigOption;
|
||||
|
||||
namespace GUI {
|
||||
class Selection;
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
|
||||
class GLGizmoSlaSupports : public GLGizmoSlaBase
|
||||
{
|
||||
private:
|
||||
static constexpr float RenderPointScale = 1.f;
|
||||
|
||||
class CacheEntry {
|
||||
public:
|
||||
CacheEntry() :
|
||||
support_point(sla::SupportPoint()), selected(false), normal(Vec3f::Zero()) {}
|
||||
|
||||
CacheEntry(const sla::SupportPoint& point, bool sel = false, const Vec3f& norm = Vec3f::Zero()) :
|
||||
support_point(point), selected(sel), normal(norm) {}
|
||||
|
||||
bool operator==(const CacheEntry& rhs) const {
|
||||
return (support_point == rhs.support_point);
|
||||
}
|
||||
|
||||
bool operator!=(const CacheEntry& rhs) const {
|
||||
return ! ((*this) == rhs);
|
||||
}
|
||||
|
||||
sla::SupportPoint support_point;
|
||||
bool selected; // whether the point is selected
|
||||
Vec3f normal;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive & ar)
|
||||
{
|
||||
ar(support_point, selected, normal);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
virtual ~GLGizmoSlaSupports() = default;
|
||||
void data_changed(bool is_serializing) override;
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
void delete_selected_points(bool force = false);
|
||||
//ClippingPlane get_sla_clipping_plane() const;
|
||||
|
||||
bool is_in_editing_mode() const override { return m_editing_mode; }
|
||||
bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); }
|
||||
bool has_backend_supports() const;
|
||||
|
||||
bool wants_enter_leave_snapshots() const override { return true; }
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering SLA support points"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving SLA support points"); }
|
||||
|
||||
/// <summary>
|
||||
/// Process mouse event
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
private:
|
||||
bool on_init() override;
|
||||
void on_render() override;
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
|
||||
void render_points(const Selection& selection);
|
||||
bool unsaved_changes() const;
|
||||
void register_point_raycasters_for_picking();
|
||||
void unregister_point_raycasters_for_picking();
|
||||
void update_point_raycasters_for_picking_transform();
|
||||
|
||||
bool m_lock_unique_islands = false;
|
||||
bool m_editing_mode = false; // Is editing mode active?
|
||||
float m_new_point_head_diameter; // Size of a new point.
|
||||
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
|
||||
float m_old_point_head_diameter = 0.; // the same
|
||||
float m_minimal_point_distance_stash = 0.f; // and again
|
||||
float m_density_stash = 0.f; // and again
|
||||
mutable std::vector<CacheEntry> m_editing_cache; // a support point and whether it is currently selected
|
||||
std::vector<sla::SupportPoint> m_normal_cache; // to restore after discarding changes or undo/redo
|
||||
ObjectID m_old_mo_id;
|
||||
|
||||
PickingModel m_sphere;
|
||||
PickingModel m_cone;
|
||||
std::vector<std::pair<std::shared_ptr<SceneRaycasterItem>, std::shared_ptr<SceneRaycasterItem>>> m_point_raycasters;
|
||||
|
||||
// This map holds all translated description texts, so they can be easily referenced during layout calculations
|
||||
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||
std::map<std::string, wxString> m_desc;
|
||||
|
||||
GLSelectionRectangle m_selection_rectangle;
|
||||
|
||||
bool m_wait_for_up_event = false;
|
||||
bool m_selection_empty = true;
|
||||
|
||||
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
|
||||
bool is_mesh_point_clipped(const Vec3d& point) const;
|
||||
//void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
|
||||
|
||||
// Methods that do the model_object and editing cache synchronization,
|
||||
// editing mode selection, etc:
|
||||
enum {
|
||||
AllPoints = -2,
|
||||
NoPoints,
|
||||
};
|
||||
void select_point(int i);
|
||||
void unselect_point(int i);
|
||||
void editing_mode_apply_changes();
|
||||
void editing_mode_discard_changes();
|
||||
void reload_cache();
|
||||
void get_data_from_backend();
|
||||
void auto_generate();
|
||||
void switch_to_editing_mode();
|
||||
void disable_editing_mode();
|
||||
|
||||
// return false if Cancel was selected
|
||||
bool ask_about_changes(std::function<void()> on_yes, std::function<void()> on_no);
|
||||
|
||||
protected:
|
||||
void on_set_state() override;
|
||||
void on_set_hover_id() override {
|
||||
if (! m_editing_mode || (int)m_editing_cache.size() <= m_hover_id)
|
||||
m_hover_id = -1;
|
||||
}
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
void on_dragging(const UpdateData &data) override;
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
bool on_is_selectable() const override;
|
||||
void on_load(cereal::BinaryInputArchive& ar) override;
|
||||
void on_save(cereal::BinaryOutputArchive& ar) const override;
|
||||
};
|
||||
|
||||
|
||||
class SlaGizmoHelpDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
SlaGizmoHelpDialog();
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoSlaSupports_hpp_
|
||||
39
src/slic3r/GUI/Gizmos/GLGizmos.hpp
Normal file
39
src/slic3r/GUI/Gizmos/GLGizmos.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef slic3r_GLGizmos_hpp_
|
||||
#define slic3r_GLGizmos_hpp_
|
||||
|
||||
// this describes events being passed from GLCanvas3D to SlaSupport gizmo
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
enum class SLAGizmoEventType : unsigned char {
|
||||
LeftDown = 1,
|
||||
LeftUp,
|
||||
RightDown,
|
||||
Dragging,
|
||||
Delete,
|
||||
SelectAll,
|
||||
ShiftUp,
|
||||
AltUp,
|
||||
ApplyChanges,
|
||||
DiscardChanges,
|
||||
AutomaticGeneration,
|
||||
ManualEditing,
|
||||
MouseWheelUp,
|
||||
MouseWheelDown,
|
||||
ResetClippingPlane
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMove.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoScale.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoRotate.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
||||
|
||||
#endif //slic3r_GLGizmos_hpp_
|
||||
557
src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp
Normal file
557
src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp
Normal file
@@ -0,0 +1,557 @@
|
||||
#include "GLGizmosCommon.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
using namespace CommonGizmosDataObjects;
|
||||
|
||||
CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas)
|
||||
: m_canvas(canvas)
|
||||
{
|
||||
using c = CommonGizmosDataID;
|
||||
m_data[c::SelectionInfo].reset( new SelectionInfo(this));
|
||||
m_data[c::InstancesHider].reset( new InstancesHider(this));
|
||||
// m_data[c::HollowedMesh].reset( new HollowedMesh(this));
|
||||
m_data[c::Raycaster].reset( new Raycaster(this));
|
||||
m_data[c::ObjectClipper].reset( new ObjectClipper(this));
|
||||
m_data[c::SupportsClipper].reset( new SupportsClipper(this));
|
||||
|
||||
}
|
||||
|
||||
void CommonGizmosDataPool::update(CommonGizmosDataID required)
|
||||
{
|
||||
assert(check_dependencies(required));
|
||||
for (auto& [id, data] : m_data) {
|
||||
if (int(required) & int(CommonGizmosDataID(id)))
|
||||
data->update();
|
||||
else
|
||||
if (data->is_valid())
|
||||
data->release();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SelectionInfo* CommonGizmosDataPool::selection_info() const
|
||||
{
|
||||
SelectionInfo* sel_info = dynamic_cast<SelectionInfo*>(m_data.at(CommonGizmosDataID::SelectionInfo).get());
|
||||
assert(sel_info);
|
||||
return sel_info->is_valid() ? sel_info : nullptr;
|
||||
}
|
||||
|
||||
|
||||
InstancesHider* CommonGizmosDataPool::instances_hider() const
|
||||
{
|
||||
InstancesHider* inst_hider = dynamic_cast<InstancesHider*>(m_data.at(CommonGizmosDataID::InstancesHider).get());
|
||||
assert(inst_hider);
|
||||
return inst_hider->is_valid() ? inst_hider : nullptr;
|
||||
}
|
||||
|
||||
Raycaster* CommonGizmosDataPool::raycaster() const
|
||||
{
|
||||
Raycaster* rc = dynamic_cast<Raycaster*>(m_data.at(CommonGizmosDataID::Raycaster).get());
|
||||
assert(rc);
|
||||
return rc->is_valid() ? rc : nullptr;
|
||||
}
|
||||
|
||||
ObjectClipper* CommonGizmosDataPool::object_clipper() const
|
||||
{
|
||||
ObjectClipper* oc = dynamic_cast<ObjectClipper*>(m_data.at(CommonGizmosDataID::ObjectClipper).get());
|
||||
// ObjectClipper is used from outside the gizmos to report current clipping plane.
|
||||
// This function can be called when oc is nullptr.
|
||||
return (oc && oc->is_valid()) ? oc : nullptr;
|
||||
}
|
||||
|
||||
SupportsClipper* CommonGizmosDataPool::supports_clipper() const
|
||||
{
|
||||
SupportsClipper* sc = dynamic_cast<SupportsClipper*>(m_data.at(CommonGizmosDataID::SupportsClipper).get());
|
||||
assert(sc);
|
||||
return sc->is_valid() ? sc : nullptr;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Check the required resources one by one and return true if all
|
||||
// dependencies are met.
|
||||
bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const
|
||||
{
|
||||
// This should iterate over currently required data. Each of them should
|
||||
// be asked about its dependencies and it must check that all dependencies
|
||||
// are also in required and before the current one.
|
||||
for (auto& [id, data] : m_data) {
|
||||
// in case we don't use this, the deps are irrelevant
|
||||
if (! (int(required) & int(CommonGizmosDataID(id))))
|
||||
continue;
|
||||
|
||||
|
||||
CommonGizmosDataID deps = data->get_dependencies();
|
||||
assert(int(deps) == (int(deps) & int(required)));
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
|
||||
|
||||
|
||||
void SelectionInfo::on_update()
|
||||
{
|
||||
const Selection& selection = get_pool()->get_canvas()->get_selection();
|
||||
|
||||
m_model_object = nullptr;
|
||||
m_print_object = nullptr;
|
||||
|
||||
if (selection.is_single_full_instance()) {
|
||||
m_model_object = selection.get_model()->objects[selection.get_object_idx()];
|
||||
if (m_model_object)
|
||||
m_print_object = get_pool()->get_canvas()->sla_print()->get_print_object_by_model_object_id(m_model_object->id());
|
||||
|
||||
m_z_shift = m_print_object ? m_print_object->get_current_elevation() : selection.get_first_volume()->get_sla_shift_z();
|
||||
}
|
||||
}
|
||||
|
||||
void SelectionInfo::on_release()
|
||||
{
|
||||
m_model_object = nullptr;
|
||||
m_model_volume = nullptr;
|
||||
}
|
||||
|
||||
ModelInstance *SelectionInfo::model_instance() const
|
||||
{
|
||||
int inst_idx = get_active_instance();
|
||||
return inst_idx < int(m_model_object->instances.size()) ?
|
||||
m_model_object->instances[get_active_instance()] : nullptr;
|
||||
}
|
||||
|
||||
int SelectionInfo::get_active_instance() const
|
||||
{
|
||||
return get_pool()->get_canvas()->get_selection().get_instance_idx();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void InstancesHider::on_update()
|
||||
{
|
||||
const ModelObject* mo = get_pool()->selection_info()->model_object();
|
||||
int active_inst = get_pool()->selection_info()->get_active_instance();
|
||||
GLCanvas3D* canvas = get_pool()->get_canvas();
|
||||
|
||||
if (mo && active_inst != -1) {
|
||||
canvas->toggle_model_objects_visibility(false);
|
||||
if (!m_hide_full_scene) {
|
||||
canvas->toggle_model_objects_visibility(true, mo, active_inst);
|
||||
canvas->toggle_sla_auxiliaries_visibility(false, mo, active_inst);
|
||||
}
|
||||
canvas->set_use_clipping_planes(true);
|
||||
// Some objects may be sinking, do not show whatever is below the bed.
|
||||
canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
|
||||
canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits<double>::max()));
|
||||
|
||||
|
||||
std::vector<const TriangleMesh*> meshes;
|
||||
for (const ModelVolume* mv : mo->volumes)
|
||||
meshes.push_back(&mv->mesh());
|
||||
|
||||
if (meshes != m_old_meshes) {
|
||||
m_clippers.clear();
|
||||
for (const TriangleMesh* mesh : meshes) {
|
||||
m_clippers.emplace_back(new MeshClipper);
|
||||
m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
|
||||
m_clippers.back()->set_mesh(mesh->its);
|
||||
}
|
||||
m_old_meshes = meshes;
|
||||
}
|
||||
}
|
||||
else
|
||||
canvas->toggle_model_objects_visibility(true);
|
||||
}
|
||||
|
||||
void InstancesHider::on_release()
|
||||
{
|
||||
get_pool()->get_canvas()->toggle_model_objects_visibility(true);
|
||||
get_pool()->get_canvas()->set_use_clipping_planes(false);
|
||||
m_old_meshes.clear();
|
||||
m_clippers.clear();
|
||||
}
|
||||
|
||||
void InstancesHider::set_hide_full_scene(bool hide)
|
||||
{
|
||||
if (m_hide_full_scene != hide) {
|
||||
m_hide_full_scene = hide;
|
||||
on_update();
|
||||
}
|
||||
}
|
||||
|
||||
void InstancesHider::render_cut() const
|
||||
{
|
||||
const SelectionInfo* sel_info = get_pool()->selection_info();
|
||||
const ModelObject* mo = sel_info->model_object();
|
||||
Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
|
||||
|
||||
size_t clipper_id = 0;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
Geometry::Transformation vol_trafo = mv->get_transformation();
|
||||
Geometry::Transformation trafo = inst_trafo * vol_trafo;
|
||||
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
|
||||
|
||||
auto& clipper = m_clippers[clipper_id];
|
||||
clipper->set_transformation(trafo);
|
||||
const ObjectClipper* obj_clipper = get_pool()->object_clipper();
|
||||
if (obj_clipper->is_valid() && obj_clipper->get_clipping_plane()
|
||||
&& obj_clipper->get_position() != 0.) {
|
||||
ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane();
|
||||
clp.set_normal(-clp.get_normal());
|
||||
clipper->set_limiting_plane(clp);
|
||||
}
|
||||
else
|
||||
clipper->set_limiting_plane(ClippingPlane::ClipsNothing());
|
||||
|
||||
#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
bool depth_test_enabled = ::glIsEnabled(GL_DEPTH_TEST);
|
||||
#else
|
||||
glsafe(::glPushAttrib(GL_DEPTH_TEST));
|
||||
#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv));
|
||||
#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
if (depth_test_enabled)
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
#else
|
||||
glsafe(::glPopAttrib());
|
||||
#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
|
||||
++clipper_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Raycaster::on_update()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
const ModelObject* mo = get_pool()->selection_info()->model_object();
|
||||
const ModelVolume* mv = get_pool()->selection_info()->model_volume();
|
||||
|
||||
if (mo == nullptr && mv == nullptr)
|
||||
return;
|
||||
|
||||
std::vector<ModelVolume*> mvs;
|
||||
if (mv != nullptr)
|
||||
mvs.push_back(const_cast<ModelVolume*>(mv));
|
||||
else
|
||||
mvs = mo->volumes;
|
||||
|
||||
std::vector<const TriangleMesh*> meshes;
|
||||
bool force_raycaster_regeneration = false;
|
||||
if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) {
|
||||
// For sla printers we use the mesh generated by the backend
|
||||
std::shared_ptr<const indexed_triangle_set> preview_mesh_ptr;
|
||||
const SLAPrintObject* po = get_pool()->selection_info()->print_object();
|
||||
if (po != nullptr)
|
||||
preview_mesh_ptr = po->get_mesh_to_print();
|
||||
else
|
||||
preview_mesh_ptr.reset();
|
||||
|
||||
m_sla_mesh_cache = (preview_mesh_ptr != nullptr) ? TriangleMesh{ *preview_mesh_ptr } : TriangleMesh();
|
||||
|
||||
if (!m_sla_mesh_cache.empty()) {
|
||||
m_sla_mesh_cache.transform(po->trafo().inverse());
|
||||
meshes.emplace_back(&m_sla_mesh_cache);
|
||||
force_raycaster_regeneration = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (meshes.empty()) {
|
||||
const std::vector<ModelVolume*>& mvs = mo->volumes;
|
||||
for (const ModelVolume* mv : mvs) {
|
||||
if (mv->is_model_part())
|
||||
meshes.push_back(&mv->mesh());
|
||||
}
|
||||
}
|
||||
|
||||
if (force_raycaster_regeneration || meshes != m_old_meshes) {
|
||||
m_raycasters.clear();
|
||||
for (const TriangleMesh* mesh : meshes)
|
||||
m_raycasters.emplace_back(new MeshRaycaster(std::make_shared<const TriangleMesh>(*mesh)));
|
||||
m_old_meshes = meshes;
|
||||
}
|
||||
}
|
||||
|
||||
void Raycaster::on_release()
|
||||
{
|
||||
m_raycasters.clear();
|
||||
m_old_meshes.clear();
|
||||
}
|
||||
|
||||
std::vector<const MeshRaycaster*> Raycaster::raycasters() const
|
||||
{
|
||||
std::vector<const MeshRaycaster*> mrcs;
|
||||
for (const auto& raycaster_unique_ptr : m_raycasters)
|
||||
mrcs.push_back(raycaster_unique_ptr.get());
|
||||
return mrcs;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
void ObjectClipper::on_update()
|
||||
{
|
||||
const ModelObject* mo = get_pool()->selection_info()->model_object();
|
||||
if (! mo)
|
||||
return;
|
||||
|
||||
// which mesh should be cut?
|
||||
std::vector<const TriangleMesh*> meshes;
|
||||
std::vector<Geometry::Transformation> trafos;
|
||||
bool force_clipper_regeneration = false;
|
||||
|
||||
std::unique_ptr<MeshClipper> mc;
|
||||
Geometry::Transformation mc_tr;
|
||||
if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) {
|
||||
// For sla printers we use the mesh generated by the backend
|
||||
const SLAPrintObject* po = get_pool()->selection_info()->print_object();
|
||||
if (po) {
|
||||
auto partstoslice = po->get_parts_to_slice();
|
||||
if (! partstoslice.empty()) {
|
||||
mc = std::make_unique<MeshClipper>();
|
||||
mc->set_mesh(range(partstoslice));
|
||||
mc_tr = Geometry::Transformation{po->trafo().inverse().cast<double>()};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mc && meshes.empty()) {
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
meshes.emplace_back(&mv->mesh());
|
||||
trafos.emplace_back(mv->get_transformation());
|
||||
}
|
||||
}
|
||||
|
||||
if (mc || force_clipper_regeneration || meshes != m_old_meshes) {
|
||||
m_clippers.clear();
|
||||
for (size_t i = 0; i < meshes.size(); ++i) {
|
||||
m_clippers.emplace_back(new MeshClipper, trafos[i]);
|
||||
m_clippers.back().first->set_mesh(meshes[i]->its);
|
||||
}
|
||||
m_old_meshes = std::move(meshes);
|
||||
|
||||
if (mc) {
|
||||
m_clippers.emplace_back(std::move(mc), mc_tr);
|
||||
}
|
||||
|
||||
m_active_inst_bb_radius =
|
||||
mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ObjectClipper::on_release()
|
||||
{
|
||||
m_clippers.clear();
|
||||
m_old_meshes.clear();
|
||||
m_clp.reset();
|
||||
m_clp_ratio = 0.;
|
||||
|
||||
}
|
||||
|
||||
void ObjectClipper::render_cut(const std::vector<size_t>* ignore_idxs) const
|
||||
{
|
||||
if (m_clp_ratio == 0.)
|
||||
return;
|
||||
const SelectionInfo* sel_info = get_pool()->selection_info();
|
||||
const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation();
|
||||
|
||||
std::vector<size_t> ignore_idxs_local = ignore_idxs ? *ignore_idxs : std::vector<size_t>();
|
||||
|
||||
for (auto& clipper : m_clippers) {
|
||||
Geometry::Transformation trafo = inst_trafo * clipper.second;
|
||||
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
|
||||
clipper.first->set_plane(*m_clp);
|
||||
clipper.first->set_transformation(trafo);
|
||||
clipper.first->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
|
||||
clipper.first->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }, &ignore_idxs_local);
|
||||
clipper.first->render_contour({ 1.f, 1.f, 1.f, 1.f }, &ignore_idxs_local);
|
||||
|
||||
// Now update the ignore idxs. Find the first element belonging to the next clipper,
|
||||
// and remove everything before it and decrement everything by current number of contours.
|
||||
const int num_of_contours = clipper.first->get_number_of_contours();
|
||||
ignore_idxs_local.erase(ignore_idxs_local.begin(), std::find_if(ignore_idxs_local.begin(), ignore_idxs_local.end(), [num_of_contours](size_t idx) { return idx >= num_of_contours; } ));
|
||||
for (size_t& idx : ignore_idxs_local)
|
||||
idx -= num_of_contours;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int ObjectClipper::get_number_of_contours() const
|
||||
{
|
||||
int sum = 0;
|
||||
for (const auto& [clipper, trafo] : m_clippers)
|
||||
sum += clipper->get_number_of_contours();
|
||||
return sum;
|
||||
}
|
||||
|
||||
int ObjectClipper::is_projection_inside_cut(const Vec3d& point) const
|
||||
{
|
||||
if (m_clp_ratio == 0.)
|
||||
return -1;
|
||||
int idx_offset = 0;
|
||||
for (const auto& [clipper, trafo] : m_clippers) {
|
||||
if (int idx = clipper->is_projection_inside_cut(point); idx != -1)
|
||||
return idx_offset + idx;
|
||||
idx_offset += clipper->get_number_of_contours();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ObjectClipper::has_valid_contour() const
|
||||
{
|
||||
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto& cl) { return cl.first->has_valid_contour(); });
|
||||
}
|
||||
|
||||
std::vector<Vec3d> ObjectClipper::point_per_contour() const
|
||||
{
|
||||
std::vector<Vec3d> pts;
|
||||
|
||||
for (const auto& clipper : m_clippers) {
|
||||
const std::vector<Vec3d> pts_clipper = clipper.first->point_per_contour();
|
||||
pts.insert(pts.end(), pts_clipper.begin(), pts_clipper.end());;
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
|
||||
void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal)
|
||||
{
|
||||
const ModelObject* mo = get_pool()->selection_info()->model_object();
|
||||
int active_inst = get_pool()->selection_info()->get_active_instance();
|
||||
double z_shift = get_pool()->selection_info()->get_sla_shift();
|
||||
|
||||
Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward();
|
||||
const Vec3d& center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift);
|
||||
float dist = normal.dot(center);
|
||||
|
||||
if (pos < 0.)
|
||||
pos = m_clp_ratio;
|
||||
|
||||
m_clp_ratio = pos;
|
||||
m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2*m_active_inst_bb_radius)));
|
||||
get_pool()->get_canvas()->set_as_dirty();
|
||||
}
|
||||
|
||||
void ObjectClipper::set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos)
|
||||
{
|
||||
m_clp.reset(new ClippingPlane(cpl_normal, cpl_offset));
|
||||
m_clp_ratio = pos;
|
||||
get_pool()->get_canvas()->set_as_dirty();
|
||||
}
|
||||
|
||||
const ClippingPlane* ObjectClipper::get_clipping_plane(bool ignore_hide_clipped) const
|
||||
{
|
||||
static const ClippingPlane no_clip = ClippingPlane::ClipsNothing();
|
||||
return (ignore_hide_clipped || m_hide_clipped) ? m_clp.get() : &no_clip;
|
||||
}
|
||||
|
||||
void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contour_width)
|
||||
{
|
||||
m_hide_clipped = hide_clipped;
|
||||
for (auto& clipper : m_clippers)
|
||||
clipper.first->set_behaviour(fill_cut, contour_width);
|
||||
}
|
||||
|
||||
|
||||
void SupportsClipper::on_update()
|
||||
{
|
||||
const ModelObject* mo = get_pool()->selection_info()->model_object();
|
||||
bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA;
|
||||
if (! mo || ! is_sla)
|
||||
return;
|
||||
|
||||
const SLAPrintObject* po = get_pool()->selection_info()->print_object();
|
||||
if (po == nullptr)
|
||||
return;
|
||||
|
||||
if (po->get_mesh_to_print() == nullptr) {
|
||||
// The object has been not sliced yet. We better dump the cached data.
|
||||
m_supports_clipper.reset();
|
||||
m_pad_clipper.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const TriangleMesh& support_mesh = po->support_mesh();
|
||||
if (support_mesh.empty()) {
|
||||
// The supports are not available yet. We better dump the cached data.
|
||||
m_supports_clipper.reset();
|
||||
}
|
||||
else {
|
||||
m_supports_clipper.reset(new MeshClipper);
|
||||
m_supports_clipper->set_mesh(support_mesh.its);
|
||||
}
|
||||
|
||||
const TriangleMesh& pad_mesh = po->pad_mesh();
|
||||
if (pad_mesh.empty()) {
|
||||
// The supports are not available yet. We better dump the cached data.
|
||||
m_pad_clipper.reset();
|
||||
}
|
||||
else {
|
||||
m_pad_clipper.reset(new MeshClipper);
|
||||
m_pad_clipper->set_mesh(pad_mesh.its);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SupportsClipper::on_release()
|
||||
{
|
||||
m_supports_clipper.reset();
|
||||
m_pad_clipper.reset();
|
||||
m_print_object_idx = -1;
|
||||
}
|
||||
|
||||
void SupportsClipper::render_cut() const
|
||||
{
|
||||
const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper();
|
||||
if (ocl->get_position() == 0.)
|
||||
return;
|
||||
|
||||
const SLAPrintObject* po = get_pool()->selection_info()->print_object();
|
||||
if (po == nullptr)
|
||||
return;
|
||||
|
||||
Geometry::Transformation po_trafo(po->trafo());
|
||||
|
||||
const SelectionInfo* sel_info = get_pool()->selection_info();
|
||||
Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation();
|
||||
inst_trafo = Geometry::Transformation(inst_trafo.get_matrix() * po_trafo.get_matrix().inverse());
|
||||
inst_trafo.set_offset(inst_trafo.get_offset() + Vec3d(0.0, 0.0, sel_info->get_sla_shift()));
|
||||
|
||||
if (m_supports_clipper != nullptr) {
|
||||
m_supports_clipper->set_plane(*ocl->get_clipping_plane());
|
||||
m_supports_clipper->set_transformation(inst_trafo);
|
||||
m_supports_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f });
|
||||
}
|
||||
|
||||
if (m_pad_clipper != nullptr) {
|
||||
m_pad_clipper->set_plane(*ocl->get_clipping_plane());
|
||||
m_pad_clipper->set_transformation(inst_trafo);
|
||||
m_pad_clipper->render_cut({ 0.6f, 0.f, 0.222f, 1.0f });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
313
src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp
Normal file
313
src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp
Normal file
@@ -0,0 +1,313 @@
|
||||
#ifndef slic3r_GUI_GLGizmosCommon_hpp_
|
||||
#define slic3r_GUI_GLGizmosCommon_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
#include "slic3r/GUI/MeshUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelObject;
|
||||
class ModelInstance;
|
||||
class SLAPrintObject;
|
||||
class ModelVolume;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class GLCanvas3D;
|
||||
|
||||
enum class SLAGizmoEventType : unsigned char {
|
||||
LeftDown = 1,
|
||||
LeftUp,
|
||||
RightDown,
|
||||
RightUp,
|
||||
Dragging,
|
||||
Delete,
|
||||
SelectAll,
|
||||
CtrlDown,
|
||||
CtrlUp,
|
||||
ShiftDown,
|
||||
ShiftUp,
|
||||
AltUp,
|
||||
Escape,
|
||||
ApplyChanges,
|
||||
DiscardChanges,
|
||||
AutomaticGeneration,
|
||||
ManualEditing,
|
||||
MouseWheelUp,
|
||||
MouseWheelDown,
|
||||
ResetClippingPlane,
|
||||
Moving
|
||||
};
|
||||
|
||||
|
||||
|
||||
class CommonGizmosDataBase;
|
||||
namespace CommonGizmosDataObjects {
|
||||
class SelectionInfo;
|
||||
class InstancesHider;
|
||||
class HollowedMesh;
|
||||
class Raycaster;
|
||||
class ObjectClipper;
|
||||
class SupportsClipper;
|
||||
}
|
||||
|
||||
// Some of the gizmos use the same data that need to be updated ocassionally.
|
||||
// It is also desirable that the data are not recalculated when the gizmos
|
||||
// are just switched, but on the other hand, they should be released when
|
||||
// they are not in use by any gizmo anymore.
|
||||
|
||||
// Enumeration of various data types that the data pool can contain.
|
||||
// Each gizmo can tell which of the data it wants to use through
|
||||
// on_get_requirements() method.
|
||||
enum class CommonGizmosDataID {
|
||||
None = 0,
|
||||
SelectionInfo = 1 << 0,
|
||||
InstancesHider = 1 << 1,
|
||||
Raycaster = 1 << 3,
|
||||
ObjectClipper = 1 << 4,
|
||||
SupportsClipper = 1 << 5,
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Following class holds pointers to the common data objects and triggers
|
||||
// their updating/releasing. There is just one object of this type (managed
|
||||
// by GLGizmoManager, the gizmos keep a pointer to it.
|
||||
class CommonGizmosDataPool {
|
||||
public:
|
||||
explicit CommonGizmosDataPool(GLCanvas3D* canvas);
|
||||
|
||||
// Update all resources and release what is not used.
|
||||
// Accepts a bitmask of currently required resources.
|
||||
void update(CommonGizmosDataID required);
|
||||
|
||||
// Getters for the data that need to be accessed from the gizmos directly.
|
||||
CommonGizmosDataObjects::SelectionInfo* selection_info() const;
|
||||
CommonGizmosDataObjects::InstancesHider* instances_hider() const;
|
||||
// CommonGizmosDataObjects::HollowedMesh* hollowed_mesh() const;
|
||||
CommonGizmosDataObjects::Raycaster* raycaster() const;
|
||||
CommonGizmosDataObjects::ObjectClipper* object_clipper() const;
|
||||
CommonGizmosDataObjects::SupportsClipper* supports_clipper() const;
|
||||
|
||||
|
||||
GLCanvas3D* get_canvas() const { return m_canvas; }
|
||||
|
||||
private:
|
||||
std::map<CommonGizmosDataID, std::unique_ptr<CommonGizmosDataBase>> m_data;
|
||||
GLCanvas3D* m_canvas;
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool check_dependencies(CommonGizmosDataID required) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Base class for a wrapper object managing a single resource.
|
||||
// Each of the enum values above (safe None) will have an object of this kind.
|
||||
class CommonGizmosDataBase {
|
||||
public:
|
||||
// Pass a backpointer to the pool, so the individual
|
||||
// objects can communicate with one another.
|
||||
explicit CommonGizmosDataBase(CommonGizmosDataPool* cgdp)
|
||||
: m_common{cgdp} {}
|
||||
virtual ~CommonGizmosDataBase() {}
|
||||
|
||||
// Update the resource.
|
||||
void update() { on_update(); m_is_valid = true; }
|
||||
|
||||
// Release any data that are stored internally.
|
||||
void release() { on_release(); m_is_valid = false; }
|
||||
|
||||
// Returns whether the resource is currently maintained.
|
||||
bool is_valid() const { return m_is_valid; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Return a bitmask of all resources that this one relies on.
|
||||
// The dependent resource must have higher ID than the one
|
||||
// it depends on.
|
||||
virtual CommonGizmosDataID get_dependencies() const { return CommonGizmosDataID::None; }
|
||||
#endif // NDEBUG
|
||||
|
||||
protected:
|
||||
virtual void on_release() = 0;
|
||||
virtual void on_update() = 0;
|
||||
CommonGizmosDataPool* get_pool() const { return m_common; }
|
||||
|
||||
private:
|
||||
bool m_is_valid = false;
|
||||
CommonGizmosDataPool* m_common = nullptr;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// The specializations of the CommonGizmosDataBase class live in this
|
||||
// namespace to avoid clashes in GUI namespace.
|
||||
namespace CommonGizmosDataObjects
|
||||
{
|
||||
|
||||
class SelectionInfo : public CommonGizmosDataBase
|
||||
{
|
||||
public:
|
||||
explicit SelectionInfo(CommonGizmosDataPool* cgdp)
|
||||
: CommonGizmosDataBase(cgdp) {}
|
||||
|
||||
// Returns a non-null pointer if the selection is a single full instance
|
||||
ModelObject* model_object() const { return m_model_object; }
|
||||
const SLAPrintObject *print_object() const { return m_print_object; }
|
||||
// Returns a non-null pointer if the selection is a single volume
|
||||
ModelVolume* model_volume() const { return m_model_volume; }
|
||||
ModelInstance *model_instance() const;
|
||||
int get_active_instance() const;
|
||||
float get_sla_shift() const { return m_use_shift ? m_z_shift : 0.f; }
|
||||
void set_use_shift(bool use) { m_use_shift = use; }
|
||||
|
||||
protected:
|
||||
void on_update() override;
|
||||
void on_release() override;
|
||||
|
||||
private:
|
||||
ModelObject* m_model_object = nullptr;
|
||||
const SLAPrintObject *m_print_object = nullptr;
|
||||
ModelVolume* m_model_volume = nullptr;
|
||||
// int m_active_inst = -1;
|
||||
float m_z_shift = 0.f;
|
||||
bool m_use_shift = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class InstancesHider : public CommonGizmosDataBase
|
||||
{
|
||||
public:
|
||||
explicit InstancesHider(CommonGizmosDataPool* cgdp)
|
||||
: CommonGizmosDataBase(cgdp) {}
|
||||
#ifndef NDEBUG
|
||||
CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
|
||||
#endif // NDEBUG
|
||||
|
||||
void set_hide_full_scene(bool hide);
|
||||
void render_cut() const;
|
||||
|
||||
protected:
|
||||
void on_update() override;
|
||||
void on_release() override;
|
||||
|
||||
private:
|
||||
bool m_hide_full_scene{ false };
|
||||
std::vector<const TriangleMesh*> m_old_meshes;
|
||||
std::vector<std::unique_ptr<MeshClipper>> m_clippers;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class Raycaster : public CommonGizmosDataBase
|
||||
{
|
||||
public:
|
||||
explicit Raycaster(CommonGizmosDataPool* cgdp)
|
||||
: CommonGizmosDataBase(cgdp) {}
|
||||
#ifndef NDEBUG
|
||||
CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
|
||||
#endif // NDEBUG
|
||||
|
||||
const MeshRaycaster* raycaster() const { assert(m_raycasters.size() == 1); return m_raycasters.front().get(); }
|
||||
std::vector<const MeshRaycaster*> raycasters() const;
|
||||
|
||||
protected:
|
||||
void on_update() override;
|
||||
void on_release() override;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<MeshRaycaster>> m_raycasters;
|
||||
std::vector<const TriangleMesh*> m_old_meshes;
|
||||
// Used to store the sla mesh coming from the backend
|
||||
TriangleMesh m_sla_mesh_cache;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class ObjectClipper : public CommonGizmosDataBase
|
||||
{
|
||||
public:
|
||||
explicit ObjectClipper(CommonGizmosDataPool* cgdp)
|
||||
: CommonGizmosDataBase(cgdp) {}
|
||||
#ifndef NDEBUG
|
||||
CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
|
||||
#endif // NDEBUG
|
||||
|
||||
void set_normal(const Vec3d& dir);
|
||||
double get_position() const { return m_clp_ratio; }
|
||||
const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const;
|
||||
void render_cut(const std::vector<size_t>* ignore_idxs = nullptr) const;
|
||||
void set_position_by_ratio(double pos, bool keep_normal);
|
||||
void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos);
|
||||
void set_behavior(bool hide_clipped, bool fill_cut, double contour_width);
|
||||
|
||||
int get_number_of_contours() const;
|
||||
std::vector<Vec3d> point_per_contour() const;
|
||||
|
||||
int is_projection_inside_cut(const Vec3d& point_in) const;
|
||||
bool has_valid_contour() const;
|
||||
|
||||
|
||||
protected:
|
||||
void on_update() override;
|
||||
void on_release() override;
|
||||
|
||||
private:
|
||||
std::vector<const TriangleMesh*> m_old_meshes;
|
||||
// Used to store the sla mesh coming from the backend
|
||||
TriangleMesh m_sla_mesh_cache;
|
||||
std::vector<std::pair<std::unique_ptr<MeshClipper>, Geometry::Transformation>> m_clippers;
|
||||
std::unique_ptr<ClippingPlane> m_clp;
|
||||
double m_clp_ratio = 0.;
|
||||
double m_active_inst_bb_radius = 0.;
|
||||
bool m_hide_clipped = true;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class SupportsClipper : public CommonGizmosDataBase
|
||||
{
|
||||
public:
|
||||
explicit SupportsClipper(CommonGizmosDataPool* cgdp)
|
||||
: CommonGizmosDataBase(cgdp) {}
|
||||
#ifndef NDEBUG
|
||||
CommonGizmosDataID get_dependencies() const override {
|
||||
return CommonGizmosDataID(
|
||||
int(CommonGizmosDataID::SelectionInfo)
|
||||
| int(CommonGizmosDataID::ObjectClipper)
|
||||
);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
void render_cut() const;
|
||||
|
||||
|
||||
protected:
|
||||
void on_update() override;
|
||||
void on_release() override;
|
||||
|
||||
private:
|
||||
int m_print_object_idx = -1;
|
||||
// int m_print_objects_count = 0;
|
||||
std::unique_ptr<MeshClipper> m_supports_clipper;
|
||||
std::unique_ptr<MeshClipper> m_pad_clipper;
|
||||
};
|
||||
|
||||
} // namespace CommonGizmosDataObjects
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
#endif // slic3r_GUI_GLGizmosCommon_hpp_
|
||||
1026
src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
Normal file
1026
src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
260
src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
Normal file
260
src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
Normal file
@@ -0,0 +1,260 @@
|
||||
#ifndef slic3r_GUI_GLGizmosManager_hpp_
|
||||
#define slic3r_GUI_GLGizmosManager_hpp_
|
||||
|
||||
#include "slic3r/GUI/GLTexture.hpp"
|
||||
#include "slic3r/GUI/GLToolbar.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
|
||||
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace UndoRedo {
|
||||
struct Snapshot;
|
||||
}
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class GLCanvas3D;
|
||||
class ClippingPlane;
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
class CommonGizmosDataPool;
|
||||
|
||||
class Rect
|
||||
{
|
||||
float m_left{ 0.0f };
|
||||
float m_top{ 0.0f };
|
||||
float m_right{ 0.0f };
|
||||
float m_bottom{ 0.0f };
|
||||
|
||||
public:
|
||||
Rect() = default;
|
||||
Rect(float left, float top, float right, float bottom) : m_left(left) , m_top(top) , m_right(right) , m_bottom(bottom) {}
|
||||
|
||||
bool operator == (const Rect& other) const {
|
||||
if (std::abs(m_left - other.m_left) > EPSILON) return false;
|
||||
if (std::abs(m_top - other.m_top) > EPSILON) return false;
|
||||
if (std::abs(m_right - other.m_right) > EPSILON) return false;
|
||||
if (std::abs(m_bottom - other.m_bottom) > EPSILON) return false;
|
||||
return true;
|
||||
}
|
||||
bool operator != (const Rect& other) const { return !operator==(other); }
|
||||
|
||||
float get_left() const { return m_left; }
|
||||
void set_left(float left) { m_left = left; }
|
||||
|
||||
float get_top() const { return m_top; }
|
||||
void set_top(float top) { m_top = top; }
|
||||
|
||||
float get_right() const { return m_right; }
|
||||
void set_right(float right) { m_right = right; }
|
||||
|
||||
float get_bottom() const { return m_bottom; }
|
||||
void set_bottom(float bottom) { m_bottom = bottom; }
|
||||
|
||||
float get_width() const { return m_right - m_left; }
|
||||
float get_height() const { return m_top - m_bottom; }
|
||||
};
|
||||
|
||||
class GLGizmosManager : public Slic3r::ObjectBase
|
||||
{
|
||||
public:
|
||||
static const float Default_Icons_Size;
|
||||
|
||||
enum EType : unsigned char
|
||||
{
|
||||
// Order must match index in m_gizmos!
|
||||
Move,
|
||||
Scale,
|
||||
Rotate,
|
||||
Flatten,
|
||||
Cut,
|
||||
Hollow,
|
||||
SlaSupports,
|
||||
FdmSupports,
|
||||
Seam,
|
||||
MmuSegmentation,
|
||||
Measure,
|
||||
Emboss,
|
||||
Simplify,
|
||||
Undefined
|
||||
};
|
||||
|
||||
private:
|
||||
struct Layout
|
||||
{
|
||||
float scale{ 1.0f };
|
||||
float icons_size{ Default_Icons_Size };
|
||||
float border{ 5.0f };
|
||||
float gap_y{ 5.0f };
|
||||
|
||||
float stride_y() const { return icons_size + gap_y;}
|
||||
|
||||
float scaled_icons_size() const { return scale * icons_size; }
|
||||
float scaled_border() const { return scale * border; }
|
||||
float scaled_gap_y() const { return scale * gap_y; }
|
||||
float scaled_stride_y() const { return scale * stride_y(); }
|
||||
};
|
||||
|
||||
GLCanvas3D& m_parent;
|
||||
bool m_enabled;
|
||||
std::vector<std::unique_ptr<GLGizmoBase>> m_gizmos;
|
||||
GLTexture m_icons_texture;
|
||||
bool m_icons_texture_dirty;
|
||||
BackgroundTexture m_background_texture;
|
||||
GLTexture m_arrow_texture;
|
||||
Layout m_layout;
|
||||
EType m_current;
|
||||
EType m_hover;
|
||||
std::pair<EType, bool> m_highlight; // bool true = higlightedShown, false = highlightedHidden
|
||||
|
||||
std::vector<size_t> get_selectable_idxs() const;
|
||||
EType get_gizmo_from_mouse(const Vec2d &mouse_pos) const;
|
||||
|
||||
bool activate_gizmo(EType type);
|
||||
|
||||
std::string m_tooltip;
|
||||
bool m_serializing;
|
||||
std::unique_ptr<CommonGizmosDataPool> m_common_gizmos_data;
|
||||
|
||||
/// <summary>
|
||||
/// Process mouse event on gizmo toolbar
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Event descriptor</param>
|
||||
/// <returns>TRUE when take responsibility for event otherwise FALSE.
|
||||
/// On true, event should not be process by others.
|
||||
/// On false, event should be process by others.</returns>
|
||||
bool gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event);
|
||||
public:
|
||||
explicit GLGizmosManager(GLCanvas3D& parent);
|
||||
|
||||
bool init();
|
||||
|
||||
bool init_arrow(const std::string& filename);
|
||||
|
||||
template<class Archive>
|
||||
void load(Archive& ar)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
m_serializing = true;
|
||||
|
||||
// Following is needed to know which to be turn on, but not actually modify
|
||||
// m_current prematurely, so activate_gizmo is not confused.
|
||||
EType old_current = m_current;
|
||||
ar(m_current);
|
||||
EType new_current = m_current;
|
||||
m_current = old_current;
|
||||
|
||||
// activate_gizmo call sets m_current and calls set_state for the gizmo
|
||||
// it does nothing in case the gizmo is already activated
|
||||
// it can safely be called for Undefined gizmo
|
||||
activate_gizmo(new_current);
|
||||
if (m_current != Undefined)
|
||||
m_gizmos[m_current]->load(ar);
|
||||
}
|
||||
|
||||
template<class Archive>
|
||||
void save(Archive& ar) const
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
ar(m_current);
|
||||
|
||||
if (m_current != Undefined && !m_gizmos.empty())
|
||||
m_gizmos[m_current]->save(ar);
|
||||
}
|
||||
|
||||
bool is_enabled() const { return m_enabled; }
|
||||
void set_enabled(bool enable) { m_enabled = enable; }
|
||||
|
||||
void set_overlay_icon_size(float size);
|
||||
void set_overlay_scale(float scale);
|
||||
|
||||
void refresh_on_off_state();
|
||||
void reset_all_states();
|
||||
bool open_gizmo(EType type);
|
||||
bool check_gizmos_closed_except(EType) const;
|
||||
|
||||
void set_hover_id(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Distribute information about different data into active gizmo
|
||||
/// Should be called when selection changed
|
||||
/// </summary>
|
||||
void update_data();
|
||||
|
||||
EType get_current_type() const { return m_current; }
|
||||
GLGizmoBase* get_current() const;
|
||||
GLGizmoBase* get_gizmo(GLGizmosManager::EType type) const;
|
||||
EType get_gizmo_from_name(const std::string& gizmo_name) const;
|
||||
|
||||
bool is_running() const;
|
||||
bool handle_shortcut(int key);
|
||||
|
||||
bool is_dragging() const;
|
||||
|
||||
ClippingPlane get_clipping_plane() const;
|
||||
bool wants_reslice_supports_on_undo() const;
|
||||
|
||||
bool is_in_editing_mode(bool error_notification = false) const;
|
||||
bool is_hiding_instances() const;
|
||||
|
||||
void render_current_gizmo() const;
|
||||
void render_painter_gizmo();
|
||||
|
||||
void render_overlay();
|
||||
|
||||
void render_arrow(const GLCanvas3D& parent, EType highlighted_type) const;
|
||||
|
||||
std::string get_tooltip() const;
|
||||
|
||||
bool on_mouse(const wxMouseEvent &mouse_event);
|
||||
bool on_mouse_wheel(const wxMouseEvent &evt);
|
||||
bool on_char(wxKeyEvent& evt);
|
||||
bool on_key(wxKeyEvent& evt);
|
||||
|
||||
void update_after_undo_redo(const UndoRedo::Snapshot& snapshot);
|
||||
|
||||
int get_selectable_icons_cnt() const { return get_selectable_idxs().size(); }
|
||||
|
||||
// To end highlight set gizmo = undefined
|
||||
void set_highlight(EType gizmo, bool highlight_shown) { m_highlight = std::pair<EType, bool>(gizmo, highlight_shown); }
|
||||
bool get_highlight_state() const { return m_highlight.second; }
|
||||
|
||||
private:
|
||||
bool gizmo_event(SLAGizmoEventType action,
|
||||
const Vec2d & mouse_position = Vec2d::Zero(),
|
||||
bool shift_down = false,
|
||||
bool alt_down = false,
|
||||
bool control_down = false);
|
||||
|
||||
void render_background(float left, float top, float right, float bottom, float border_w, float border_h) const;
|
||||
|
||||
void do_render_overlay() const;
|
||||
|
||||
float get_scaled_total_height() const;
|
||||
float get_scaled_total_width() const;
|
||||
|
||||
bool generate_icons_texture();
|
||||
|
||||
void update_hover_state(const EType &type);
|
||||
bool grabber_contains_mouse() const;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
template <class Archive> struct specialize<Archive, Slic3r::GUI::GLGizmosManager, cereal::specialization::member_load_save> {};
|
||||
}
|
||||
|
||||
#endif // slic3r_GUI_GLGizmosManager_hpp_
|
||||
Reference in New Issue
Block a user