QIDISlicer1.0.0

This commit is contained in:
sunsets
2023-06-10 10:14:12 +08:00
parent f2e20e1a90
commit b4cd486f2d
3475 changed files with 1973675 additions and 0 deletions

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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&center_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_

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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_

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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_

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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_