From 550ef48fe170f67a7f3058f0c361bbbd18254897 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 3 Jan 2023 13:24:01 +0100 Subject: [PATCH] Add function for check whether transformation contain reflection Extend RayCast hit by square distance Use distance to distiguish closest place on surface when move origin Move origin after job (only on success) --- src/libslic3r/Point.hpp | 8 +++ src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 65 +++++++++++--------- src/slic3r/GUI/Jobs/EmbossJob.cpp | 81 ++++++++++++++++--------- src/slic3r/Utils/RaycastManager.cpp | 25 ++------ src/slic3r/Utils/RaycastManager.hpp | 5 +- 5 files changed, 105 insertions(+), 79 deletions(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 32dcb82d05..7ddbcb20bf 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -136,6 +136,14 @@ inline std::string to_string(const Vec3d &pt) { return std::string("[") + floa std::vector transform(const std::vector& points, const Transform3f& t); Pointf3s transform(const Pointf3s& points, const Transform3d& t); +/// +/// Check whether transformation matrix contains odd number of mirroring. +/// NOTE: In code is sometime function named is_left_handed +/// +/// Transformation to check +/// Is positive determinant +inline bool has_reflection(const Transform3d &transform) { return transform.matrix().determinant() < 0; } + template using Vec = Eigen::Matrix; class Point : public Vec2crd diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index ecbfd5a8c0..11c1db2672 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1120,6 +1120,17 @@ static inline void execute_job(std::shared_ptr j) }); } +namespace priv { +/// +/// Calculate translation of text volume onto surface of model +/// +/// Text +/// AABB trees of object. Actualize object containing text +/// Transformation of actual instance +/// Offset of volume in volume coordinate +std::optional calc_surface_offset(const ModelVolume &volume, RaycastManager &raycast_manager, const Selection &selection); +} // namespace priv + bool GLGizmoEmboss::process() { // no volume is selected -> selection from right panel @@ -1160,6 +1171,13 @@ bool GLGizmoEmboss::process() if (fix_3mf.has_value()) text_tr = text_tr * fix_3mf->inverse(); + // when it is new applying of use surface than move origin onto surfaca + if (!m_volume->text_configuration->style.prop.use_surface) { + auto offset = priv::calc_surface_offset(*m_volume, m_raycast_manager, m_parent.get_selection()); + if (offset.has_value()) + text_tr *= Eigen::Translation(*offset); + } + bool is_outside = m_volume->is_model_part(); // check that there is not unexpected volume type assert(is_outside || m_volume->is_negative_volume() || @@ -2231,18 +2249,7 @@ void GLGizmoEmboss::draw_delete_style_button() { } } -namespace priv { -/// -/// Transform origin of Text volume onto surface of model. -/// -/// Text -/// AABB trees of object -/// Transformation of actual instance -/// True when transform otherwise false -bool transform_on_surface(ModelVolume &volume, RaycastManager &raycast_manager, const Selection &selection); -} // namespace priv - - +// FIX IT: it should not change volume position before successfull change void GLGizmoEmboss::fix_transformation(const FontProp &from, const FontProp &to) { @@ -2264,10 +2271,6 @@ void GLGizmoEmboss::fix_transformation(const FontProp &from, float t_move = t_move_opt.has_value() ? *t_move_opt : .0f; do_translate(Vec3d::UnitZ() * (t_move - f_move)); } - - // when start using surface than move volume origin onto surface - if (!from.use_surface && to.use_surface) - priv::transform_on_surface(*m_volume, m_raycast_manager, m_parent.get_selection()); } void GLGizmoEmboss::draw_style_list() { @@ -2887,8 +2890,7 @@ void GLGizmoEmboss::do_rotate(float relative_z_angle) m_parent.do_rotate(snapshot_name); } -bool priv::transform_on_surface(ModelVolume &volume, RaycastManager &raycast_manager, const Selection &selection) -{ +std::optional priv::calc_surface_offset(const ModelVolume &volume, RaycastManager &raycast_manager, const Selection &selection) { // Move object on surface auto cond = RaycastManager::SkipVolume({volume.id().id}); raycast_manager.actualize(volume.get_object(), &cond); @@ -2901,26 +2903,31 @@ bool priv::transform_on_surface(ModelVolume &volume, RaycastManager &raycast_man // ray in direction of text projection(from volume zero to z-dir) std::optional hit_opt = raycast_manager.unproject(point, direction, &cond); + // start point lay on surface could appear slightly behind surface + std::optional hit_opt_opposit = raycast_manager.unproject(point, -direction, &cond); + if (!hit_opt.has_value() || + (hit_opt_opposit.has_value() && hit_opt->squared_distance > hit_opt_opposit->squared_distance)) + hit_opt = hit_opt_opposit; + + // Try to find closest point when no hit object in emboss direction if (!hit_opt.has_value()) hit_opt = raycast_manager.closest(point); + // It should NOT appear. Closest point always exists. if (!hit_opt.has_value()) - return false; - const RaycastManager::Hit &hit = *hit_opt; + return {}; + // It is no neccesary to move with origin by very small value + if (hit_opt->squared_distance < EPSILON) + return {}; + + const RaycastManager::Hit &hit = *hit_opt; Transform3d hit_tr = raycast_manager.get_transformation(hit.tr_key); Vec3d hit_world = hit_tr * hit.position.cast(); Vec3d offset_world = hit_world - point; // vector in world // TIP: It should be close to only z move Vec3d offset_volume = to_world.inverse().linear() * offset_world; - - // when try to use surface on just loaded text from 3mf - auto fix = volume.text_configuration->fix_3mf_tr; - if (fix.has_value()) - offset_volume = fix->linear() * offset_volume; - - volume.set_transformation(volume.get_matrix() * Eigen::Translation(offset_volume)); - return true; + return offset_volume; } void GLGizmoEmboss::draw_advanced() @@ -2975,8 +2982,6 @@ void GLGizmoEmboss::draw_advanced() // there should be minimal embossing depth if (font_prop.emboss < 0.1) font_prop.emboss = 1; - - priv::transform_on_surface(*m_volume, m_raycast_manager, m_parent.get_selection()); } process(); } diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index bdedc53ee2..ce98e972b1 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -63,7 +63,8 @@ static TriangleMesh create_default_mesh(); /// /// New mesh data /// Text configuration, ... -static void update_volume(TriangleMesh &&mesh, const DataUpdate &data); +/// Transformation of volume +static void update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d *tr = nullptr); /// /// Add new volume to object @@ -316,16 +317,8 @@ void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { // doesn't care about exception when process was canceled by user if (canceled) return; if (priv::process(eptr)) return; - - // TODO: Find better way to Not center volume data when add !!! - TriangleMesh mesh = m_result; // Part1: copy - priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.text_tr, m_input); - - // Part2: update volume data - //auto vol = wxGetApp().plater()->model().objects[m_input.object_idx]->volumes.back(); - //UpdateJob::update_volume(vol, std::move(mesh), m_input.text_configuration, m_input.volume_name); } ///////////////// @@ -358,7 +351,11 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) } if (canceled) return; if (priv::process(eptr)) return; - priv::update_volume(std::move(m_result), m_input); + + // when start using surface it is wanted to move text origin on surface of model + // also when repeteadly move above surface result position should match + Transform3d *tr = &m_input.text_tr; + priv::update_volume(std::move(m_result), m_input, tr); } //////////////////////////// @@ -535,31 +532,37 @@ void UpdateJob::update_volume(ModelVolume *volume, canvas->reload_scene(refresh_immediately); } -void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data) +void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d* tr) { // for sure that some object will be created - if (mesh.its.empty()) - return priv::create_message("Empty mesh can't be created."); + if (mesh.its.empty()) + return create_message("Empty mesh can't be created."); Plater *plater = wxGetApp().plater(); GLCanvas3D *canvas = plater->canvas3D(); // Check emboss gizmo is still open GLGizmosManager &manager = canvas->get_gizmos_manager(); - if (manager.get_current_type() != GLGizmosManager::Emboss) return; + if (manager.get_current_type() != GLGizmosManager::Emboss) + return; std::string snap_name = GUI::format(_L("Text: %1%"), data.text_configuration.text); Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction); ModelVolume *volume = get_volume(plater->model().objects, data.volume_id); + // could appear when user delete edited volume if (volume == nullptr) return; - // apply fix matrix made by store to .3mf - const auto &tc = volume->text_configuration; - assert(tc.has_value()); - if (tc.has_value() && tc->fix_3mf_tr.has_value()) - volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse()); + if (tr) { + volume->set_transformation(*tr); + } else { + // apply fix matrix made by store to .3mf + const auto &tc = volume->text_configuration; + assert(tc.has_value()); + if (tc.has_value() && tc->fix_3mf_tr.has_value()) + volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse()); + } UpdateJob::update_volume(volume, std::move(mesh), data.text_configuration, data.volume_name); } @@ -726,30 +729,48 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 biggest_count = its.vertices.size(); biggest = &s; } - s_to_itss[&s - &sources.front()] = itss.size(); + size_t source_index = &s - &sources.front(); + size_t its_index = itss.size(); + s_to_itss[source_index] = its_index; itss.emplace_back(std::move(its)); } if (itss.empty()) throw JobException(_u8L("There is no volume in projection direction.").c_str()); - Transform3d tr_inv = biggest->tr.inverse(); + Transform3d tr_inv = biggest->tr.inverse(); + Transform3d cut_projection_tr = tr_inv * input2.text_tr; + // Cut surface in reflected system? + bool use_reflection = Slic3r::has_reflection(cut_projection_tr); + if (use_reflection) + cut_projection_tr *= Eigen::Scaling(-1., 1., 1.); + size_t itss_index = s_to_itss[biggest - &sources.front()]; BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]); for (const SurfaceVolumeData::ModelSource &s : sources) { - if (&s == biggest) continue; size_t itss_index = s_to_itss[&s - &sources.front()]; if (itss_index == std::numeric_limits::max()) continue; - Transform3d tr = s.tr * tr_inv; + Transform3d tr; + if (&s == biggest) { + if (!use_reflection) + continue; + // add reflection for biggest source + tr = Eigen::Scaling(-1., 1., 1.); + } else { + tr = s.tr * tr_inv; + if (use_reflection) + tr *= Eigen::Scaling(-1., 1., 1.); + } + + bool fix_reflected = true; indexed_triangle_set &its = itss[itss_index]; - its_transform(its, tr); + its_transform(its, tr, fix_reflected); BoundingBoxf3 bb = bounding_box(its); mesh_bb.merge(bb); } // tr_inv = transformation of mesh inverted - Transform3d cut_projection_tr = tr_inv * input2.text_tr; - Transform3d emboss_tr = cut_projection_tr.inverse(); - BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr); + Transform3d emboss_tr = cut_projection_tr.inverse(); + BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr); std::pair z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()}; OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range); float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension); @@ -761,9 +782,13 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 // !! Projection needs to transform cut OrthoProject3d projection = create_emboss_projection(input2.is_outside, fp.emboss, emboss_tr, cut); - indexed_triangle_set new_its = cut2model(cut, projection); assert(!new_its.empty()); + if (use_reflection) { + // when cut was made in reflected system it must be converted back + Transform3d tr(Eigen::Scaling(-1., 1., 1.)); + its_transform(new_its, tr, true); + } if (was_canceled()) return {}; return TriangleMesh(std::move(new_its)); diff --git a/src/slic3r/Utils/RaycastManager.cpp b/src/slic3r/Utils/RaycastManager.cpp index afaf053a82..4667be3e9c 100644 --- a/src/slic3r/Utils/RaycastManager.cpp +++ b/src/slic3r/Utils/RaycastManager.cpp @@ -73,23 +73,10 @@ void RaycastManager::actualize(const ModelObject *object, const ISkip *skip) m_transformations.erase(m_transformations.begin() + i); } -namespace priv { -struct HitWithDistance : public RaycastManager::Hit -{ - double squared_distance; - HitWithDistance(double squared_distance, - const Hit::Key &key, - const SurfacePoint &surface_point) - : Hit(key, surface_point.position, surface_point.normal) - , squared_distance(squared_distance) - {} -}; -} - std::optional RaycastManager::unproject( const Vec2d &mouse_pos, const Camera &camera, const ISkip *skip) const { - std::optional closest; + std::optional closest; for (const auto &item : m_transformations) { const TrKey &key = item.first; size_t volume_id = key.second; @@ -112,7 +99,7 @@ std::optional RaycastManager::unproject( if (closest.has_value() && closest->squared_distance < squared_distance) continue; - closest = priv::HitWithDistance(squared_distance, key, surface_point); + closest = Hit(key, surface_point, squared_distance); } //if (!closest.has_value()) return {}; @@ -121,7 +108,7 @@ std::optional RaycastManager::unproject( std::optional RaycastManager::unproject(const Vec3d &point, const Vec3d &direction, const ISkip *skip) const { - std::optional closest; + std::optional closest; for (const auto &item : m_transformations) { const TrKey &key = item.first; size_t volume_id = key.second; @@ -144,14 +131,14 @@ std::optional RaycastManager::unproject(const Vec3d &point, closest->squared_distance < squared_distance) continue; SurfacePoint surface_point(hit.position().cast(), hit.normal().cast()); - closest = priv::HitWithDistance(squared_distance, key, surface_point); + closest = Hit(key, surface_point, squared_distance); } } return closest; } std::optional RaycastManager::closest(const Vec3d &point, const ISkip *skip) const { - std::optional closest; + std::optional closest; for (const auto &item : m_transformations) { const TrKey &key = item.first; size_t volume_id = key.second; @@ -172,7 +159,7 @@ std::optional RaycastManager::closest(const Vec3d &point, c if (closest.has_value() && closest->squared_distance < squared_distance) continue; SurfacePoint surface_point(p,n); - closest = priv::HitWithDistance(squared_distance, key, surface_point); + closest = Hit(key, surface_point, squared_distance); } return closest; } diff --git a/src/slic3r/Utils/RaycastManager.hpp b/src/slic3r/Utils/RaycastManager.hpp index e1e72e8459..a29977150c 100644 --- a/src/slic3r/Utils/RaycastManager.hpp +++ b/src/slic3r/Utils/RaycastManager.hpp @@ -67,8 +67,9 @@ public: { using Key = TrKey; Key tr_key; - Hit(Key tr_key, Vec3f position, Vec3f normal) - : SurfacePoint(position, normal), tr_key(tr_key) + double squared_distance; + Hit(const Key& tr_key, const SurfacePoint& surface_point, double squared_distance) + : SurfacePoint(surface_point), tr_key(tr_key), squared_distance(squared_distance) {} };