diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 299705ce86..2ca1998e31 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -159,6 +159,8 @@ set(SLIC3R_GUI_SOURCES GUI/RemovableDriveManager.hpp GUI/SendSystemInfoDialog.cpp GUI/SendSystemInfoDialog.hpp + GUI/SurfaceDrag.cpp + GUI/SurfaceDrag.hpp GUI/BonjourDialog.cpp GUI/BonjourDialog.hpp GUI/ButtonsDescription.cpp diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 427c0e99fc..f4573d3c4e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7112,5 +7112,95 @@ const ModelVolume *get_model_volume(const GLVolume &v, const Model &model) return ret; } +const ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects) +{ + for (const ModelObject *obj : objects) + for (const ModelVolume *vol : obj->volumes) + if (vol->id() == volume_id) + return vol; + return nullptr; +} + +ModelVolume *get_model_volume(const GLVolume &v, const ModelObject& object) { + if (v.volume_idx() < 0) + return nullptr; + + size_t volume_idx = static_cast(v.volume_idx()); + if (volume_idx >= object.volumes.size()) + return nullptr; + + return object.volumes[volume_idx]; +} + +ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects) +{ + if (v.object_idx() < 0) + return nullptr; + size_t objext_idx = static_cast(v.object_idx()); + if (objext_idx >= objects.size()) + return nullptr; + if (objects[objext_idx] == nullptr) + return nullptr; + return get_model_volume(v, *objects[objext_idx]); +} + +GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas) { + int hovered_id_signed = canvas.get_first_hover_volume_idx(); + if (hovered_id_signed < 0) + return nullptr; + + size_t hovered_id = static_cast(hovered_id_signed); + const GLVolumePtrs &volumes = canvas.get_volumes().volumes; + if (hovered_id >= volumes.size()) + return nullptr; + + return volumes[hovered_id]; +} + +GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas) { + const GLVolume *gl_volume = get_selected_gl_volume(canvas.get_selection()); + if (gl_volume == nullptr) + return nullptr; + + const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; + for (GLVolume *v : gl_volumes) + if (v->composite_id == gl_volume->composite_id) + return v; + return nullptr; +} + +ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model) { + return get_model_object(gl_volume, model.objects); +} + +ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects) { + if (gl_volume.object_idx() < 0) + return nullptr; + size_t objext_idx = static_cast(gl_volume.object_idx()); + if (objext_idx >= objects.size()) + return nullptr; + return objects[objext_idx]; +} + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model& model) { + return get_model_instance(gl_volume, model.objects); +} + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects) { + if (gl_volume.instance_idx() < 0) + return nullptr; + ModelObject *object = get_model_object(gl_volume, objects); + return get_model_instance(gl_volume, *object); +} + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object) { + if (gl_volume.instance_idx() < 0) + return nullptr; + size_t instance_idx = static_cast(gl_volume.instance_idx()); + if (instance_idx >= object.instances.size()) + return nullptr; + return object.instances[instance_idx]; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index f8f5a0efcd..573ef879af 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1056,7 +1056,20 @@ private: float get_overlay_window_width() { return LayersEditing::get_overlay_window_width(); } }; -const ModelVolume * get_model_volume(const GLVolume &v, const Model &model); +const ModelVolume *get_model_volume(const GLVolume &v, const Model &model); +const ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects); +ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects); +ModelVolume *get_model_volume(const GLVolume &v, const ModelObject &object); + +GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas); +GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas); + +ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model); +ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects); + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model &model); +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects); +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object); } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 2992b957f0..14e6c7aa14 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -18,7 +18,7 @@ // TODO: remove include #include "libslic3r/SVG.hpp" // debug store #include "libslic3r/Geometry.hpp" // covex hull 2d -#include "libslic3r/Timer.hpp" // covex hull 2d +#include "libslic3r/Timer.hpp" #include "libslic3r/NSVGUtils.hpp" #include "libslic3r/Model.hpp" @@ -109,11 +109,6 @@ static const struct Limits // Define where is up vector on model constexpr double up_limit = 0.9; -static bool is_text_empty(const std::string &text){ - return text.empty() || - text.find_first_not_of(" \n\t\r") == std::string::npos; -} - // Normalize radian angle from -PI to PI template void to_range_pi_pi(T& angle) { @@ -123,6 +118,63 @@ template void to_range_pi_pi(T& angle) } } } // namespace priv +using namespace priv; + +// 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 GLGizmoEmboss::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; + + 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 size; + std::string depth; + std::string use_surface; + + // advanced + std::string char_gap; + std::string line_gap; + std::string boldness; + std::string italic; + std::string surface_distance; + std::string angle; + std::string collection; + }; + Translations translations; +}; GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) : GLGizmoBase(parent, M_ICON_FILENAME, -2) @@ -141,7 +193,6 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) // Private namespace with helper function for create volume namespace priv { - /// /// Prepare data for emboss /// @@ -165,21 +216,6 @@ static void start_create_volume_job(const ModelObject *object, DataBase &emboss_data, ModelVolumeType volume_type); -static GLVolume *get_hovered_gl_volume(const GLCanvas3D &canvas); - -/// -/// Unproject on mesh by Mesh raycasters -/// -/// Position of mouse on screen -/// Projection params -/// Define which caster will be skipped, null mean no skip -/// Position on surface, normal direction in world coorinate -/// + key, to know hitted instance and volume -static std::optional ray_from_camera(const RaycastManager &raycaster, - const Vec2d &mouse_pos, - const Camera &camera, - const RaycastManager::ISkip *skip); - /// /// Start job for add new volume on surface of object defined by screen coor /// @@ -220,13 +256,26 @@ static void find_closest_volume(const Selection &selection, /// Screen coordinat, where to create new object laying on bed static void start_create_object_job(DataBase &emboss_data, const Vec2d &coor); -/// -/// Search if exist model volume for given id in object lists -/// -/// List to search volume -/// Unique Identifier of volume -/// Volume when found otherwise nullptr -static const ModelVolume *get_volume(const ModelObjectPtrs &objects, const ObjectID &volume_id); +// Have to match order of files in function GLGizmoEmboss::init_icons() +enum class IconType : unsigned { + rename = 0, + erase, + add, + save, + undo, + italic, + unitalic, + bold, + unbold, + system_selector, + open_file, + // automatic calc of icon's count + _count +}; +// Define rendered version of icon +enum class IconState : unsigned { activable = 0, hovered /*1*/, disabled /*2*/ }; +const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType type, IconState state); +bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false); } // namespace priv @@ -246,7 +295,7 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous m_style_manager.discard_style_changes(); set_default_text(); - GLVolume *gl_volume = priv::get_hovered_gl_volume(m_parent); + GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); if (gl_volume != nullptr) { // Try to cast ray into scene and find object for add volume @@ -353,33 +402,6 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) } namespace priv { - -/// -/// Access to model from gl_volume -/// TODO: it is more general function --> move to utils -/// -/// Volume to model belongs to -/// Object containing gl_volume -/// Model for volume -static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObject *object); - -/// -/// Access to model from gl_volume -/// TODO: it is more general function --> move to utils -/// -/// Volume to model belongs to -/// All objects -/// Model for volume -static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObjectPtrs &objects); - -/// -/// Access to model by selection -/// TODO: it is more general function --> move to select utils -/// -/// Actual selection -/// Model from selection -static ModelVolume *get_selected_volume(const Selection &selection); - /// /// Calculate offset from mouse position to center of text /// @@ -388,14 +410,6 @@ static ModelVolume *get_selected_volume(const Selection &selection); /// Offset in screan coordinate static Vec2d calc_mouse_to_center_text_offset(const Vec2d &mouse, const ModelVolume &mv); -/// -/// Access to one selected volume -/// -/// Containe what is selected -/// Slected when only one volume otherwise nullptr -static const GLVolume *get_gl_volume(const Selection &selection); -static GLVolume *get_gl_volume(const GLCanvas3D &canvas); - /// /// Get transformation to world /// - use fix after store to 3mf when exists @@ -414,28 +428,6 @@ static Transform3d world_matrix(const Selection &selection); static void change_window_position(std::optional &output_window_offset, bool try_to_fix); } // namespace priv -const GLVolume *priv::get_gl_volume(const Selection &selection) { - // return selection.get_first_volume(); - const auto &list = selection.get_volume_idxs(); - if (list.size() != 1) - return nullptr; - unsigned int volume_idx = *list.begin(); - return selection.get_volume(volume_idx); -} - -GLVolume *priv::get_gl_volume(const GLCanvas3D &canvas) { - const GLVolume *gl_volume = get_gl_volume(canvas.get_selection()); - if (gl_volume == nullptr) - return nullptr; - - const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; - for (GLVolume *v : gl_volumes) - if (v->composite_id == gl_volume->composite_id) - return v; - - return nullptr; -} - Transform3d priv::world_matrix(const GLVolume *gl_volume, const Model *model) { if (!gl_volume) @@ -444,7 +436,8 @@ Transform3d priv::world_matrix(const GLVolume *gl_volume, const Model *model) if (!model) return res; - ModelVolume* mv = get_model_volume(gl_volume, model->objects); + + const ModelVolume* mv = get_model_volume(*gl_volume, model->objects); if (!mv) return res; @@ -461,7 +454,7 @@ Transform3d priv::world_matrix(const GLVolume *gl_volume, const Model *model) Transform3d priv::world_matrix(const Selection &selection) { - const GLVolume *gl_volume = get_gl_volume(selection); + const GLVolume *gl_volume = get_selected_gl_volume(selection); return world_matrix(gl_volume, selection.get_model()); } @@ -502,231 +495,47 @@ Vec2d priv::calc_mouse_to_center_text_offset(const Vec2d& mouse, const ModelVolu return nearest_offset; } -namespace priv { - // Calculate scale in world -static std::optional calc_scale(const Matrix3d &from, const Matrix3d &to, const Vec3d &dir) -{ - Vec3d from_dir = from * dir; - Vec3d to_dir = to * dir; - double from_scale_sq = from_dir.squaredNorm(); - double to_scale_sq = to_dir.squaredNorm(); - if (is_approx(from_scale_sq, to_scale_sq, 1e-3)) - return {}; // no scale - return sqrt(from_scale_sq / to_scale_sq); -}; - -RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes& condition) -{ - SceneRaycaster::EType type = SceneRaycaster::EType::Volume; - auto scene_casters = canvas.get_raycasters_for_picking(type); - const std::vector> &casters = *scene_casters; - const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; - const ModelObjectPtrs &objects = canvas.get_model()->objects; - - RaycastManager::Meshes meshes; - for (const std::shared_ptr &caster : casters) { - int index = SceneRaycaster::decode_id(type, caster->get_id()); - if (index < 0 || index >= gl_volumes.size()) continue; - const GLVolume *gl_volume = gl_volumes[index]; - const ModelVolume *volume = priv::get_model_volume(gl_volume, objects); - size_t id = volume->id().id; - if (condition.skip(id)) - continue; - auto mesh = std::make_unique(caster->get_raycaster()->get_aabb_mesh()); - meshes.emplace_back(std::make_pair(id, std::move(mesh))); - } - return meshes; -} -} - bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) { - // Fix when leave window during dragging - // Fix when click right button - if (m_surface_drag.has_value() && !mouse_event.Dragging()) { - // write transformation from UI into model - m_parent.do_move(L("Surface move")); + // exist selected volume? + if (m_volume == nullptr) + return false; + + const Camera &camera = wxGetApp().plater()->get_camera(); + bool was_dragging = m_surface_drag.has_value(); + bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager); + bool is_dragging = m_surface_drag.has_value(); - // Update surface by new position + // End with surface dragging? + if (was_dragging && !is_dragging) { + // Update surface by new position if (m_volume->text_configuration->style.prop.use_surface) process(); // Show correct value of height & depth inside of inputs calculate_scale(); - - // allow moving with object again - m_parent.enable_moving(true); - m_surface_drag.reset(); - - // only left up is correct - // otherwise it is fix state and return false - return mouse_event.LeftUp(); } - if (mouse_event.Moving()) - return false; - - // detect start text dragging - if (mouse_event.LeftDown()) { - // exist selected volume? - if (m_volume == nullptr) - return false; - - GLVolume *gl_volume = priv::get_gl_volume(m_parent); - if (gl_volume == nullptr) - return false; - - // is text hovered? - const GLVolumePtrs& gl_volumes = m_parent.get_volumes().volumes; - int hovered_idx = m_parent.get_first_hover_volume_idx(); - if (hovered_idx < 0 || hovered_idx >= gl_volumes.size() || - gl_volumes[hovered_idx] != gl_volume) - return false; - - // hovered object must be actual text volume - const ModelObjectPtrs &objects = m_parent.get_model()->objects; - if (m_volume != priv::get_model_volume(gl_volume, objects)) - return false; - - const ModelInstancePtrs instances = m_volume->get_object()->instances; - int instance_id = gl_volume->instance_idx(); - if (instance_id < 0 || static_cast(instance_id) >= instances.size()) - return false; // should not happen - const ModelInstance *instance = instances[instance_id]; - - const ModelVolumePtrs &volumes = m_volume->get_object()->volumes; - std::vector allowed_volumes_id; - if (volumes.size() > 1) { - allowed_volumes_id.reserve(volumes.size() - 1); - for (auto &v : volumes) { - if (v->id() == m_volume->id()) - continue; - if (!v->is_model_part()) - continue; - allowed_volumes_id.emplace_back(v->id().id); - } - } - RaycastManager::AllowVolumes condition(std::move(allowed_volumes_id)); - RaycastManager::Meshes meshes = priv::create_meshes(m_parent, condition); - // initialize raycasters - // INFO: It could slows down for big objects - // (may be move to thread and do not show drag until it finish) - m_raycast_manager.actualize(instance, &condition, &meshes); - - // wxCoord == int --> wx/types.h - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); - Vec2d mouse_pos = mouse_coord.cast(); - Vec2d mouse_offset = priv::calc_mouse_to_center_text_offset(mouse_pos, *m_volume); - Transform3d volume_tr = gl_volume->get_volume_transformation().get_matrix(); - TextConfiguration &tc = *m_volume->text_configuration; - // fix baked transformation from .3mf store process - if (tc.fix_3mf_tr.has_value()) - volume_tr = volume_tr * tc.fix_3mf_tr->inverse(); - - Transform3d instance_tr = gl_volume->get_instance_transformation().get_matrix(); - Transform3d instance_tr_inv = instance_tr.inverse(); - Transform3d world_tr = instance_tr * volume_tr; - m_surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv, gl_volume, condition}; - + // Start with dragging + else if (!was_dragging && is_dragging) { // Cancel job to prevent interuption of dragging (duplicit result) - if (m_job_cancel != nullptr) + if (m_job_cancel != nullptr) m_job_cancel->store(true); - - // disable moving with object by mouse - m_parent.enable_moving(false); - return true; } - // Dragging starts out of window - if (!m_surface_drag.has_value()) - return false; - - if (mouse_event.Dragging()) { - // wxCoord == int --> wx/types.h - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); - Vec2d mouse_pos = mouse_coord.cast(); - Vec2d offseted_mouse = mouse_pos + m_surface_drag->mouse_offset; - const Camera &camera = wxGetApp().plater()->get_camera(); - auto hit = priv::ray_from_camera(m_raycast_manager, offseted_mouse, camera, &m_surface_drag->condition); - m_surface_drag->exist_hit = hit.has_value(); - if (!hit.has_value()) { - // cross hair need redraw - m_parent.set_as_dirty(); - return true; - } - - auto world_linear = m_surface_drag->world.linear(); - // Calculate offset: transformation to wanted position - { - // Reset skew of the text Z axis: - // Project the old Z axis into a new Z axis, which is perpendicular to the old XY plane. - Vec3d old_z = world_linear.col(2); - Vec3d new_z = world_linear.col(0).cross(world_linear.col(1)); - world_linear.col(2) = new_z * (old_z.dot(new_z) / new_z.squaredNorm()); - } - - Vec3d text_z_world = world_linear.col(2); // world_linear * Vec3d::UnitZ() - auto z_rotation = Eigen::Quaternion::FromTwoVectors(text_z_world, hit->normal); - Transform3d world_new = z_rotation * m_surface_drag->world; - auto world_new_linear = world_new.linear(); - - if (true) - { - // Fix direction of up vector - Vec3d z_world = world_new_linear.col(2); - z_world.normalize(); - Vec3d wanted_up = suggest_up(z_world); - - Vec3d y_world = world_new_linear.col(1); - auto y_rotation = Eigen::Quaternion::FromTwoVectors(y_world, wanted_up); - - world_new = y_rotation * world_new; - world_new_linear = world_new.linear(); - } - - // Edit position from right - Transform3d volume_new{Eigen::Translation(m_surface_drag->instance_inv * hit->position)}; - volume_new.linear() = m_surface_drag->instance_inv.linear() * world_new_linear; - - // Check that transformation matrix is valid transformation - assert(volume_new.matrix()(0, 0) == volume_new.matrix()(0, 0)); // Check valid transformation not a NAN - if (volume_new.matrix()(0, 0) != volume_new.matrix()(0, 0)) - return true; - - // Check that scale in world did not changed - assert(!priv::calc_scale(world_linear, world_new_linear, Vec3d::UnitY()).has_value()); - assert(!priv::calc_scale(world_linear, world_new_linear, Vec3d::UnitZ()).has_value()); - - const TextConfiguration &tc = *m_volume->text_configuration; - // fix baked transformation from .3mf store process - if (tc.fix_3mf_tr.has_value()) - volume_new = volume_new * (*tc.fix_3mf_tr); - - // apply move in Z direction and rotation by up vector - apply_transformation(tc.style.prop, volume_new); - - // Update transformation for all instances - for (GLVolume *vol : m_parent.get_volumes().volumes) { - if (vol->object_idx() != m_surface_drag->gl_volume->object_idx() || - vol->volume_idx() != m_surface_drag->gl_volume->volume_idx()) - continue; - vol->set_volume_transformation(volume_new); - } - + // during drag + else if (was_dragging && is_dragging) { // update scale of selected volume --> should be approx the same calculate_scale(); - - m_parent.set_as_dirty(); - return true; } - return false; + return res; } bool GLGizmoEmboss::on_mouse(const wxMouseEvent &mouse_event) { // not selected volume if (m_volume == nullptr || - priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr || + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr || !m_volume->text_configuration.has_value()) return false; if (on_mouse_for_rotation(mouse_event)) return true; @@ -755,7 +564,7 @@ std::string GLGizmoEmboss::on_get_name() const { return _u8L("Emboss"); } void GLGizmoEmboss::on_render() { // no volume selected if (m_volume == nullptr || - priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr) + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr) return; Selection &selection = m_parent.get_selection(); if (selection.is_empty()) return; @@ -861,7 +670,7 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) set_volume_by_selection(); // Do not render window for not selected text volume if (m_volume == nullptr || - priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr || + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr || !m_volume->text_configuration.has_value()) { close(); return; @@ -870,15 +679,15 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) // Configuration creation double screen_scale = wxDisplay(wxGetApp().plater()).GetScaleFactor(); float main_toolbar_height = m_parent.get_main_toolbar_height(); - if (!m_gui_cfg.has_value() || // Exist configuration - first run + if (m_gui_cfg == nullptr || // Exist configuration - first run m_gui_cfg->screen_scale != screen_scale || // change of DPI m_gui_cfg->main_toolbar_height != main_toolbar_height // change size of view port ) { // Create cache for gui offsets - GuiCfg cfg = create_gui_configuration(); + GuiCfg cfg = create_gui_configuration(); cfg.screen_scale = screen_scale; cfg.main_toolbar_height = main_toolbar_height; - m_gui_cfg.emplace(std::move(cfg)); + m_gui_cfg = std::make_unique(std::move(cfg)); // set position near toolbar m_set_window_offset = ImVec2(-1.f, -1.f); @@ -1009,13 +818,13 @@ void GLGizmoEmboss::on_set_state() // when open window by "T" and no valid volume is selected, so Create new one if (m_volume == nullptr || - priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr ) { + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr ) { // reopen gizmo when new object is created GLGizmoBase::m_state = GLGizmoBase::Off; if (wxGetApp().get_mode() == comSimple) // It's impossible to add a part in simple mode return; - // start creating new object + // start creating new object create_volume(ModelVolumeType::MODEL_PART); } @@ -1023,7 +832,7 @@ void GLGizmoEmboss::on_set_state() if (m_allow_open_near_volume) { m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); } else { - if (m_gui_cfg.has_value()) + if (m_gui_cfg != nullptr) priv::change_window_position(m_set_window_offset, false); else m_set_window_offset = ImVec2(-1, -1); @@ -1226,7 +1035,7 @@ void GLGizmoEmboss::set_default_text(){ m_text = _u8L("Embossed text"); } void GLGizmoEmboss::set_volume_by_selection() { const Selection &selection = m_parent.get_selection(); - ModelVolume *vol = priv::get_selected_volume(selection); + ModelVolume *vol = get_selected_volume(selection); // is same volume selected? if (vol != nullptr && vol->id() == m_volume_id) return; @@ -1351,7 +1160,7 @@ bool GLGizmoEmboss::set_volume(ModelVolume *volume) // The change of volume could show or hide part with setter on volume type if (m_volume == nullptr || - priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr || + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr || (m_volume->get_object()->volumes.size() == 1) != (volume->get_object()->volumes.size() == 1)){ m_should_set_minimal_windows_size = true; @@ -1409,35 +1218,6 @@ void GLGizmoEmboss::calculate_scale() { m_style_manager.clear_imgui_font(); } -ModelVolume *priv::get_model_volume(const GLVolume *gl_volume, const ModelObject *object) -{ - int volume_id = gl_volume->volume_idx(); - if (volume_id < 0 || static_cast(volume_id) >= object->volumes.size()) return nullptr; - return object->volumes[volume_id]; -} - -ModelVolume *priv::get_model_volume(const GLVolume *gl_volume, const ModelObjectPtrs &objects) -{ - int object_id = gl_volume->object_idx(); - if (object_id < 0 || static_cast(object_id) >= objects.size()) return nullptr; - return get_model_volume(gl_volume, objects[object_id]); -} - -ModelVolume *priv::get_selected_volume(const Selection &selection) -{ - int object_idx = selection.get_object_idx(); - // is more object selected? - if (object_idx == -1) return nullptr; - - auto volume_idxs = selection.get_volume_idxs(); - // is more volumes selected? - if (volume_idxs.size() != 1) return nullptr; - unsigned int vol_id_gl = *volume_idxs.begin(); - const GLVolume *vol_gl = selection.get_volume(vol_id_gl); - const ModelObjectPtrs &objects = selection.get_model()->objects; - return get_model_volume(vol_gl, objects); -} - // Run Job on main thread (blocking) - ONLY DEBUG static inline void execute_job(std::shared_ptr j) { @@ -1529,6 +1309,10 @@ bool GLGizmoEmboss::process() return true; } +namespace priv { +static bool is_text_empty(const std::string &text) { return text.empty() || text.find_first_not_of(" \n\t\r") == std::string::npos; } +} + void GLGizmoEmboss::close() { // remove volume when text is empty @@ -1579,10 +1363,10 @@ bool priv::apply_camera_dir(const Camera &camera, GLCanvas3D &canvas) { if (is_approx(cam_dir_tr, emboss_dir)) return false; assert(sel.get_volume_idxs().size() == 1); - GLVolume *vol = sel.get_volume(*sel.get_volume_idxs().begin()); + GLVolume *gl_volume = sel.get_volume(*sel.get_volume_idxs().begin()); Transform3d vol_rot; - Transform3d vol_tr = vol->get_volume_transformation().get_matrix(); + Transform3d vol_tr = gl_volume->get_volume_transformation().get_matrix(); // check whether cam_dir is opposit to emboss dir if (is_approx(cam_dir_tr, -emboss_dir)) { // rotate 180 DEG by y @@ -1602,8 +1386,8 @@ bool priv::apply_camera_dir(const Camera &camera, GLCanvas3D &canvas) { vol_rot * Eigen::Translation(offset_inv); //Transform3d res = vol_tr * vol_rot; - vol->set_volume_transformation(Geometry::Transformation(res)); - priv::get_model_volume(vol, sel.get_model()->objects)->set_transformation(res); + gl_volume->set_volume_transformation(Geometry::Transformation(res)); + get_model_volume(*gl_volume, sel.get_model()->objects)->set_transformation(res); return true; } @@ -2467,7 +2251,7 @@ void GLGizmoEmboss::draw_style_rename_button() bool can_rename = m_style_manager.exist_stored_style(); std::string title = _u8L("Rename style"); const char * popup_id = title.c_str(); - if (draw_button(IconType::rename, !can_rename)) { + if (priv::draw_button(m_icons, IconType::rename, !can_rename)) { assert(m_style_manager.get_stored_style()); ImGui::OpenPopup(popup_id); } @@ -2484,7 +2268,7 @@ void GLGizmoEmboss::draw_style_rename_button() void GLGizmoEmboss::draw_style_save_button(bool is_modified) { - if (draw_button(IconType::save, !is_modified)) { + if (draw_button(m_icons, IconType::save, !is_modified)) { // save styles to app config m_style_manager.store_styles_to_app_config(); }else if (ImGui::IsItemHovered()) { @@ -2554,7 +2338,7 @@ void GLGizmoEmboss::draw_style_add_button() const char *popup_id = title.c_str(); // save as new style ImGui::SameLine(); - if (draw_button(IconType::add, !can_add)) { + if (draw_button(m_icons, IconType::add, !can_add)) { if (!m_style_manager.exist_stored_style()) { m_style_manager.store_styles_to_app_config(wxGetApp().app_config); } else { @@ -2585,7 +2369,7 @@ void GLGizmoEmboss::draw_delete_style_button() { std::string title = _u8L("Remove style"); const char * popup_id = title.c_str(); static size_t next_style_index = std::numeric_limits::max(); - if (draw_button(IconType::erase, !can_delete)) { + if (draw_button(m_icons, IconType::erase, !can_delete)) { while (true) { // NOTE: can't use previous loaded activ index -> erase could change index size_t active_index = m_style_manager.get_style_index(); @@ -2807,8 +2591,8 @@ bool GLGizmoEmboss::draw_italic_button() bool is_font_italic = skew.has_value() || WxFontUtils::is_italic(wx_font); if (is_font_italic) { // unset italic - if (clickable(get_icon(IconType::italic, IconState::hovered), - get_icon(IconType::unitalic, IconState::hovered))) { + if (clickable(get_icon(m_icons, IconType::italic, IconState::hovered), + get_icon(m_icons, IconType::unitalic, IconState::hovered))) { skew.reset(); if (wx_font.GetStyle() != wxFontStyle::wxFONTSTYLE_NORMAL) { wxFont new_wx_font = wx_font; // copy @@ -2822,7 +2606,7 @@ bool GLGizmoEmboss::draw_italic_button() ImGui::SetTooltip("%s", _u8L("Unset italic").c_str()); } else { // set italic - if (draw_button(IconType::italic)) { + if (draw_button(m_icons, IconType::italic)) { wxFont new_wx_font = wx_font; // copy auto new_ff = WxFontUtils::set_italic(new_wx_font, *ff.font_file); if (new_ff != nullptr) { @@ -2845,7 +2629,7 @@ bool GLGizmoEmboss::draw_bold_button() { const std::optional &wx_font_opt = m_style_manager.get_wx_font(); const auto& ff = m_style_manager.get_font_file_with_cache(); if (!wx_font_opt.has_value() || !ff.has_value()) { - draw(get_icon(IconType::bold, IconState::disabled)); + draw(get_icon(m_icons, IconType::bold, IconState::disabled)); return false; } const wxFont &wx_font = *wx_font_opt; @@ -2854,8 +2638,8 @@ bool GLGizmoEmboss::draw_bold_button() { bool is_font_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font); if (is_font_bold) { // unset bold - if (clickable(get_icon(IconType::bold, IconState::hovered), - get_icon(IconType::unbold, IconState::hovered))) { + if (clickable(get_icon(m_icons, IconType::bold, IconState::hovered), + get_icon(m_icons, IconType::unbold, IconState::hovered))) { boldness.reset(); if (wx_font.GetWeight() != wxFontWeight::wxFONTWEIGHT_NORMAL) { wxFont new_wx_font = wx_font; // copy @@ -2869,7 +2653,7 @@ bool GLGizmoEmboss::draw_bold_button() { ImGui::SetTooltip("%s", _u8L("Unset bold").c_str()); } else { // set bold - if (draw_button(IconType::bold)) { + if (draw_button(m_icons, IconType::bold)) { wxFont new_wx_font = wx_font; // copy auto new_ff = WxFontUtils::set_bold(new_wx_font, *ff.font_file); if (new_ff != nullptr) { @@ -2922,7 +2706,7 @@ bool GLGizmoEmboss::revertible(const std::string &name, // render revert changes button if (changed) { ImGui::SameLine(undo_offset); - if (draw_button(IconType::undo)) { + if (draw_button(m_icons, IconType::undo)) { value = *default_value; return true; } else if (ImGui::IsItemHovered()) @@ -3074,7 +2858,7 @@ void GLGizmoEmboss::draw_style_edit() { EmbossStyle &style = m_style_manager.get_style(); if (exist_change_in_font) { ImGui::SameLine(ImGui::GetStyle().FramePadding.x); - if (draw_button(IconType::undo)) { + if (draw_button(m_icons, IconType::undo)) { const EmbossStyle *stored_style = m_style_manager.get_stored_style(); style.path = stored_style->path; style.prop.boldness = stored_style->prop.boldness; @@ -3293,7 +3077,7 @@ std::optional priv::calc_surface_offset(const ModelVolume &volume, Raycas raycast_manager.actualize(volume.get_object(), &cond); //const Selection &selection = m_parent.get_selection(); - const GLVolume *gl_volume = priv::get_gl_volume(selection); + const GLVolume *gl_volume = get_selected_gl_volume(selection); Transform3d to_world = priv::world_matrix(gl_volume, selection.get_model()); Vec3d point = to_world * Vec3d::Zero(); Vec3d direction = to_world.linear() * (-Vec3d::UnitZ()); @@ -3555,7 +3339,7 @@ void GLGizmoEmboss::draw_advanced() } if (ImGui::Button(_u8L("Set text to face camera").c_str())) { - assert(priv::get_selected_volume(m_parent.get_selection()) == m_volume); + assert(get_selected_volume(m_parent.get_selection()) == m_volume); const Camera &cam = wxGetApp().plater()->get_camera(); bool use_surface = m_style_manager.get_style().prop.use_surface; if (priv::apply_camera_dir(cam, m_parent) && use_surface) @@ -3800,13 +3584,13 @@ void GLGizmoEmboss::init_icons() m_icons = m_icon_manager.init(filenames, size, type); } -const IconManager::Icon &GLGizmoEmboss::get_icon(IconType type, IconState state) { return *m_icons[(unsigned) type][(unsigned) state]; } -bool GLGizmoEmboss::draw_button(IconType type, bool disable) +const IconManager::Icon &priv::get_icon(const IconManager::VIcons& icons, IconType type, IconState state) { return *icons[(unsigned) type][(unsigned) state]; } +bool priv::draw_button(const IconManager::VIcons &icons, IconType type, bool disable) { return Slic3r::GUI::button( - get_icon(type, IconState::activable), - get_icon(type, IconState::hovered), - get_icon(type, IconState::disabled), + get_icon(icons, type, IconState::activable), + get_icon(icons, type, IconState::hovered), + get_icon(icons, type, IconState::disabled), disable ); } @@ -3921,37 +3705,6 @@ void priv::start_create_volume_job(const ModelObject *object, queue_job(worker, std::move(job)); } -const ModelVolume *priv::get_volume(const ModelObjectPtrs &objects, const ObjectID &volume_id) -{ - for (const ModelObject *obj : objects) - for (const ModelVolume *vol : obj->volumes) - if (vol->id() == volume_id) - return vol; - return nullptr; -}; - -GLVolume * priv::get_hovered_gl_volume(const GLCanvas3D &canvas) { - int hovered_id_signed = canvas.get_first_hover_volume_idx(); - if (hovered_id_signed < 0) return nullptr; - - size_t hovered_id = static_cast(hovered_id_signed); - const GLVolumePtrs &volumes = canvas.get_volumes().volumes; - if (hovered_id >= volumes.size()) return nullptr; - - return volumes[hovered_id]; -} - -std::optional priv::ray_from_camera(const RaycastManager &raycaster, - const Vec2d &mouse_pos, - const Camera &camera, - const RaycastManager::ISkip *skip) -{ - Vec3d point; - Vec3d direction; - CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); - return raycaster.first_hit(point, direction, skip); -} - bool priv::start_create_volume_on_surface_job( DataBase &emboss_data, ModelVolumeType volume_type, const Vec2d &screen_coor, const GLVolume *gl_volume, RaycastManager &raycaster, GLCanvas3D& canvas) { @@ -3967,11 +3720,11 @@ bool priv::start_create_volume_on_surface_job( size_t vol_id = obj->volumes[gl_volume->volume_idx()]->id().id; auto cond = RaycastManager::AllowVolumes({vol_id}); - RaycastManager::Meshes meshes = priv::create_meshes(canvas, cond); + RaycastManager::Meshes meshes = create_meshes(canvas, cond); raycaster.actualize(obj, &cond, &meshes); const Camera &camera = plater->get_camera(); - std::optional hit = priv::ray_from_camera(raycaster, screen_coor, camera, &cond); + std::optional hit = ray_from_camera(raycaster, screen_coor, camera, &cond); // context menu for add text could be open only by right click on an // object. After right click, object is selected and object_idx is set @@ -4009,7 +3762,7 @@ void priv::find_closest_volume(const Selection &selection, double center_sq_distance = std::numeric_limits::max(); for (unsigned int id : indices) { const GLVolume *gl_volume = selection.get_volume(id); - ModelVolume *volume = priv::get_model_volume(gl_volume, objects); + const ModelVolume *volume = get_model_volume(*gl_volume, objects); if (!volume->is_model_part()) continue; Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume); Vec2d c = hull.centroid().cast(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 2bf50ce5e0..5c5a63ebe5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -6,6 +6,7 @@ #include "GLGizmoBase.hpp" #include "GLGizmoRotate.hpp" #include "slic3r/GUI/IconManager.hpp" +#include "slic3r/GUI/SurfaceDrag.hpp" #include "slic3r/Utils/RaycastManager.hpp" #include "slic3r/Utils/EmbossStyleManager.hpp" @@ -25,7 +26,6 @@ class wxFont; namespace Slic3r{ class AppConfig; class GLVolume; - enum class ModelVolumeType : int; } @@ -145,7 +145,7 @@ private: template 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; + bool m_should_set_minimal_windows_size = false; void set_minimal_window_size(bool is_advance_edit_style); ImVec2 get_minimal_window_size() const; @@ -161,65 +161,12 @@ private: bool m_is_unknown_font; void create_notification_not_valid_font(const TextConfiguration& tc); 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; - - 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 size; - std::string depth; - std::string use_surface; - - // advanced - std::string char_gap; - std::string line_gap; - std::string boldness; - std::string italic; - std::string surface_distance; - std::string angle; - std::string collection; - }; - Translations translations; - }; - std::optional m_gui_cfg; + + struct GuiCfg; + std::unique_ptr m_gui_cfg = nullptr; 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 @@ -228,6 +175,7 @@ private: // setted only when wanted to use - not all the time std::optional m_set_window_offset; + // Keep information about stored styles and loaded actual style to compare with Emboss::StyleManager m_style_manager; struct FaceName{ @@ -290,7 +238,8 @@ private: // Text to emboss std::string m_text; - // actual volume + // 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 @@ -308,27 +257,6 @@ private: // Value is set only when dragging rotation to calculate actual angle std::optional m_rotate_start_angle; - // Data for drag&drop over surface with mouse - struct SurfaceDrag - { - // hold screen coor offset of cursor from object center - Vec2d mouse_offset; - - // Start dragging text transformations to world - Transform3d world; - - // Invers transformation of text volume instance - // Help convert world transformation to instance space - Transform3d instance_inv; - - // Dragged gl volume - GLVolume *gl_volume; - - // condition for raycaster - RaycastManager::AllowVolumes condition; - - bool exist_hit = true; - }; // Keep data about dragging only during drag&drop std::optional m_surface_drag; @@ -343,27 +271,8 @@ private: // drawing icons IconManager m_icon_manager; - std::vector m_icons; - + IconManager::VIcons m_icons; void init_icons(); - enum class IconType : unsigned { - rename = 0, - erase, - add, - save, - undo, - italic, - unitalic, - bold, - unbold, - system_selector, - open_file, - // automatic calc of icon's count - _count - }; - enum class IconState : unsigned { activable = 0, hovered /*1*/, disabled /*2*/ }; - const IconManager::Icon& get_icon(IconType type, IconState state); - bool draw_button(IconType icon, bool disable = false); // only temporary solution static const std::string M_ICON_FILENAME; diff --git a/src/slic3r/GUI/IconManager.hpp b/src/slic3r/GUI/IconManager.hpp index 78b1395dce..aa7afda800 100644 --- a/src/slic3r/GUI/IconManager.hpp +++ b/src/slic3r/GUI/IconManager.hpp @@ -63,6 +63,8 @@ public: // && size.x > 0 && size.y > 0 && tl.x != br.x && tl.y != br.y; }; using Icons = std::vector >; + // Vector of icons, each vector contain multiple use of a SVG render + using VIcons = std::vector; /// /// Initialize raster texture on GPU with given images @@ -71,7 +73,7 @@ public: /// Define files and its /// Rasterized icons stored on GPU, /// Same size and order as input, each item of vector is set of texture in order by RasterType - std::vector init(const InitTypes &input); + VIcons init(const InitTypes &input); /// /// Initialize multiple icons with same settings for size and type @@ -83,7 +85,7 @@ public: /// together color, white and gray = RasterType::color | RasterType::white_only_data | RasterType::gray_only_data /// Rasterized icons stored on GPU, /// Same size and order as file_paths, each item of vector is set of texture in order by RasterType - std::vector init(const std::vector &file_paths, const ImVec2 &size, RasterType type = RasterType::color); + VIcons init(const std::vector &file_paths, const ImVec2 &size, RasterType type = RasterType::color); /// /// Release icons which are hold only by this manager diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index fd0a5ec595..3e85d1d884 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -3364,5 +3364,28 @@ void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& v } #endif // ENABLE_WORLD_COORDINATE +ModelVolume *get_selected_volume(const Selection &selection) +{ + const GLVolume *vol_gl = get_selected_gl_volume(selection); + const ModelObjectPtrs &objects = selection.get_model()->objects; + return get_model_volume(*vol_gl, objects); +} + +const GLVolume *get_selected_gl_volume(const Selection &selection) +{ + int object_idx = selection.get_object_idx(); + // is more object selected? + if (object_idx == -1) + return nullptr; + + const auto &list = selection.get_volume_idxs(); + // is more volumes selected? + if (list.size() != 1) + return nullptr; + + unsigned int volume_idx = *list.begin(); + return selection.get_volume(volume_idx); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c1c97bd2e6..a000f661bf 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -17,6 +17,7 @@ namespace Slic3r { class Shader; class Model; class ModelObject; +class ModelVolume; class GLVolume; class GLArrow; class GLCurvedArrow; @@ -519,6 +520,9 @@ private: #endif // ENABLE_WORLD_COORDINATE }; +ModelVolume *get_selected_volume(const Selection &selection); +const GLVolume *get_selected_gl_volume(const Selection &selection); + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/SurfaceDrag.cpp b/src/slic3r/GUI/SurfaceDrag.cpp new file mode 100644 index 0000000000..d2986bbf42 --- /dev/null +++ b/src/slic3r/GUI/SurfaceDrag.cpp @@ -0,0 +1,244 @@ +#include "SurfaceDrag.hpp" + +#include "libslic3r/Model.hpp" // ModelVolume +#include "GLCanvas3D.hpp" +#include "slic3r/Utils/RaycastManager.hpp" + +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/CameraUtils.hpp" + +#include "libslic3r/Emboss.hpp" + +// getter on camera needs +//#include "slic3r/GUI/GUI_App.hpp" +//#include "Plater.hpp" + +namespace Slic3r::GUI { + +static Vec2d calc_screen_offset_to_volume_center(const Vec2d &screen_coor, const ModelVolume &volume, const Camera &camera) +{ + const Transform3d &volume_tr = volume.get_matrix(); + assert(volume.text_configuration.has_value()); + + auto calc_offset = [&screen_coor, &volume_tr, &camera, &volume](const Transform3d &instrance_tr) -> Vec2d { + Transform3d to_world = instrance_tr * volume_tr; + + // Use fix of .3mf loaded tranformation when exist + if (volume.text_configuration->fix_3mf_tr.has_value()) + to_world = to_world * (*volume.text_configuration->fix_3mf_tr); + // zero point of volume in world coordinate system + Vec3d volume_center = to_world.translation(); + // screen coordinate of volume center + Vec2i coor = CameraUtils::project(camera, volume_center); + return coor.cast() - screen_coor; + }; + + auto object = volume.get_object(); + assert(!object->instances.empty()); + // Speed up for one instance + if (object->instances.size() == 1) + return calc_offset(object->instances.front()->get_matrix()); + + Vec2d nearest_offset; + double nearest_offset_size = std::numeric_limits::max(); + for (const ModelInstance *instance : object->instances) { + Vec2d offset = calc_offset(instance->get_matrix()); + double offset_size = offset.norm(); + if (nearest_offset_size < offset_size) + continue; + nearest_offset_size = offset_size; + nearest_offset = offset; + } + return nearest_offset; +} + + // Calculate scale in world +static std::optional calc_scale(const Matrix3d &from, const Matrix3d &to, const Vec3d &dir) +{ + Vec3d from_dir = from * dir; + Vec3d to_dir = to * dir; + double from_scale_sq = from_dir.squaredNorm(); + double to_scale_sq = to_dir.squaredNorm(); + if (is_approx(from_scale_sq, to_scale_sq, 1e-3)) + return {}; // no scale + return sqrt(from_scale_sq / to_scale_sq); +} + +bool on_mouse_surface_drag(const wxMouseEvent &mouse_event, + const Camera &camera, + std::optional &surface_drag, + GLCanvas3D &canvas, + RaycastManager &raycast_manager) +{ + // Fix when leave window during dragging + // Fix when click right button + if (surface_drag.has_value() && !mouse_event.Dragging()) { + // write transformation from UI into model + canvas.do_move(L("Surface move")); + + // allow moving with object again + canvas.enable_moving(true); + surface_drag.reset(); + + // only left up is correct + // otherwise it is fix state and return false + return mouse_event.LeftUp(); + } + + if (mouse_event.Moving()) + return false; + + // detect start text dragging + if (mouse_event.LeftDown()) { + // selected volume + GLVolume *gl_volume = get_selected_gl_volume(canvas); + if (gl_volume == nullptr) + return false; + + // is selected volume closest hovered? + const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; + int hovered_idx = canvas.get_first_hover_volume_idx(); + if (hovered_idx < 0 || + hovered_idx >= gl_volumes.size() || + gl_volumes[hovered_idx] != gl_volume) + return false; + + const ModelObject *object = get_model_object(*gl_volume, canvas.get_model()->objects); + const ModelInstance *instance = get_model_instance(*gl_volume, *object); + const ModelVolume *volume = get_model_volume(*gl_volume, *object); + + assert(object != nullptr && instance != nullptr && volume != nullptr); + if (object == nullptr || instance == nullptr || volume == nullptr) + return false; + + const ModelVolumePtrs &volumes = object->volumes; + std::vector allowed_volumes_id; + if (volumes.size() > 1) { + allowed_volumes_id.reserve(volumes.size() - 1); + for (auto &v : volumes) { + // skip actual selected object + if (v->id() == volume->id()) + continue; + // drag only above part not modifiers or negative surface + if (!v->is_model_part()) + continue; + allowed_volumes_id.emplace_back(v->id().id); + } + } + RaycastManager::AllowVolumes condition(std::move(allowed_volumes_id)); + RaycastManager::Meshes meshes = create_meshes(canvas, condition); + // initialize raycasters + // INFO: It could slows down for big objects + // (may be move to thread and do not show drag until it finish) + raycast_manager.actualize(instance, &condition, &meshes); + + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + Vec2d mouse_offset = calc_screen_offset_to_volume_center(mouse_pos, *volume, camera); + + Transform3d volume_tr = gl_volume->get_volume_transformation().get_matrix(); + + if (volume->text_configuration.has_value()) { + const TextConfiguration &tc = *volume->text_configuration; + // fix baked transformation from .3mf store process + if (tc.fix_3mf_tr.has_value()) + volume_tr = volume_tr * tc.fix_3mf_tr->inverse(); + } + + Transform3d instance_tr = instance->get_matrix(); + Transform3d instance_tr_inv = instance_tr.inverse(); + Transform3d world_tr = instance_tr * volume_tr; + surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv, gl_volume, condition}; + + // disable moving with object by mouse + canvas.enable_moving(false); + return true; + } + + // Dragging starts out of window + if (!surface_drag.has_value()) + return false; + + if (mouse_event.Dragging()) { + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + Vec2d offseted_mouse = mouse_pos + surface_drag->mouse_offset; + + std::optional hit = ray_from_camera( + raycast_manager, offseted_mouse, camera, &surface_drag->condition); + + surface_drag->exist_hit = hit.has_value(); + if (!hit.has_value()) { + // cross hair need redraw + canvas.set_as_dirty(); + return true; + } + + auto world_linear = surface_drag->world.linear(); + // Calculate offset: transformation to wanted position + { + // Reset skew of the text Z axis: + // Project the old Z axis into a new Z axis, which is perpendicular to the old XY plane. + Vec3d old_z = world_linear.col(2); + Vec3d new_z = world_linear.col(0).cross(world_linear.col(1)); + world_linear.col(2) = new_z * (old_z.dot(new_z) / new_z.squaredNorm()); + } + + Vec3d text_z_world = world_linear.col(2); // world_linear * Vec3d::UnitZ() + auto z_rotation = Eigen::Quaternion::FromTwoVectors(text_z_world, hit->normal); + Transform3d world_new = z_rotation * surface_drag->world; + auto world_new_linear = world_new.linear(); + + // Fix direction of up vector + { + Vec3d z_world = world_new_linear.col(2); + z_world.normalize(); + Vec3d wanted_up = Emboss::suggest_up(z_world); + + Vec3d y_world = world_new_linear.col(1); + auto y_rotation = Eigen::Quaternion::FromTwoVectors(y_world, wanted_up); + + world_new = y_rotation * world_new; + world_new_linear = world_new.linear(); + } + + // Edit position from right + Transform3d volume_new{Eigen::Translation(surface_drag->instance_inv * hit->position)}; + volume_new.linear() = surface_drag->instance_inv.linear() * world_new_linear; + + // Check that transformation matrix is valid transformation + assert(volume_new.matrix()(0, 0) == volume_new.matrix()(0, 0)); // Check valid transformation not a NAN + if (volume_new.matrix()(0, 0) != volume_new.matrix()(0, 0)) + return true; + + // Check that scale in world did not changed + assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitY()).has_value()); + assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitZ()).has_value()); + + const ModelVolume *volume = get_model_volume(*surface_drag->gl_volume, canvas.get_model()->objects); + if (volume != nullptr && volume->text_configuration.has_value()) { + const TextConfiguration &tc = *volume->text_configuration; + // fix baked transformation from .3mf store process + if (tc.fix_3mf_tr.has_value()) + volume_new = volume_new * (*tc.fix_3mf_tr); + + // apply move in Z direction and rotation by up vector + Emboss::apply_transformation(tc.style.prop, volume_new); + } + + // Update transformation for all instances + for (GLVolume *vol : canvas.get_volumes().volumes) { + if (vol->object_idx() != surface_drag->gl_volume->object_idx() || vol->volume_idx() != surface_drag->gl_volume->volume_idx()) + continue; + vol->set_volume_transformation(volume_new); + } + + canvas.set_as_dirty(); + return true; + } + return false; +} + +} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/SurfaceDrag.hpp b/src/slic3r/GUI/SurfaceDrag.hpp new file mode 100644 index 0000000000..c3dc9cd2db --- /dev/null +++ b/src/slic3r/GUI/SurfaceDrag.hpp @@ -0,0 +1,47 @@ +#ifndef slic3r_SurfaceDrag_hpp_ +#define slic3r_SurfaceDrag_hpp_ + +#include +#include "libslic3r/Point.hpp" // Vec2d, Transform3d +#include "slic3r/Utils/RaycastManager.hpp" +#include "wx/event.h" // wxMouseEvent + +namespace Slic3r { +class GLVolume; +} // namespace Slic3r + +namespace Slic3r::GUI { +class GLCanvas3D; +struct Camera; + +// Data for drag&drop over surface with mouse +struct SurfaceDrag +{ + // hold screen coor offset of cursor from object center + Vec2d mouse_offset; + + // Start dragging text transformations to world + Transform3d world; + + // Invers transformation of text volume instance + // Help convert world transformation to instance space + Transform3d instance_inv; + + // Dragged gl volume + GLVolume *gl_volume; + + // condition for raycaster + RaycastManager::AllowVolumes condition; + + // Flag whether coordinate hit some volume + bool exist_hit = true; +}; + +bool on_mouse_surface_drag(const wxMouseEvent &mouse_event, + const Camera &camera, + std::optional &surface_drag, + GLCanvas3D &canvas, + RaycastManager &raycast_manager); + +} // namespace Slic3r::GUI +#endif // slic3r_SurfaceDrag_hpp_ \ No newline at end of file diff --git a/src/slic3r/Utils/RaycastManager.cpp b/src/slic3r/Utils/RaycastManager.cpp index 3b0ace2a34..18c9bb2f19 100644 --- a/src/slic3r/Utils/RaycastManager.cpp +++ b/src/slic3r/Utils/RaycastManager.cpp @@ -295,3 +295,47 @@ RaycastManager::TrItems::iterator priv::find(RaycastManager::TrItems &items, con return items.end(); return it; } + +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/CameraUtils.hpp" + +namespace Slic3r::GUI{ + +RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition) +{ + SceneRaycaster::EType type = SceneRaycaster::EType::Volume; + auto scene_casters = canvas.get_raycasters_for_picking(type); + const std::vector> &casters = *scene_casters; + const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; + const ModelObjectPtrs &objects = canvas.get_model()->objects; + + RaycastManager::Meshes meshes; + for (const std::shared_ptr &caster : casters) { + int index = SceneRaycaster::decode_id(type, caster->get_id()); + if (index < 0 || index >= gl_volumes.size()) + continue; + const GLVolume *gl_volume = gl_volumes[index]; + const ModelVolume *volume = get_model_volume(*gl_volume, objects); + size_t id = volume->id().id; + if (condition.skip(id)) + continue; + auto mesh = std::make_unique(caster->get_raycaster()->get_aabb_mesh()); + meshes.emplace_back(std::make_pair(id, std::move(mesh))); + } + return meshes; +} + + +std::optional ray_from_camera(const RaycastManager &raycaster, + const Vec2d &mouse_pos, + const Camera &camera, + const RaycastManager::ISkip *skip) +{ + Vec3d point; + Vec3d direction; + CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); + return raycaster.first_hit(point, direction, skip); +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/Utils/RaycastManager.hpp b/src/slic3r/Utils/RaycastManager.hpp index 4ef9b7ca76..a3cd3ef910 100644 --- a/src/slic3r/Utils/RaycastManager.hpp +++ b/src/slic3r/Utils/RaycastManager.hpp @@ -144,6 +144,29 @@ public: Transform3d get_transformation(const TrKey &tr_key) const; }; +class GLCanvas3D; +/// +/// Use scene Raycasters and prepare data for actualize RaycasterManager +/// +/// contain Scene raycasters +/// Limit for scene casters +/// Meshes +RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition); + +struct Camera; +/// +/// Unproject on mesh by Mesh raycasters +/// +/// Position of mouse on screen +/// Projection params +/// Define which caster will be skipped, null mean no skip +/// Position on surface, normal direction in world coorinate +/// + key, to know hitted instance and volume +std::optional ray_from_camera(const RaycastManager &raycaster, + const Vec2d &mouse_pos, + const Camera &camera, + const RaycastManager::ISkip *skip); + } // namespace Slic3r::GUI #endif // slic3r_RaycastManager_hpp_