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) {} };