Separate drag manager

This commit is contained in:
Filip Sykala - NTB T15p 2023-02-28 09:17:25 +01:00
parent 4d0b8679eb
commit e86dff528d
12 changed files with 646 additions and 492 deletions

View file

@ -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

View file

@ -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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(gl_volume.instance_idx());
if (instance_idx >= object.instances.size())
return nullptr;
return object.instances[instance_idx];
}
} // namespace GUI
} // namespace Slic3r

View file

@ -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

View file

@ -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<typename T> void to_range_pi_pi(T& angle)
{
@ -123,6 +118,63 @@ template<typename T> 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 {
/// <summary>
/// Prepare data for emboss
/// </summary>
@ -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);
/// <summary>
/// Unproject on mesh by Mesh raycasters
/// </summary>
/// <param name="mouse_pos">Position of mouse on screen</param>
/// <param name="camera">Projection params</param>
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
/// <returns>Position on surface, normal direction in world coorinate
/// + key, to know hitted instance and volume</returns>
static std::optional<RaycastManager::Hit> ray_from_camera(const RaycastManager &raycaster,
const Vec2d &mouse_pos,
const Camera &camera,
const RaycastManager::ISkip *skip);
/// <summary>
/// Start job for add new volume on surface of object defined by screen coor
/// </summary>
@ -220,13 +256,26 @@ static void find_closest_volume(const Selection &selection,
/// <param name="coor">Screen coordinat, where to create new object laying on bed</param>
static void start_create_object_job(DataBase &emboss_data, const Vec2d &coor);
/// <summary>
/// Search if exist model volume for given id in object lists
/// </summary>
/// <param name="objects">List to search volume</param>
/// <param name="volume_id">Unique Identifier of volume</param>
/// <returns>Volume when found otherwise nullptr</returns>
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 {
/// <summary>
/// Access to model from gl_volume
/// TODO: it is more general function --> move to utils
/// </summary>
/// <param name="gl_volume">Volume to model belongs to</param>
/// <param name="object">Object containing gl_volume</param>
/// <returns>Model for volume</returns>
static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObject *object);
/// <summary>
/// Access to model from gl_volume
/// TODO: it is more general function --> move to utils
/// </summary>
/// <param name="gl_volume">Volume to model belongs to</param>
/// <param name="objects">All objects</param>
/// <returns>Model for volume</returns>
static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObjectPtrs &objects);
/// <summary>
/// Access to model by selection
/// TODO: it is more general function --> move to select utils
/// </summary>
/// <param name="selection">Actual selection</param>
/// <returns>Model from selection</returns>
static ModelVolume *get_selected_volume(const Selection &selection);
/// <summary>
/// Calculate offset from mouse position to center of text
/// </summary>
@ -388,14 +410,6 @@ static ModelVolume *get_selected_volume(const Selection &selection);
/// <returns>Offset in screan coordinate</returns>
static Vec2d calc_mouse_to_center_text_offset(const Vec2d &mouse, const ModelVolume &mv);
/// <summary>
/// Access to one selected volume
/// </summary>
/// <param name="selection">Containe what is selected</param>
/// <returns>Slected when only one volume otherwise nullptr</returns>
static const GLVolume *get_gl_volume(const Selection &selection);
static GLVolume *get_gl_volume(const GLCanvas3D &canvas);
/// <summary>
/// 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<ImVec2> &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<double> 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<std::shared_ptr<SceneRaycasterItem>> &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<SceneRaycasterItem> &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<AABBMesh>(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<size_t>(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<size_t> 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<double>();
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<double>();
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<double, Eigen::DontAlign>::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<double, Eigen::DontAlign>::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<double, 3>(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<const GuiCfg>(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<size_t>(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<size_t>(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<Job> 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<double, 3>(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<size_t>::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<wxFont> &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<Vec3d> 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<size_t>(hovered_id_signed);
const GLVolumePtrs &volumes = canvas.get_volumes().volumes;
if (hovered_id >= volumes.size()) return nullptr;
return volumes[hovered_id];
}
std::optional<RaycastManager::Hit> 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<RaycastManager::Hit> hit = priv::ray_from_camera(raycaster, screen_coor, camera, &cond);
std::optional<RaycastManager::Hit> 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<double>::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<double>();

View file

@ -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<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;
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<const GuiCfg> m_gui_cfg;
struct GuiCfg;
std::unique_ptr<const GuiCfg> 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<ImVec2> 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<float> 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<SurfaceDrag> m_surface_drag;
@ -343,27 +271,8 @@ private:
// drawing icons
IconManager m_icon_manager;
std::vector<IconManager::Icons> 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;

View file

@ -63,6 +63,8 @@ public:
// && size.x > 0 && size.y > 0 && tl.x != br.x && tl.y != br.y;
};
using Icons = std::vector<std::shared_ptr<Icon> >;
// Vector of icons, each vector contain multiple use of a SVG render
using VIcons = std::vector<Icons>;
/// <summary>
/// Initialize raster texture on GPU with given images
@ -71,7 +73,7 @@ public:
/// <param name="input">Define files and its </param>
/// <returns>Rasterized icons stored on GPU,
/// Same size and order as input, each item of vector is set of texture in order by RasterType</returns>
std::vector<Icons> init(const InitTypes &input);
VIcons init(const InitTypes &input);
/// <summary>
/// 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</param>
/// <returns>Rasterized icons stored on GPU,
/// Same size and order as file_paths, each item of vector is set of texture in order by RasterType</returns>
std::vector<Icons> init(const std::vector<std::string> &file_paths, const ImVec2 &size, RasterType type = RasterType::color);
VIcons init(const std::vector<std::string> &file_paths, const ImVec2 &size, RasterType type = RasterType::color);
/// <summary>
/// Release icons which are hold only by this manager

View file

@ -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

View file

@ -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

View file

@ -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<double>() - 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<double>::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<double> 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<SurfaceDrag> &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<size_t> 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<double>();
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<double>();
Vec2d offseted_mouse = mouse_pos + surface_drag->mouse_offset;
std::optional<RaycastManager::Hit> 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<double, Eigen::DontAlign>::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<double, Eigen::DontAlign>::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<double, 3>(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

View file

@ -0,0 +1,47 @@
#ifndef slic3r_SurfaceDrag_hpp_
#define slic3r_SurfaceDrag_hpp_
#include <optional>
#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<SurfaceDrag> &surface_drag,
GLCanvas3D &canvas,
RaycastManager &raycast_manager);
} // namespace Slic3r::GUI
#endif // slic3r_SurfaceDrag_hpp_

View file

@ -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<std::shared_ptr<SceneRaycasterItem>> &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<SceneRaycasterItem> &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<AABBMesh>(caster->get_raycaster()->get_aabb_mesh());
meshes.emplace_back(std::make_pair(id, std::move(mesh)));
}
return meshes;
}
std::optional<RaycastManager::Hit> 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

View file

@ -144,6 +144,29 @@ public:
Transform3d get_transformation(const TrKey &tr_key) const;
};
class GLCanvas3D;
/// <summary>
/// Use scene Raycasters and prepare data for actualize RaycasterManager
/// </summary>
/// <param name="canvas">contain Scene raycasters</param>
/// <param name="condition">Limit for scene casters</param>
/// <returns>Meshes</returns>
RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition);
struct Camera;
/// <summary>
/// Unproject on mesh by Mesh raycasters
/// </summary>
/// <param name="mouse_pos">Position of mouse on screen</param>
/// <param name="camera">Projection params</param>
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
/// <returns>Position on surface, normal direction in world coorinate
/// + key, to know hitted instance and volume</returns>
std::optional<RaycastManager::Hit> ray_from_camera(const RaycastManager &raycaster,
const Vec2d &mouse_pos,
const Camera &camera,
const RaycastManager::ISkip *skip);
} // namespace Slic3r::GUI
#endif // slic3r_RaycastManager_hpp_