From b6d84fb276ff109e074b6938e2e1c3b55d48065f Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 14 Mar 2023 19:56:52 +0100 Subject: [PATCH] Emboss on job SVG and Text together --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 156 +----- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 196 +++++--- src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp | 12 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 644 +++++++++++++++++++----- src/slic3r/GUI/Jobs/EmbossJob.hpp | 211 ++------ 6 files changed, 695 insertions(+), 528 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 56f5e433d7..dc94546859 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -14,7 +14,6 @@ #include "slic3r/GUI/Jobs/NotificationProgressIndicator.hpp" #include "slic3r/Utils/WxFontUtils.hpp" #include "slic3r/Utils/UndoRedo.hpp" -#include "slic3r/Utils/EmbossGui.hpp" // TODO: remove include #include "libslic3r/SVG.hpp" // debug store @@ -108,14 +107,6 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) // Private namespace with helper function for create volume namespace priv { - -/// -/// Check if volume type is possible use for new text volume -/// -/// Type -/// True when allowed otherwise false -static bool is_valid(ModelVolumeType volume_type); - /// /// Prepare data for emboss /// @@ -134,7 +125,7 @@ struct TextDataBase : public DataBase TextDataBase(DataBase &&parent, const FontFileWithCache &font_file, TextConfiguration &&text_configuration); // Create shape from text + font configuration EmbossShape &create_shape() override; - void write(ModelVolume &volume) const override; + void write(ModelVolume &volume) const override; // private: // Keep pointer on Data of font (glyph shapes) @@ -143,31 +134,6 @@ struct TextDataBase : public DataBase TextConfiguration text_configuration; }; -/// -/// Start job for add object with text into scene -/// -/// Define params of text -/// Screen coordinat, where to create new object laying on bed -static void start_create_object_job(std::unique_ptr emboss_data, const Vec2d &coor); - -/// -/// Start job for add new volume on surface of object defined by screen coor -/// -/// Define params of text -/// Emboss / engrave -/// Mouse position which define position -/// Volume to find surface for create -/// Ability to ray cast to model -/// Contain already used scene RayCasters -/// True when start creation, False when there is no hit surface by screen coor -static bool start_create_volume_on_surface_job(const FontProp &fp, - std::unique_ptr emboss_data, - ModelVolumeType volume_type, - const Vec2d &screen_coor, - const GLVolume &gl_volume, - RaycastManager &raycaster, - GLCanvas3D &canvas); - // Loaded icons enum // Have to match order of files in function GLGizmoEmboss::init_icons() enum class IconType : unsigned { @@ -194,69 +160,26 @@ const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType typ static bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false); } // namespace priv -void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) +bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) { - if (!priv::is_valid(volume_type)) return; m_style_manager.discard_style_changes(); - set_default_text(); - - std::unique_ptr emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); - GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); - if (gl_volume == nullptr) - // object is not under mouse position soo create object on plater - return priv::start_create_object_job(std::move(emboss_data), mouse_pos); - + set_default_text(); + DataBasePtr base = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); + Plater *plater_ptr = wxGetApp().plater(); const FontProp &fp = m_style_manager.get_style().prop; - if (!priv::start_create_volume_on_surface_job(fp, std::move(emboss_data), volume_type, mouse_pos, *gl_volume, m_raycast_manager, m_parent)) - return create_volume(volume_type); + return start_create_volume(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Emboss, mouse_pos, fp.distance, fp.angle); } // Designed for create volume without information of mouse in scene -void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) +bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type) { - if (!priv::is_valid(volume_type)) return; m_style_manager.discard_style_changes(); set_default_text(); - - // select position by camera position and view direction - const Selection &selection = m_parent.get_selection(); - int object_idx = selection.get_object_idx(); - - Size s = m_parent.get_canvas_size(); - Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); std::unique_ptr emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); - const ModelObjectPtrs &objects = selection.get_model()->objects; - // No selected object so create new object - if (selection.is_empty() || object_idx < 0 || static_cast(object_idx) >= objects.size()) { - // create Object on center of screen - // when ray throw center of screen not hit bed it create object on center of bed - priv::start_create_object_job(std::move(emboss_data), screen_center); - return; - } - - // create volume inside of selected object - Vec2d coor; - const Camera &camera = wxGetApp().plater()->get_camera(); - const GLVolume *gl_volume = find_closest(selection, screen_center, camera, objects, &coor); - - FontProp &fp = m_style_manager.get_style().prop; - if (gl_volume == nullptr) { - return priv::start_create_object_job(std::move(emboss_data), screen_center); - } else if (!priv::start_create_volume_on_surface_job(fp, std::move(emboss_data), volume_type, coor, *gl_volume, m_raycast_manager, m_parent)){ - // In centroid of convex hull is not hit with object. e.g. torid - // soo create transfomation on border of object - - // unique pointer is already destoyed need to create again - std::unique_ptr emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); - // there is no point on surface so no use of surface will be applied - if (emboss_data->shape.use_surface) - emboss_data->shape.use_surface = false; - - const ModelObject *object = get_model_object(*gl_volume, objects); - Worker &worker = wxGetApp().plater()->get_ui_job_worker(); - Transform3d volume_trmat = create_volume_transformation(*gl_volume, objects, fp.size_in_mm, fp.emboss); - start_create_volume_job(worker, *object, volume_trmat, std::move(emboss_data), volume_type); - } + Plater *plater_ptr = wxGetApp().plater(); + const FontProp &fp = m_style_manager.get_style().prop; + return start_create_volume_without_position(plater_ptr, std::move(emboss_data), + volume_type, m_raycast_manager, GLGizmosManager::Emboss, fp.distance, fp.angle); } bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) @@ -1057,7 +980,7 @@ bool GLGizmoEmboss::process() if (use_surface) { // Model to cut surface from. - SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume); + SurfaceVolumeData::ModelSources sources = create_volume_sources(*m_volume); if (sources.empty()) return false; Transform3d text_tr = m_volume->get_matrix(); @@ -3254,17 +3177,6 @@ bool GLGizmoEmboss::is_text_object(const ModelVolume *text) { // priv namespace implementation /////////////// -bool priv::is_valid(ModelVolumeType volume_type) -{ - if (volume_type == ModelVolumeType::MODEL_PART || - volume_type == ModelVolumeType::NEGATIVE_VOLUME || - volume_type == ModelVolumeType::PARAMETER_MODIFIER) - return true; - - BOOST_LOG_TRIVIAL(error) << "Can't create embossed text with this type: " << (int) volume_type; - return false; -} - priv::TextDataBase::TextDataBase(DataBase &&parent, const FontFileWithCache &font_file, TextConfiguration &&text_configuration) : DataBase(std::move(parent)), font_file(font_file) /* copy */, text_configuration(std::move(text_configuration)) { @@ -3351,50 +3263,6 @@ std::unique_ptr priv::create_emboss_data_base(const std::string &text, return std::make_unique(std::move(base), font, std::move(tc)); } -void priv::start_create_object_job(std::unique_ptr emboss_data, const Vec2d &coor) { - Plater *plater = wxGetApp().plater(); - const Camera &camera = plater->get_camera(); - const Pointfs &bed_shape = plater->build_volume().bed_shape(); - Worker &worker = plater->get_ui_job_worker(); - DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape}; - auto job = std::make_unique(std::move(data)); - queue_job(worker, std::move(job)); -} - -bool priv::start_create_volume_on_surface_job(const FontProp &fp, - std::unique_ptr emboss_data, - ModelVolumeType volume_type, - const Vec2d &screen_coor, - const GLVolume &gl_volume, - RaycastManager &raycaster, - GLCanvas3D &canvas) -{ - const ModelObjectPtrs &objects = canvas.get_model()->objects; - const ModelVolume* volume = get_model_volume(gl_volume, objects); - const ModelInstance *instance = get_model_instance(gl_volume, objects); - if (volume == nullptr || instance == nullptr || - volume->get_object() == nullptr) { - // weird situation - assert(false); - return false; - } - - Plater* plater = wxGetApp().plater(); - const Camera &camera = plater->get_camera(); - std::optional transform = create_volume_transformation_on_surface( - screen_coor, camera, *volume, *instance, raycaster, canvas, fp.distance, fp.angle); - - if (!transform.has_value()) { - // When model is broken. It could appear that hit miss the object. - // So add part near by in simmilar manner as right panel do - return false; - } - - // Try to cast ray into scene and find object for add volume - Worker &worker = plater->get_ui_job_worker(); - return start_create_volume_job(worker, *volume->get_object(), *transform, std::move(emboss_data), volume_type); -} - ImVec2 priv::calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size) { const Selection::IndicesList indices = selection.get_volume_idxs(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index bf78fc3eb6..3b9b167ef8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -39,13 +39,13 @@ public: /// /// Object part / Negative volume / Modifier /// Define position of new volume - void create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); + bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); /// /// Create new text without given position /// /// Object part / Negative volume / Modifier - void create_volume(ModelVolumeType volume_type); + bool create_volume(ModelVolumeType volume_type); protected: bool on_init() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 30a3540f3d..c8eb662b55 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -9,6 +9,7 @@ #include "slic3r/GUI/MsgDialog.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/CameraUtils.hpp" +#include "slic3r/GUI/Jobs/EmbossJob.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/Point.hpp" @@ -32,11 +33,11 @@ using namespace Slic3r; using namespace Slic3r::Emboss; using namespace Slic3r::GUI; +using namespace Slic3r::GUI::Emboss; namespace priv { // Variable keep limits for variables -static const struct Limits -{ +static const struct Limits{ MinMax emboss{0.01f, 1e4f}; // in mm // distance text object from surface MinMax angle{-180.f, 180.f}; // in degrees @@ -55,20 +56,34 @@ GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent) // Private functions to create emboss volume namespace priv { - -/// -/// Check if volume type is possible use for new text volume -/// -/// Type -/// True when allowed otherwise false -static bool is_valid(ModelVolumeType volume_type); - /// /// Open file dialog with svg files /// /// File path to svg static std::string choose_svg_file(); +/// +/// Let user to choose file with (S)calable (V)ector (G)raphics - SVG. +/// Than let select contour +/// +/// EmbossShape to create +static EmbossShape select_shape(); + +/// +/// Create new embos data +/// +/// Cancel for previous job +/// Base data for emboss SVG +static DataBasePtr create_emboss_data_base(std::shared_ptr> &cancel); + +/// +/// Create symbol '?' as default shape +/// without source file +/// with size 2cm +/// +/// Default shape to emboss +static ExPolygons default_shape(); + /// /// Separate file name from file path. /// String after last delimiter and before last point @@ -77,53 +92,39 @@ static std::string choose_svg_file(); /// File name without directory path static std::string get_file_name(const std::string &file_path); +/// +/// Create volume name from shape information +/// +/// File path +/// Name for volume +static std::string volume_name(const EmbossShape& shape); } // namespace priv -void GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos){ - std::string path = priv::choose_svg_file(); - if (path.empty()) return; - create_volume(path, volume_type, mouse_pos); -} -void GLGizmoSVG::create_volume(ModelVolumeType volume_type) { - std::string path = priv::choose_svg_file(); - if (path.empty()) return; - create_volume(path, volume_type); +bool GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos) +{ + DataBasePtr base = priv::create_emboss_data_base(m_job_cancel); + Plater *plater_ptr = wxGetApp().plater(); + return start_create_volume(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Svg, mouse_pos); } -void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos) { - std::string name = priv::get_file_name(svg_file_path); - NSVGimage *image = nsvgParseFromFile(svg_file_path.c_str(), "mm", 96.0f); - ExPolygons polys = NSVGUtils::to_ExPolygons(image); - nsvgDelete(image); - - BoundingBox bb; - for (const auto &p : polys) - bb.merge(p.contour.points); - - double scale = 1e-4; - auto project = std::make_unique(std::make_unique(10 / scale), scale); - indexed_triangle_set its = polygons2model(polys, *project); - // add volume +bool GLGizmoSVG::create_volume(ModelVolumeType volume_type) +{ + DataBasePtr base = priv::create_emboss_data_base(m_job_cancel); + Plater *plater_ptr = wxGetApp().plater(); + return start_create_volume_without_position(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Svg); } -void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type) { - +bool GLGizmoSVG::is_svg(const ModelVolume &volume) { + return volume.emboss_shape.has_value(); } -bool GLGizmoSVG::is_svg(const ModelVolume *volume) { - if (volume == nullptr) - return false; - return volume->emboss_shape.has_value(); -} - -bool GLGizmoSVG::is_svg_object(const ModelVolume *volume) { - if (volume == nullptr) return false; - if (!volume->emboss_shape.has_value()) return false; - if (volume->type() != ModelVolumeType::MODEL_PART) return false; - for (const ModelVolume *v : volume->get_object()->volumes) { - if (v == volume) continue; +bool GLGizmoSVG::is_svg_object(const ModelVolume &volume) { + if (!volume.emboss_shape.has_value()) return false; + if (volume.type() != ModelVolumeType::MODEL_PART) return false; + for (const ModelVolume *v : volume.get_object()->volumes) { + if (v->id() == volume.id()) continue; if (v->type() == ModelVolumeType::MODEL_PART) return false; } return true; @@ -423,7 +424,7 @@ void GLGizmoSVG::set_volume_by_selection() ImGuiWrapper::left_inputs(); // is valid svg volume? - if (!is_svg(volume)) + if (!is_svg(*volume)) return reset_volume(); // cancel previous job @@ -527,8 +528,8 @@ void GLGizmoSVG::close() { // close gizmo == open it again auto& mng = m_parent.get_gizmos_manager(); - if (mng.get_current_type() == GLGizmosManager::Emboss) - mng.open_gizmo(GLGizmosManager::Emboss); + if (mng.get_current_type() == GLGizmosManager::Svg) + mng.open_gizmo(GLGizmosManager::Svg); } void GLGizmoSVG::draw_window() @@ -543,7 +544,8 @@ void GLGizmoSVG::draw_window() void GLGizmoSVG::draw_model_type() { - bool is_last_solid_part = is_svg_object(m_volume); + assert(m_volume != nullptr); + bool is_last_solid_part = is_svg_object(*m_volume); std::string title = _u8L("SVG is to object"); if (is_last_solid_part) { ImVec4 color{.5f, .5f, .5f, 1.f}; @@ -561,7 +563,7 @@ void GLGizmoSVG::draw_model_type() if (ImGui::RadioButton(_u8L("Added").c_str(), type == part)) new_type = part; else if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", _u8L("Click to change text into object part.").c_str()); + ImGui::SetTooltip("%s", _u8L("Change to object's part.").c_str()); ImGui::SameLine(); std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object."); @@ -571,11 +573,16 @@ void GLGizmoSVG::draw_model_type() if (is_last_solid_part) ImGui::SetTooltip("%s", last_solid_part_hint.c_str()); else if (type != negative) - ImGui::SetTooltip("%s", _u8L("Click to change part type into negative volume.").c_str()); + ImGui::SetTooltip("%s", _u8L("Change to negative volume.").c_str()); } - // In simple mode are not modifiers - if (wxGetApp().plater()->printer_technology() != ptSLA && wxGetApp().get_mode() != ConfigOptionMode::comSimple) { + GUI_App &app = wxGetApp(); + Plater *plater = app.plater(); + // SLA printers do not use modifiers + bool is_sla = plater->printer_technology() == ptSLA; + // In simple mode are not modifiers ?!? + bool is_simple = app.get_mode() == ConfigOptionMode::comSimple; + if (!is_sla && !is_simple) { ImGui::SameLine(); if (ImGui::RadioButton(_u8L("Modifier").c_str(), type == modifier)) new_type = modifier; @@ -583,13 +590,11 @@ void GLGizmoSVG::draw_model_type() if (is_last_solid_part) ImGui::SetTooltip("%s", last_solid_part_hint.c_str()); else if (type != modifier) - ImGui::SetTooltip("%s", _u8L("Click to change part type into modifier.").c_str()); + ImGui::SetTooltip("%s", _u8L("Change to modifier.").c_str()); } } if (m_volume != nullptr && new_type.has_value() && !is_last_solid_part) { - GUI_App &app = wxGetApp(); - Plater * plater = app.plater(); Plater::TakeSnapshot snapshot(plater, _L("Change Text Type"), UndoRedo::SnapshotType::GizmoAction); m_volume->set_type(*new_type); @@ -612,8 +617,8 @@ void GLGizmoSVG::draw_model_type() // NOTE: on linux, function reorder_volumes_and_get_selection call GLCanvas3D::reload_scene(refresh_immediately = false) // which discard m_volume pointer and set it to nullptr also selection is cleared so gizmo is automaticaly closed auto &mng = m_parent.get_gizmos_manager(); - if (mng.get_current_type() != GLGizmosManager::Emboss) - mng.open_gizmo(GLGizmosManager::Emboss); + if (mng.get_current_type() != GLGizmosManager::Svg) + mng.open_gizmo(GLGizmosManager::Svg); // TODO: select volume back - Ask @Sasa } } @@ -623,17 +628,6 @@ void GLGizmoSVG::draw_model_type() // priv namespace implementation /////////////// -bool priv::is_valid(ModelVolumeType volume_type) -{ - if (volume_type == ModelVolumeType::MODEL_PART || - volume_type == ModelVolumeType::NEGATIVE_VOLUME || - volume_type == ModelVolumeType::PARAMETER_MODIFIER ) - return true; - - BOOST_LOG_TRIVIAL(error) << "Can't create embossed SVG with this type: " << (int) volume_type; - return false; -} - std::string priv::get_file_name(const std::string &file_path) { if (file_path.empty()) @@ -660,6 +654,14 @@ std::string priv::get_file_name(const std::string &file_path) return file_path.substr(offset, count); } +std::string priv::volume_name(const EmbossShape &shape) +{ + std::string file_name = get_file_name(shape.svg_file_path); + if (!file_name.empty()) + return file_name; + return "SVG shape"; +} + std::string priv::choose_svg_file() { wxWindow* parent = nullptr; @@ -689,5 +691,57 @@ std::string priv::choose_svg_file() return path; } +ExPolygons priv::default_shape() { + std::string file = Slic3r::resources_dir() + "/icons/question.svg"; + NSVGimage *image = nsvgParseFromFile(file.c_str(), "px", 96.0f); + ExPolygons shape = NSVGUtils::to_ExPolygons(image); + nsvgDelete(image); + return shape; +} + +EmbossShape priv::select_shape() { + EmbossShape shape; + shape.depth = 10.; + shape.distance = 0; + shape.use_surface = false; + + shape.svg_file_path = choose_svg_file(); + if (shape.svg_file_path.empty()) + return {}; + + NSVGimage *image = nsvgParseFromFile(shape.svg_file_path.c_str(), "mm", 96.0f); + ScopeGuard sg([image]() { nsvgDelete(image); }); + shape.shapes = NSVGUtils::to_ExPolygons(image); + + // TODO: get scale from file + shape.scale = 1.; + + // Must contain some shapes !!! + if (shape.shapes.empty()) + shape.shapes = default_shape(); + + return shape; +} + +DataBasePtr priv::create_emboss_data_base(std::shared_ptr> &cancel) { + EmbossShape shape = priv::select_shape(); + + if (shape.shapes.empty()) + // canceled selection of SVG file + return nullptr; + + // Cancel previous Job, when it is in process + // worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs + // Cancel only EmbossUpdateJob no others + if (cancel != nullptr) + cancel->store(true); + // create new shared ptr to cancel new job + cancel = std::make_shared>(false); + + std::string name = priv::volume_name(shape); + + return std::make_unique(name, cancel /*copy*/, std::move(shape)); +} + // any existing icon filename to not influence GUI const std::string GLGizmoSVG::M_ICON_FILENAME = "cut.svg"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp index 7ca9637393..9d810cdde9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -37,29 +37,29 @@ public: /// /// Object part / Negative volume / Modifier /// Define position of new volume - void create_volume(const std::string& svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos); - void create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); // first open file dialog + /// True on succesfull start creation otherwise False + bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); // first open file dialog /// /// Create new text without given position /// /// Object part / Negative volume / Modifier - void create_volume(const std::string &svg_file_path, ModelVolumeType volume_type); - void create_volume(ModelVolumeType volume_type); // first open file dialog + /// True on succesfull start creation otherwise False + bool create_volume(ModelVolumeType volume_type); // first open file dialog /// /// Check whether volume is object containing only emboss volume /// /// Pointer to volume /// True when object otherwise False - static bool is_svg_object(const ModelVolume *volume); + static bool is_svg_object(const ModelVolume &volume); /// /// Check whether volume has emboss data /// /// Pointer to volume /// True when constain emboss data otherwise False - static bool is_svg(const ModelVolume *volume); + static bool is_svg(const ModelVolume &volume); protected: bool on_init() override; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index c56b347b9b..136399bc64 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -5,6 +5,7 @@ #include #include // load_obj for default mesh #include // use surface cuts +#include // create object #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/NotificationManager.hpp" @@ -27,6 +28,120 @@ using namespace Slic3r::Emboss; using namespace Slic3r::GUI; using namespace Slic3r::GUI::Emboss; +// Private implementation for create volume and objects jobs +namespace Slic3r::GUI::Emboss { +/// +/// Hold neccessary data to create ModelVolume in job +/// Volume is created on the surface of existing volume in object. +/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!! +/// +struct DataCreateVolume +{ + // Hold data about shape + DataBasePtr base; + + // define embossed volume type + ModelVolumeType volume_type; + + // parent ModelObject index where to create volume + ObjectID object_id; + + // new created volume transformation + std::optional trmat; + + // Define which gizmo open on the success + GLGizmosManager::EType gizmo; +}; + +/// +/// Create new TextVolume on the surface of ModelObject +/// Should not be stopped +/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!! +/// +class CreateVolumeJob : public Job +{ + DataCreateVolume m_input; + TriangleMesh m_result; + +public: + CreateVolumeJob(DataCreateVolume &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; +}; + +/// +/// Hold neccessary data to create ModelObject in job +/// Object is placed on bed under screen coor +/// OR to center of scene when it is out of bed shape +/// +struct DataCreateObject +{ + // Hold data about shape + DataBasePtr base; + + // define position on screen where to create object + Vec2d screen_coor; + + // projection property + Camera camera; + + // shape of bed in case of create volume on bed + std::vector bed_shape; + + // Define which gizmo open on the success + GLGizmosManager::EType gizmo; +}; + +/// +/// Create new TextObject on the platter +/// Should not be stopped +/// +class CreateObjectJob : public Job +{ + DataCreateObject m_input; + TriangleMesh m_result; + Transform3d m_transformation; + +public: + CreateObjectJob(DataCreateObject &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; +}; + +/// +/// Hold neccessary data to create(cut) volume from surface object in job +/// +struct CreateSurfaceVolumeData : public SurfaceVolumeData +{ + // Hold data about shape + DataBasePtr base; + + // define embossed volume type + ModelVolumeType volume_type; + + // parent ModelObject index where to create volume + ObjectID object_id; + + // Define which gizmo open on the success + GLGizmosManager::EType gizmo; +}; + +/// +/// Cut surface from object and create cutted volume +/// Should not be stopped +/// +class CreateSurfaceVolumeJob : public Job +{ + CreateSurfaceVolumeData m_input; + TriangleMesh m_result; + +public: + CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; +}; +} // namespace Slic3r::GUI::Emboss + // private namespace namespace priv{ // create sure that emboss object is bigger than source object [in mm] @@ -37,12 +152,13 @@ constexpr float safe_extension = 1.0f; /// /// /// -bool check(const DataBase &input, bool check_fontfile = true, bool use_surface = false); -bool check(const DataCreateVolume &input, bool is_main_thread = false); -bool check(const DataCreateObject &input); -bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false); -bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); -bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); +static bool check(const DataBase &input, bool check_fontfile = true, bool use_surface = false); +static bool check(GLGizmosManager::EType gizmo); +static bool check(const DataCreateVolume &input, bool is_main_thread = false); +static bool check(const DataCreateObject &input); +static bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false); +static bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); +static bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); // /// Try to create mesh from text @@ -85,8 +201,9 @@ static void update_name_in_list(const ObjectList &object_list, const ModelVolume /// Type of new volume /// Transformation of volume inside of object /// Text configuration and New VolumeName -static void create_volume(TriangleMesh &&mesh, const ObjectID& object_id, - const ModelVolumeType type, const Transform3d trmat, const DataBase &data); +/// Gizmo to open +static void create_volume(TriangleMesh &&mesh, const ObjectID& object_id, const ModelVolumeType type, + const std::optional& trmat, const DataBase &data, GLGizmosManager::EType gizmo); /// /// Select Volume from objects @@ -124,6 +241,14 @@ static OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Tr /// Extruded object from cuted surace static TriangleMesh cut_surface(/*const*/ DataBase &input1, const SurfaceVolumeData &input2, std::function was_canceled); +/// +/// Copied triangles from object to be able create mesh for cut surface from +/// +/// Source object volumes for cut surface from +/// Source volume id +/// Source data for cut surface from +static SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id = {}); + static void create_message(const std::string &message); // only in finalize static bool process(std::exception_ptr &eptr); static bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input); @@ -141,6 +266,11 @@ auto was_canceled(Job::Ctl &ctl, DataBase &base){ }// namespace priv +void Slic3r::GUI::Emboss::DataBase::write(ModelVolume &volume) const{ + volume.name = volume_name; + volume.emboss_shape = shape; +} + ///////////////// /// Create Volume CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input): m_input(std::move(input)){ assert(priv::check(m_input, true)); } @@ -159,7 +289,7 @@ void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { return; if (m_result.its.empty()) return priv::create_message(_u8L("Can't create empty volume.")); - priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, *m_input.base); + priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, *m_input.base, m_input.gizmo); } @@ -194,7 +324,7 @@ void CreateObjectJob::process(Ctl &ctl) bed_shape_.reserve(m_input.bed_shape.size()); for (const Vec2d &p : m_input.bed_shape) bed_shape_.emplace_back(p.cast()); - Polygon bed(bed_shape_); + Slic3r::Polygon bed(bed_shape_); if (!bed.contains(bed_coor.cast())) // mouse pose is out of build plate so create object in center of plate bed_coor = bed.centroid().cast(); @@ -254,8 +384,8 @@ void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr) // Manager::reset_all() So Gizmo could be closed before end of creation object GLCanvas3D *canvas = plater->canvas3D(); GLGizmosManager &manager = canvas->get_gizmos_manager(); - if (manager.get_current_type() != GLGizmosManager::Emboss) - manager.open_gizmo(GLGizmosManager::Emboss); + if (manager.get_current_type() != m_input.gizmo) + manager.open_gizmo(m_input.gizmo); // redraw scene canvas->reload_scene(true); @@ -306,7 +436,7 @@ void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { if (!priv::finalize(canceled, eptr, *m_input.base)) return; priv::create_volume(std::move(m_result), m_input.object_id, - m_input.volume_type, m_input.transform, *m_input.base); + m_input.volume_type, m_input.transform, *m_input.base, m_input.gizmo); } ///////////////// @@ -335,143 +465,203 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) priv::update_volume(std::move(m_result), m_input, tr); } -namespace Slic3r::GUI::Emboss { +namespace priv { +/// +/// Check if volume type is possible use for new text volume +/// +/// Type +/// True when allowed otherwise false +static bool is_valid(ModelVolumeType volume_type); + +/// +/// Start job for add new volume to object with given transformation +/// +/// Define where to queue the job. e.g. wxGetApp().plater()->get_ui_job_worker() +/// Define where to add +/// Wanted volume transformation, when not set will be calculated after creation to be near the object +/// Define what to emboss - shape +/// Type of volume: Part, negative, modifier +/// Define which gizmo open on the success +/// Nullptr when job is sucessfully add to worker otherwise return data to be processed different way +static bool start_create_volume_job(Worker &worker, const ModelObject &object, const std::optional& volume_tr, DataBasePtr data, ModelVolumeType volume_type, GLGizmosManager::EType gizmo); + +/// +/// Find volume in selected objects with closest convex hull to screen center. +/// +/// Define where to search for closest +/// Canvas center(dependent on camera settings) +/// Actual objects +/// OUT: coordinate of controid of closest volume +/// closest volume when exists otherwise nullptr +static const GLVolume *find_closest( + const Selection &selection, const Vec2d &screen_center, + const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center); + +/// +/// Start job for add object with text into scene +/// +/// Contain worker and build shape +/// Define params of text +/// Screen coordinat, where to create new object laying on bed +/// Define which gizmo open on the success +/// True when can add job to worker otherwise FALSE +static bool start_create_object_job(Plater &plater, DataBasePtr emboss_data, const Vec2d &coor, GLGizmosManager::EType gizmo); + +/// +/// Start job to create volume on the surface of object +/// +/// scene RayCasters + Objects + Camera + worker +/// Describe what to emboss +/// Type of new volume +/// Where to add +/// Surface point is belonge to +/// For project on surface +/// Define which gizmo open on the success +/// Distance from surface +/// Angle around emboss direction +/// True on add job to worker otherwise false +/// Nullptr when job is sucessfully add to worker otherwise return data to be processed different way +static DataBasePtr start_create_volume_on_surface_job(Plater &plater, + DataBasePtr data, + ModelVolumeType volume_type, + const Vec2d &screen_coor, + const GLVolume &gl_volume, + RaycastManager &raycaster, + GLGizmosManager::EType gizmo, + const std::optional &distance, + const std::optional &angle, + bool &success); -SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id) -{ - SurfaceVolumeData::ModelSources result; - result.reserve(volumes.size() - 1); - for (const ModelVolume *v : volumes) { - if (text_volume_id.has_value() && v->id().id == *text_volume_id) - continue; - // skip modifiers and negative volumes, ... - if (!v->is_model_part()) - continue; - const TriangleMesh &tm = v->mesh(); - if (tm.empty()) - continue; - if (tm.its.empty()) - continue; - result.push_back({v->get_mesh_shared_ptr(), v->get_matrix()}); - } - return result; } -SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume) +namespace Slic3r::GUI::Emboss { + +SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &text_volume) { - if (text_volume == nullptr) - return {}; - if (!text_volume->text_configuration.has_value()) - return {}; - const ModelVolumePtrs &volumes = text_volume->get_object()->volumes; + const ModelVolumePtrs &volumes = text_volume.get_object()->volumes; // no other volume in object if (volumes.size() <= 1) return {}; - return create_sources(volumes, text_volume->id().id); + return priv::create_sources(volumes, text_volume.id().id); } -bool start_create_volume_job( - Worker &worker, const ModelObject &object, const Transform3d volume_tr, DataBasePtr data, ModelVolumeType volume_type) +bool start_create_volume(Plater *plater_ptr, + DataBasePtr data, + ModelVolumeType volume_type, + RaycastManager &raycaster, + unsigned char gizmo, + const Vec2d &mouse_pos, + const std::optional &distance, + const std::optional &angle) { - bool &use_surface = data->shape.use_surface; - std::unique_ptr job; - if (use_surface) { - // Model to cut surface from. - SurfaceVolumeData::ModelSources sources = create_sources(object.volumes); - if (sources.empty()) { - use_surface = false; - } else { - bool is_outside = volume_type == ModelVolumeType::MODEL_PART; - // check that there is not unexpected volume type - assert(is_outside || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER); - SurfaceVolumeData sfvd{volume_tr, is_outside, std::move(sources)}; - CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(data), volume_type, object.id()}; - job = std::make_unique(std::move(surface_data)); - } - } - if (!use_surface) { - // create volume - DataCreateVolume create_volume_data{std::move(data), volume_type, object.id(), volume_tr}; - job = std::make_unique(std::move(create_volume_data)); - } - return queue_job(worker, std::move(job)); + if (!priv::is_valid(volume_type)) + return false; + + assert(plater_ptr); + if (plater_ptr == nullptr) + return false; + Plater &plater = *plater_ptr; + + GLCanvas3D *canvas_ptr = plater.get_current_canvas3D(); + assert(canvas_ptr); + if (canvas_ptr == nullptr) + return false; + + GLGizmosManager::EType gizmo_type = static_cast(gizmo); + + GLVolume *gl_volume = get_first_hovered_gl_volume(*canvas_ptr); + if (gl_volume == nullptr) + // object is not under mouse position soo create object on plater + return priv::start_create_object_job(plater, std::move(data), mouse_pos, gizmo_type); + + bool success = true; + DataBasePtr data2 = priv::start_create_volume_on_surface_job(plater, std::move(data), + volume_type, mouse_pos, *gl_volume, raycaster, gizmo_type, distance, angle, success); + + // Is successfull created job for add volume on surface? + if (success) + return true; + + // not success and consume data + if (data2 == nullptr) + return false; + + // Can't create on coordinate try to create somewhere + return start_create_volume_without_position(plater_ptr, std::move(data2), volume_type, raycaster, gizmo, distance, angle); } -std::optional create_volume_transformation_on_surface(const Vec2d &screen_coor, - const Camera &camera, - const ModelVolume &volume, - const ModelInstance &instance, - RaycastManager &raycaster, - GLCanvas3D &canvas, - const std::optional &distance, - const std::optional &angle) +bool start_create_volume_without_position(Plater *plater_ptr, + DataBasePtr data, + ModelVolumeType volume_type, + RaycastManager &raycaster, + unsigned char gizmo, + const std::optional &distance, + const std::optional &angle) { - auto cond = RaycastManager::AllowVolumes({volume.id().id}); - RaycastManager::Meshes meshes = create_meshes(canvas, cond); - raycaster.actualize(instance, &cond, &meshes); + if (!priv::is_valid(volume_type)) + return false; - std::optional hit = ray_from_camera(raycaster, screen_coor, camera, &cond); + assert(plater_ptr); + if (plater_ptr == nullptr) + return false; + Plater &plater = *plater_ptr; - // 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 - // also hit must exist. But there is options to add text by object list - if (!hit.has_value()) - return {}; + GLCanvas3D *canvas_ptr = plater.get_current_canvas3D(); + assert(canvas_ptr); + if (canvas_ptr == nullptr) + return false; + const GLCanvas3D &canvas = *canvas_ptr; - // Create result volume transformation - Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit); + // select position by camera position and view direction + const Selection &selection = canvas.get_selection(); + int object_idx = selection.get_object_idx(); - apply_transformation(angle, distance, surface_trmat); + Size s = canvas.get_canvas_size(); + Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); + const ModelObjectPtrs &objects = selection.get_model()->objects; - return instance.get_matrix().inverse() * surface_trmat; -} + GLGizmosManager::EType gizmo_type = static_cast(gizmo); -Transform3d create_volume_transformation(const GLVolume &gl_volume, const ModelObjectPtrs &objects, float volume_height, float volume_depth) -{ - // Transformation is inspired add generic volumes in ObjectList::load_generic_subobject - const ModelObject *obj = objects[gl_volume.object_idx()]; - BoundingBoxf3 instance_bb = obj->instance_bounding_box(gl_volume.instance_idx()); - // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. - Transform3d tr = gl_volume.get_instance_transformation().get_matrix_no_offset().inverse(); - Vec3d offset_tr(0, // center of instance - Can't suggest width of text before it will be created - - instance_bb.size().y() / 2 - volume_height / 2, // under - volume_depth / 2 - instance_bb.size().z() / 2 // lay on bed - ); - return tr * Eigen::Translation3d(offset_tr); -} + // No selected object so create new object + if (selection.is_empty() || object_idx < 0 || + static_cast(object_idx) >= objects.size()) + // create Object on center of screen + // when ray throw center of screen not hit bed it create object on center of bed + return priv::start_create_object_job(plater, std::move(data), screen_center, gizmo_type); -const GLVolume *find_closest( - const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center) -{ - assert(closest_center != nullptr); - const GLVolume * closest = nullptr; - const Selection::IndicesList &indices = selection.get_volume_idxs(); - assert(!indices.empty()); // no selected volume - if (indices.empty()) - return closest; + // create volume inside of selected object + Vec2d coor; + const Camera &camera = wxGetApp().plater()->get_camera(); + const GLVolume *gl_volume = priv::find_closest(selection, screen_center, camera, objects, &coor); - double center_sq_distance = std::numeric_limits::max(); - for (unsigned int id : indices) { - const GLVolume *gl_volume = selection.get_volume(id); - 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(); - Vec2d d = c - screen_center; - bool is_bigger_x = std::fabs(d.x()) > std::fabs(d.y()); - if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || (!is_bigger_x && d.y() * d.y() > center_sq_distance)) - continue; + if (gl_volume == nullptr) + return priv::start_create_object_job(plater, std::move(data), screen_center, gizmo_type); + + bool success = true; + DataBasePtr data2 = priv::start_create_volume_on_surface_job(plater, std::move(data), + volume_type, coor, *gl_volume, raycaster, gizmo_type, distance, angle, success); - double distance = d.squaredNorm(); - if (center_sq_distance < distance) - continue; - center_sq_distance = distance; + // Is successfull created job for add volume on surface? + if (success) + return true; - *closest_center = c; - closest = gl_volume; - } - return closest; + // not success and consume data + if (data2 == nullptr) + return false; + + // In centroid of convex hull is not hit with object. e.g. torid + // soo create transfomation on border of object + + // there is no point on surface so no use of surface will be applied + if (data2->shape.use_surface) + data2->shape.use_surface = false; + + Worker &worker = plater.get_ui_job_worker(); + const ModelObject *object = get_model_object(*gl_volume, objects); + if (object == nullptr) + return false; + + return priv::start_create_volume_job(worker, *object, {}, std::move(data2), volume_type, gizmo_type); } } // namespace Slic3r::GUI::Emboss @@ -495,6 +685,13 @@ bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface) //res &= input.text_configuration.style.prop.use_surface == use_surface; return res; } + +bool priv::check(GLGizmosManager::EType gizmo) +{ + assert(gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg); + return gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg; +} + bool priv::check(const DataCreateVolume &input, bool is_main_thread) { bool check_fontfile = false; assert(input.base != nullptr); @@ -504,6 +701,7 @@ bool priv::check(const DataCreateVolume &input, bool is_main_thread) { res &= input.volume_type != ModelVolumeType::INVALID; assert(input.object_id.id >= 0); res &= input.object_id.id >= 0; + res &= check(input.gizmo); return res; } bool priv::check(const DataCreateObject &input) { @@ -517,6 +715,7 @@ bool priv::check(const DataCreateObject &input) { res &= input.screen_coor.y() >= 0.; assert(input.bed_shape.size() >= 3); // at least triangle res &= input.bed_shape.size() >= 3; + res &= check(input.gizmo); return res; } bool priv::check(const DataUpdate &input, bool is_main_thread, bool use_surface){ @@ -542,6 +741,7 @@ bool priv::check(const CreateSurfaceVolumeData &input, bool is_main_thread) res &= check(*input.base, is_main_thread, use_surface); assert(!input.sources.empty()); res &= !input.sources.empty(); + res &= check(input.gizmo); return res; } bool priv::check(const UpdateSurfaceVolumeData &input, bool is_main_thread){ @@ -711,9 +911,12 @@ void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3 UpdateJob::update_volume(volume, std::move(mesh), *data.base); } -void priv::create_volume( - TriangleMesh &&mesh, const ObjectID& object_id, - const ModelVolumeType type, const Transform3d trmat, const DataBase &data) +void priv::create_volume(TriangleMesh &&mesh, + const ObjectID &object_id, + const ModelVolumeType type, + const std::optional &trmat, + const DataBase &data, + GLGizmosManager::EType gizmo) { GUI_App &app = wxGetApp(); Plater *plater = app.plater(); @@ -741,6 +944,13 @@ void priv::create_volume( plater->take_snapshot(_L("Add Emboss text Volume")); + BoundingBoxf3 instance_bb; + if (!trmat.has_value()) { + // used for align to instance + size_t instance_index = 0; // must exist + instance_bb = obj->instance_bounding_box(instance_index); + } + // NOTE: be carefull add volume also center mesh !!! // So first add simple shape(convex hull is also calculated) ModelVolume *volume = obj->add_volume(make_cube(1., 1., 1.), type); @@ -757,7 +967,24 @@ void priv::create_volume( volume->source.is_from_builtin_objects = true; volume->name = data.volume_name; // copy - volume->set_transformation(trmat); + + if (trmat.has_value()) { + volume->set_transformation(*trmat); + } else { + assert(!data.shape.use_surface); + // Create transformation for volume near from object(defined by glVolume) + // Transformation is inspired add generic volumes in ObjectList::load_generic_subobject + Vec3d volume_size = volume->mesh().bounding_box().size(); + // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. + Vec3d offset_tr(0, // center of instance - Can't suggest width of text before it will be created + -instance_bb.size().y() / 2 - volume_size.y() / 2, // under + volume_size.z() / 2 - instance_bb.size().z() / 2); // lay on bed + // use same instance as for calculation of instance_bounding_box + Transform3d tr = obj->instances.front()->get_transformation().get_matrix_no_offset().inverse(); + Transform3d volume_trmat = tr * Eigen::Translation3d(offset_tr); + volume->set_transformation(volume_trmat); + } + data.write(*volume); // update printable state on canvas @@ -935,6 +1162,26 @@ TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, return TriangleMesh(std::move(new_its)); } +SurfaceVolumeData::ModelSources priv::create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id) +{ + SurfaceVolumeData::ModelSources result; + result.reserve(volumes.size() - 1); + for (const ModelVolume *v : volumes) { + if (text_volume_id.has_value() && v->id().id == *text_volume_id) + continue; + // skip modifiers and negative volumes, ... + if (!v->is_model_part()) + continue; + const TriangleMesh &tm = v->mesh(); + if (tm.empty()) + continue; + if (tm.its.empty()) + continue; + result.push_back({v->get_mesh_shared_ptr(), v->get_matrix()}); + } + return result; +} + bool priv::process(std::exception_ptr &eptr) { if (!eptr) return false; try { @@ -956,6 +1203,141 @@ bool priv::finalize(bool canceled, std::exception_ptr &eptr, const DataBase &inp return !process(eptr); } +bool priv::is_valid(ModelVolumeType volume_type) +{ + if (volume_type == ModelVolumeType::MODEL_PART || + volume_type == ModelVolumeType::NEGATIVE_VOLUME || + volume_type == ModelVolumeType::PARAMETER_MODIFIER ) + return true; + + BOOST_LOG_TRIVIAL(error) << "Can't create embossed volume with this type: " << (int) volume_type; + return false; +} + +bool priv::start_create_volume_job( + Worker &worker, const ModelObject &object, const std::optional& volume_tr, DataBasePtr data, ModelVolumeType volume_type, GLGizmosManager::EType gizmo) +{ + bool &use_surface = data->shape.use_surface; + std::unique_ptr job; + if (use_surface) { + // Model to cut surface from. + SurfaceVolumeData::ModelSources sources = create_sources(object.volumes); + if (sources.empty() || !volume_tr.has_value()) { + use_surface = false; + } else { + bool is_outside = volume_type == ModelVolumeType::MODEL_PART; + // check that there is not unexpected volume type + assert(is_outside || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER); + SurfaceVolumeData sfvd{*volume_tr, is_outside, std::move(sources)}; + CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(data), volume_type, object.id(), gizmo}; + job = std::make_unique(std::move(surface_data)); + } + } + if (!use_surface) { + // create volume + DataCreateVolume create_volume_data{std::move(data), volume_type, object.id(), volume_tr, gizmo}; + job = std::make_unique(std::move(create_volume_data)); + } + return queue_job(worker, std::move(job)); +} + +const GLVolume * priv::find_closest( + const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center) +{ + assert(closest_center != nullptr); + const GLVolume *closest = nullptr; + const Selection::IndicesList &indices = selection.get_volume_idxs(); + assert(!indices.empty()); // no selected volume + if (indices.empty()) + return closest; + + double center_sq_distance = std::numeric_limits::max(); + for (unsigned int id : indices) { + const GLVolume *gl_volume = selection.get_volume(id); + 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(); + Vec2d d = c - screen_center; + bool is_bigger_x = std::fabs(d.x()) > std::fabs(d.y()); + if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || (!is_bigger_x && d.y() * d.y() > center_sq_distance)) + continue; + + double distance = d.squaredNorm(); + if (center_sq_distance < distance) + continue; + center_sq_distance = distance; + + *closest_center = c; + closest = gl_volume; + } + return closest; +} + +bool priv::start_create_object_job(Plater &plater, DataBasePtr emboss_data, const Vec2d &coor, GLGizmosManager::EType gizmo) +{ + const Camera &camera = plater.get_camera(); + const Pointfs &bed_shape = plater.build_volume().bed_shape(); + + DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape, gizmo}; + auto job = std::make_unique(std::move(data)); + + Worker &worker = plater.get_ui_job_worker(); + return queue_job(worker, std::move(job)); +} + +DataBasePtr priv::start_create_volume_on_surface_job(Plater &plater, + DataBasePtr data, + ModelVolumeType volume_type, + const Vec2d &screen_coor, + const GLVolume &gl_volume, + RaycastManager &raycaster, + GLGizmosManager::EType gizmo, + const std::optional &distance, + const std::optional &angle, + bool &success) +{ + success = false; + const ModelObjectPtrs &objects = plater.model().objects; + const ModelVolume* volume = get_model_volume(gl_volume, objects); + const ModelInstance *instance = get_model_instance(gl_volume, objects); + if (volume == nullptr || instance == nullptr || + volume->get_object() == nullptr) { + // weird situation + assert(false); + return data; + } + + const Camera &camera = plater.get_camera(); + GLCanvas3D &canvas = *plater.get_current_canvas3D(); + + auto cond = RaycastManager::AllowVolumes({volume->id().id}); + RaycastManager::Meshes meshes = create_meshes(canvas, cond); + raycaster.actualize(*instance, &cond, &meshes); + 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 + // also hit must exist. But there is options to add text by object list + if (!hit.has_value()) + // When model is broken. It could appear that hit miss the object. + // So add part near by in simmilar manner as right panel do + return data; + + // Create result volume transformation + Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit); + + apply_transformation(angle, distance, surface_trmat); + + Transform3d transform = instance->get_matrix().inverse() * surface_trmat; + + // Try to cast ray into scene and find object for add volume + Worker &worker = plater.get_ui_job_worker(); + + success = priv::start_create_volume_job(worker, *volume->get_object(), transform, std::move(data), volume_type, gizmo); + return nullptr; +} #include diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index b1b43f6f83..00de9b69f9 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -7,8 +7,7 @@ #include "libslic3r/Emboss.hpp" #include "libslic3r/EmbossShape.hpp" #include "libslic3r/Point.hpp" // Transform3d - -#include "slic3r/Utils/RaycastManager.hpp" +#include "libslic3r/ObjectID.hpp" #include "slic3r/GUI/Jobs/EmbossJob.hpp" // Emboss::DataBase #include "slic3r/GUI/Camera.hpp" @@ -17,16 +16,12 @@ // forward declarations namespace Slic3r { -class GLVolume; -class ModelVolume; -class ModelObject; class TriangleMesh; -typedef std::vector ModelObjectPtrs; -typedef std::vector ModelVolumePtrs; +class ModelVolume; +enum class ModelVolumeType : int; namespace GUI { -class Selection; class RaycastManager; -class Worker; +class Plater; }} namespace Slic3r::GUI::Emboss { @@ -38,7 +33,7 @@ class DataBase { public: DataBase(std::string volume_name, std::shared_ptr> cancel) : volume_name(volume_name), cancel(std::move(cancel)) {} - DataBase(std::string volume_name, std::shared_ptr> cancel, EmbossShape shape) + DataBase(std::string volume_name, std::shared_ptr> cancel, EmbossShape&& shape) : volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape)) {} virtual ~DataBase() {} @@ -54,11 +49,7 @@ public: /// Write data how to reconstruct shape to volume /// /// Data object for store emboss params - virtual void write(ModelVolume &volume) const - { - volume.name = volume_name; - volume.emboss_shape = shape; - }; + virtual void write(ModelVolume &volume) const; // new volume name std::string volume_name; @@ -70,77 +61,7 @@ public: // shape to emboss EmbossShape shape; }; - -/// -/// Hold neccessary data to create ModelVolume in job -/// Volume is created on the surface of existing volume in object. -/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!! -/// -struct DataCreateVolume -{ - // Hold data about shape - std::unique_ptr base; - - // define embossed volume type - ModelVolumeType volume_type; - - // parent ModelObject index where to create volume - ObjectID object_id; - - // new created volume transformation - Transform3d trmat; -}; - -/// -/// Create new TextVolume on the surface of ModelObject -/// Should not be stopped -/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!! -/// -class CreateVolumeJob : public Job -{ - DataCreateVolume m_input; - TriangleMesh m_result; - -public: - CreateVolumeJob(DataCreateVolume&& input); - void process(Ctl &ctl) override; - void finalize(bool canceled, std::exception_ptr &eptr) override; -}; - -/// -/// Hold neccessary data to create ModelObject in job -/// Object is placed on bed under screen coor -/// OR to center of scene when it is out of bed shape -/// -struct DataCreateObject -{ - // Hold data about shape - std::unique_ptr base; - - // define position on screen where to create object - Vec2d screen_coor; - - // projection property - Camera camera; - - // shape of bed in case of create volume on bed - std::vector bed_shape; -}; - -/// -/// Create new TextObject on the platter -/// Should not be stopped -/// -class CreateObjectJob : public Job -{ - DataCreateObject m_input; - TriangleMesh m_result; - Transform3d m_transformation; -public: - CreateObjectJob(DataCreateObject&& input); - void process(Ctl &ctl) override; - void finalize(bool canceled, std::exception_ptr &eptr) override; -}; +using DataBasePtr = std::unique_ptr; /// /// Hold neccessary data to update embossed text object in job @@ -148,7 +69,7 @@ public: struct DataUpdate { // Hold data about shape - std::unique_ptr base; + DataBasePtr base; // unique identifier of volume to change ObjectID volume_id; @@ -212,35 +133,6 @@ struct SurfaceVolumeData ModelSources sources; }; -/// -/// Hold neccessary data to create(cut) volume from surface object in job -/// -struct CreateSurfaceVolumeData : public SurfaceVolumeData{ - // Hold data about shape - std::unique_ptr base; - - // define embossed volume type - ModelVolumeType volume_type; - - // parent ModelObject index where to create volume - ObjectID object_id; -}; - -/// -/// Cut surface from object and create cutted volume -/// Should not be stopped -/// -class CreateSurfaceVolumeJob : public Job -{ - CreateSurfaceVolumeData m_input; - TriangleMesh m_result; - -public: - CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input); - void process(Ctl &ctl) override; - void finalize(bool canceled, std::exception_ptr &eptr) override; -}; - /// /// Hold neccessary data to update embossed text object in job /// @@ -264,72 +156,43 @@ public: /// /// Copied triangles from object to be able create mesh for cut surface from /// -/// Source object volumes for cut surface from -/// Source volume id +/// Define embossed volume /// Source data for cut surface from -SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id = {}); +SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume); /// -/// Copied triangles from object to be able create mesh for cut surface from +/// Create new volume on position of mouse cursor /// -/// Define text in object -/// Source data for cut surface from -SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume); +/// canvas + camera + bed shape + +/// Shape of emboss +/// New created volume type +/// Knows object in scene +/// Define which gizmo open on the success - enum GLGizmosManager::EType +/// Define position where to create volume +/// Wanted additionl move in Z(emboss) direction of new created volume +/// Wanted additionl rotation around Z of new created volume +/// True on success otherwise False +bool start_create_volume(Plater *plater_ptr, + DataBasePtr data, + ModelVolumeType volume_type, + RaycastManager &raycaster, + unsigned char gizmo, + const Vec2d &mouse_pos, + const std::optional &distance = {}, + const std::optional &angle = {}); -using DataBasePtr = std::unique_ptr; /// -/// Start job for add new volume to object with given transformation +/// Same as previous function but without mouse position +/// Need to suggest position or put near the selection /// -/// Define where to queue the job. e.g. wxGetApp().plater()->get_ui_job_worker() -/// Define where to add -/// Wanted volume transformation -/// Define what to emboss - shape -/// Type of volume: Part, negative, modifier -/// True on success otherwise false -bool start_create_volume_job(Worker &worker, const ModelObject &object, const Transform3d volume_tr, DataBasePtr data, ModelVolumeType volume_type); - -/// -/// Start job for add new volume on surface of object defined by screen coor -/// -/// Mouse position which define position -/// Volume to find surface for create -/// Instance to find surface for create -/// Ability to ray cast to model -/// Contain already used scene RayCasters -/// Initial z move -/// Initial z rotation -/// Volume transformation otherwise there is no hit surface by screen coor -std::optional create_volume_transformation_on_surface(const Vec2d &screen_coor, - const Camera &camera, - const ModelVolume &volume, - const ModelInstance &instance, - RaycastManager &raycaster, - GLCanvas3D &canvas, - const std::optional &distance = {}, - const std::optional &angle = {}); - -/// -/// Create transformation for volume near from object(defined by glVolume) -/// -/// Define object -/// All objects -/// Y Size of embossed volume [mm in instance] -/// Z size of embossed volume - emboss depth[mm in instance] -/// Transformation for new created volume -Transform3d create_volume_transformation(const GLVolume& gl_volume, const ModelObjectPtrs &objects, float volume_height, float volume_depth); - -/// -/// Find volume in selected objects with closest convex hull to screen center. -/// -/// Define where to search for closest -/// Canvas center(dependent on camera settings) -/// Actual objects -/// OUT: coordinate of controid of closest volume -/// closest volume when exists otherwise nullptr -const GLVolume *find_closest( - const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center); - +bool start_create_volume_without_position(Plater *plater_ptr, + DataBasePtr data, + ModelVolumeType volume_type, + RaycastManager &raycaster, + unsigned char gizmo, + const std::optional &distance = {}, + const std::optional &angle = {}); } // namespace Slic3r::GUI #endif // slic3r_EmbossJob_hpp_