diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 05970389c..77fdf5a83 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,5 @@ min_slic3r_version = 2.6.0-alpha5 +1.9.0-alpha0 Updated output filename format. 1.7.0-alpha2 Updated compatibility condition in some filament profiles (Prusa XL). 1.7.0-alpha1 Added profiles for Original Prusa XL. Added filament profile for Prusament PETG Tungsten 75%. min_slic3r_version = 2.6.0-alpha1 diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 3fb3aee3d..f57a8110d 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.7.0-alpha2 +config_version = 1.9.0-alpha0 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -192,7 +192,7 @@ notes = overhangs = 1 only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 -output_filename_format = {input_filename_base}_{layer_height}mm_{initial_filament_type}_{printer_model}_{print_time}.gcode +output_filename_format = {input_filename_base}_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode perimeters = 2 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 @@ -390,7 +390,7 @@ support_material_xy_spacing = 80% support_material_interface_spacing = 0.2 support_material_spacing = 2.2 raft_first_layer_expansion = 2 -output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{initial_filament_type}_{printer_model}_{print_time}.gcode +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode infill_anchor = 2 infill_anchor_max = 15 thick_bridges = 0 diff --git a/src/libslic3r/AABBTreeLines.hpp b/src/libslic3r/AABBTreeLines.hpp index 21678bfcd..2136e8edb 100644 --- a/src/libslic3r/AABBTreeLines.hpp +++ b/src/libslic3r/AABBTreeLines.hpp @@ -165,9 +165,9 @@ inline std::vector> get_intersections_with_line(si // Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies // during tree traversal. template -inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(const std::vector &lines) +inline AABBTreeIndirect::Tree build_aabb_tree_over_indexed_lines(const std::vector &lines) { - using TreeType = AABBTreeIndirect::Tree<2, typename LineType::Scalar>; + using TreeType = AABBTreeIndirect::Tree; // using CoordType = typename TreeType::CoordType; using VectorType = typename TreeType::VectorType; using BoundingBox = typename TreeType::BoundingBox; diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index e3f7454a7..65aa5a333 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1264,15 +1264,17 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, return result; } -void Emboss::apply_transformation(const FontProp &font_prop, - Transform3d &transformation) -{ - if (font_prop.angle.has_value()) { - double angle_z = *font_prop.angle; +void Emboss::apply_transformation(const FontProp &font_prop, Transform3d &transformation){ + apply_transformation(font_prop.angle, font_prop.distance, transformation); +} + +void Emboss::apply_transformation(const std::optional& angle, const std::optional& distance, Transform3d &transformation) { + if (angle.has_value()) { + double angle_z = *angle; transformation *= Eigen::AngleAxisd(angle_z, Vec3d::UnitZ()); } - if (font_prop.distance.has_value()) { - Vec3d translate = Vec3d::UnitZ() * (*font_prop.distance); + if (distance.has_value()) { + Vec3d translate = Vec3d::UnitZ() * (*distance); transformation.translate(translate); } } @@ -1540,60 +1542,95 @@ std::optional Emboss::ProjectZ::unproject(const Vec3d &p, double *depth) return Vec2d(p.x() / SHAPE_SCALE, p.y() / SHAPE_SCALE); } -Transform3d Emboss::create_transformation_onto_surface(const Vec3f &position, - const Vec3f &normal, - float up_limit) + +Vec3d Emboss::suggest_up(const Vec3d normal, double up_limit) { - // up and emboss direction for generated model - Vec3d text_up_dir = Vec3d::UnitY(); - Vec3d text_emboss_dir = Vec3d::UnitZ(); + // Normal must be 1 + assert(is_approx(normal.squaredNorm(), 1.)); // wanted up direction of result - Vec3d wanted_up_side = Vec3d::UnitZ(); - if (std::fabs(normal.z()) > up_limit) wanted_up_side = Vec3d::UnitY(); - - Vec3d wanted_emboss_dir = normal.cast(); - // after cast from float it needs to be normalized again - wanted_emboss_dir.normalize(); + Vec3d wanted_up_side = + (std::fabs(normal.z()) > up_limit)? + Vec3d::UnitY() : Vec3d::UnitZ(); // create perpendicular unit vector to surface triangle normal vector // lay on surface of triangle and define up vector for text - Vec3d wanted_up_dir = wanted_emboss_dir - .cross(wanted_up_side) - .cross(wanted_emboss_dir); + Vec3d wanted_up_dir = normal.cross(wanted_up_side).cross(normal); // normal3d is NOT perpendicular to normal_up_dir - wanted_up_dir.normalize(); + wanted_up_dir.normalize(); + + return wanted_up_dir; +} + +std::optional Emboss::calc_up(const Transform3d &tr, double up_limit) +{ + auto tr_linear = tr.linear(); + // z base of transformation ( tr * UnitZ ) + Vec3d normal = tr_linear.col(2); + // scaled matrix has base with different size + normal.normalize(); + Vec3d suggested = suggest_up(normal); + assert(is_approx(suggested.squaredNorm(), 1.)); + + Vec3d up = tr_linear.col(1); // tr * UnitY() + up.normalize(); + + double dot = suggested.dot(up); + if (dot >= 1. || dot <= -1.) + return {}; // zero angle + + Matrix3d m; + m.row(0) = up; + m.row(1) = suggested; + m.row(2) = normal; + double det = m.determinant(); + + return -atan2(det, dot); +} + +Transform3d Emboss::create_transformation_onto_surface(const Vec3d &position, + const Vec3d &normal, + double up_limit) +{ + // is normalized ? + assert(is_approx(normal.squaredNorm(), 1.)); + + // up and emboss direction for generated model + Vec3d up_dir = Vec3d::UnitY(); + Vec3d emboss_dir = Vec3d::UnitZ(); + + // after cast from float it needs to be normalized again + Vec3d wanted_up_dir = suggest_up(normal, up_limit); // perpendicular to emboss vector of text and normal Vec3d axis_view; double angle_view; - if (wanted_emboss_dir == -Vec3d::UnitZ()) { + if (normal == -Vec3d::UnitZ()) { // text_emboss_dir has opposit direction to wanted_emboss_dir axis_view = Vec3d::UnitY(); angle_view = M_PI; } else { - axis_view = text_emboss_dir.cross(wanted_emboss_dir); - angle_view = std::acos(text_emboss_dir.dot(wanted_emboss_dir)); // in rad + axis_view = emboss_dir.cross(normal); + angle_view = std::acos(emboss_dir.dot(normal)); // in rad axis_view.normalize(); } Eigen::AngleAxis view_rot(angle_view, axis_view); Vec3d wanterd_up_rotated = view_rot.matrix().inverse() * wanted_up_dir; wanterd_up_rotated.normalize(); - double angle_up = std::acos(text_up_dir.dot(wanterd_up_rotated)); + double angle_up = std::acos(up_dir.dot(wanterd_up_rotated)); - // text_view and text_view2 should have same direction - Vec3d text_view2 = text_up_dir.cross(wanterd_up_rotated); - Vec3d diff_view = text_emboss_dir - text_view2; + Vec3d text_view = up_dir.cross(wanterd_up_rotated); + Vec3d diff_view = emboss_dir - text_view; if (std::fabs(diff_view.x()) > 1. || std::fabs(diff_view.y()) > 1. || std::fabs(diff_view.z()) > 1.) // oposit direction angle_up *= -1.; - Eigen::AngleAxis up_rot(angle_up, text_emboss_dir); + Eigen::AngleAxis up_rot(angle_up, emboss_dir); Transform3d transform = Transform3d::Identity(); - transform.translate(position.cast()); + transform.translate(position); transform.rotate(view_rot); transform.rotate(up_rot); return transform; diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index ca27afe45..fc0f0a0a3 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -193,6 +193,7 @@ namespace Emboss /// Z-rotation as angle to Y axis(FontProp::angle) /// In / Out transformation to modify by property void apply_transformation(const FontProp &font_prop, Transform3d &transformation); + void apply_transformation(const std::optional &angle, const std::optional &distance, Transform3d &transformation); /// /// Read information from naming table of font file @@ -273,7 +274,23 @@ namespace Emboss /// Define transformation from 2d to 3d(orientation, position, scale, ...) /// Projected shape into space indexed_triangle_set polygons2model(const ExPolygons &shape2d, const IProjection& projection); + + /// + /// Suggest wanted up vector of embossed text by emboss direction + /// + /// Normalized vector of emboss direction in world + /// Is compared with normal.z to suggest up direction + /// Wanted up vector + Vec3d suggest_up(const Vec3d normal, double up_limit = 0.9); + /// + /// By transformation calculate angle between suggested and actual up vector + /// + /// Transformation of embossed volume in world + /// Is compared with normal.z to suggest up direction + /// Rotation of suggested up-vector[in rad] in the range [-Pi, Pi], When rotation is not zero + std::optional calc_up(const Transform3d &tr, double up_limit = 0.9); + /// /// Create transformation for emboss text object to lay on surface point /// @@ -282,7 +299,7 @@ namespace Emboss /// Is compared with normal.z to suggest up direction /// Transformation onto surface point Transform3d create_transformation_onto_surface( - const Vec3f &position, const Vec3f &normal, float up_limit = 0.9f); + const Vec3d &position, const Vec3d &normal, double up_limit = 0.9); class ProjectZ : public IProjection { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 5e1574e60..6fdec8b14 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1397,6 +1397,25 @@ void ModelObject::clone_for_cut(ModelObject** obj) (*obj)->input_file.clear(); } +bool ModelVolume::is_the_only_one_part() const +{ + if (m_type != ModelVolumeType::MODEL_PART) + return false; + if (object == nullptr) + return false; + for (const ModelVolume *v : object->volumes) { + if (v == nullptr) + continue; + // is this volume? + if (v->id() == this->id()) + continue; + // exist another model part in object? + if (v->type() == ModelVolumeType::MODEL_PART) + return false; + } + return true; +} + void ModelVolume::reset_extra_facets() { this->supported_facets.reset(); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 08fa79481..3fd9f21bc 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -835,6 +835,7 @@ public: bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } bool is_text() const { return text_configuration.has_value(); } + bool is_the_only_one_part() const; // behave like an object t_model_material_id material_id() const { return m_material_id; } void reset_extra_facets(); void apply_tolerance(); diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index cb0760d4d..5f8624ad3 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -587,7 +587,7 @@ namespace client param1.set_s(buf); } - static void regex_op(expr &lhs, boost::iterator_range &rhs, char op) + static void regex_op(const expr &lhs, boost::iterator_range &rhs, char op, expr &out) { const std::string *subject = nullptr; if (lhs.type() == TYPE_STRING) { @@ -601,7 +601,7 @@ namespace client bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern)); if (op == '!') result = ! result; - lhs.set_b(result); + out.set_b(result); } catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) { // Syntax error in the regular expression boost::throw_exception(qi::expectation_failure( @@ -609,8 +609,37 @@ namespace client } } - static void regex_matches (expr &lhs, boost::iterator_range &rhs) { return regex_op(lhs, rhs, '='); } - static void regex_doesnt_match(expr &lhs, boost::iterator_range &rhs) { return regex_op(lhs, rhs, '!'); } + static void regex_matches (expr &lhs, boost::iterator_range &rhs) { return regex_op(lhs, rhs, '=', lhs); } + static void regex_doesnt_match(expr &lhs, boost::iterator_range &rhs) { return regex_op(lhs, rhs, '!', lhs); } + + static void one_of_test_init(expr &out) { + out.set_b(false); + } + template + static void one_of_test(const expr &match, const expr &pattern, expr &out) { + if (! out.b()) { + if (match.type() != TYPE_STRING) + match.throw_exception("one_of(): First parameter (the string to match against) has to be a string value"); + if (pattern.type() != TYPE_STRING) + match.throw_exception("one_of(): Pattern has to be a string value"); + if (RegEx) { + try { + out.set_b(SLIC3R_REGEX_NAMESPACE::regex_match(match.s(), SLIC3R_REGEX_NAMESPACE::regex(pattern.s()))); + } catch (SLIC3R_REGEX_NAMESPACE::regex_error &) { + // Syntax error in the regular expression + pattern.throw_exception("Regular expression compilation failed"); + } + } else + out.set_b(match.s() == pattern.s()); + } + } + static void one_of_test_regex(const expr &match, boost::iterator_range &pattern, expr &out) { + if (! out.b()) { + if (match.type() != TYPE_STRING) + match.throw_exception("one_of(): First parameter (the string to match against) has to be a string value"); + regex_op(match, pattern, '=', out); + } + } static void logical_op(expr &lhs, expr &rhs, char op) { @@ -1101,6 +1130,7 @@ namespace client { "multiplicative_expression", "Expecting an expression." }, { "unary_expression", "Expecting an expression." }, { "optional_parameter", "Expecting a closing brace or an optional parameter." }, + { "one_of_list", "Expecting a list of string patterns (simple text or rexep)" }, { "variable_reference", "Expecting a variable reference."}, { "is_nil_test", "Expecting a scalar variable reference."}, { "variable", "Expecting a variable name."}, @@ -1221,6 +1251,7 @@ namespace client qi::_a_type _a; qi::_b_type _b; qi::_r1_type _r1; + qi::_r2_type _r2; // Starting symbol of the grammer. // The leading eps is required by the "expectation point" operator ">". @@ -1395,7 +1426,8 @@ namespace client [ px::bind(&expr::template digits, _val, _2, _3) ] | (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ] | (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ] - | (kw["is_nil"] > '(' > is_nil_test(_r1) > ')') [_val = _1] + | (kw["is_nil"] > '(' > is_nil_test(_r1) > ')') [ _val = _1 ] + | (kw["one_of"] > '(' > one_of(_r1) > ')') [ _val = _1 ] | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ] @@ -1404,6 +1436,20 @@ namespace client ); unary_expression.name("unary_expression"); + one_of = (unary_expression(_r1)[_a = _1] > one_of_list(_r1, _a))[_val = _2]; + one_of.name("one_of"); + one_of_list = + eps[px::bind(&expr::one_of_test_init, _val)] > + ( ',' > *( + ( + unary_expression(_r1)[px::bind(&expr::template one_of_test, _r2, _1, _val)] + | (lit('~') > unary_expression(_r1))[px::bind(&expr::template one_of_test, _r2, _1, _val)] + | regular_expression[px::bind(&expr::one_of_test_regex, _r2, _1, _val)] + ) >> -lit(',')) + | eps + ); + one_of_list.name("one_of_list"); + optional_parameter = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( lit(')') [ px::bind(&FactorActions::noexpr, _val) ] | (lit(',') > conditional_expression(_r1) > ')') [ _val = _1 ] @@ -1445,6 +1491,7 @@ namespace client ("random") ("round") ("not") + ("one_of") ("or") ("true"); @@ -1466,6 +1513,8 @@ namespace client debug(additive_expression); debug(multiplicative_expression); debug(unary_expression); + debug(one_of); + debug(one_of_list); debug(optional_parameter); debug(variable_reference); debug(variable); @@ -1517,6 +1566,9 @@ namespace client qi::rule(const MyContext*), spirit_encoding::space_type> variable; // Evaluating whether a nullable variable is nil. qi::rule(const MyContext*), spirit_encoding::space_type> is_nil_test; + // Evaluating "one of" list of patterns. + qi::rule(const MyContext*), qi::locals>, spirit_encoding::space_type> one_of; + qi::rule(const MyContext*, const expr ¶m), spirit_encoding::space_type> one_of_list; qi::rule, spirit_encoding::space_type> if_else_output; qi::rule, int>, spirit_encoding::space_type> assignment_statement; diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index f303b17e5..1c1ce7756 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -49,10 +49,12 @@ struct FontProp // used for move over model surface // When not set value is zero and is not stored std::optional distance; // [in mm] - - // change up vector direction of font + + // Angle of rotation around emboss direction (Z axis) + // It is calculate on the fly from volume world transformation + // only StyleManager keep actual value for comparision with style // When not set value is zero and is not stored - std::optional angle; // [in radians] + std::optional angle; // [in radians] form -Pi to Pi // Parameter for True Type Font collections // Select index of font in collection diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 453d7eeb5..2ca1998e3 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -97,6 +97,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_Geometry.hpp GUI/I18N.cpp GUI/I18N.hpp + GUI/IconManager.cpp + GUI/IconManager.hpp GUI/MainFrame.cpp GUI/MainFrame.hpp GUI/Plater.cpp @@ -157,6 +159,8 @@ set(SLIC3R_GUI_SOURCES GUI/RemovableDriveManager.hpp GUI/SendSystemInfoDialog.cpp GUI/SendSystemInfoDialog.hpp + GUI/SurfaceDrag.cpp + GUI/SurfaceDrag.hpp GUI/BonjourDialog.cpp GUI/BonjourDialog.hpp GUI/ButtonsDescription.cpp diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a898d08fc..e6b23c4b8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1407,7 +1407,6 @@ void GLCanvas3D::enable_legend_texture(bool enable) void GLCanvas3D::enable_picking(bool enable) { m_picking_enabled = enable; - m_selection.set_mode(Selection::Instance); } void GLCanvas3D::enable_moving(bool enable) @@ -7335,5 +7334,95 @@ const ModelVolume *get_model_volume(const GLVolume &v, const Model &model) return ret; } +const ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects) +{ + for (const ModelObject *obj : objects) + for (const ModelVolume *vol : obj->volumes) + if (vol->id() == volume_id) + return vol; + return nullptr; +} + +ModelVolume *get_model_volume(const GLVolume &v, const ModelObject& object) { + if (v.volume_idx() < 0) + return nullptr; + + size_t volume_idx = static_cast(v.volume_idx()); + if (volume_idx >= object.volumes.size()) + return nullptr; + + return object.volumes[volume_idx]; +} + +ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects) +{ + if (v.object_idx() < 0) + return nullptr; + size_t objext_idx = static_cast(v.object_idx()); + if (objext_idx >= objects.size()) + return nullptr; + if (objects[objext_idx] == nullptr) + return nullptr; + return get_model_volume(v, *objects[objext_idx]); +} + +GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas) { + int hovered_id_signed = canvas.get_first_hover_volume_idx(); + if (hovered_id_signed < 0) + return nullptr; + + size_t hovered_id = static_cast(hovered_id_signed); + const GLVolumePtrs &volumes = canvas.get_volumes().volumes; + if (hovered_id >= volumes.size()) + return nullptr; + + return volumes[hovered_id]; +} + +GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas) { + const GLVolume *gl_volume = get_selected_gl_volume(canvas.get_selection()); + if (gl_volume == nullptr) + return nullptr; + + const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; + for (GLVolume *v : gl_volumes) + if (v->composite_id == gl_volume->composite_id) + return v; + return nullptr; +} + +ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model) { + return get_model_object(gl_volume, model.objects); +} + +ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects) { + if (gl_volume.object_idx() < 0) + return nullptr; + size_t objext_idx = static_cast(gl_volume.object_idx()); + if (objext_idx >= objects.size()) + return nullptr; + return objects[objext_idx]; +} + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model& model) { + return get_model_instance(gl_volume, model.objects); +} + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects) { + if (gl_volume.instance_idx() < 0) + return nullptr; + ModelObject *object = get_model_object(gl_volume, objects); + return get_model_instance(gl_volume, *object); +} + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object) { + if (gl_volume.instance_idx() < 0) + return nullptr; + size_t instance_idx = static_cast(gl_volume.instance_idx()); + if (instance_idx >= object.instances.size()) + return nullptr; + return object.instances[instance_idx]; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ad33a5d68..6ebe7bcc6 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1068,7 +1068,20 @@ private: float get_overlay_window_width() { return LayersEditing::get_overlay_window_width(); } }; -const ModelVolume * get_model_volume(const GLVolume &v, const Model &model); +const ModelVolume *get_model_volume(const GLVolume &v, const Model &model); +const ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects); +ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects); +ModelVolume *get_model_volume(const GLVolume &v, const ModelObject &object); + +GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas); +GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas); + +ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model); +ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects); + +ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model &model); +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects); +ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object); } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index d9dfc7db2..656bc0b53 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -65,6 +65,7 @@ bool View3D::init(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig m_canvas->allow_multisample(OpenGLManager::can_multisample()); m_canvas->enable_picking(true); + m_canvas->get_selection().set_mode(Selection::Instance); m_canvas->enable_moving(true); // XXX: more config from 3D.pm m_canvas->set_model(model); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 0210de3b5..dd18cb878 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -18,7 +18,7 @@ // TODO: remove include #include "libslic3r/SVG.hpp" // debug store #include "libslic3r/Geometry.hpp" // covex hull 2d -#include "libslic3r/Timer.hpp" // covex hull 2d +#include "libslic3r/Timer.hpp" #include "libslic3r/NSVGUtils.hpp" #include "libslic3r/Model.hpp" @@ -34,6 +34,7 @@ #include #include #include +#include // detection of change DPI #include @@ -52,11 +53,13 @@ #define SHOW_IMGUI_ATLAS #define SHOW_ICONS_TEXTURE #define SHOW_FINE_POSITION // draw convex hull around volume -#define SHOW_WX_WEIGHT_INPUT #define DRAW_PLACE_TO_ADD_TEXT // Interactive draw of window position #define ALLOW_OPEN_NEAR_VOLUME +#define EXECUTE_PROCESS_ON_MAIN_THREAD // debug execution on main thread #endif // ALLOW_DEBUG_MODE +//#define USE_PIXEL_SIZE_IN_WX_FONT + using namespace Slic3r; using namespace Slic3r::Emboss; using namespace Slic3r::GUI; @@ -102,12 +105,11 @@ static const struct Limits } return false; } + } limits; -static bool is_text_empty(const std::string &text){ - return text.empty() || - text.find_first_not_of(" \n\t\r") == std::string::npos; -} +// Define where is up vector on model +constexpr double up_limit = 0.9; // Normalize radian angle from -PI to PI template void to_range_pi_pi(T& angle) @@ -118,6 +120,7 @@ template void to_range_pi_pi(T& angle) } } } // namespace priv +using namespace priv; GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) : GLGizmoBase(parent, M_ICON_FILENAME, -2) @@ -137,6 +140,13 @@ 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 /// @@ -146,8 +156,6 @@ namespace priv { /// Base data for emboss text static DataBase create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr> &cancel); -static bool is_valid(ModelVolumeType volume_type); - /// /// Start job for add new volume to object with given transformation /// @@ -160,8 +168,6 @@ static void start_create_volume_job(const ModelObject *object, DataBase &emboss_data, ModelVolumeType volume_type); -static GLVolume *get_hovered_gl_volume(const GLCanvas3D &canvas); - /// /// Start job for add new volume on surface of object defined by screen coor /// @@ -170,12 +176,14 @@ static GLVolume *get_hovered_gl_volume(const GLCanvas3D &canvas); /// 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(DataBase &emboss_data, ModelVolumeType volume_type, const Vec2d &screen_coor, const GLVolume *gl_volume, - RaycastManager &raycaster); + RaycastManager &raycaster, + GLCanvas3D &canvas); /// /// Find volume in selected object with closest convex hull to screen center. @@ -200,51 +208,68 @@ static void find_closest_volume(const Selection &selection, /// Screen coordinat, where to create new object laying on bed static void start_create_object_job(DataBase &emboss_data, const Vec2d &coor); +// Loaded icons enum +// Have to match order of files in function GLGizmoEmboss::init_icons() +enum class IconType : unsigned { + rename = 0, + erase, + add, + save, + undo, + italic, + unitalic, + bold, + unbold, + system_selector, + open_file, + exclamation, + // automatic calc of icon's count + _count +}; +// Define rendered version of icon +enum class IconState : unsigned { activable = 0, hovered /*1*/, disabled /*2*/ }; +// selector for icon by enum +const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType type, IconState state); +// short call of Slic3r::GUI::button +static bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false); + /// -/// Search if exist model volume for given id in object lists +/// Apply camera direction for emboss direction /// -/// List to search volume -/// Unique Identifier of volume -/// Volume when found otherwise nullptr -static const ModelVolume *get_volume(const ModelObjectPtrs &objects, const ObjectID &volume_id); +/// Define view vector +/// Containe Selected Model to modify +/// True when apply change otherwise false +static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas); } // namespace priv -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; -} - void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) { if (!priv::is_valid(volume_type)) return; - if (!m_gui_cfg.has_value()) initialize(); - set_default_text(); m_style_manager.discard_style_changes(); + set_default_text(); - GLVolume *gl_volume = priv::get_hovered_gl_volume(m_parent); + GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); - // Try to cast ray into scene and find object for add volume - if (priv::start_create_volume_on_surface_job(emboss_data, volume_type, mouse_pos, gl_volume, m_raycast_manager)) - // object found - return; - - // object is not under mouse position soo create object on plater - priv::start_create_object_job(emboss_data, mouse_pos); + if (gl_volume != nullptr) { + // Try to cast ray into scene and find object for add volume + if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, mouse_pos, gl_volume, m_raycast_manager, m_parent)) { + // When model is broken. It could appear that hit miss the object. + // So add part near by in simmilar manner as right panel do + create_volume(volume_type); + } + } else { + // object is not under mouse position soo create object on plater + priv::start_create_object_job(emboss_data, mouse_pos); + } } // Designed for create volume without information of mouse in scene void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) { if (!priv::is_valid(volume_type)) return; - if (!m_gui_cfg.has_value()) initialize(); - set_default_text(); m_style_manager.discard_style_changes(); + set_default_text(); // select position by camera position and view direction const Selection &selection = m_parent.get_selection(); @@ -267,8 +292,9 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) const GLVolume *vol = nullptr; const Camera &camera = wxGetApp().plater()->get_camera(); priv::find_closest_volume(selection, screen_center, camera, objects, &coor, &vol); - if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, coor, vol, m_raycast_manager)) { - assert(vol != nullptr); + if (vol == nullptr) { + priv::start_create_object_job(emboss_data, screen_center); + } else if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, coor, vol, m_raycast_manager, m_parent)) { // in centroid of convex hull is not hit with object // soo create transfomation on border of object @@ -299,9 +325,13 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) if (!m_dragging) return used; if (mouse_event.Dragging()) { - auto &angle_opt = m_volume->text_configuration->style.prop.angle; - if (!m_rotate_start_angle.has_value()) - m_rotate_start_angle = angle_opt.has_value() ? *angle_opt : 0.f; + if (!m_rotate_start_angle.has_value()) { + // when m_rotate_start_angle is not set mean it is not Dragging + // when angle_opt is not set mean angle is Zero + const std::optional &angle_opt = m_style_manager.get_font_prop().angle; + m_rotate_start_angle = angle_opt.has_value() ? *angle_opt : 0.f; + } + double angle = m_rotate_gizmo.get_angle(); angle -= PI / 2; // Grabber is upward @@ -313,274 +343,73 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) angle += *m_rotate_start_angle; // move to range <-M_PI, M_PI> priv::to_range_pi_pi(angle); - // propagate angle into property - angle_opt = static_cast(angle); - - // do not store zero - if (is_approx(*angle_opt, 0.f)) - angle_opt.reset(); // set into activ style assert(m_style_manager.is_active_font()); - if (m_style_manager.is_active_font()) + if (m_style_manager.is_active_font()) { + std::optional angle_opt; + if (!is_approx(angle, 0.)) + angle_opt = angle; m_style_manager.get_font_prop().angle = angle_opt; - + } } return used; } -namespace priv { - -/// -/// Access to model from gl_volume -/// TODO: it is more general function --> move to utils -/// -/// Volume to model belongs to -/// Object containing gl_volume -/// Model for volume -static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObject *object); - -/// -/// Access to model from gl_volume -/// TODO: it is more general function --> move to utils -/// -/// Volume to model belongs to -/// All objects -/// Model for volume -static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObjectPtrs &objects); - -/// -/// Access to model by selection -/// TODO: it is more general function --> move to select utils -/// -/// Actual selection -/// Model from selection -static ModelVolume *get_selected_volume(const Selection &selection); - -/// -/// Calculate offset from mouse position to center of text -/// -/// Screan mouse position -/// Selected volume(text) -/// Offset in screan coordinate -static Vec2d calc_mouse_to_center_text_offset(const Vec2d &mouse, const ModelVolume &mv); - -/// -/// Access to one selected volume -/// -/// Containe what is selected -/// Slected when only one volume otherwise nullptr -static const GLVolume *get_gl_volume(const Selection &selection); - -/// -/// Get transformation to world -/// - use fix after store to 3mf when exists -/// -/// -/// To identify MovelVolume with fix transformation -/// -static Transform3d world_matrix(const GLVolume *gl_volume, const Model *model); -static Transform3d world_matrix(const Selection &selection); - -/// -/// Change position of emboss window -/// -/// -/// When True Only move to be full visible otherwise reset position -static void change_window_position(std::optional &output_window_offset, bool try_to_fix); -} // namespace priv - -const GLVolume *priv::get_gl_volume(const Selection &selection) { - const auto &list = selection.get_volume_idxs(); - if (list.size() != 1) - return nullptr; - unsigned int volume_idx = *list.begin(); - return selection.get_volume(volume_idx); -} - -Transform3d priv::world_matrix(const GLVolume *gl_volume, const Model *model) -{ - if (!gl_volume) - return Transform3d::Identity(); - Transform3d res = gl_volume->world_matrix(); - - if (!model) - return res; - ModelVolume* mv = get_model_volume(gl_volume, model->objects); - if (!mv) - return res; - - const std::optional &tc = mv->text_configuration; - if (!tc.has_value()) - return res; - - const std::optional &fix = tc->fix_3mf_tr; - if (!fix.has_value()) - return res; - - return res * fix->inverse(); -} - -Transform3d priv::world_matrix(const Selection &selection) -{ - const GLVolume *gl_volume = get_gl_volume(selection); - return world_matrix(gl_volume, selection.get_model()); -} - -Vec2d priv::calc_mouse_to_center_text_offset(const Vec2d& mouse, const ModelVolume& mv) { - const Transform3d &volume_tr = mv.get_matrix(); - const Camera &camera = wxGetApp().plater()->get_camera(); - assert(mv.text_configuration.has_value()); - - auto calc_offset = [&mouse, &volume_tr, &camera, &mv] - (const Transform3d &instrance_tr) -> Vec2d { - Transform3d to_world = instrance_tr * volume_tr; - - // Use fix of .3mf loaded tranformation when exist - if (mv.text_configuration->fix_3mf_tr.has_value()) - to_world = to_world * (*mv.text_configuration->fix_3mf_tr); - // zero point of volume in world coordinate system - Vec3d volume_center = to_world.translation(); - // screen coordinate of volume center - Vec2i coor = CameraUtils::project(camera, volume_center); - return coor.cast() - mouse; - }; - - auto object = mv.get_object(); - assert(!object->instances.empty()); - // Speed up for one instance - if (object->instances.size() == 1) - return calc_offset(object->instances.front()->get_matrix()); - - Vec2d nearest_offset; - double nearest_offset_size = std::numeric_limits::max(); - for (const ModelInstance *instance : object->instances) { - Vec2d offset = calc_offset(instance->get_matrix()); - double offset_size = offset.norm(); - if (nearest_offset_size < offset_size) continue; - nearest_offset_size = offset_size; - nearest_offset = offset; - } - return nearest_offset; -} - bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) { - // filter events - if (!(mouse_event.Dragging() && mouse_event.LeftIsDown()) && - !mouse_event.LeftUp() && - !mouse_event.LeftDown()) + // exist selected volume? + if (m_volume == nullptr) return false; + + std::optional up_limit; + if (m_keep_up) up_limit = priv::up_limit; + const Camera &camera = wxGetApp().plater()->get_camera(); + bool was_dragging = m_surface_drag.has_value(); + bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager, up_limit); + bool is_dragging = m_surface_drag.has_value(); - // must exist hover object - int hovered_id = m_parent.get_first_hover_volume_idx(); - if (hovered_id < 0) return false; - - GLVolume *gl_volume = m_parent.get_volumes().volumes[hovered_id]; - const ModelObjectPtrs &objects = wxGetApp().plater()->model().objects; - ModelVolume *act_model_volume = priv::get_model_volume(gl_volume, objects); - - // hovered object must be actual text volume - if (m_volume != act_model_volume) return false; - - const ModelVolumePtrs &volumes = m_volume->get_object()->volumes; - std::vector allowed_volumes_id; - if (volumes.size() > 1) { - allowed_volumes_id.reserve(volumes.size() - 1); - for (auto &v : volumes) { - if (v->id() == m_volume->id()) continue; - if (!v->is_model_part()) continue; - allowed_volumes_id.emplace_back(v->id().id); - } - } - - // wxCoord == int --> wx/types.h - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); - Vec2d mouse_pos = mouse_coord.cast(); - RaycastManager::AllowVolumes condition(std::move(allowed_volumes_id)); - - // detect start text dragging - if (mouse_event.LeftDown()) { - // initialize raycasters - // IMPROVE: move to job, for big scene it slows down - ModelObject *act_model_object = act_model_volume->get_object(); - m_raycast_manager.actualize(act_model_object, &condition); - m_dragging_mouse_offset = priv::calc_mouse_to_center_text_offset(mouse_pos, *m_volume); - // Cancel job to prevent interuption of dragging (duplicit result) - if (m_job_cancel != nullptr) - m_job_cancel->store(true); - return false; - } - - // Dragging starts out of window - if (!m_dragging_mouse_offset.has_value()) - return false; - - if (mouse_event.Dragging()) { - const Camera &camera = wxGetApp().plater()->get_camera(); - Vec2d offseted_mouse = mouse_pos + *m_dragging_mouse_offset; - auto hit = m_raycast_manager.unproject(offseted_mouse, camera, &condition); - if (!hit.has_value()) - return false; - TextConfiguration &tc = *m_volume->text_configuration; - // INFO: GLVolume is transformed by common movement but we need move over surface - // so hide common dragging of object - m_parent.toggle_model_objects_visibility(false, m_volume->get_object(), gl_volume->instance_idx(), m_volume); - - // Calculate temporary position - Transform3d object_trmat = m_raycast_manager.get_transformation(hit->tr_key); - Transform3d trmat = create_transformation_onto_surface(hit->position, hit->normal); - const FontProp& font_prop = tc.style.prop; - apply_transformation(font_prop, trmat); - - // fix baked transformation from .3mf store process - if (tc.fix_3mf_tr.has_value()) - trmat = trmat * (*tc.fix_3mf_tr); - - // temp is in world coors - m_temp_transformation = object_trmat * trmat; - - // calculate scale - calculate_scale(); - } else if (mouse_event.LeftUp()) { - // Added because of weird case after double click into scene - // with Mesa driver OR on Linux - if (!m_temp_transformation.has_value()) return false; - - // Override of common transformation after draggig by set transformation into gl_volume - Transform3d volume_trmat = - gl_volume->get_instance_transformation().get_matrix().inverse() * - *m_temp_transformation; - gl_volume->set_volume_transformation(Geometry::Transformation(volume_trmat)); - m_parent.toggle_model_objects_visibility(true); - // Apply temporary position - m_temp_transformation = {}; - m_dragging_mouse_offset = {}; - + // End with surface dragging? + if (was_dragging && !is_dragging) { // Update surface by new position - if (m_volume->text_configuration->style.prop.use_surface) { - // need actual position - m_volume->set_transformation(volume_trmat); + if (m_volume->text_configuration->style.prop.use_surface) process(); - } - // calculate scale + // Show correct value of height & depth inside of inputs calculate_scale(); } - return false; + + // Start with dragging + else if (!was_dragging && is_dragging) { + // Cancel job to prevent interuption of dragging (duplicit result) + if (m_job_cancel != nullptr) + m_job_cancel->store(true); + } + + // during drag + else if (was_dragging && is_dragging) { + // update scale of selected volume --> should be approx the same + calculate_scale(); + + // Recalculate angle for GUI + if (!m_keep_up) { + const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection()); + assert(gl_volume != nullptr); + assert(m_style_manager.is_active_font()); + if (gl_volume == nullptr || !m_style_manager.is_active_font()) + return res; + + m_style_manager.get_style().prop.angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + } + } + return res; } bool GLGizmoEmboss::on_mouse(const wxMouseEvent &mouse_event) { - // do not process moving event - if (mouse_event.Moving()) return false; - // not selected volume - assert(m_volume != nullptr); - assert(priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) != nullptr); - assert(m_volume->text_configuration.has_value()); if (m_volume == nullptr || - priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr || + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr || !m_volume->text_configuration.has_value()) return false; if (on_mouse_for_rotation(mouse_event)) return true; @@ -595,6 +424,12 @@ bool GLGizmoEmboss::on_init() ColorRGBA gray_color(.6f, .6f, .6f, .3f); m_rotate_gizmo.set_highlight_color(gray_color); m_shortcut_key = WXK_CONTROL_T; + + // initialize text styles + m_style_manager.init(wxGetApp().app_config); + + // Set rotation gizmo upwardrotate + m_rotate_gizmo.set_angle(PI / 2); return true; } @@ -603,58 +438,15 @@ std::string GLGizmoEmboss::on_get_name() const { return _u8L("Emboss"); } void GLGizmoEmboss::on_render() { // no volume selected if (m_volume == nullptr || - priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr) + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr) return; Selection &selection = m_parent.get_selection(); if (selection.is_empty()) return; - if (m_temp_transformation.has_value()) { - // draw text volume on temporary position - GLVolume& gl_volume = *selection.get_volume(*selection.get_volume_idxs().begin()); - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - shader->start_using(); - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d matrix = camera.get_view_matrix() * (*m_temp_transformation); - shader->set_uniform("view_model_matrix", matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("view_normal_matrix", (Matrix3d) (matrix).matrix().block(0, 0, 3, 3).inverse().transpose()); - shader->set_uniform("emission_factor", 0.0f); - - // dragging object must be selected so draw it with correct color - //auto color = gl_volume.color; - //auto color = gl_volume.render_color; - auto color = GLVolume::SELECTED_COLOR; - // Set transparent color for NEGATIVE_VOLUME & PARAMETER_MODIFIER - bool is_transparent = m_volume->type() != ModelVolumeType::MODEL_PART; - if (is_transparent) { - color.a(0.5f); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - } - - bool is_left_handed = has_reflection(*m_temp_transformation); - if (is_left_handed) - glsafe(::glFrontFace(GL_CW)); - - glsafe(::glEnable(GL_DEPTH_TEST)); - gl_volume.model.set_color(color); - gl_volume.model.render(); - glsafe(::glDisable(GL_DEPTH_TEST)); - - // set it back to pevious state - if (is_left_handed) - glsafe(::glFrontFace(GL_CCW)); - if (is_transparent) - glsafe(::glDisable(GL_BLEND)); - - shader->stop_using(); - } - // prevent get local coordinate system on multi volumes if (!selection.is_single_volume_or_modifier() && !selection.is_single_volume_instance()) return; - bool is_surface_dragging = m_temp_transformation.has_value(); + bool is_surface_dragging = m_surface_drag.has_value(); bool is_parent_dragging = m_parent.is_mouse_dragging(); // Do NOT render rotation grabbers when dragging object bool is_rotate_by_grabbers = m_dragging; @@ -726,55 +518,61 @@ static void draw_mouse_offset(const std::optional &offset) draw_list->AddLine(p1, p2, color, thickness); } #endif // SHOW_OFFSET_DURING_DRAGGING -namespace priv { -static void draw_origin(const GLCanvas3D& canvas) { - auto draw_list = ImGui::GetOverlayDrawList(); - const Selection &selection = canvas.get_selection(); - Transform3d to_world = priv::world_matrix(selection); - Vec3d volume_zero = to_world * Vec3d::Zero(); - - const Camera &camera = wxGetApp().plater()->get_camera(); - Point screen_coor = CameraUtils::project(camera, volume_zero); - ImVec2 center(screen_coor.x(), screen_coor.y()); - float radius = 16.f; - ImU32 color = ImGui::GetColorU32(ImVec4(1.f, 1.f, 1.f, .75f)); - - int num_segments = 0; - float thickness = 4.f; - draw_list->AddCircle(center, radius, color, num_segments, thickness); - auto dirs = {ImVec2{0, 1}, ImVec2{1, 0}, ImVec2{0, -1}, ImVec2{-1, 0}}; - for (const ImVec2 &dir : dirs) { - ImVec2 start( - center.x + dir.x * 0.5 * radius, - center.y + dir.y * 0.5 * radius); - ImVec2 end( - center.x + dir.x * 1.5 * radius, - center.y + dir.y * 1.5 * radius); - draw_list->AddLine(start, end, color, thickness); - } -} - -} // namespace priv void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) { - if (!m_gui_cfg.has_value()) initialize(); set_volume_by_selection(); - // Do not render window for not selected text volume if (m_volume == nullptr || - priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr || + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr || !m_volume->text_configuration.has_value()) { close(); return; } + // Not known situation when could happend this is only for sure + if (!m_is_unknown_font && !m_style_manager.is_active_font()) + create_notification_not_valid_font("No active font in style. Select correct one."); + else if (!m_is_unknown_font && !m_style_manager.get_wx_font().IsOk()) + create_notification_not_valid_font("WxFont is not loaded properly."); + + // Configuration creation + double screen_scale = wxDisplay(wxGetApp().plater()).GetScaleFactor(); + float main_toolbar_height = m_parent.get_main_toolbar_height(); + if (!m_gui_cfg.has_value() || // Exist configuration - first run + m_gui_cfg->screen_scale != screen_scale || // change of DPI + m_gui_cfg->main_toolbar_height != main_toolbar_height // change size of view port + ) { + // Create cache for gui offsets + GuiCfg cfg = create_gui_configuration(); + cfg.screen_scale = screen_scale; + cfg.main_toolbar_height = main_toolbar_height; + m_gui_cfg.emplace(std::move(cfg)); + // set position near toolbar + m_set_window_offset = ImVec2(-1.f, -1.f); + + // change resolution regenerate icons + init_icons(); + m_style_manager.clear_imgui_font(); + } + const ImVec2 &min_window_size = get_minimal_window_size(); ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, min_window_size); // Draw origin position of text during dragging - if (m_temp_transformation.has_value()) - priv::draw_origin(m_parent); + if (m_surface_drag.has_value()) { + ImVec2 mouse_pos = ImGui::GetMousePos(); + ImVec2 center( + mouse_pos.x + m_surface_drag->mouse_offset.x(), + mouse_pos.y + m_surface_drag->mouse_offset.y()); + ImU32 color = ImGui::GetColorU32( + m_surface_drag->exist_hit ? + ImVec4(1.f, 1.f, 1.f, .75f) : // transparent white + ImVec4(1.f, .3f, .3f, .75f) + ); // Warning color + const float radius = 16.f; + ImGuiWrapper::draw_cross_hair(center, radius, color); + } #ifdef SHOW_FINE_POSITION draw_fine_position(m_parent.get_selection(), m_parent.get_canvas_size(), min_window_size); @@ -828,22 +626,14 @@ namespace priv { /// Move window for edit emboss text near to embossed object /// NOTE: embossed object must be selected /// -ImVec2 calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size) -{ - const Selection::IndicesList indices = selection.get_volume_idxs(); - // no selected volume - if (indices.empty()) return {}; - const GLVolume *volume = selection.get_volume(*indices.begin()); - // bad volume selected (e.g. deleted one) - if (volume == nullptr) return {}; +static ImVec2 calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size); - const Camera &camera = wxGetApp().plater()->get_camera(); - Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *volume); - - ImVec2 c_size(canvas_size.get_width(), canvas_size.get_height()); - ImVec2 offset = ImGuiWrapper::suggest_location(windows_size, hull, c_size); - return offset; -} +/// +/// Change position of emboss window +/// +/// +/// When True Only move to be full visible otherwise reset position +static void change_window_position(std::optional &output_window_offset, bool try_to_fix); } // namespace priv void GLGizmoEmboss::on_set_state() @@ -866,37 +656,40 @@ void GLGizmoEmboss::on_set_state() _u8L("ERROR: Wait until ends or Cancel process.")); return; } - set_volume(nullptr); + reset_volume(); // Store order and last activ index into app.ini // TODO: what to do when can't store into file? m_style_manager.store_styles_to_app_config(false); remove_notification_not_valid_font(); } else if (GLGizmoBase::m_state == GLGizmoBase::On) { - if (!m_gui_cfg.has_value()) initialize(); - // to reload fonts from system, when install new one wxFontEnumerator::InvalidateCache(); // Try(when exist) set text configuration by volume - set_volume(priv::get_selected_volume(m_parent.get_selection())); + set_volume_by_selection(); - // when open window by "T" and no valid volume is selected, so Create new one + // when open window by "T" and no valid volume is selected, so Create new one if (m_volume == nullptr || - priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr ) { + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr ) { // reopen gizmo when new object is created GLGizmoBase::m_state = GLGizmoBase::Off; if (wxGetApp().get_mode() == comSimple || wxGetApp().obj_list()->has_selected_cut_object()) // It's impossible to add a part in simple mode return; - // start creating new object + // start creating new object create_volume(ModelVolumeType::MODEL_PART); } // change position of just opened emboss window - if (m_allow_open_near_volume) + if (m_allow_open_near_volume) { m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); - else - priv::change_window_position(m_set_window_offset, false); + } else { + if (m_gui_cfg.has_value()) + priv::change_window_position(m_set_window_offset, false); + else + m_set_window_offset = ImVec2(-1, -1); + } + // when open by hyperlink it needs to show up // or after key 'T' windows doesn't appear m_parent.set_as_dirty(); @@ -920,6 +713,13 @@ void GLGizmoEmboss::on_stop_dragging() // apply rotation m_parent.do_rotate(L("Text-Rotate")); + // Re-Calculate current angle of up vector + const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection()); + assert(m_style_manager.is_active_font()); + assert(gl_volume != nullptr); + if (m_style_manager.is_active_font() && gl_volume != nullptr) + m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + m_rotate_start_angle.reset(); // recalculate for surface cut @@ -928,18 +728,18 @@ void GLGizmoEmboss::on_stop_dragging() } void GLGizmoEmboss::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(data); } -void GLGizmoEmboss::initialize() +GLGizmoEmboss::GuiCfg GLGizmoEmboss::create_gui_configuration() { - if (m_gui_cfg.has_value()) return; + GuiCfg cfg; // initialize by default values; - GuiCfg cfg; // initialize by default values; float line_height = ImGui::GetTextLineHeight(); float line_height_with_spacing = ImGui::GetTextLineHeightWithSpacing(); float space = line_height_with_spacing - line_height; + const ImGuiStyle &style = ImGui::GetStyle(); cfg.max_style_name_width = ImGui::CalcTextSize("Maximal font name, extended").x; - cfg.icon_width = std::ceil(line_height); + cfg.icon_width = static_cast(std::ceil(line_height)); // make size pair number if (cfg.icon_width % 2 != 0) ++cfg.icon_width; @@ -950,38 +750,42 @@ void GLGizmoEmboss::initialize() int count_letter_M_in_input = 12; cfg.input_width = letter_m_size.x * count_letter_M_in_input; GuiCfg::Translations &tr = cfg.translations; - tr.font = _u8L("Font"); - tr.size = _u8L("Height"); - tr.depth = _u8L("Depth"); + + tr.font = _u8L("Font"); + tr.height = _u8L("Height"); + tr.depth = _u8L("Depth"); + float max_text_width = std::max({ ImGui::CalcTextSize(tr.font.c_str()).x, - ImGui::CalcTextSize(tr.size.c_str()).x, + ImGui::CalcTextSize(tr.height.c_str()).x, ImGui::CalcTextSize(tr.depth.c_str()).x}); - cfg.input_offset = max_text_width - + 3 * space + ImGui::GetTreeNodeToLabelSpacing(); + cfg.indent = static_cast(cfg.icon_width); + cfg.input_offset = style.WindowPadding.x + cfg.indent + max_text_width + space; + + tr.use_surface = _u8L("Use surface"); + tr.char_gap = _u8L("Char gap"); + tr.line_gap = _u8L("Line gap"); + tr.boldness = _u8L("Boldness"); + tr.skew_ration = _u8L("Skew ratio"); + tr.from_surface = _u8L("From surface"); + tr.rotation = _u8L("Rotation"); + tr.keep_up = _u8L("Keep Up"); + tr.collection = _u8L("Collection"); - tr.use_surface = _u8L("Use surface"); - tr.char_gap = _u8L("Char gap"); - tr.line_gap = _u8L("Line gap"); - tr.boldness = _u8L("Boldness"); - tr.italic = _u8L("Skew ratio"); - tr.surface_distance = _u8L("Z-move"); - tr.angle = _u8L("Z-rot"); - tr.collection = _u8L("Collection"); float max_advanced_text_width = std::max({ ImGui::CalcTextSize(tr.use_surface.c_str()).x, ImGui::CalcTextSize(tr.char_gap.c_str()).x, ImGui::CalcTextSize(tr.line_gap.c_str()).x, ImGui::CalcTextSize(tr.boldness.c_str()).x, - ImGui::CalcTextSize(tr.italic.c_str()).x, - ImGui::CalcTextSize(tr.surface_distance.c_str()).x, - ImGui::CalcTextSize(tr.angle.c_str()).x, + ImGui::CalcTextSize(tr.skew_ration.c_str()).x, + ImGui::CalcTextSize(tr.from_surface.c_str()).x, + ImGui::CalcTextSize(tr.rotation.c_str()).x, + ImGui::CalcTextSize(tr.keep_up.c_str()).x, ImGui::CalcTextSize(tr.collection.c_str()).x }); cfg.advanced_input_offset = max_advanced_text_width - + 3 * space + ImGui::GetTreeNodeToLabelSpacing(); + + 3 * space + cfg.indent; // calculate window size - const ImGuiStyle &style = ImGui::GetStyle(); float window_title = line_height + 2*style.FramePadding.y + 2 * style.WindowTitleAlign.y; float input_height = line_height_with_spacing + 2*style.FramePadding.y; float tree_header = line_height_with_spacing; @@ -1002,9 +806,9 @@ void GLGizmoEmboss::initialize() + 2 * (cfg.icon_width + space); cfg.minimal_window_size = ImVec2(window_width, window_height); - // 6 = charGap, LineGap, Bold, italic, surfDist, angle + // 9 = useSurface, charGap, lineGap, bold, italic, surfDist, rotation, keepUp, textFaceToCamera // 4 = 1px for fix each edit image of drag float - float advance_height = input_height * 8 + 8; + float advance_height = input_height * 9 + 8; cfg.minimal_window_size_with_advance = ImVec2(cfg.minimal_window_size.x, cfg.minimal_window_size.y + advance_height); @@ -1018,34 +822,30 @@ void GLGizmoEmboss::initialize() int max_style_image_height = 1.5 * input_height; cfg.max_style_image_size = Vec2i(max_style_image_width, max_style_image_height); cfg.face_name_size.y() = line_height_with_spacing; - - m_gui_cfg.emplace(std::move(cfg)); - - init_icons(); - - // initialize text styles - m_style_manager.init(wxGetApp().app_config); - set_default_text(); - - // Set rotation gizmo upwardrotate - m_rotate_gizmo.set_angle(PI/2); + return cfg; } EmbossStyles GLGizmoEmboss::create_default_styles() { - wxFont wx_font_normal = *wxNORMAL_FONT; - wxFont wx_font_small = *wxSMALL_FONT; + wxFontEnumerator::InvalidateCache(); + wxArrayString facenames = wxFontEnumerator::GetFacenames(Facenames::encoding); + wxFont wx_font_normal = *wxNORMAL_FONT; #ifdef __APPLE__ - wx_font_normal.SetFaceName("Helvetica"); - wx_font_small.SetFaceName("Helvetica"); + // Set normal font to helvetica when possible + for (const wxString &facename : facenames) { + if (facename.IsSameAs("Helvetica")) { + wx_font_normal = wxFont(wxFontInfo().FaceName(facename).Encoding(Facenames::encoding)); + break; + } + } #endif // __APPLE__ // https://docs.wxwidgets.org/3.0/classwx_font.html // Predefined objects/pointers: wxNullFont, wxNORMAL_FONT, wxSMALL_FONT, wxITALIC_FONT, wxSWISS_FONT EmbossStyles styles = { WxFontUtils::create_emboss_style(wx_font_normal, _u8L("NORMAL")), // wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) - WxFontUtils::create_emboss_style(wx_font_normal, _u8L("SMALL")), // A font using the wxFONTFAMILY_SWISS family and 2 points smaller than wxNORMAL_FONT. + WxFontUtils::create_emboss_style(*wxSMALL_FONT, _u8L("SMALL")), // A font using the wxFONTFAMILY_SWISS family and 2 points smaller than wxNORMAL_FONT. WxFontUtils::create_emboss_style(*wxITALIC_FONT, _u8L("ITALIC")), // A font using the wxFONTFAMILY_ROMAN family and wxFONTSTYLE_ITALIC style and of the same size of wxNORMAL_FONT. WxFontUtils::create_emboss_style(*wxSWISS_FONT, _u8L("SWISS")), // A font identic to wxNORMAL_FONT except for the family used which is wxFONTFAMILY_SWISS. WxFontUtils::create_emboss_style(wxFont(10, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD), _u8L("MODERN")), @@ -1074,7 +874,6 @@ EmbossStyles GLGizmoEmboss::create_default_styles() // No valid style in defult list // at least one style must contain loadable font - wxArrayString facenames = wxFontEnumerator::GetFacenames(wxFontEncoding::wxFONTENCODING_SYSTEM); wxFont wx_font; for (const wxString &face : facenames) { wx_font = wxFont(face); @@ -1097,12 +896,20 @@ EmbossStyles GLGizmoEmboss::create_default_styles() void GLGizmoEmboss::set_default_text(){ m_text = _u8L("Embossed text"); } -#include "imgui/imgui_internal.h" // to unfocus input --> ClearActiveID void GLGizmoEmboss::set_volume_by_selection() { - ModelVolume *vol = priv::get_selected_volume(m_parent.get_selection()); - // is same volume selected? - if (vol != nullptr && vol->id() == m_volume_id) + const Selection &selection = m_parent.get_selection(); + const GLVolume *gl_volume = get_selected_gl_volume(selection); + if (gl_volume == nullptr) + return reset_volume(); + + const ModelObjectPtrs &objects = selection.get_model()->objects; + ModelVolume *volume =get_model_volume(*gl_volume, objects); + if (volume == nullptr) + return reset_volume(); + + // is same volume as actual selected? + if (volume->id() == m_volume_id) return; // for changed volume notification is NOT valid @@ -1110,63 +917,16 @@ void GLGizmoEmboss::set_volume_by_selection() // Do not use focused input value when switch volume(it must swith value) if (m_volume != nullptr && - m_volume != vol) // when update volume it changed id BUT not pointer - ImGui::ClearActiveID(); + m_volume != volume) // when update volume it changed id BUT not pointer + ImGuiWrapper::left_inputs(); - // is select embossed volume? - set_volume(vol); -} + // Is selected volume text volume? + const std::optional& tc_opt = volume->text_configuration; + if (!tc_opt.has_value()) + return reset_volume(); -// Need internals to get window -void priv::change_window_position(std::optional& output_window_offset, bool try_to_fix) { - const char* name = "Emboss"; - ImGuiWindow *window = ImGui::FindWindowByName(name); - // is window just created - if (window == NULL) - return; - - // position of window on screen - ImVec2 position = window->Pos; - ImVec2 size = window->SizeFull; - - // screen size - ImVec2 screen = ImGui::GetMainViewport()->Size; - - if (position.x < 0) { - if (position.y < 0) - output_window_offset = ImVec2(0, 0); - else - output_window_offset = ImVec2(0, position.y); - } else if (position.y < 0) { - output_window_offset = ImVec2(position.x, 0); - } else if (screen.x < (position.x + size.x)) { - if (screen.y < (position.y + size.y)) - output_window_offset = ImVec2(screen.x - size.x, screen.y - size.y); - else - output_window_offset = ImVec2(screen.x - size.x, position.y); - } else if (screen.y < (position.y + size.y)) { - output_window_offset = ImVec2(position.x, screen.y - size.y); - } - - if (!try_to_fix && output_window_offset.has_value()) - output_window_offset = ImVec2(-1, -1); // Cannot -} - -bool GLGizmoEmboss::set_volume(ModelVolume *volume) -{ - if (volume == nullptr) { - if (m_volume == nullptr) - return false; - m_volume = nullptr; - // TODO: check if it is neccessary to set default text - // Idea is to set default text when create object - set_default_text(); - return false; - } - const std::optional tc_opt = volume->text_configuration; - if (!tc_opt.has_value()) return false; - const TextConfiguration &tc = *tc_opt; - const EmbossStyle &style = tc.style; + const TextConfiguration &tc = *tc_opt; + const EmbossStyle &style = tc.style; // Could exist OS without getter on face_name, // but it is able to restore font from descriptor @@ -1179,8 +939,9 @@ bool GLGizmoEmboss::set_volume(ModelVolume *volume) // search in enumerated fonts // refresh list of installed font in the OS. - init_face_names(); + init_face_names(m_face_names); m_face_names.is_init = false; + auto cmp = [](const FaceName &fn, const wxString& face_name)->bool { return fn.wx_name < face_name; }; const std::vector &faces = m_face_names.faces; auto it = std::lower_bound(faces.begin(), faces.end(), face_name, cmp); @@ -1255,7 +1016,7 @@ bool GLGizmoEmboss::set_volume(ModelVolume *volume) // The change of volume could show or hide part with setter on volume type if (m_volume == nullptr || - priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr || + get_model_volume(m_volume_id, objects) == nullptr || (m_volume->get_object()->volumes.size() == 1) != (volume->get_object()->volumes.size() == 1)){ m_should_set_minimal_windows_size = true; @@ -1271,15 +1032,29 @@ bool GLGizmoEmboss::set_volume(ModelVolume *volume) m_volume = volume; m_volume_id = volume->id(); + // Calculate current angle of up vector + assert(m_style_manager.is_active_font()); + if (m_style_manager.is_active_font()) + m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + // calculate scale for height and depth inside of scaled object instance - calculate_scale(); - return true; + calculate_scale(); +} + +void GLGizmoEmboss::reset_volume() +{ + if (m_volume == nullptr) + return; // already reseted + + m_volume = nullptr; + m_volume_id.id = 0; + + // No more need of current notification + remove_notification_not_valid_font(); } void GLGizmoEmboss::calculate_scale() { - Transform3d to_world = m_temp_transformation.has_value()? - *m_temp_transformation : - priv::world_matrix(m_parent.get_selection()); + Transform3d to_world = m_parent.get_selection().get_first_volume()->world_matrix(); auto to_world_linear = to_world.linear(); auto calc = [&to_world_linear](const Vec3d &axe, std::optional& scale)->bool { Vec3d axe_world = to_world_linear * axe; @@ -1303,35 +1078,8 @@ void GLGizmoEmboss::calculate_scale() { m_style_manager.clear_imgui_font(); } -ModelVolume *priv::get_model_volume(const GLVolume *gl_volume, const ModelObject *object) -{ - int volume_id = gl_volume->volume_idx(); - if (volume_id < 0 || static_cast(volume_id) >= object->volumes.size()) return nullptr; - return object->volumes[volume_id]; -} - -ModelVolume *priv::get_model_volume(const GLVolume *gl_volume, const ModelObjectPtrs &objects) -{ - int object_id = gl_volume->object_idx(); - if (object_id < 0 || static_cast(object_id) >= objects.size()) return nullptr; - return get_model_volume(gl_volume, objects[object_id]); -} - -ModelVolume *priv::get_selected_volume(const Selection &selection) -{ - int object_idx = selection.get_object_idx(); - // is more object selected? - if (object_idx == -1) return nullptr; - - auto volume_idxs = selection.get_volume_idxs(); - // is more volumes selected? - if (volume_idxs.size() != 1) return nullptr; - unsigned int vol_id_gl = *volume_idxs.begin(); - const GLVolume *vol_gl = selection.get_volume(vol_id_gl); - const ModelObjectPtrs &objects = selection.get_model()->objects; - return get_model_volume(vol_gl, objects); -} - +#ifdef EXECUTE_PROCESS_ON_MAIN_THREAD +namespace priv { // Run Job on main thread (blocking) - ONLY DEBUG static inline void execute_job(std::shared_ptr j) { @@ -1350,17 +1098,8 @@ static inline void execute_job(std::shared_ptr j) j->finalize(false, e_ptr); }); } - -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 +#endif bool GLGizmoEmboss::process() { @@ -1396,7 +1135,7 @@ bool GLGizmoEmboss::process() // 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()); + auto offset = calc_surface_offset(m_parent.get_selection(), m_raycast_manager); if (offset.has_value()) text_tr *= Eigen::Translation(*offset); } @@ -1405,24 +1144,29 @@ bool GLGizmoEmboss::process() // check that there is not unexpected volume type assert(is_outside || m_volume->is_negative_volume() || m_volume->is_modifier()); - UpdateSurfaceVolumeData surface_data{std::move(data), text_tr, is_outside, std::move(sources)}; + UpdateSurfaceVolumeData surface_data{std::move(data), {text_tr, is_outside, std::move(sources)}}; job = std::make_unique(std::move(surface_data)); } else { job = std::make_unique(std::move(data)); } - //* +#ifndef EXECUTE_PROCESS_ON_MAIN_THREAD auto &worker = wxGetApp().plater()->get_ui_job_worker(); queue_job(worker, std::move(job)); - /*/ // Run Job on main thread (blocking) - ONLY DEBUG - execute_job(std::move(job)); - // */ +#else + // Run Job on main thread (blocking) - ONLY DEBUG + priv::execute_job(std::move(job)); +#endif // EXECUTE_PROCESS_ON_MAIN_THREAD // notification is removed befor object is changed by job remove_notification_not_valid_font(); return true; } +namespace priv { +static bool is_text_empty(const std::string &text) { return text.empty() || text.find_first_not_of(" \n\t\r") == std::string::npos; } +} + void GLGizmoEmboss::close() { // remove volume when text is empty @@ -1430,7 +1174,8 @@ void GLGizmoEmboss::close() m_volume->text_configuration.has_value() && priv::is_text_empty(m_text)) { Plater &p = *wxGetApp().plater(); - if (is_text_object(m_volume)) { + // is the text object? + if (m_volume->is_the_only_one_part()) { // delete whole object p.remove(m_parent.get_selection().get_object_idx()); } else { @@ -1445,62 +1190,6 @@ void GLGizmoEmboss::close() mng.open_gizmo(GLGizmosManager::Emboss); } -namespace priv { - -/// -/// Apply camera direction for emboss direction -/// -/// Define view vector -/// Containe Selected Model to modify -/// True when apply change otherwise false -static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas); -} - -bool priv::apply_camera_dir(const Camera &camera, GLCanvas3D &canvas) { - const Vec3d &cam_dir = camera.get_dir_forward(); - - Selection &sel = canvas.get_selection(); - if (sel.is_empty()) return false; - - // camera direction transformed into volume coordinate system - Transform3d to_world = priv::world_matrix(sel); - Vec3d cam_dir_tr = to_world.inverse().linear() * cam_dir; - cam_dir_tr.normalize(); - - Vec3d emboss_dir(0., 0., -1.); - - // check wether cam_dir is already used - if (is_approx(cam_dir_tr, emboss_dir)) return false; - - assert(sel.get_volume_idxs().size() == 1); - GLVolume *vol = sel.get_volume(*sel.get_volume_idxs().begin()); - - Transform3d vol_rot; - Transform3d vol_tr = vol->get_volume_transformation().get_matrix(); - // check whether cam_dir is opposit to emboss dir - if (is_approx(cam_dir_tr, -emboss_dir)) { - // rotate 180 DEG by y - vol_rot = Eigen::AngleAxis(M_PI_2, Vec3d(0., 1., 0.)); - } else { - // calc params for rotation - Vec3d axe = emboss_dir.cross(cam_dir_tr); - axe.normalize(); - double angle = std::acos(emboss_dir.dot(cam_dir_tr)); - vol_rot = Eigen::AngleAxis(angle, axe); - } - - Vec3d offset = vol_tr * Vec3d::Zero(); - Vec3d offset_inv = vol_rot.inverse() * offset; - Transform3d res = vol_tr * - Eigen::Translation(-offset) * - vol_rot * - Eigen::Translation(offset_inv); - //Transform3d res = vol_tr * vol_rot; - vol->set_volume_transformation(Geometry::Transformation(res)); - priv::get_model_volume(vol, sel.get_model()->objects)->set_transformation(res); - return true; -} - void GLGizmoEmboss::draw_window() { #ifdef ALLOW_DEBUG_MODE @@ -1508,21 +1197,25 @@ void GLGizmoEmboss::draw_window() if (ImGui::Button("add svg")) choose_svg_file(); #endif // ALLOW_DEBUG_MODE - bool is_active_font = m_style_manager.is_active_font(); - if (!is_active_font) - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Warning: No font is selected. Select correct one.")); - + // Setter of indent must be befor disable !!! + ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, m_gui_cfg->indent); + ScopeGuard indent_sc([](){ ImGui::PopStyleVar(/*ImGuiStyleVar_IndentSpacing*/); }); + // Disable all except selection of font, when open text from 3mf with unknown font m_imgui->disabled_begin(m_is_unknown_font); - ScopeGuard unknown_font_sc([&]() { - m_imgui->disabled_end(); - }); + ScopeGuard unknown_font_sc([imgui = m_imgui]() { imgui->disabled_end(/*m_is_unknown_font*/); }); draw_text_input(); - m_imgui->disabled_begin(!is_active_font); - ImGui::TreePush(); - draw_style_edit(); - ImGui::TreePop(); + + ImGui::Indent(); + // When unknown font is inside .3mf only font selection is allowed + m_imgui->disabled_end(/*m_is_unknown_font*/); + draw_font_list_line(); + m_imgui->disabled_begin(m_is_unknown_font); + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + draw_height(use_inch); + draw_depth(use_inch); + ImGui::Unindent(); // close advanced style property when unknown font is selected if (m_is_unknown_font && m_is_advanced_edit_style) @@ -1547,8 +1240,6 @@ void GLGizmoEmboss::draw_window() ImGui::Separator(); draw_model_type(); } - - m_imgui->disabled_end(); // !is_active_font #ifdef SHOW_WX_FONT_DESCRIPTOR if (is_selected_style) @@ -1598,6 +1289,8 @@ void GLGizmoEmboss::draw_window() #endif // ALLOW_FLOAT_WINDOW } +#include "imgui/imgui_internal.h" // scroll bar existence + void GLGizmoEmboss::draw_text_input() { auto create_range_text_prep = [&mng = m_style_manager, &text = m_text, &exist_unknown = m_text_contain_unknown_glyph]() { @@ -1612,7 +1305,9 @@ void GLGizmoEmboss::draw_text_input() ImFont *imgui_font = m_style_manager.get_imgui_font(); if (imgui_font == nullptr) { // try create new imgui font - m_style_manager.create_imgui_font(create_range_text_prep(), scale); + double screen_scale = wxDisplay(wxGetApp().plater()).GetScaleFactor(); + double imgui_scale = scale * screen_scale; + m_style_manager.create_imgui_font(create_range_text_prep(), imgui_scale); imgui_font = m_style_manager.get_imgui_font(); } bool exist_font = @@ -1625,75 +1320,69 @@ void GLGizmoEmboss::draw_text_input() if (exist_font) ImGui::PushFont(imgui_font); // show warning about incorrectness view of font - std::string warning; - std::string tool_tip; + std::string warning_tool_tip; if (!exist_font) { - warning = _u8L("Can't write text by selected font."); - tool_tip = _u8L("Try to choose another font."); + warning_tool_tip = _u8L("Can't write text by selected font.Try to choose another font."); } else { - std::string who; - auto append_warning = [&who, &tool_tip](std::string w, std::string t) { - if (!w.empty()) { - if (!who.empty()) who += " & "; - who += w; - } - if (!t.empty()) { - if (!tool_tip.empty()) tool_tip += "\n"; - tool_tip += t; - } + auto append_warning = [&warning_tool_tip](std::string t) { + if (!warning_tool_tip.empty()) + warning_tool_tip += "\n"; + warning_tool_tip += t; }; - if (priv::is_text_empty(m_text)) append_warning(_u8L("Empty"), _u8L("Embossed text can NOT contain only white spaces.")); + if (priv::is_text_empty(m_text)) + append_warning(_u8L("Embossed text can NOT contain only white spaces.")); if (m_text_contain_unknown_glyph) - append_warning(_u8L("Bad symbol"), _u8L("Text contain character glyph (represented by '?') unknown by font.")); + append_warning(_u8L("Text contain character glyph (represented by '?') unknown by font.")); const FontProp &prop = m_style_manager.get_font_prop(); - if (prop.skew.has_value()) append_warning(_u8L("Skew"), _u8L("Unsupported visualization of font skew for text input.")); - if (prop.boldness.has_value()) append_warning(_u8L("Boldness"), _u8L("Unsupported visualization of font boldness for text input.")); + if (prop.skew.has_value()) append_warning(_u8L("Text input do not show font skew.")); + if (prop.boldness.has_value()) append_warning(_u8L("Text input do not show font boldness.")); if (prop.line_gap.has_value()) - append_warning(_u8L("Line gap"), _u8L("Unsupported visualization of gap between lines inside text input.")); + append_warning(_u8L("Text input do not show gap between lines.")); auto &ff = m_style_manager.get_font_file_with_cache(); float imgui_size = StyleManager::get_imgui_font_size(prop, *ff.font_file, scale); if (imgui_size > StyleManager::max_imgui_font_size) - append_warning(_u8L("Too tall"), _u8L("Diminished font height inside text input.")); + append_warning(_u8L("Too tall, diminished font height inside text input.")); if (imgui_size < StyleManager::min_imgui_font_size) - append_warning(_u8L("Too small"), _u8L("Enlarged font height inside text input.")); - if (!who.empty()) warning = GUI::format(_L("%1% is NOT shown."), who); + append_warning(_u8L("Too small, enlarged font height inside text input.")); } - - // add border around input when warning appears - ScopeGuard input_border_sg; - if (!warning.empty()) { - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImGuiWrapper::COL_ORANGE_LIGHT); - input_border_sg.closure = []() { ImGui::PopStyleColor(); ImGui::PopStyleVar(); }; - } - + // flag for extend font ranges if neccessary // ranges can't be extend during font is activ(pushed) std::string range_text; float window_height = ImGui::GetWindowHeight(); float minimal_height = get_minimal_window_size().y; float extra_height = window_height - minimal_height; - ImVec2 text_size(m_gui_cfg->text_size.x, - m_gui_cfg->text_size.y + extra_height); + ImVec2 input_size(m_gui_cfg->text_size.x, m_gui_cfg->text_size.y + extra_height); const ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_AutoSelectAll; - if (ImGui::InputTextMultiline("##Text", &m_text, text_size, flags)) { + if (ImGui::InputTextMultiline("##Text", &m_text, input_size, flags)) { process(); range_text = create_range_text_prep(); } if (exist_font) ImGui::PopFont(); - if (!warning.empty()) { - if (ImGui::IsItemHovered() && !tool_tip.empty()) - ImGui::SetTooltip("%s", tool_tip.c_str()); + // warning tooltip has to be with default font + if (!warning_tool_tip.empty()) { + // Multiline input has hidden window for scrolling + ImGuiWindow *input = ImGui::GetCurrentWindow()->DC.ChildWindows.front(); + + const ImGuiStyle &style = ImGui::GetStyle(); + float scrollbar_width = (input->ScrollbarY) ? style.ScrollbarSize : 0.f; + float scrollbar_height = (input->ScrollbarX) ? style.ScrollbarSize : 0.f; + + bool hovered = ImGui::IsItemHovered(); + if (hovered) + ImGui::SetTooltip("%s", warning_tool_tip.c_str()); + ImVec2 cursor = ImGui::GetCursorPos(); float width = ImGui::GetContentRegionAvailWidth(); - ImVec2 size = ImGui::CalcTextSize(warning.c_str()); - ImVec2 padding = ImGui::GetStyle().FramePadding; - ImGui::SetCursorPos(ImVec2(width - size.x + padding.x, - cursor.y - size.y - padding.y)); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, warning); + const ImVec2& padding = style.FramePadding; + ImVec2 icon_pos(width - m_gui_cfg->icon_width - scrollbar_width + padding.x, + cursor.y - m_gui_cfg->icon_width - scrollbar_height - 2*padding.y); + + ImGui::SetCursorPos(icon_pos); + draw(get_icon(m_icons, IconType::exclamation, IconState::hovered)); ImGui::SetCursorPos(cursor); } @@ -1702,10 +1391,8 @@ void GLGizmoEmboss::draw_text_input() // IMPROVE: only extend not clear // Extend font ranges if (!range_text.empty() && - !m_imgui->contain_all_glyphs(imgui_font, range_text) ) { - m_style_manager.clear_imgui_font(); - m_style_manager.create_imgui_font(range_text, scale); - } + !m_imgui->contain_all_glyphs(imgui_font, range_text) ) + m_style_manager.clear_imgui_font(); } #include @@ -1813,31 +1500,34 @@ bool GLGizmoEmboss::load(Facenames &facenames) { return true; } -void GLGizmoEmboss::init_face_names() { +void GLGizmoEmboss::init_truncated_names(Facenames &face_names, float max_width) +{ + for (FaceName &face : face_names.faces) { + std::string name_str(face.wx_name.ToUTF8().data()); + face.name_truncated = ImGuiWrapper::trunc(name_str, max_width); + } + face_names.has_truncated_names = true; +} + +void GLGizmoEmboss::init_face_names(Facenames &face_names) +{ Timer t("enumerate_fonts"); - if (m_face_names.is_init) return; - m_face_names.is_init = true; + if (face_names.is_init) return; + face_names.is_init = true; // to reload fonts from system, when install new one wxFontEnumerator::InvalidateCache(); - auto create_truncated_names = [&facenames = m_face_names, &width = m_gui_cfg->face_name_max_width]() { - for (FaceName &face : facenames.faces) { - std::string name_str(face.wx_name.ToUTF8().data()); - face.name_truncated = ImGuiWrapper::trunc(name_str, width); - } - }; - // try load cache // Only not OS enumerated face has hash value 0 - if (m_face_names.hash == 0) { - load(m_face_names); - create_truncated_names(); + if (face_names.hash == 0) { + load(face_names); + face_names.has_truncated_names = false; } using namespace std::chrono; steady_clock::time_point enumerate_start = steady_clock::now(); - ScopeGuard sg([&enumerate_start, &face_names = m_face_names]() { + ScopeGuard sg([&enumerate_start, &face_names = face_names]() { steady_clock::time_point enumerate_end = steady_clock::now(); long long enumerate_duration = duration_cast(enumerate_end - enumerate_start).count(); BOOST_LOG_TRIVIAL(info) << "OS enumerate " << face_names.faces.size() << " fonts " @@ -1845,25 +1535,25 @@ void GLGizmoEmboss::init_face_names() { << "= " << face_names.faces.size() + face_names.bad.size() << " fonts) " << "in " << enumerate_duration << " ms\n" << concat(face_names.bad); }); - wxArrayString facenames = wxFontEnumerator::GetFacenames(m_face_names.encoding); + wxArrayString facenames = wxFontEnumerator::GetFacenames(face_names.encoding); size_t hash = boost::hash_range(facenames.begin(), facenames.end()); // Zero value is used as uninitialized hash if (hash == 0) hash = 1; // check if it is same as last time - if (m_face_names.hash == hash) { + if (face_names.hash == hash) { // no new installed font BOOST_LOG_TRIVIAL(info) << "Same FontNames hash, cache is used. " << "For clear cache delete file: " << get_fontlist_cache_path().string(); return; } - BOOST_LOG_TRIVIAL(info) << ((m_face_names.hash == 0) ? + BOOST_LOG_TRIVIAL(info) << ((face_names.hash == 0) ? "FontName list is generate from scratch." : "Hash are different. Only previous bad fonts are used and set again as bad"); - m_face_names.hash = hash; + face_names.hash = hash; // validation lambda - auto is_valid_font = [encoding = m_face_names.encoding, bad = m_face_names.bad /*copy*/](const wxString &name) { + auto is_valid_font = [encoding = face_names.encoding, bad = face_names.bad /*copy*/](const wxString &name) { if (name.empty()) return false; // vertical font start with @, we will filter it out @@ -1890,20 +1580,20 @@ void GLGizmoEmboss::init_face_names() { return true; }; - m_face_names.faces.clear(); - m_face_names.bad.clear(); - m_face_names.faces.reserve(facenames.size()); + face_names.faces.clear(); + face_names.bad.clear(); + face_names.faces.reserve(facenames.size()); std::sort(facenames.begin(), facenames.end()); for (const wxString &name : facenames) { if (is_valid_font(name)) { - m_face_names.faces.push_back({name}); + face_names.faces.push_back({name}); }else{ - m_face_names.bad.push_back(name); + face_names.bad.push_back(name); } } - assert(std::is_sorted(m_face_names.bad.begin(), m_face_names.bad.end())); - create_truncated_names(); - store(m_face_names); + assert(std::is_sorted(face_names.bad.begin(), face_names.bad.end())); + face_names.has_truncated_names = false; + store(face_names); } // create texture for visualization font face @@ -2030,24 +1720,79 @@ bool GLGizmoEmboss::select_facename(const wxString &facename) if (!wxFontEnumerator::IsValidFacename(facename)) return false; // Select font const wxFontEncoding &encoding = m_face_names.encoding; - wxFont wx_font(wxFontInfo().FaceName(facename).Encoding(encoding)); + wxFont wx_font(wxFontInfo().FaceName(facename).Encoding(encoding)); if (!wx_font.IsOk()) return false; +#ifdef USE_PIXEL_SIZE_IN_WX_FONT // wx font could change source file by size of font int point_size = static_cast(m_style_manager.get_font_prop().size_in_mm); wx_font.SetPointSize(point_size); +#endif // USE_PIXEL_SIZE_IN_WX_FONT if (!m_style_manager.set_wx_font(wx_font)) return false; process(); return true; } +void GLGizmoEmboss::draw_font_list_line() +{ + bool exist_stored_style = m_style_manager.exist_stored_style(); + bool exist_change_in_font = m_style_manager.is_font_changed(); + const std::string& font_text = m_gui_cfg->translations.font; + if (exist_change_in_font || !exist_stored_style) + ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, font_text); + else + ImGuiWrapper::text(font_text); + + ImGui::SameLine(m_gui_cfg->input_offset); + + draw_font_list(); + + bool exist_change = false; + if (!m_is_unknown_font) { + ImGui::SameLine(); + if (draw_italic_button()) + exist_change = true; + ImGui::SameLine(); + if (draw_bold_button()) + exist_change = true; + } else { + // when exist unknown font add confirmation button + ImGui::SameLine(); + // Apply for actual selected font + if (ImGui::Button(_u8L("Apply").c_str())) + exist_change = true; + } + + EmbossStyle &style = m_style_manager.get_style(); + if (exist_change_in_font) { + ImGui::SameLine(ImGui::GetStyle().FramePadding.x); + if (draw_button(m_icons, IconType::undo)) { + const EmbossStyle *stored_style = m_style_manager.get_stored_style(); + + style.path = stored_style->path; + style.prop.boldness = stored_style->prop.boldness; + style.prop.skew = stored_style->prop.skew; + + wxFont new_wx_font = WxFontUtils::load_wxFont(style.path); + if (new_wx_font.IsOk() && m_style_manager.set_wx_font(new_wx_font)) + exist_change = true; + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", _u8L("Revert font changes.").c_str()); + } + + if (exist_change) { + m_style_manager.clear_glyphs_cache(); + process(); + } +} + void GLGizmoEmboss::draw_font_list() { // Set partial wxString actual_face_name; if (m_style_manager.is_active_font()) { - const std::optional &wx_font_opt = m_style_manager.get_wx_font(); - if (wx_font_opt.has_value()) - actual_face_name = wx_font_opt->GetFaceName(); + const wxFont &wx_font = m_style_manager.get_wx_font(); + if (wx_font.IsOk()) + actual_face_name = wx_font.GetFaceName(); } // name of actual selected font const char * selected = (!actual_face_name.empty()) ? @@ -2057,40 +1802,42 @@ void GLGizmoEmboss::draw_font_list() // When deletation of font appear this variable is set std::optional del_index; - // When is unknown font is inside .3mf only font selection is allowed - // Stop Imgui disable + Guard again start disabling - ScopeGuard unknown_font_sc; - if (m_is_unknown_font) { - m_imgui->disabled_end(); - unknown_font_sc.closure = [&]() { - m_imgui->disabled_begin(true); - }; - } - // Code const char *popup_id = "##font_list_popup"; const char *input_id = "##font_list_input"; ImGui::SetNextItemWidth(m_gui_cfg->input_width); - ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_CharsUppercase; // change color of hint to normal text bool is_popup_open = ImGui::IsPopupOpen(popup_id); - if (!is_popup_open) + if (!is_popup_open) { ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyleColorVec4(ImGuiCol_Text)); - if (ImGui::InputTextWithHint(input_id, selected, &m_face_names.search, input_flags)) { + + // Fix clearance of search input, + // Sometime happens that search text not disapear after font select + m_face_names.search.clear(); + } + + if (ImGui::InputTextWithHint(input_id, selected, &m_face_names.search)) { // update filtration result m_face_names.hide = std::vector(m_face_names.faces.size(), {false}); + + // search to uppercase + std::string search = m_face_names.search; // copy + std::transform(search.begin(), search.end(), search.begin(), ::toupper); + for (FaceName &face : m_face_names.faces) { - size_t index = &face - &m_face_names.faces.front(); + size_t index = &face - &m_face_names.faces.front(); + + // font name to uppercase std::string name(face.wx_name.ToUTF8().data()); std::transform(name.begin(), name.end(), name.begin(), ::toupper); // It should use C++ 20 feature https://en.cppreference.com/w/cpp/string/basic_string/starts_with - bool start_with = boost::starts_with(name, m_face_names.search); + bool start_with = boost::starts_with(name, search); m_face_names.hide[index] = !start_with; } } - if (!is_popup_open) + if (!is_popup_open) ImGui::PopStyleColor(); // revert changes for hint color const bool is_input_text_active = ImGui::IsItemActive(); @@ -2107,9 +1854,13 @@ void GLGizmoEmboss::draw_font_list() { bool set_selection_focus = false; if (!m_face_names.is_init) { - init_face_names(); + init_face_names(m_face_names); set_selection_focus = true; } + + if (!m_face_names.has_truncated_names) + init_truncated_names(m_face_names, m_gui_cfg->face_name_max_width); + if (m_face_names.texture_id == 0) init_font_name_texture(); @@ -2152,7 +1903,6 @@ void GLGizmoEmboss::draw_font_list() // Just one after close combo box // free texture and set id to zero m_face_names.is_init = false; - m_face_names.search.clear(); m_face_names.hide.clear(); // cancel all process for generation of texture for (FaceName &face : m_face_names.faces) @@ -2160,6 +1910,10 @@ void GLGizmoEmboss::draw_font_list() face.cancel->store(true); glsafe(::glDeleteTextures(1, &m_face_names.texture_id)); m_face_names.texture_id = 0; + + // Remove value from search input + ImGuiWrapper::left_inputs(); + m_face_names.search.clear(); } // delete unloadable face name when try to use @@ -2174,13 +1928,6 @@ void GLGizmoEmboss::draw_font_list() store(m_face_names); } - if (m_is_unknown_font) { - ImGui::SameLine(); - // Apply for actual selected font - if (ImGui::Button(_u8L("Apply").c_str())) - process(); - } - #ifdef ALLOW_ADD_FONT_BY_FILE ImGui::SameLine(); // select font file by file browser @@ -2206,7 +1953,7 @@ void GLGizmoEmboss::draw_font_list() void GLGizmoEmboss::draw_model_type() { - bool is_last_solid_part = is_text_object(m_volume); + bool is_last_solid_part = m_volume->is_the_only_one_part(); std::string title = _u8L("Text is to object"); if (is_last_solid_part) { ImVec4 color{.5f, .5f, .5f, 1.f}; @@ -2336,7 +2083,7 @@ void GLGizmoEmboss::draw_style_rename_button() bool can_rename = m_style_manager.exist_stored_style(); std::string title = _u8L("Rename style"); const char * popup_id = title.c_str(); - if (draw_button(IconType::rename, !can_rename)) { + if (priv::draw_button(m_icons, IconType::rename, !can_rename)) { assert(m_style_manager.get_stored_style()); ImGui::OpenPopup(popup_id); } @@ -2353,7 +2100,7 @@ void GLGizmoEmboss::draw_style_rename_button() void GLGizmoEmboss::draw_style_save_button(bool is_modified) { - if (draw_button(IconType::save, !is_modified)) { + if (draw_button(m_icons, IconType::save, !is_modified)) { // save styles to app config m_style_manager.store_styles_to_app_config(); }else if (ImGui::IsItemHovered()) { @@ -2423,7 +2170,7 @@ void GLGizmoEmboss::draw_style_add_button() const char *popup_id = title.c_str(); // save as new style ImGui::SameLine(); - if (draw_button(IconType::add, !can_add)) { + if (draw_button(m_icons, IconType::add, !can_add)) { if (!m_style_manager.exist_stored_style()) { m_style_manager.store_styles_to_app_config(wxGetApp().app_config); } else { @@ -2454,7 +2201,7 @@ void GLGizmoEmboss::draw_delete_style_button() { std::string title = _u8L("Remove style"); const char * popup_id = title.c_str(); static size_t next_style_index = std::numeric_limits::max(); - if (draw_button(IconType::erase, !can_delete)) { + if (draw_button(m_icons, IconType::erase, !can_delete)) { while (true) { // NOTE: can't use previous loaded activ index -> erase could change index size_t active_index = m_style_manager.get_style_index(); @@ -2664,20 +2411,19 @@ void GLGizmoEmboss::draw_style_list() { bool GLGizmoEmboss::draw_italic_button() { - const std::optional &wx_font_opt = m_style_manager.get_wx_font(); + const wxFont &wx_font = m_style_manager.get_wx_font(); const auto& ff = m_style_manager.get_font_file_with_cache(); - if (!wx_font_opt.has_value() || !ff.has_value()) { - draw_icon(IconType::italic, IconState::disabled); + if (!wx_font.IsOk() || !ff.has_value()) { + draw(get_icon(m_icons, IconType::italic, IconState::disabled)); return false; } - const wxFont& wx_font = *wx_font_opt; std::optional &skew = m_style_manager.get_font_prop().skew; bool is_font_italic = skew.has_value() || WxFontUtils::is_italic(wx_font); if (is_font_italic) { // unset italic - if (draw_clickable(IconType::italic, IconState::hovered, - IconType::unitalic, IconState::hovered)) { + if (clickable(get_icon(m_icons, IconType::italic, IconState::hovered), + get_icon(m_icons, IconType::unitalic, IconState::hovered))) { skew.reset(); if (wx_font.GetStyle() != wxFontStyle::wxFONTSTYLE_NORMAL) { wxFont new_wx_font = wx_font; // copy @@ -2691,7 +2437,7 @@ bool GLGizmoEmboss::draw_italic_button() ImGui::SetTooltip("%s", _u8L("Unset italic").c_str()); } else { // set italic - if (draw_button(IconType::italic)) { + if (draw_button(m_icons, IconType::italic)) { wxFont new_wx_font = wx_font; // copy auto new_ff = WxFontUtils::set_italic(new_wx_font, *ff.font_file); if (new_ff != nullptr) { @@ -2711,20 +2457,19 @@ bool GLGizmoEmboss::draw_italic_button() } bool GLGizmoEmboss::draw_bold_button() { - const std::optional &wx_font_opt = m_style_manager.get_wx_font(); + const wxFont &wx_font = m_style_manager.get_wx_font(); const auto& ff = m_style_manager.get_font_file_with_cache(); - if (!wx_font_opt.has_value() || !ff.has_value()) { - draw_icon(IconType::bold, IconState::disabled); + if (!wx_font.IsOk() || !ff.has_value()) { + draw(get_icon(m_icons, IconType::bold, IconState::disabled)); return false; } - const wxFont &wx_font = *wx_font_opt; std::optional &boldness = m_style_manager.get_font_prop().boldness; bool is_font_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font); if (is_font_bold) { // unset bold - if (draw_clickable(IconType::bold, IconState::hovered, - IconType::unbold, IconState::hovered)) { + if (clickable(get_icon(m_icons, IconType::bold, IconState::hovered), + get_icon(m_icons, IconType::unbold, IconState::hovered))) { boldness.reset(); if (wx_font.GetWeight() != wxFontWeight::wxFONTWEIGHT_NORMAL) { wxFont new_wx_font = wx_font; // copy @@ -2738,7 +2483,7 @@ bool GLGizmoEmboss::draw_bold_button() { ImGui::SetTooltip("%s", _u8L("Unset bold").c_str()); } else { // set bold - if (draw_button(IconType::bold)) { + if (draw_button(m_icons, IconType::bold)) { wxFont new_wx_font = wx_font; // copy auto new_ff = WxFontUtils::set_bold(new_wx_font, *ff.font_file); if (new_ff != nullptr) { @@ -2791,7 +2536,7 @@ bool GLGizmoEmboss::revertible(const std::string &name, // render revert changes button if (changed) { ImGui::SameLine(undo_offset); - if (draw_button(IconType::undo)) { + if (draw_button(m_icons, IconType::undo)) { value = *default_value; return true; } else if (ImGui::IsItemHovered()) @@ -2875,122 +2620,31 @@ bool GLGizmoEmboss::rev_checkbox(const std::string &name, undo_offset, draw_offseted_input); } -bool is_font_changed( - const wxFont &wx_font, const wxFont &wx_font_stored, - const FontProp &prop, const FontProp &prop_stored) -{ - // Exist change in face name? - if(wx_font_stored.GetFaceName() != wx_font.GetFaceName()) return true; +bool GLGizmoEmboss::set_height() { + float &value = m_style_manager.get_style().prop.size_in_mm; - const std::optional &skew = prop.skew; - bool is_italic = skew.has_value() || WxFontUtils::is_italic(wx_font); - const std::optional &skew_stored = prop_stored.skew; - bool is_stored_italic = skew_stored.has_value() || WxFontUtils::is_italic(wx_font_stored); - // is italic changed - if (is_italic != is_stored_italic) - return true; + // size can't be zero or negative + priv::Limits::apply(value, priv::limits.size_in_mm); - const std::optional &boldness = prop.boldness; - bool is_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font); - const std::optional &boldness_stored = prop_stored.boldness; - bool is_stored_bold = boldness_stored.has_value() || WxFontUtils::is_bold(wx_font_stored); - // is bold changed - return is_bold != is_stored_bold; -} - -bool is_font_changed(const StyleManager &mng) { - const std::optional &wx_font_opt = mng.get_wx_font(); - if (!wx_font_opt.has_value()) + if (m_volume == nullptr || !m_volume->text_configuration.has_value()) { + assert(false); return false; - if (!mng.exist_stored_style()) + } + + // only different value need process + if (is_approx(value, m_volume->text_configuration->style.prop.size_in_mm)) return false; - const EmbossStyle *stored_style = mng.get_stored_style(); - if (stored_style == nullptr) - return false; - - const std::optional &wx_font_stored_opt = mng.get_stored_wx_font(); - if (!wx_font_stored_opt.has_value()) - return false; - - return is_font_changed(*wx_font_opt, *wx_font_stored_opt, mng.get_style().prop, stored_style->prop); -} - -void GLGizmoEmboss::draw_style_edit() { - const std::optional &wx_font_opt = m_style_manager.get_wx_font(); - assert(wx_font_opt.has_value()); - if (!wx_font_opt.has_value()) { - ImGui::TextColored(ImGuiWrapper::COL_ORANGE_DARK, "%s", _u8L("WxFont is not loaded properly.").c_str()); - return; + +#ifdef USE_PIXEL_SIZE_IN_WX_FONT + // store font size into path serialization + const wxFont &wx_font = m_style_manager.get_wx_font(); + if (wx_font.IsOk()) { + wxFont wx_font_new = wx_font; // copy + wx_font_new.SetPointSize(static_cast(value)); + m_style_manager.set_wx_font(wx_font_new); } - bool exist_stored_style = m_style_manager.exist_stored_style(); - bool exist_change_in_font = is_font_changed(m_style_manager); - const GuiCfg::Translations &tr = m_gui_cfg->translations; - if (exist_change_in_font || !exist_stored_style) - ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr.font); - else - ImGuiWrapper::text(tr.font); - ImGui::SameLine(m_gui_cfg->input_offset); - draw_font_list(); - bool exist_change = false; - if (!m_is_unknown_font) { - ImGui::SameLine(); - if (draw_italic_button()) - exist_change = true; - ImGui::SameLine(); - if (draw_bold_button()) - exist_change = true; - } - EmbossStyle &style = m_style_manager.get_style(); - if (exist_change_in_font) { - ImGui::SameLine(ImGui::GetStyle().FramePadding.x); - if (draw_button(IconType::undo)) { - const EmbossStyle *stored_style = m_style_manager.get_stored_style(); - style.path = stored_style->path; - style.prop.boldness = stored_style->prop.boldness; - style.prop.skew = stored_style->prop.skew; - - wxFont new_wx_font = WxFontUtils::load_wxFont(style.path); - if (new_wx_font.IsOk() && - m_style_manager.set_wx_font(new_wx_font)) - exist_change = true; - } else if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", _u8L("Revert font changes.").c_str()); - } - - if (exist_change) { - m_style_manager.clear_glyphs_cache(); - process(); - } - - bool use_inch = wxGetApp().app_config->get_bool("use_inches"); - draw_height(use_inch); - draw_depth(use_inch); - -#ifdef SHOW_WX_WEIGHT_INPUT - if (wx_font.has_value()) { - ImGui::Text("%s", "weight"); - ImGui::SameLine(m_gui_cfg->input_offset); - ImGui::SetNextItemWidth(m_gui_cfg->input_width); - int weight = wx_font->GetNumericWeight(); - int min_weight = 1, max_weight = 1000; - if (ImGui::SliderInt("##weight", &weight, min_weight, max_weight)) { - wx_font->SetNumericWeight(weight); - m_style_manager.wx_font_changed(); - process(); - } - - wxFont f = wx_font->Bold(); - bool disable = f == *wx_font; - ImGui::SameLine(); - if (draw_button(IconType::bold, disable)) { - *wx_font = f; - m_style_manager.wx_font_changed(); - process(); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", _u8L("wx Make bold").c_str()); - } -#endif // SHOW_WX_WEIGHT_INPUT +#endif + return true; } void GLGizmoEmboss::draw_height(bool use_inch) @@ -3000,25 +2654,21 @@ void GLGizmoEmboss::draw_height(bool use_inch) const float *stored = ((stored_style)? &stored_style->prop.size_in_mm : nullptr); const char *size_format = ((use_inch) ? "%.2f in" : "%.1f mm"); const std::string revert_text_size = _u8L("Revert text size."); - const std::string& name = m_gui_cfg->translations.size; - if (rev_input_mm(name, value, stored, revert_text_size, 0.1f, 1.f, size_format, use_inch, m_scale_height)) { - // size can't be zero or negative - priv::Limits::apply(value, priv::limits.size_in_mm); - // only different value need process - if (!is_approx(value, m_volume->text_configuration->style.prop.size_in_mm)) { - // store font size into path - EmbossStyle &style = m_style_manager.get_style(); - if (style.type == WxFontUtils::get_actual_type()) { - const std::optional &wx_font_opt = m_style_manager.get_wx_font(); - if (wx_font_opt.has_value()) { - wxFont wx_font = *wx_font_opt; - wx_font.SetPointSize(static_cast(value)); - m_style_manager.set_wx_font(wx_font); - } - } + const std::string& name = m_gui_cfg->translations.height; + if (rev_input_mm(name, value, stored, revert_text_size, 0.1f, 1.f, size_format, use_inch, m_scale_height)) + if (set_height()) process(); - } - } +} + +bool GLGizmoEmboss::set_depth() +{ + float &value = m_style_manager.get_style().prop.emboss; + + // size can't be zero or negative + priv::Limits::apply(value, priv::limits.emboss); + + // only different value need process + return !is_approx(value, m_volume->text_configuration->style.prop.emboss); } void GLGizmoEmboss::draw_depth(bool use_inch) @@ -3029,14 +2679,11 @@ void GLGizmoEmboss::draw_depth(bool use_inch) const std::string revert_emboss_depth = _u8L("Revert embossed depth."); const char *size_format = ((use_inch) ? "%.3f in" : "%.2f mm"); const std::string name = m_gui_cfg->translations.depth; - if (rev_input_mm(name, value, stored, revert_emboss_depth, 0.1f, 1.f, size_format, use_inch, m_scale_depth)) { - // size can't be zero or negative - priv::Limits::apply(value, priv::limits.emboss); - process(); - } + if (rev_input_mm(name, value, stored, revert_emboss_depth, 0.1f, 1.f, size_format, use_inch, m_scale_depth)) + if (set_depth()) + process(); } - bool GLGizmoEmboss::rev_slider(const std::string &name, std::optional& value, const std::optional *default_value, @@ -3136,41 +2783,6 @@ void GLGizmoEmboss::do_rotate(float relative_z_angle) m_parent.do_rotate(snapshot_name); } -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); - - //const Selection &selection = m_parent.get_selection(); - const GLVolume *gl_volume = priv::get_gl_volume(selection); - Transform3d to_world = priv::world_matrix(gl_volume, selection.get_model()); - Vec3d point = to_world * Vec3d::Zero(); - Vec3d direction = to_world.linear() * (-Vec3d::UnitZ()); - - // ray in direction of text projection(from volume zero to z-dir) - std::optional hit_opt = raycast_manager.unproject(point, direction, &cond); - - // 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 {}; - - // 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; - return offset_volume; -} - void GLGizmoEmboss::draw_advanced() { const auto &ff = m_style_manager.get_font_file_with_cache(); @@ -3280,7 +2892,7 @@ void GLGizmoEmboss::draw_advanced() // input italic auto def_skew = stored_style ? &stored_style->prop.skew : nullptr; - if (rev_slider(tr.italic, font_prop.skew, def_skew, _u8L("Undo letter's skew"), + if (rev_slider(tr.skew_ration, font_prop.skew, def_skew, _u8L("Undo letter's skew"), priv::limits.skew.gui.min, priv::limits.skew.gui.max, "%.2f", _L("Italic strength ratio"))){ if (!priv::Limits::apply(font_prop.skew, priv::limits.skew.values) || !m_volume->text_configuration->style.prop.skew.has_value() || @@ -3291,7 +2903,7 @@ void GLGizmoEmboss::draw_advanced() // input surface distance bool allowe_surface_distance = !m_volume->text_configuration->style.prop.use_surface && - !is_text_object(m_volume); + !m_volume->is_the_only_one_part(); std::optional &distance = font_prop.distance; float prev_distance = distance.has_value() ? *distance : .0f, min_distance = -2 * font_prop.emboss, @@ -3314,7 +2926,7 @@ void GLGizmoEmboss::draw_advanced() } min_distance *= ObjectManipulation::mm_to_in; max_distance *= ObjectManipulation::mm_to_in; - if (rev_slider(tr.surface_distance, distance_inch, def_distance, undo_move_tooltip, min_distance, max_distance, "%.3f in", move_tooltip)) { + if (rev_slider(tr.from_surface, distance_inch, def_distance, undo_move_tooltip, min_distance, max_distance, "%.3f in", move_tooltip)) { if (distance_inch.has_value()) { font_prop.distance = *distance_inch * ObjectManipulation::in_to_mm; } else { @@ -3323,7 +2935,7 @@ void GLGizmoEmboss::draw_advanced() is_moved = true; } } else { - if (rev_slider(tr.surface_distance, distance, def_distance, undo_move_tooltip, + if (rev_slider(tr.from_surface, distance, def_distance, undo_move_tooltip, min_distance, max_distance, "%.2f mm", move_tooltip)) is_moved = true; } @@ -3336,33 +2948,50 @@ void GLGizmoEmboss::draw_advanced() // slider for Clock-wise angle in degress // stored angle is optional CCW and in radians - std::optional &angle = font_prop.angle; - float prev_angle = angle.has_value() ? *angle : .0f; // Convert stored value to degress // minus create clock-wise roation from CCW - float angle_deg = angle.has_value() ? - static_cast(-(*angle) * 180 / M_PI) : .0f; + const std::optional &angle_opt = m_style_manager.get_font_prop().angle; + float angle = angle_opt.has_value() ? *angle_opt: 0.f; + float angle_deg = static_cast(-angle * 180 / M_PI); float def_angle_deg_val = (!stored_style || !stored_style->prop.angle.has_value()) ? 0.f : (*stored_style->prop.angle * -180 / M_PI); float* def_angle_deg = stored_style ? &def_angle_deg_val : nullptr; - if (rev_slider(tr.angle, angle_deg, def_angle_deg, _u8L("Undo rotation"), + if (rev_slider(tr.rotation, angle_deg, def_angle_deg, _u8L("Undo rotation"), priv::limits.angle.min, priv::limits.angle.max, u8"%.2f °", _L("Rotate text Clock-wise."))) { // convert back to radians and CCW - angle = -angle_deg * M_PI / 180.0; - priv::to_range_pi_pi(*angle); - if (is_approx(*angle, 0.f)) - angle.reset(); + float angle_rad = static_cast(-angle_deg * M_PI / 180.0); + priv::to_range_pi_pi(angle_rad); + + + float diff_angle = angle_rad - angle; + do_rotate(diff_angle); + + // calc angle after rotation + const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection()); + assert(gl_volume != nullptr); + assert(m_style_manager.is_active_font()); + if (m_style_manager.is_active_font() && gl_volume != nullptr) + m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); - m_volume->text_configuration->style.prop.angle = angle; - float act_angle = angle.has_value() ? *angle : .0f; - do_rotate(act_angle - prev_angle); // recalculate for surface cut - if (font_prop.use_surface) process(); + if (font_prop.use_surface) + process(); } + ImGui::Text("%s", tr.keep_up.c_str()); + ImGui::SameLine(m_gui_cfg->advanced_input_offset); + if (ImGui::Checkbox("##keep_up", &m_keep_up)) { + if (m_keep_up) { + // copy angle to volume + m_volume->text_configuration->style.prop.angle = font_prop.angle; + } + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", _u8L("Keep text orientation during surface dragging.\nNot stable between horizontal and vertical alignment.").c_str()); + // when more collection add selector if (ff.font_file->infos.size() > 1) { ImGui::Text("%s", tr.collection.c_str()); @@ -3393,7 +3022,7 @@ void GLGizmoEmboss::draw_advanced() } if (ImGui::Button(_u8L("Set text to face camera").c_str())) { - assert(priv::get_selected_volume(m_parent.get_selection()) == m_volume); + assert(get_selected_volume(m_parent.get_selection()) == m_volume); const Camera &cam = wxGetApp().plater()->get_camera(); bool use_surface = m_style_manager.get_style().prop.use_surface; if (priv::apply_camera_dir(cam, m_parent) && use_surface) @@ -3401,7 +3030,6 @@ void GLGizmoEmboss::draw_advanced() } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Use camera direction for text orientation").c_str()); } - #ifdef ALLOW_DEBUG_MODE ImGui::Text("family = %s", (font_prop.family.has_value() ? font_prop.family->c_str() : @@ -3503,6 +3131,19 @@ bool GLGizmoEmboss::choose_font_by_wxdialog() } #endif // ALLOW_ADD_FONT_BY_OS_SELECTOR +#if defined ALLOW_ADD_FONT_BY_FILE or defined ALLOW_DEBUG_MODE +namespace priv { +static std::string get_file_name(const std::string &file_path) +{ + size_t pos_last_delimiter = file_path.find_last_of("/\\"); + size_t pos_point = file_path.find_last_of('.'); + size_t offset = pos_last_delimiter + 1; + size_t count = pos_point - pos_last_delimiter - 1; + return file_path.substr(offset, count); +} +} // namespace priv +#endif // ALLOW_ADD_FONT_BY_FILE || ALLOW_DEBUG_MODE + #ifdef ALLOW_ADD_FONT_BY_FILE bool GLGizmoEmboss::choose_true_type_file() { @@ -3518,7 +3159,7 @@ bool GLGizmoEmboss::choose_true_type_file() // use first valid font for (auto &input_file : input_files) { std::string path = std::string(input_file.c_str()); - std::string name = get_file_name(path); + std::string name = priv::get_file_name(path); //make_unique_name(name, m_font_list); const FontProp& prop = m_style_manager.get_font_prop(); EmbossStyle style{ name, path, EmbossStyle::Type::file_path, prop }; @@ -3531,6 +3172,7 @@ bool GLGizmoEmboss::choose_true_type_file() } #endif // ALLOW_ADD_FONT_BY_FILE +#ifdef ALLOW_DEBUG_MODE bool GLGizmoEmboss::choose_svg_file() { wxArrayString input_files; @@ -3544,7 +3186,7 @@ bool GLGizmoEmboss::choose_svg_file() if (input_files.size() != 1) return false; auto & input_file = input_files.front(); std::string path = std::string(input_file.c_str()); - std::string name = get_file_name(path); + std::string name = priv::get_file_name(path); NSVGimage *image = nsvgParseFromFile(path.c_str(), "mm", 96.0f); ExPolygons polys = NSVGUtils::to_ExPolygons(image); @@ -3564,43 +3206,41 @@ bool GLGizmoEmboss::choose_svg_file() // svg.draw(polys); //return add_volume(name, its); } +#endif // ALLOW_DEBUG_MODE void GLGizmoEmboss::create_notification_not_valid_font( const TextConfiguration &tc) { - // not neccessary, but for sure that old notification doesnt exist - if (m_is_unknown_font) remove_notification_not_valid_font(); - m_is_unknown_font = true; - - auto type = NotificationType::UnknownFont; - auto level = - NotificationManager::NotificationLevel::WarningNotificationLevel; - const EmbossStyle &es = m_style_manager.get_style(); const auto &face_name_opt = es.prop.face_name; - const auto &face_name_3mf_opt = tc.style.prop.face_name; + const std::string &face_name_3mf = tc.style.prop.face_name.value_or(tc.style.path); - const std::string &face_name_3mf = face_name_3mf_opt.has_value() ? - *face_name_3mf_opt : - tc.style.path; - - std::string face_name_by_wx; + std::optional face_name_by_wx; if (!face_name_opt.has_value()) { - const auto& wx_font = m_style_manager.get_wx_font(); - if (wx_font.has_value()) { - wxString wx_face_name = wx_font->GetFaceName(); - face_name_by_wx = std::string((const char *) wx_face_name.ToUTF8()); + const wxFont& wx_font = m_style_manager.get_wx_font(); + if (wx_font.IsOk()) { + wxString wx_face_name = wx_font.GetFaceName(); + if (!wx_face_name.empty()) + face_name_by_wx = std::string(wx_face_name.ToUTF8().data()); } } - - const std::string &face_name = face_name_opt.has_value() ? *face_name_opt : - (!face_name_by_wx.empty() ? face_name_by_wx : es.path); - + const std::string &face_name = face_name_opt.value_or(face_name_by_wx.value_or(es.path)); std::string text = GUI::format(_L("Can't load exactly same font(\"%1%\"), " "Aplication selected a similar one(\"%2%\"). " "You have to specify font for enable edit text."), face_name_3mf, face_name); + create_notification_not_valid_font(text); +} + +void GLGizmoEmboss::create_notification_not_valid_font(const std::string &text) { + // not neccessary, but for sure that old notification doesnt exist + if (m_is_unknown_font) + remove_notification_not_valid_font(); + m_is_unknown_font = true; + + auto type = NotificationType::UnknownFont; + auto level = NotificationManager::NotificationLevel::WarningNotificationLevel; auto notification_manager = wxGetApp().plater()->get_notification_manager(); notification_manager->push_notification(type, level, text); } @@ -3628,136 +3268,43 @@ void GLGizmoEmboss::init_icons() "make_bold.svg", "make_unbold.svg", "search.svg", - "open.svg" + "open.svg", + "exclamation.svg" }; assert(filenames.size() == static_cast(IconType::_count)); std::string path = resources_dir() + "/icons/"; for (std::string &filename : filenames) filename = path + filename; - // state order has to match the enum IconState - std::vector> states; - states.push_back(std::make_pair(1, false)); // Activable - states.push_back(std::make_pair(0, false)); // Hovered - states.push_back(std::make_pair(2, false)); // Disabled - - bool compress = false; - bool is_loaded = m_icons_texture.load_from_svg_files_as_sprites_array( - filenames, states, m_gui_cfg->icon_width, compress); - - if (!is_loaded || - (size_t)m_icons_texture.get_width() < (states.size() * m_gui_cfg->icon_width) || - (size_t)m_icons_texture.get_height() < (filenames.size() * m_gui_cfg->icon_width)) { - // bad load of icons, but all usage of m_icons_texture check that texture is initialized - assert(false); - m_icons_texture.reset(); - } + ImVec2 size(m_gui_cfg->icon_width, m_gui_cfg->icon_width); + auto type = IconManager::RasterType::color_wite_gray; + m_icons = m_icon_manager.init(filenames, size, type); } -void GLGizmoEmboss::draw_icon(IconType icon, IconState state, ImVec2 size) +const IconManager::Icon &priv::get_icon(const IconManager::VIcons& icons, IconType type, IconState state) { return *icons[(unsigned) type][(unsigned) state]; } +bool priv::draw_button(const IconManager::VIcons &icons, IconType type, bool disable) { - // canot draw count - assert(icon != IconType::_count); - if (icon == IconType::_count) return; - - unsigned int icons_texture_id = m_icons_texture.get_id(); - int tex_width = m_icons_texture.get_width(); - int tex_height = m_icons_texture.get_height(); - // is icon loaded - if ((icons_texture_id == 0) || (tex_width <= 1) || (tex_height <= 1)){ - ImGui::Text("â–®"); - return; - } - - int icon_width = m_gui_cfg->icon_width; - ImTextureID tex_id = (void *) (intptr_t) (GLuint) icons_texture_id; - int start_x = static_cast(state) * (icon_width + 1) + 1, - start_y = static_cast(icon) * (icon_width + 1) + 1; - - ImVec2 uv0(start_x / (float) tex_width, - start_y / (float) tex_height); - ImVec2 uv1((start_x + icon_width) / (float) tex_width, - (start_y + icon_width) / (float) tex_height); - - if (size.x < 1 || size.y < 1) - size = ImVec2(m_gui_cfg->icon_width, m_gui_cfg->icon_width); - - ImGui::Image(tex_id, size, uv0, uv1); -} - -void GLGizmoEmboss::draw_transparent_icon() -{ - unsigned int icons_texture_id = m_icons_texture.get_id(); - int tex_width = m_icons_texture.get_width(); - int tex_height = m_icons_texture.get_height(); - // is icon loaded - if ((icons_texture_id == 0) || (tex_width <= 1) || (tex_height <= 1)) { - ImGui::Text("â–¯"); - return; - } - - ImTextureID tex_id = (void *) (intptr_t) (GLuint) icons_texture_id; - int icon_width = m_gui_cfg->icon_width; - ImVec2 icon_size(icon_width, icon_width); - ImVec2 pixel_size(1.f / tex_width, 1.f / tex_height); - // zero pixel is transparent in texture - ImGui::Image(tex_id, icon_size, ImVec2(0, 0), pixel_size); -} - -bool GLGizmoEmboss::draw_clickable( - IconType icon, IconState state, - IconType hover_icon, IconState hover_state) -{ - // check of hover - float cursor_x = ImGui::GetCursorPosX(); - draw_transparent_icon(); - ImGui::SameLine(cursor_x); - - if (ImGui::IsItemHovered()) { - // redraw image - draw_icon(hover_icon, hover_state); - } else { - // redraw normal image - draw_icon(icon, state); - } - return ImGui::IsItemClicked(); -} - -bool GLGizmoEmboss::draw_button(IconType icon, bool disable) -{ - if (disable) { - draw_icon(icon, IconState::disabled); - return false; - } - return draw_clickable( - icon, IconState::activable, - icon, IconState::hovered + return Slic3r::GUI::button( + get_icon(icons, type, IconState::activable), + get_icon(icons, type, IconState::hovered), + get_icon(icons, type, IconState::disabled), + disable ); } -bool GLGizmoEmboss::is_text_object(const ModelVolume *text) { - if (text == nullptr) return false; - if (!text->text_configuration.has_value()) return false; - if (text->type() != ModelVolumeType::MODEL_PART) return false; - for (const ModelVolume *v : text->get_object()->volumes) { - if (v == text) continue; - if (v->type() == ModelVolumeType::MODEL_PART) return false; - } - return true; -} - -std::string GLGizmoEmboss::get_file_name(const std::string &file_path) -{ - size_t pos_last_delimiter = file_path.find_last_of("/\\"); - size_t pos_point = file_path.find_last_of('.'); - size_t offset = pos_last_delimiter + 1; - size_t count = pos_point - pos_last_delimiter - 1; - return file_path.substr(offset, count); -} - ///////////// // 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 volume with this type: " << (int) volume_type; + return false; +} + DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr>& cancel) { // create volume_name @@ -3774,10 +3321,8 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &st const EmbossStyle &es = style_manager.get_style(); // actualize font path - during changes in gui it could be corrupted // volume must store valid path - assert(style_manager.get_wx_font().has_value()); - assert(style_manager.get_wx_font()->IsOk()); - assert(es.path.compare(WxFontUtils::store_wxFont(*style_manager.get_wx_font())) == 0); - // style.path = WxFontUtils::store_wxFont(*m_style_manager.get_wx_font()); + assert(style_manager.get_wx_font().IsOk()); + assert(es.path.compare(WxFontUtils::store_wxFont(style_manager.get_wx_font())) == 0); TextConfiguration tc{es, text}; // Cancel previous Job, when it is in process @@ -3828,8 +3373,8 @@ void priv::start_create_volume_job(const ModelObject *object, 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); - CreateSurfaceVolumeData surface_data{std::move(emboss_data), volume_trmat, is_outside, - std::move(sources), volume_type, object->id()}; + SurfaceVolumeData sfvd{volume_trmat, is_outside, std::move(sources)}; + CreateSurfaceVolumeData surface_data{std::move(emboss_data), std::move(sfvd), volume_type, object->id()}; job = std::make_unique(std::move(surface_data)); } } @@ -3844,30 +3389,12 @@ void priv::start_create_volume_job(const ModelObject *object, queue_job(worker, std::move(job)); } -const ModelVolume *priv::get_volume(const ModelObjectPtrs &objects, const ObjectID &volume_id) -{ - for (const ModelObject *obj : objects) - for (const ModelVolume *vol : obj->volumes) - if (vol->id() == volume_id) - return vol; - return nullptr; -}; - -GLVolume * priv::get_hovered_gl_volume(const GLCanvas3D &canvas) { - int hovered_id_signed = canvas.get_first_hover_volume_idx(); - if (hovered_id_signed < 0) return nullptr; - - size_t hovered_id = static_cast(hovered_id_signed); - const GLVolumePtrs &volumes = canvas.get_volumes().volumes; - if (hovered_id >= volumes.size()) return nullptr; - - return volumes[hovered_id]; -} - bool priv::start_create_volume_on_surface_job( - DataBase &emboss_data, ModelVolumeType volume_type, const Vec2d &screen_coor, const GLVolume *gl_volume, RaycastManager &raycaster) + DataBase &emboss_data, ModelVolumeType volume_type, const Vec2d &screen_coor, const GLVolume *gl_volume, RaycastManager &raycaster, GLCanvas3D& canvas) { + assert(gl_volume != nullptr); if (gl_volume == nullptr) return false; + Plater *plater = wxGetApp().plater(); const ModelObjectPtrs &objects = plater->model().objects; @@ -3876,24 +3403,25 @@ bool priv::start_create_volume_on_surface_job( ModelObject *obj = objects[object_idx]; size_t vol_id = obj->volumes[gl_volume->volume_idx()]->id().id; auto cond = RaycastManager::AllowVolumes({vol_id}); - raycaster.actualize(obj, &cond); + + RaycastManager::Meshes meshes = create_meshes(canvas, cond); + raycaster.actualize(*obj, &cond, &meshes); const Camera &camera = plater->get_camera(); - std::optional hit = raycaster.unproject(screen_coor, camera); + 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()) return false; - - Transform3d hit_object_trmat = raycaster.get_transformation(hit->tr_key); - Transform3d hit_instance_trmat = gl_volume->get_instance_transformation().get_matrix(); + if (!hit.has_value()) + return false; // Create result volume transformation - Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal); - const FontProp &font_prop = emboss_data.text_configuration.style.prop; + Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, priv::up_limit); + const FontProp &font_prop = emboss_data.text_configuration.style.prop; apply_transformation(font_prop, surface_trmat); - Transform3d volume_trmat = hit_instance_trmat.inverse() * hit_object_trmat * surface_trmat; + Transform3d instance = gl_volume->get_instance_transformation().get_matrix(); + Transform3d volume_trmat = instance.inverse() * surface_trmat; start_create_volume_job(obj, volume_trmat, emboss_data, volume_type); return true; } @@ -3915,7 +3443,7 @@ void priv::find_closest_volume(const Selection &selection, double center_sq_distance = std::numeric_limits::max(); for (unsigned int id : indices) { const GLVolume *gl_volume = selection.get_volume(id); - ModelVolume *volume = priv::get_model_volume(gl_volume, objects); + const ModelVolume *volume = get_model_volume(*gl_volume, objects); if (!volume->is_model_part()) continue; Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume); Vec2d c = hull.centroid().cast(); @@ -3932,5 +3460,106 @@ void priv::find_closest_volume(const Selection &selection, } } +ImVec2 priv::calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size) +{ + const Selection::IndicesList indices = selection.get_volume_idxs(); + // no selected volume + if (indices.empty()) + return {}; + const GLVolume *volume = selection.get_volume(*indices.begin()); + // bad volume selected (e.g. deleted one) + if (volume == nullptr) + return {}; + + const Camera &camera = wxGetApp().plater()->get_camera(); + Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *volume); + + ImVec2 c_size(canvas_size.get_width(), canvas_size.get_height()); + ImVec2 offset = ImGuiWrapper::suggest_location(windows_size, hull, c_size); + return offset; +} + +// Need internals to get window +#include "imgui/imgui_internal.h" +void priv::change_window_position(std::optional& output_window_offset, bool try_to_fix) { + const char* name = "Emboss"; + ImGuiWindow *window = ImGui::FindWindowByName(name); + // is window just created + if (window == NULL) + return; + + // position of window on screen + ImVec2 position = window->Pos; + ImVec2 size = window->SizeFull; + + // screen size + ImVec2 screen = ImGui::GetMainViewport()->Size; + + if (position.x < 0) { + if (position.y < 0) + output_window_offset = ImVec2(0, 0); + else + output_window_offset = ImVec2(0, position.y); + } else if (position.y < 0) { + output_window_offset = ImVec2(position.x, 0); + } else if (screen.x < (position.x + size.x)) { + if (screen.y < (position.y + size.y)) + output_window_offset = ImVec2(screen.x - size.x, screen.y - size.y); + else + output_window_offset = ImVec2(screen.x - size.x, position.y); + } else if (screen.y < (position.y + size.y)) { + output_window_offset = ImVec2(position.x, screen.y - size.y); + } + + if (!try_to_fix && output_window_offset.has_value()) + output_window_offset = ImVec2(-1, -1); // Cannot +} + + +bool priv::apply_camera_dir(const Camera &camera, GLCanvas3D &canvas) { + const Vec3d &cam_dir = camera.get_dir_forward(); + + Selection &sel = canvas.get_selection(); + if (sel.is_empty()) return false; + + // camera direction transformed into volume coordinate system + Transform3d to_world = world_matrix_fixed(sel); + Vec3d cam_dir_tr = to_world.inverse().linear() * cam_dir; + cam_dir_tr.normalize(); + + Vec3d emboss_dir(0., 0., -1.); + + // check wether cam_dir is already used + if (is_approx(cam_dir_tr, emboss_dir)) return false; + + assert(sel.get_volume_idxs().size() == 1); + GLVolume *gl_volume = sel.get_volume(*sel.get_volume_idxs().begin()); + + Transform3d vol_rot; + Transform3d vol_tr = gl_volume->get_volume_transformation().get_matrix(); + // check whether cam_dir is opposit to emboss dir + if (is_approx(cam_dir_tr, -emboss_dir)) { + // rotate 180 DEG by y + vol_rot = Eigen::AngleAxis(M_PI_2, Vec3d(0., 1., 0.)); + } else { + // calc params for rotation + Vec3d axe = emboss_dir.cross(cam_dir_tr); + axe.normalize(); + double angle = std::acos(emboss_dir.dot(cam_dir_tr)); + vol_rot = Eigen::AngleAxis(angle, axe); + } + + Vec3d offset = vol_tr * Vec3d::Zero(); + Vec3d offset_inv = vol_rot.inverse() * offset; + Transform3d res = vol_tr * + Eigen::Translation(-offset) * + vol_rot * + Eigen::Translation(offset_inv); + //Transform3d res = vol_tr * vol_rot; + gl_volume->set_volume_transformation(Geometry::Transformation(res)); + get_model_volume(*gl_volume, sel.get_model()->objects)->set_transformation(res); + return true; +} + // any existing icon filename to not influence GUI const std::string GLGizmoEmboss::M_ICON_FILENAME = "cut.svg"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 2ed6c2000..ebbdf616c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -5,15 +5,13 @@ // which overrides our localization "L" macro. #include "GLGizmoBase.hpp" #include "GLGizmoRotate.hpp" -#include "slic3r/GUI/GLTexture.hpp" +#include "slic3r/GUI/IconManager.hpp" +#include "slic3r/GUI/SurfaceDrag.hpp" #include "slic3r/Utils/RaycastManager.hpp" #include "slic3r/Utils/EmbossStyleManager.hpp" -#include "admesh/stl.h" // indexed_triangle_set #include #include -#include -#include #include #include "libslic3r/Emboss.hpp" @@ -28,12 +26,10 @@ class wxFont; namespace Slic3r{ class AppConfig; class GLVolume; - enum class ModelVolumeType : int; } namespace Slic3r::GUI { -class MeshRaycaster; class GLGizmoEmboss : public GLGizmoBase { public: @@ -82,14 +78,12 @@ protected: std::string get_gizmo_leaving_text() const override { return _u8L("Leave emboss gizmo"); } std::string get_action_snapshot_name() override { return _u8L("Embossing actions"); } private: - void initialize(); static EmbossStyles create_default_styles(); // localized default text void set_default_text(); void set_volume_by_selection(); - // load text configuration from volume into gizmo - bool set_volume(ModelVolume *volume); + void reset_volume(); // create volume from text - main functionality bool process(); @@ -108,17 +102,21 @@ private: void init_font_name_texture(); struct FaceName; void draw_font_preview(FaceName &face, bool is_visible); + void draw_font_list_line(); void draw_font_list(); - void draw_style_edit(); void draw_height(bool use_inch); void draw_depth(bool use_inch); + // call after set m_style_manager.get_style().prop.size_in_mm + bool set_height(); + // call after set m_style_manager.get_style().prop.emboss + bool set_depth(); + bool draw_italic_button(); bool draw_bold_button(); void draw_advanced(); bool select_facename(const wxString& facename); - void init_face_names(); void do_translate(const Vec3d& relative_move); void do_rotate(float relative_z_angle); @@ -145,7 +143,7 @@ private: template bool revertible(const std::string &name, T &value, const T *default_value, const std::string &undo_tooltip, float undo_offset, Draw draw); - bool m_should_set_minimal_windows_size = false; + bool m_should_set_minimal_windows_size = false; void set_minimal_window_size(bool is_advance_edit_style); ImVec2 get_minimal_window_size() const; @@ -153,41 +151,43 @@ private: bool on_mouse_for_rotation(const wxMouseEvent &mouse_event); bool on_mouse_for_translate(const wxMouseEvent &mouse_event); - bool choose_font_by_wxdialog(); - bool choose_true_type_file(); - bool choose_svg_file(); - // When open text loaded from .3mf it could be written with unknown font bool m_is_unknown_font; void create_notification_not_valid_font(const TextConfiguration& tc); + void create_notification_not_valid_font(const std::string& text); void remove_notification_not_valid_font(); - + // This configs holds GUI layout size given by translated texts. // etc. When language changes, GUI is recreated and this class constructed again, // so the change takes effect. (info by GLGizmoFdmSupports.hpp) struct GuiCfg { + // Detect invalid config values when change monitor DPI + double screen_scale; + float main_toolbar_height; + // Zero means it is calculated in init function - ImVec2 minimal_window_size = ImVec2(0, 0); - ImVec2 minimal_window_size_with_advance = ImVec2(0, 0); - ImVec2 minimal_window_size_with_collections = ImVec2(0, 0); - float height_of_volume_type_selector = 0.f; - float input_width = 0.f; - float delete_pos_x = 0.f; - float max_style_name_width = 0.f; - unsigned int icon_width = 0; + ImVec2 minimal_window_size = ImVec2(0, 0); + ImVec2 minimal_window_size_with_advance = ImVec2(0, 0); + ImVec2 minimal_window_size_with_collections = ImVec2(0, 0); + float height_of_volume_type_selector = 0.f; + float input_width = 0.f; + float delete_pos_x = 0.f; + float max_style_name_width = 0.f; + unsigned int icon_width = 0; // maximal width and height of style image Vec2i max_style_image_size = Vec2i(0, 0); + float indent = 0.f; float input_offset = 0.f; float advanced_input_offset = 0.f; ImVec2 text_size; // maximal size of face name image - Vec2i face_name_size = Vec2i(100, 0); - float face_name_max_width = 100.f; + Vec2i face_name_size = Vec2i(100, 0); + float face_name_max_width = 100.f; float face_name_texture_offset_x = 105.f; // maximal texture generate jobs running at once @@ -197,7 +197,7 @@ private: struct Translations { std::string font; - std::string size; + std::string height; std::string depth; std::string use_surface; @@ -205,16 +205,18 @@ private: std::string char_gap; std::string line_gap; std::string boldness; - std::string italic; - std::string surface_distance; - std::string angle; + std::string skew_ration; + std::string from_surface; + std::string rotation; + std::string keep_up; std::string collection; }; Translations translations; - - GuiCfg() = default; }; - std::optional m_gui_cfg; + std::optional m_gui_cfg; + static GuiCfg create_gui_configuration(); + + // Is open tree with advanced options bool m_is_advanced_edit_style = false; // when true window will appear near to text volume when open @@ -223,6 +225,7 @@ private: // setted only when wanted to use - not all the time std::optional m_set_window_offset; + // Keep information about stored styles and loaded actual style to compare with Emboss::StyleManager m_style_manager; struct FaceName{ @@ -244,13 +247,15 @@ private: // true .. already enumerated(During opened combo box) bool is_init = false; + bool has_truncated_names = false; + // data of can_load() faces std::vector faces = {}; // Sorter set of Non valid face names in OS std::vector bad = {}; // Configuration of font encoding - const wxFontEncoding encoding = wxFontEncoding::wxFONTENCODING_SYSTEM; + static const wxFontEncoding encoding = wxFontEncoding::wxFONTENCODING_SYSTEM; // Identify if preview texture exists GLuint texture_id = 0; @@ -277,11 +282,17 @@ private: static bool store(const Facenames &facenames); static bool load(Facenames &facenames); + static void init_face_names(Facenames &facenames); + static void init_truncated_names(Facenames &face_names, float max_width); // Text to emboss - std::string m_text; + std::string m_text; // Sequence of Unicode UTF8 symbols - // actual volume + // When true keep up vector otherwise relative rotation + bool m_keep_up = true; + + // current selected volume + // NOTE: Be carefull could be uninitialized (removed from Model) ModelVolume *m_volume; // When work with undo redo stack there could be situation that @@ -299,59 +310,25 @@ private: // Value is set only when dragging rotation to calculate actual angle std::optional m_rotate_start_angle; - // when draging with text object hold screen offset of cursor from object center - std::optional m_dragging_mouse_offset; + // Keep data about dragging only during drag&drop + std::optional m_surface_drag; // TODO: it should be accessible by other gizmo too. // May be move to plater? RaycastManager m_raycast_manager; - // Only when drag text object it stores world position - std::optional m_temp_transformation; - // For text on scaled objects std::optional m_scale_height; std::optional m_scale_depth; void calculate_scale(); // drawing icons - GLTexture m_icons_texture; + IconManager m_icon_manager; + IconManager::VIcons m_icons; void init_icons(); - enum class IconType : unsigned { - rename = 0, - erase, - add, - save, - undo, - italic, - unitalic, - bold, - unbold, - system_selector, - open_file, - // automatic calc of icon's count - _count - }; - enum class IconState: unsigned { activable = 0, hovered /*1*/, disabled /*2*/}; - void draw_icon(IconType icon, IconState state, ImVec2 size = ImVec2(0,0)); - void draw_transparent_icon(); - bool draw_clickable(IconType icon, IconState state, IconType hover_icon, IconState hover_state); - bool draw_button(IconType icon, bool disable = false); // only temporary solution static const std::string M_ICON_FILENAME; - -public: - /// - /// Check if text is last solid part of object - /// TODO: move to emboss gui utils - /// - /// Model volume of Text - /// True when object otherwise False - static bool is_text_object(const ModelVolume *text); - - // TODO: move to file utils - static std::string get_file_name(const std::string &file_path); }; } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/IconManager.cpp b/src/slic3r/GUI/IconManager.cpp new file mode 100644 index 000000000..45c76887c --- /dev/null +++ b/src/slic3r/GUI/IconManager.cpp @@ -0,0 +1,204 @@ +#include "IconManager.hpp" +#include +#include + +using namespace Slic3r::GUI; + +namespace priv { +// set shared pointer to point on bad texture +static void clear(IconManager::Icons &icons); +static const std::vector>& get_states(IconManager::RasterType type); +static void draw_transparent_icon(const IconManager::Icon &icon); // only help function +} + +IconManager::~IconManager() { + priv::clear(m_icons); + // release opengl texture is made in ~GLTexture() +} + +std::vector IconManager::init(const InitTypes &input) +{ + BOOST_LOG_TRIVIAL(error) << "Not implemented yet"; + return {}; +} + +std::vector IconManager::init(const std::vector &file_paths, const ImVec2 &size, RasterType type) +{ + // TODO: remove in future + if (!m_icons.empty()) { + // not first initialization + priv::clear(m_icons); + m_icons.clear(); + m_icons_texture.reset(); + } + + // only rectangle are supported + assert(size.x == size.y); + // no subpixel supported + unsigned int width = static_cast(std::abs(std::round(size.x))); + assert(size.x == static_cast(width)); + + // state order has to match the enum IconState + const auto& states = priv::get_states(type); + + bool compress = false; + bool is_loaded = m_icons_texture.load_from_svg_files_as_sprites_array(file_paths, states, width, compress); + if (!is_loaded || (size_t) m_icons_texture.get_width() < (states.size() * width) || + (size_t) m_icons_texture.get_height() < (file_paths.size() * width)) { + // bad load of icons, but all usage of m_icons_texture check that texture is initialized + assert(false); + m_icons_texture.reset(); + return {}; + } + + unsigned count_files = file_paths.size(); + // count icons per file + unsigned count = states.size(); + // create result + std::vector result; + result.reserve(count_files); + + Icon def_icon; + def_icon.tex_id = m_icons_texture.get_id(); + def_icon.size = size; + + // float beacouse of dividing + float tex_height = static_cast(m_icons_texture.get_height()); + float tex_width = static_cast(m_icons_texture.get_width()); + + //for (const auto &f: file_paths) { + for (unsigned f = 0; f < count_files; ++f) { + // NOTE: there are space between icons + unsigned start_y = static_cast(f) * (width + 1) + 1; + float y1 = start_y / tex_height; + float y2 = (start_y + width) / tex_height; + Icons file_icons; + file_icons.reserve(count); + //for (const auto &s : states) { + for (unsigned j = 0; j < count; ++j) { + auto icon = std::make_shared(def_icon); + // NOTE: there are space between icons + unsigned start_x = static_cast(j) * (width + 1) + 1; + float x1 = start_x / tex_width; + float x2 = (start_x + width) / tex_width; + icon->tl = ImVec2(x1, y1); + icon->br = ImVec2(x2, y2); + file_icons.push_back(icon); + m_icons.push_back(std::move(icon)); + } + result.emplace_back(std::move(file_icons)); + } + return result; +} + +void IconManager::release() { + BOOST_LOG_TRIVIAL(error) << "Not implemented yet"; +} + +void priv::clear(IconManager::Icons &icons) { + std::string message; + for (auto &icon : icons) { + // Exist more than this instance of shared ptr? + long count = icon.use_count(); + if (count != 1) { + // in existing icon change texture to non existing one + icon->tex_id = 0; + + std::string descr = + ((count > 2) ? (std::to_string(count - 1) + "x") : "") + // count + std::to_string(icon->size.x) + "x" + std::to_string(icon->size.y); // resolution + if (message.empty()) + message = descr; + else + message += ", " + descr; + } + } + + if (!message.empty()) + BOOST_LOG_TRIVIAL(warning) << "There is still used icons(" << message << ")."; +} + +const std::vector> &priv::get_states(IconManager::RasterType type) { + static std::vector> color = {std::make_pair(0, false)}; + static std::vector> white = {std::make_pair(1, false)}; + static std::vector> gray = {std::make_pair(2, false)}; + static std::vector> color_wite_gray = { + std::make_pair(1, false), // Activable + std::make_pair(0, false), // Hovered + std::make_pair(2, false) // Disabled + }; + + switch (type) { + case IconManager::RasterType::color: return color; + case IconManager::RasterType::white_only_data: return white; + case IconManager::RasterType::gray_only_data: return gray; + case IconManager::RasterType::color_wite_gray: return color_wite_gray; + default: return color; + } +} + +void priv::draw_transparent_icon(const IconManager::Icon &icon) +{ + // Check input + if (!icon.is_valid()) { + assert(false); + BOOST_LOG_TRIVIAL(warning) << "Drawing invalid Icon."; + ImGui::Text("?"); + return; + } + + // size UV texture coors [in texture ratio] + ImVec2 size_uv(icon.br.x - icon.tl.x, icon.br.y - icon.tl.y); + ImVec2 one_px(size_uv.x / icon.size.x, size_uv.y / icon.size.y); + + // use top left corner of first icon + IconManager::Icon icon_px = icon; // copy + // reduce uv coors to one pixel + icon_px.tl = ImVec2(0, 0); + icon_px.br = one_px; + draw(icon_px); +} + +namespace Slic3r::GUI { + +void draw(const IconManager::Icon &icon, const ImVec2 &size, const ImVec4 &tint_col, const ImVec4 &border_col) +{ + // Check input + if (!icon.is_valid()) { + assert(false); + BOOST_LOG_TRIVIAL(warning) << "Drawing invalid Icon."; + ImGui::Text("?"); + return; + } + + ImTextureID id = (void *)static_cast(icon.tex_id); + const ImVec2 &s = (size.x < 1 || size.y < 1) ? icon.size : size; + ImGui::Image(id, s, icon.tl, icon.br, tint_col, border_col); +} + +bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover) +{ + // check of hover + float cursor_x = ImGui::GetCursorPosX(); + priv::draw_transparent_icon(icon); + ImGui::SameLine(cursor_x); + if (ImGui::IsItemHovered()) { + // redraw image + draw(icon_hover); + } else { + // redraw normal image + draw(icon); + } + return ImGui::IsItemClicked(); +} + +bool button(const IconManager::Icon &activ, const IconManager::Icon &hover, const IconManager::Icon &disable, bool disabled) +{ + if (disabled) { + draw(disable); + return false; + } + return clickable(activ, hover); +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/IconManager.hpp b/src/slic3r/GUI/IconManager.hpp new file mode 100644 index 000000000..aa7afda80 --- /dev/null +++ b/src/slic3r/GUI/IconManager.hpp @@ -0,0 +1,132 @@ +#ifndef slic3r_IconManager_hpp_ +#define slic3r_IconManager_hpp_ + +#include +#include +#include "imgui/imgui.h" // ImVec2 +#include "slic3r/GUI/GLTexture.hpp" // texture storage + +namespace Slic3r::GUI { + +/// +/// Keep texture with icons for UI +/// Manage texture live -> create and destruct texture +/// by live of icon shared pointers. +/// +class IconManager +{ +public: + /// + /// Release texture + /// Set shared pointers to invalid texture + /// + ~IconManager(); + + /// + /// Define way to convert svg data to raster + /// + enum class RasterType: int{ + color = 1 << 1, + white_only_data = 1 << 2, + gray_only_data = 1 << 3, + color_wite_gray = color | white_only_data | gray_only_data + // TODO: add type with backgrounds + }; + + struct InitType { + // path to file with image .. svg + std::string filepath; + + // resolution of stored rasterized icon + ImVec2 size; // float will be rounded + + // could contain more than one type + RasterType type = RasterType::color; + // together color, white and gray = color | white_only_data | gray_only_data + }; + using InitTypes = std::vector; + + /// + /// Data for render texture with icon + /// + struct Icon { + // stored texture size + ImVec2 size = ImVec2(-1, -1); // [in px] --> unsigned int values stored as float + + // SubTexture UV coordinate in range from 0. to 1. + ImVec2 tl; // top left -> uv0 + ImVec2 br; // bottom right -> uv1 + + // OpenGL texture id + unsigned int tex_id = 0; + bool is_valid() const { return tex_id != 0;} + // && size.x > 0 && size.y > 0 && tl.x != br.x && tl.y != br.y; + }; + using Icons = std::vector >; + // Vector of icons, each vector contain multiple use of a SVG render + using VIcons = std::vector; + + /// + /// Initialize raster texture on GPU with given images + /// NOTE: Have to be called after OpenGL initialization + /// + /// Define files and its + /// Rasterized icons stored on GPU, + /// Same size and order as input, each item of vector is set of texture in order by RasterType + VIcons init(const InitTypes &input); + + /// + /// Initialize multiple icons with same settings for size and type + /// NOTE: Have to be called after OpenGL initialization + /// + /// Define files with icon + /// Size of stored texture[in px], float will be rounded + /// Define way to rasterize icon, + /// together color, white and gray = RasterType::color | RasterType::white_only_data | RasterType::gray_only_data + /// Rasterized icons stored on GPU, + /// Same size and order as file_paths, each item of vector is set of texture in order by RasterType + VIcons init(const std::vector &file_paths, const ImVec2 &size, RasterType type = RasterType::color); + + /// + /// Release icons which are hold only by this manager + /// May change texture and position of icons. + /// + void release(); + +private: + // keep data stored on GPU + GLTexture m_icons_texture; + Icons m_icons; +}; + +/// +/// Draw imgui image with icon +/// +/// Place in texture +/// [optional]Size of image, wen zero than use same size as stored texture +/// viz ImGui::Image +/// viz ImGui::Image +void draw(const IconManager::Icon &icon, + const ImVec2 &size = ImVec2(0, 0), + const ImVec4 &tint_col = ImVec4(1, 1, 1, 1), + const ImVec4 &border_col = ImVec4(0, 0, 0, 0)); + +/// +/// Draw icon which change on hover +/// +/// Draw when no hover +/// Draw when hover +/// True when click, otherwise False +bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover); + +/// +/// Use icon as button with 3 states activ hover and disabled +/// +/// Not disabled not hovered image +/// Hovered image +/// Disabled image +/// True when click on enabled, otherwise False +bool button(const IconManager::Icon &activ, const IconManager::Icon &hover, const IconManager::Icon &disable, bool disabled = false); + +} // namespace Slic3r::GUI +#endif // slic3r_IconManager_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 6b85af966..55bf57602 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1369,6 +1369,10 @@ bool ImGuiWrapper::slider_optional_int(const char *label, } else return false; } +void ImGuiWrapper::left_inputs() { + ImGui::ClearActiveID(); +} + std::string ImGuiWrapper::trunc(const std::string &text, float width, const char * tail) @@ -1510,6 +1514,17 @@ void ImGuiWrapper::draw( } } +void ImGuiWrapper::draw_cross_hair(const ImVec2 &position, float radius, ImU32 color, int num_segments, float thickness) { + auto draw_list = ImGui::GetOverlayDrawList(); + draw_list->AddCircle(position, radius, color, num_segments, thickness); + auto dirs = {ImVec2{0, 1}, ImVec2{1, 0}, ImVec2{0, -1}, ImVec2{-1, 0}}; + for (const ImVec2 &dir : dirs) { + ImVec2 start(position.x + dir.x * 0.5 * radius, position.y + dir.y * 0.5 * radius); + ImVec2 end(position.x + dir.x * 1.5 * radius, position.y + dir.y * 1.5 * radius); + draw_list->AddLine(start, end, color, thickness); + } +} + bool ImGuiWrapper::contain_all_glyphs(const ImFont *font, const std::string &text) { diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 4d8b4944c..077bf568d 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -146,6 +146,12 @@ public: // Extended function ImGuiWrapper::slider_float to work with std::optional, when value == def_val than optional release its value bool slider_optional_int(const char* label, std::optional &v, int v_min, int v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true, int def_val = 0); + /// + /// Use ImGui internals to unactivate (lose focus) in input. + /// When input is activ it can't change value by application. + /// + static void left_inputs(); + /// /// Truncate text by ImGui draw function to specific width /// NOTE 1: ImGui must be initialized @@ -193,6 +199,20 @@ public: ImU32 color = ImGui::GetColorU32(COL_ORANGE_LIGHT), float thickness = 3.f); + /// + /// Draw symbol of cross hair + /// + /// Center of cross hair + /// Circle radius + /// Color of symbol + /// Precission of circle + /// Thickness of Line in symbol + static void draw_cross_hair(const ImVec2 &position, + float radius = 16.f, + ImU32 color = ImGui::GetColorU32(ImVec4(1.f, 1.f, 1.f, .75f)), + int num_segments = 0, + float thickness = 4.f); + /// /// Check that font ranges contain all chars in string /// (rendered Unicodes are stored in GlyphRanges) diff --git a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp index d1a671330..8aa9e23cb 100644 --- a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp +++ b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp @@ -2,11 +2,6 @@ // rasterization of ExPoly #include "libslic3r/SLA/AGGRaster.hpp" - -// for get DPI -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/MainFrame.hpp" - #include "slic3r/GUI/3DScene.hpp" // ::glsafe // ability to request new frame after finish rendering @@ -20,15 +15,15 @@ using namespace Slic3r::GUI; using namespace Slic3r::GUI::Emboss; -CreateFontStyleImagesJob::CreateFontStyleImagesJob( - StyleManager::StyleImagesData &&input) - : m_input(std::move(input)) +CreateFontStyleImagesJob::CreateFontStyleImagesJob(StyleManager::StyleImagesData &&input) + : m_input(std::move(input)), m_width(0), m_height(0) { assert(m_input.result != nullptr); assert(!m_input.styles.empty()); assert(!m_input.text.empty()); assert(m_input.max_size.x() > 1); assert(m_input.max_size.y() > 1); + assert(m_input.ppm > 1e-5); } void CreateFontStyleImagesJob::process(Ctl &ctl) @@ -36,7 +31,7 @@ void CreateFontStyleImagesJob::process(Ctl &ctl) // create shapes and calc size (bounding boxes) std::vector name_shapes(m_input.styles.size()); std::vector scales(m_input.styles.size()); - images = std::vector(m_input.styles.size()); + m_images = std::vector(m_input.styles.size()); for (auto &item : m_input.styles) { size_t index = &item - &m_input.styles.front(); @@ -44,21 +39,17 @@ void CreateFontStyleImagesJob::process(Ctl &ctl) shapes = text2shapes(item.font, m_input.text.c_str(), item.prop); // create image description - StyleManager::StyleImage &image = images[index]; + StyleManager::StyleImage &image = m_images[index]; BoundingBox &bounding_box = image.bounding_box; for (ExPolygon &shape : shapes) bounding_box.merge(BoundingBox(shape.contour.points)); for (ExPolygon &shape : shapes) shape.translate(-bounding_box.min); // calculate conversion from FontPoint to screen pixels by size of font - auto mf = wxGetApp().mainframe; - // dot per inch for monitor - int dpi = get_dpi_for_window(mf); - double ppm = dpi / 25.4; // pixel per milimeter const auto &cn = item.prop.collection_number; unsigned int font_index = (cn.has_value()) ? *cn : 0; double unit_per_em = item.font.font_file->infos[font_index].unit_per_em; - double scale = item.prop.size_in_mm / unit_per_em * SHAPE_SCALE * ppm; + double scale = item.prop.size_in_mm / unit_per_em * SHAPE_SCALE * m_input.ppm; scales[index] = scale; //double scale = font_prop.size_in_mm * SCALING_FACTOR; @@ -78,30 +69,30 @@ void CreateFontStyleImagesJob::process(Ctl &ctl) // arrange bounding boxes int offset_y = 0; - width = 0; - for (StyleManager::StyleImage &image : images) { + m_width = 0; + for (StyleManager::StyleImage &image : m_images) { image.offset.y() = offset_y; offset_y += image.tex_size.y+1; - if (width < image.tex_size.x) - width = image.tex_size.x; + if (m_width < image.tex_size.x) + m_width = image.tex_size.x; } - height = offset_y; - for (StyleManager::StyleImage &image : images) { + m_height = offset_y; + for (StyleManager::StyleImage &image : m_images) { const Point &o = image.offset; const ImVec2 &s = image.tex_size; - image.uv0 = ImVec2(o.x() / (double) width, - o.y() / (double) height); - image.uv1 = ImVec2((o.x() + s.x) / (double) width, - (o.y() + s.y) / (double) height); + image.uv0 = ImVec2(o.x() / (double) m_width, + o.y() / (double) m_height); + image.uv1 = ImVec2((o.x() + s.x) / (double) m_width, + (o.y() + s.y) / (double) m_height); } // Set up result - pixels = std::vector(4*width * height, {255}); + m_pixels = std::vector(4 * m_width * m_height, {255}); // upload sub textures - for (StyleManager::StyleImage &image : images) { + for (StyleManager::StyleImage &image : m_images) { sla::Resolution resolution(image.tex_size.x, image.tex_size.y); - size_t index = &image - &images.front(); + size_t index = &image - &m_images.front(); double pixel_dim = SCALING_FACTOR / scales[index]; sla::PixelDim dim(pixel_dim, pixel_dim); double gamma = 1.; @@ -110,7 +101,7 @@ void CreateFontStyleImagesJob::process(Ctl &ctl) for (const ExPolygon &shape : name_shapes[index]) r->draw(shape); // copy rastered data to pixels - sla::RasterEncoder encoder = [&offset = image.offset, &pix = pixels, w=width,h=height] + sla::RasterEncoder encoder = [&offset = image.offset, &pix = m_pixels, w=m_width,h=m_height] (const void *ptr, size_t width, size_t height, size_t num_components) { // bigger value create darker image unsigned char gray_level = 5; @@ -142,18 +133,18 @@ void CreateFontStyleImagesJob::finalize(bool canceled, std::exception_ptr &) glsafe(::glBindTexture(target, tex_id)); glsafe(::glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); glsafe(::glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); - GLint w = width, h=height; + GLint w = m_width, h = m_height; glsafe(::glTexImage2D(target, level, GL_RGBA, w, h, border, format, type, - (const void *) pixels.data())); + (const void *) m_pixels.data())); // set up texture id void *texture_id = (void *) (intptr_t) tex_id; - for (StyleManager::StyleImage &image : images) + for (StyleManager::StyleImage &image : m_images) image.texture_id = texture_id; // move to result m_input.result->styles = std::move(m_input.styles); - m_input.result->images = std::move(images); + m_input.result->images = std::move(m_images); // bind default texture GLuint no_texture_id = 0; diff --git a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp index c220f2ee0..b8c2757a6 100644 --- a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp +++ b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp @@ -19,11 +19,11 @@ class CreateFontStyleImagesJob : public Job // Output data // texture size - int width, height; + int m_width, m_height; // texture data - std::vector pixels; + std::vector m_pixels; // descriptors of sub textures - std::vector images; + std::vector m_images; public: CreateFontStyleImagesJob(StyleManager::StyleImagesData &&input); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index bc80285ea..f5c315b30 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -502,6 +502,9 @@ void UpdateJob::update_volume(ModelVolume *volume, volume->calculate_convex_hull(); volume->get_object()->invalidate_bounding_box(); volume->text_configuration = text_configuration; + + // discard information about rotation, should not be stored in volume + volume->text_configuration->style.prop.angle.reset(); GUI_App &app = wxGetApp(); // may be move to input GLCanvas3D *canvas = app.plater()->canvas3D(); @@ -615,6 +618,10 @@ void priv::create_volume( volume->name = data.volume_name; // copy volume->text_configuration = data.text_configuration; // copy + + // discard information about rotation, should not be stored in volume + volume->text_configuration->style.prop.angle.reset(); + volume->set_transformation(trmat); // update printable state on canvas diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 0841d61a1..2b77fe7ff 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -3410,5 +3410,30 @@ void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& v } #endif // ENABLE_WORLD_COORDINATE +ModelVolume *get_selected_volume(const Selection &selection) +{ + const GLVolume *gl_volume = get_selected_gl_volume(selection); + if (gl_volume == nullptr) + return nullptr; + const ModelObjectPtrs &objects = selection.get_model()->objects; + return get_model_volume(*gl_volume, objects); +} + +const GLVolume *get_selected_gl_volume(const Selection &selection) +{ + int object_idx = selection.get_object_idx(); + // is more object selected? + if (object_idx == -1) + return nullptr; + + const auto &list = selection.get_volume_idxs(); + // is more volumes selected? + if (list.size() != 1) + return nullptr; + + unsigned int volume_idx = *list.begin(); + return selection.get_volume(volume_idx); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index eb69d4491..d3f456702 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -17,6 +17,7 @@ namespace Slic3r { class Shader; class Model; class ModelObject; +class ModelVolume; class GLVolume; class GLArrow; class GLCurvedArrow; @@ -523,6 +524,9 @@ private: #endif // ENABLE_WORLD_COORDINATE }; +ModelVolume *get_selected_volume(const Selection &selection); +const GLVolume *get_selected_gl_volume(const Selection &selection); + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/SurfaceDrag.cpp b/src/slic3r/GUI/SurfaceDrag.cpp new file mode 100644 index 000000000..ecee0a4e0 --- /dev/null +++ b/src/slic3r/GUI/SurfaceDrag.cpp @@ -0,0 +1,348 @@ +#include "SurfaceDrag.hpp" + +#include "libslic3r/Model.hpp" // ModelVolume +#include "GLCanvas3D.hpp" +#include "slic3r/Utils/RaycastManager.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/CameraUtils.hpp" +#include "libslic3r/Emboss.hpp" + +namespace Slic3r::GUI { + +/// +/// Calculate offset from mouse position to center of text +/// +/// Position on screen[in Px] e.g. mouse position +/// Selected volume(text) +/// Actual position and view direction of camera +/// Offset in screen coordinate +static Vec2d calc_screen_offset_to_volume_center(const Vec2d &screen_coor, const ModelVolume &volume, const Camera &camera) +{ + const Transform3d &volume_tr = volume.get_matrix(); + assert(volume.text_configuration.has_value()); + + auto calc_offset = [&screen_coor, &volume_tr, &camera, &volume](const Transform3d &instrance_tr) -> Vec2d { + Transform3d to_world = instrance_tr * volume_tr; + + // Use fix of .3mf loaded tranformation when exist + if (volume.text_configuration->fix_3mf_tr.has_value()) + to_world = to_world * (*volume.text_configuration->fix_3mf_tr); + // zero point of volume in world coordinate system + Vec3d volume_center = to_world.translation(); + // screen coordinate of volume center + Vec2i coor = CameraUtils::project(camera, volume_center); + return coor.cast() - screen_coor; + }; + + auto object = volume.get_object(); + assert(!object->instances.empty()); + // Speed up for one instance + if (object->instances.size() == 1) + return calc_offset(object->instances.front()->get_matrix()); + + Vec2d nearest_offset; + double nearest_offset_size = std::numeric_limits::max(); + for (const ModelInstance *instance : object->instances) { + Vec2d offset = calc_offset(instance->get_matrix()); + double offset_size = offset.norm(); + if (nearest_offset_size < offset_size) + continue; + nearest_offset_size = offset_size; + nearest_offset = offset; + } + return nearest_offset; +} + + // Calculate scale in world for check in debug +[[maybe_unused]] static std::optional calc_scale(const Matrix3d &from, const Matrix3d &to, const Vec3d &dir) +{ + Vec3d from_dir = from * dir; + Vec3d to_dir = to * dir; + double from_scale_sq = from_dir.squaredNorm(); + double to_scale_sq = to_dir.squaredNorm(); + if (is_approx(from_scale_sq, to_scale_sq, 1e-3)) + return {}; // no scale + return sqrt(from_scale_sq / to_scale_sq); +} + +bool on_mouse_surface_drag(const wxMouseEvent &mouse_event, + const Camera &camera, + std::optional &surface_drag, + GLCanvas3D &canvas, + RaycastManager &raycast_manager, + std::optional up_limit) +{ + // Fix when leave window during dragging + // Fix when click right button + if (surface_drag.has_value() && !mouse_event.Dragging()) { + // write transformation from UI into model + canvas.do_move(L("Surface move")); + + // allow moving with object again + canvas.enable_moving(true); + canvas.enable_picking(true); + surface_drag.reset(); + + // only left up is correct + // otherwise it is fix state and return false + return mouse_event.LeftUp(); + } + + if (mouse_event.Moving()) + return false; + + // detect start text dragging + if (mouse_event.LeftDown()) { + // selected volume + GLVolume *gl_volume = get_selected_gl_volume(canvas); + if (gl_volume == nullptr) + return false; + + // is selected volume closest hovered? + const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; + if (int hovered_idx = canvas.get_first_hover_volume_idx(); + hovered_idx < 0) + return false; + else if (auto hovered_idx_ = static_cast(hovered_idx); + hovered_idx_ >= gl_volumes.size() || + gl_volumes[hovered_idx_] != gl_volume) + return false; + + const ModelObject *object = get_model_object(*gl_volume, canvas.get_model()->objects); + assert(object != nullptr); + if (object == nullptr) + return false; + + const ModelInstance *instance = get_model_instance(*gl_volume, *object); + const ModelVolume *volume = get_model_volume(*gl_volume, *object); + assert(instance != nullptr && volume != nullptr); + if (object == nullptr || instance == nullptr || volume == nullptr) + return false; + + // allowed drag&drop by canvas for object + if (volume->is_the_only_one_part()) + return false; + + const ModelVolumePtrs &volumes = object->volumes; + std::vector allowed_volumes_id; + if (volumes.size() > 1) { + allowed_volumes_id.reserve(volumes.size() - 1); + for (auto &v : volumes) { + // skip actual selected object + if (v->id() == volume->id()) + continue; + // drag only above part not modifiers or negative surface + if (!v->is_model_part()) + continue; + allowed_volumes_id.emplace_back(v->id().id); + } + } + RaycastManager::AllowVolumes condition(std::move(allowed_volumes_id)); + RaycastManager::Meshes meshes = create_meshes(canvas, condition); + // initialize raycasters + // INFO: It could slows down for big objects + // (may be move to thread and do not show drag until it finish) + raycast_manager.actualize(*instance, &condition, &meshes); + + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + Vec2d mouse_offset = calc_screen_offset_to_volume_center(mouse_pos, *volume, camera); + + Transform3d volume_tr = gl_volume->get_volume_transformation().get_matrix(); + + if (volume->text_configuration.has_value()) { + const TextConfiguration &tc = *volume->text_configuration; + // fix baked transformation from .3mf store process + if (tc.fix_3mf_tr.has_value()) + volume_tr = volume_tr * tc.fix_3mf_tr->inverse(); + } + + Transform3d instance_tr = instance->get_matrix(); + Transform3d instance_tr_inv = instance_tr.inverse(); + Transform3d world_tr = instance_tr * volume_tr; + std::optional start_angle; + if (up_limit.has_value()) + start_angle = Emboss::calc_up(world_tr, *up_limit); + surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv, gl_volume, condition, start_angle}; + + // disable moving with object by mouse + canvas.enable_moving(false); + canvas.enable_picking(false); + return true; + } + + // Dragging starts out of window + if (!surface_drag.has_value()) + return false; + + if (mouse_event.Dragging()) { + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + Vec2d offseted_mouse = mouse_pos + surface_drag->mouse_offset; + + std::optional hit = ray_from_camera( + raycast_manager, offseted_mouse, camera, &surface_drag->condition); + + surface_drag->exist_hit = hit.has_value(); + if (!hit.has_value()) { + // cross hair need redraw + canvas.set_as_dirty(); + return true; + } + + auto world_linear = surface_drag->world.linear(); + // Calculate offset: transformation to wanted position + { + // Reset skew of the text Z axis: + // Project the old Z axis into a new Z axis, which is perpendicular to the old XY plane. + Vec3d old_z = world_linear.col(2); + Vec3d new_z = world_linear.col(0).cross(world_linear.col(1)); + world_linear.col(2) = new_z * (old_z.dot(new_z) / new_z.squaredNorm()); + } + + Vec3d text_z_world = world_linear.col(2); // world_linear * Vec3d::UnitZ() + auto z_rotation = Eigen::Quaternion::FromTwoVectors(text_z_world, hit->normal); + Transform3d world_new = z_rotation * surface_drag->world; + auto world_new_linear = world_new.linear(); + + // Fix direction of up vector to zero initial rotation + if(up_limit.has_value()){ + Vec3d z_world = world_new_linear.col(2); + z_world.normalize(); + Vec3d wanted_up = Emboss::suggest_up(z_world, *up_limit); + + Vec3d y_world = world_new_linear.col(1); + auto y_rotation = Eigen::Quaternion::FromTwoVectors(y_world, wanted_up); + + world_new = y_rotation * world_new; + world_new_linear = world_new.linear(); + } + + // Edit position from right + Transform3d volume_new{Eigen::Translation(surface_drag->instance_inv * hit->position)}; + volume_new.linear() = surface_drag->instance_inv.linear() * world_new_linear; + + // Check that transformation matrix is valid transformation + assert(volume_new.matrix()(0, 0) == volume_new.matrix()(0, 0)); // Check valid transformation not a NAN + if (volume_new.matrix()(0, 0) != volume_new.matrix()(0, 0)) + return true; + + // Check that scale in world did not changed + assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitY()).has_value()); + assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitZ()).has_value()); + + const ModelVolume *volume = get_model_volume(*surface_drag->gl_volume, canvas.get_model()->objects); + if (volume != nullptr && volume->text_configuration.has_value()) { + const TextConfiguration &tc = *volume->text_configuration; + // fix baked transformation from .3mf store process + if (tc.fix_3mf_tr.has_value()) + volume_new = volume_new * (*tc.fix_3mf_tr); + + // apply move in Z direction and rotation by up vector + Emboss::apply_transformation(surface_drag->start_angle, tc.style.prop.distance, volume_new); + } + + // Update transformation for all instances + for (GLVolume *vol : canvas.get_volumes().volumes) { + if (vol->object_idx() != surface_drag->gl_volume->object_idx() || vol->volume_idx() != surface_drag->gl_volume->volume_idx()) + continue; + vol->set_volume_transformation(volume_new); + } + + canvas.set_as_dirty(); + return true; + } + return false; +} + +std::optional calc_surface_offset(const Selection &selection, RaycastManager &raycast_manager) { + const GLVolume *gl_volume_ptr = get_selected_gl_volume(selection); + if (gl_volume_ptr == nullptr) + return {}; + const GLVolume& gl_volume = *gl_volume_ptr; + + const ModelObjectPtrs &objects = selection.get_model()->objects; + const ModelVolume* volume = get_model_volume(gl_volume, objects); + if (volume == nullptr) + return {}; + + const ModelInstance* instance = get_model_instance(gl_volume, objects); + if (instance == nullptr) + return {}; + + // Move object on surface + auto cond = RaycastManager::SkipVolume(volume->id().id); + raycast_manager.actualize(*instance, &cond); + + Transform3d to_world = world_matrix_fixed(gl_volume, selection.get_model()->objects); + Vec3d point = to_world * Vec3d::Zero(); + Vec3d direction = to_world.linear() * (-Vec3d::UnitZ()); + + // ray in direction of text projection(from volume zero to z-dir) + std::optional hit_opt = raycast_manager.closest_hit(point, direction, &cond); + + // Try to find closest point when no hit object in emboss direction + if (!hit_opt.has_value()) { + std::optional close_point_opt = raycast_manager.closest(point); + + // It should NOT appear. Closest point always exists. + assert(close_point_opt.has_value()); + if (!close_point_opt.has_value()) + return {}; + + // It is no neccesary to move with origin by very small value + if (close_point_opt->squared_distance < EPSILON) + return {}; + + const RaycastManager::ClosePoint &close_point = *close_point_opt; + Transform3d hit_tr = raycast_manager.get_transformation(close_point.tr_key); + Vec3d hit_world = hit_tr * close_point.point; + Vec3d offset_world = hit_world - point; // vector in world + Vec3d offset_volume = to_world.inverse().linear() * offset_world; + return offset_volume; + } + + // It is no neccesary to move with origin by very small value + const RaycastManager::Hit &hit = *hit_opt; + if (hit.squared_distance < EPSILON) + return {}; + Transform3d hit_tr = raycast_manager.get_transformation(hit.tr_key); + Vec3d hit_world = hit_tr * hit.position; + 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; + return offset_volume; +} + +Transform3d world_matrix_fixed(const GLVolume &gl_volume, const ModelObjectPtrs &objects) +{ + Transform3d res = gl_volume.world_matrix(); + + const ModelVolume *mv = get_model_volume(gl_volume, objects); + if (!mv) + return res; + + const std::optional &tc = mv->text_configuration; + if (!tc.has_value()) + return res; + + const std::optional &fix = tc->fix_3mf_tr; + if (!fix.has_value()) + return res; + + return res * fix->inverse(); +} + +Transform3d world_matrix_fixed(const Selection &selection) +{ + const GLVolume *gl_volume = get_selected_gl_volume(selection); + assert(gl_volume != nullptr); + if (gl_volume == nullptr) + return Transform3d::Identity(); + + return world_matrix_fixed(*gl_volume, selection.get_model()->objects); +} + +} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/SurfaceDrag.hpp b/src/slic3r/GUI/SurfaceDrag.hpp new file mode 100644 index 000000000..bb2600c28 --- /dev/null +++ b/src/slic3r/GUI/SurfaceDrag.hpp @@ -0,0 +1,90 @@ +#ifndef slic3r_SurfaceDrag_hpp_ +#define slic3r_SurfaceDrag_hpp_ + +#include +#include "libslic3r/Point.hpp" // Vec2d, Transform3d +#include "slic3r/Utils/RaycastManager.hpp" +#include "wx/event.h" // wxMouseEvent + +namespace Slic3r { +class GLVolume; +} // namespace Slic3r + +namespace Slic3r::GUI { +class GLCanvas3D; +class Selection; +struct Camera; + +// Data for drag&drop over surface with mouse +struct SurfaceDrag +{ + // hold screen coor offset of cursor from object center + Vec2d mouse_offset; + + // Start dragging text transformations to world + Transform3d world; + + // Invers transformation of text volume instance + // Help convert world transformation to instance space + Transform3d instance_inv; + + // Dragged gl volume + GLVolume *gl_volume; + + // condition for raycaster + RaycastManager::AllowVolumes condition; + + // initial rotation in Z axis of volume + std::optional start_angle; + + // Flag whether coordinate hit some volume + bool exist_hit = true; +}; + +/// +/// Mouse event handler, when move(drag&drop) volume over model surface +/// NOTE: Dragged volume has to be selected. And also has to be hovered on start of dragging. +/// +/// Contain type of event and mouse position +/// Actual viewport of camera +/// Structure which keep information about dragging +/// Contain gl_volumes and selection +/// AABB trees for raycast in object +/// Refresh state inside of function +/// When set than use correction of up vector +/// True when event is processed otherwise false +bool on_mouse_surface_drag(const wxMouseEvent &mouse_event, + const Camera &camera, + std::optional &surface_drag, + GLCanvas3D &canvas, + RaycastManager &raycast_manager, + std::optional up_limit = {}); + +/// +/// Calculate translation of volume onto surface of model +/// +/// Must contain only one selected volume, Transformation of current instance +/// AABB trees of object. Actualize object +/// Offset of volume in volume coordinate +std::optional calc_surface_offset(const Selection &selection, RaycastManager &raycast_manager); + +/// +/// Get transformation to world +/// - use fix after store to 3mf when exists +/// +/// Scene volume +/// To identify Model volume with fix transformation +/// Fixed Transformation of gl_volume +Transform3d world_matrix_fixed(const GLVolume &gl_volume, const ModelObjectPtrs& objects); + +/// +/// Get transformation to world +/// - use fix after store to 3mf when exists +/// NOTE: when not one volume selected return identity +/// +/// Selected volume +/// Fixed Transformation of selected volume in selection +Transform3d world_matrix_fixed(const Selection &selection); + +} // namespace Slic3r::GUI +#endif // slic3r_SurfaceDrag_hpp_ \ No newline at end of file diff --git a/src/slic3r/Utils/EmbossStyleManager.cpp b/src/slic3r/Utils/EmbossStyleManager.cpp index 9c6738ece..100a532b8 100644 --- a/src/slic3r/Utils/EmbossStyleManager.cpp +++ b/src/slic3r/Utils/EmbossStyleManager.cpp @@ -228,6 +228,43 @@ bool StyleManager::load_style(const EmbossStyle &style, const wxFont &font) return true; } +bool StyleManager::is_font_changed() const +{ + const wxFont &wx_font = get_wx_font(); + if (!wx_font.IsOk()) + return false; + if (!exist_stored_style()) + return false; + const EmbossStyle *stored_style = get_stored_style(); + if (stored_style == nullptr) + return false; + + const wxFont &wx_font_stored = get_stored_wx_font(); + if (!wx_font_stored.IsOk()) + return false; + + const FontProp &prop = get_style().prop; + const FontProp &prop_stored = stored_style->prop; + + // Exist change in face name? + if(wx_font_stored.GetFaceName() != wx_font.GetFaceName()) return true; + + const std::optional &skew = prop.skew; + bool is_italic = skew.has_value() || WxFontUtils::is_italic(wx_font); + const std::optional &skew_stored = prop_stored.skew; + bool is_stored_italic = skew_stored.has_value() || WxFontUtils::is_italic(wx_font_stored); + // is italic changed + if (is_italic != is_stored_italic) + return true; + + const std::optional &boldness = prop.boldness; + bool is_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font); + const std::optional &boldness_stored = prop_stored.boldness; + bool is_stored_bold = boldness_stored.has_value() || WxFontUtils::is_bold(wx_font_stored); + // is bold changed + return is_bold != is_stored_bold; +} + bool StyleManager::is_active_font() { return m_style_cache.font_file.has_value(); } const EmbossStyle* StyleManager::get_stored_style() const @@ -304,12 +341,15 @@ void StyleManager::init_trunc_names(float max_width) { } } -#include "slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp" - // for access to worker #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" +// for get DPI +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/MainFrame.hpp" +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" + void StyleManager::init_style_images(const Vec2i &max_size, const std::string &text) { @@ -361,8 +401,15 @@ void StyleManager::init_style_images(const Vec2i &max_size, style.prop }); } + + auto mf = wxGetApp().mainframe; + // dot per inch for monitor + int dpi = get_dpi_for_window(mf); + // pixel per milimeter + double ppm = dpi / ObjectManipulation::in_to_mm; + auto &worker = wxGetApp().plater()->get_ui_job_worker(); - StyleImagesData data{std::move(styles), max_size, text, m_temp_style_images}; + StyleImagesData data{std::move(styles), max_size, text, m_temp_style_images, ppm}; queue_job(worker, std::make_unique(std::move(data))); } @@ -484,7 +531,7 @@ bool StyleManager::set_wx_font(const wxFont &wx_font) { bool StyleManager::set_wx_font(const wxFont &wx_font, std::unique_ptr font_file) { if (font_file == nullptr) return false; - m_style_cache.wx_font = wx_font; // copy + m_style_cache.wx_font = wx_font; // copy m_style_cache.font_file = FontFileWithCache(std::move(font_file)); diff --git a/src/slic3r/Utils/EmbossStyleManager.hpp b/src/slic3r/Utils/EmbossStyleManager.hpp index 8183214e9..dd6b9ca12 100644 --- a/src/slic3r/Utils/EmbossStyleManager.hpp +++ b/src/slic3r/Utils/EmbossStyleManager.hpp @@ -116,15 +116,21 @@ public: const ImFontAtlas &get_atlas() const { return m_style_cache.atlas; } const FontProp &get_font_prop() const { return get_style().prop; } FontProp &get_font_prop() { return get_style().prop; } - const std::optional &get_wx_font() const { return m_style_cache.wx_font; } - const std::optional &get_stored_wx_font() const { return m_style_cache.stored_wx_font; } + const wxFont &get_wx_font() const { return m_style_cache.wx_font; } + const wxFont &get_stored_wx_font() const { return m_style_cache.stored_wx_font; } Slic3r::Emboss::FontFileWithCache &get_font_file_with_cache() { return m_style_cache.font_file; } bool has_collections() const { return m_style_cache.font_file.font_file != nullptr && m_style_cache.font_file.font_file->infos.size() > 1; } // True when activ style has same name as some of stored style bool exist_stored_style() const { return m_style_cache.style_index != std::numeric_limits::max(); } - + + /// + /// check whether current style differ to selected + /// + /// + bool is_font_changed() const; + /// /// Setter on wx_font when changed /// @@ -221,7 +227,7 @@ private: ImFontAtlas atlas = {}; // wx widget font - std::optional wx_font = {}; + wxFont wx_font = {}; // cache for view font name with maximal width in imgui std::string truncated_name; @@ -230,7 +236,7 @@ private: EmbossStyle style = {}; // cache for stored wx font to not create every frame - std::optional stored_wx_font; + wxFont stored_wx_font = {}; // index into m_style_items size_t style_index = std::numeric_limits::max(); @@ -277,6 +283,9 @@ private: // place to store result in main thread in Finalize std::shared_ptr result; + + // pixel per milimeter (scaled DPI) + double ppm; }; std::shared_ptr m_temp_style_images; bool m_exist_style_images; diff --git a/src/slic3r/Utils/RaycastManager.cpp b/src/slic3r/Utils/RaycastManager.cpp index 4132fd647..b2b758855 100644 --- a/src/slic3r/Utils/RaycastManager.cpp +++ b/src/slic3r/Utils/RaycastManager.cpp @@ -1,54 +1,45 @@ #include "RaycastManager.hpp" #include -// include for earn camera -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/CameraUtils.hpp" -using namespace Slic3r::GUI; +using namespace Slic3r::GUI; -void RaycastManager::actualize(const ModelObject *object, const ISkip *skip) +namespace{ +using namespace Slic3r; +void actualize(RaycastManager::Meshes &meshes, const ModelVolumePtrs &volumes, const RaycastManager::ISkip *skip, RaycastManager::Meshes *input = nullptr); +const AABBMesh * get_mesh(const RaycastManager::Meshes &meshes, size_t volume_id); +RaycastManager::TrKey create_key(const ModelVolume& volume, const ModelInstance& instance){ + return std::make_pair(instance.id().id, volume.id().id); } +RaycastManager::TrItems::iterator find(RaycastManager::TrItems &items, const RaycastManager::TrKey &key); +RaycastManager::TrItems::const_iterator find(const RaycastManager::TrItems &items, const RaycastManager::TrKey &key); +bool is_lower_key(const RaycastManager::TrKey &k1, const RaycastManager::TrKey &k2) { + return k1.first < k2.first || (k1.first == k2.first && k1.second < k2.second); } +bool is_lower(const RaycastManager::TrItem &i1, const RaycastManager::TrItem &i2) { + return is_lower_key(i1.first, i2.first); }; +template inline void erase(std::vector &vec, const std::vector &flags); +} + +void RaycastManager::actualize(const ModelObject &object, const ISkip *skip, Meshes *meshes) { - // check if volume was removed - std::vector removed_casters(m_raycasters.size(), {true}); + // actualize MeshRaycaster + ::actualize(m_meshes, object.volumes, skip, meshes); + // check if inscance was removed std::vector removed_transf(m_transformations.size(), {true}); - - // actualize MeshRaycaster - for (const ModelVolume *volume : object->volumes) { - size_t oid = volume->id().id; - if (skip != nullptr && skip->skip(oid)) - continue; - auto item = std::find_if(m_raycasters.begin(), m_raycasters.end(), - [oid](const RaycastManager::Raycaster &it)->bool { - return oid == it.first; - }); - if (item == m_raycasters.end()) { - // add new raycaster - auto raycaster = std::make_unique(volume->get_mesh_shared_ptr()); - m_raycasters.emplace_back(std::make_pair(oid, std::move(raycaster))); - } else { - size_t index = item - m_raycasters.begin(); - removed_casters[index] = false; - } - } + bool need_sort = false; // actualize transformation matrices - for (const ModelVolume *volume : object->volumes) { + for (const ModelVolume *volume : object.volumes) { if (skip != nullptr && skip->skip(volume->id().id)) continue; const Transform3d &volume_tr = volume->get_matrix(); - for (const ModelInstance *instance : object->instances) { - const Transform3d &instrance_tr = instance->get_matrix(); - Transform3d transformation = instrance_tr * volume_tr; - // TODO: add SLA shift Z - // transformation.translation()(2) += m_sla_shift_z; - TrKey tr_key = std::make_pair(instance->id().id, volume->id().id); - auto item = std::find_if(m_transformations.begin(), - m_transformations.end(), - [&tr_key](const TrItem &it) -> bool { - return it.first == tr_key; - }); + for (const ModelInstance *instance : object.instances) { + const Transform3d &instrance_tr = instance->get_matrix(); + Transform3d transformation = instrance_tr * volume_tr; + TrKey key = ::create_key(*volume, *instance); + auto item = ::find(m_transformations, key); if (item != m_transformations.end()) { // actualize transformation all the time item->second = transformation; @@ -56,71 +47,128 @@ void RaycastManager::actualize(const ModelObject *object, const ISkip *skip) removed_transf[index] = false; } else { // add new transformation - m_transformations.emplace_back( - std::make_pair(tr_key, transformation)); + m_transformations.emplace_back(key, transformation); + need_sort = true; } } } - // clean other raycasters - for (int i = removed_casters.size() - 1; i >= 0; --i) - if (removed_casters[i]) - m_raycasters.erase(m_raycasters.begin() + i); - // clean other transformation - for (int i = removed_transf.size() - 1; i >= 0; --i) - if (removed_transf[i]) - m_transformations.erase(m_transformations.begin() + i); + ::erase(m_transformations, removed_transf); + + if (need_sort) + std::sort(m_transformations.begin(), m_transformations.end(), ::is_lower); } -std::optional RaycastManager::unproject( - const Vec2d &mouse_pos, const Camera &camera, const ISkip *skip) const +void RaycastManager::actualize(const ModelInstance &instance, const ISkip *skip, Meshes *meshes) { - std::optional closest; - for (const auto &item : m_transformations) { - const TrKey &key = item.first; - size_t volume_id = key.second; - if (skip != nullptr && skip->skip(volume_id)) continue; - const Transform3d &transformation = item.second; - auto raycaster_it = - std::find_if(m_raycasters.begin(), m_raycasters.end(), - [volume_id](const RaycastManager::Raycaster &it) - -> bool { return volume_id == it.first; }); - if (raycaster_it == m_raycasters.end()) continue; - const MeshRaycaster &raycaster = *(raycaster_it->second); - SurfacePoint surface_point; - bool success = raycaster.unproject_on_mesh( - mouse_pos, transformation, camera, - surface_point.position, surface_point.normal); - if (!success) continue; + const ModelVolumePtrs &volumes = instance.get_object()->volumes; - Vec3d act_hit_tr = transformation * surface_point.position.cast(); - double squared_distance = (camera.get_position() - act_hit_tr).squaredNorm(); - if (closest.has_value() && - closest->squared_distance < squared_distance) + // actualize MeshRaycaster + ::actualize(m_meshes, volumes, skip, meshes); + + // check if inscance was removed + std::vector removed_transf(m_transformations.size(), {true}); + + bool need_sort = false; + // actualize transformation matrices + for (const ModelVolume *volume : volumes) { + if (skip != nullptr && skip->skip(volume->id().id)) continue; - closest = Hit(key, surface_point, squared_distance); + const Transform3d &volume_tr = volume->get_matrix(); + const Transform3d &instrance_tr = instance.get_matrix(); + Transform3d transformation = instrance_tr * volume_tr; + TrKey key = ::create_key(*volume, instance); + auto item = ::find(m_transformations, key); + if (item != m_transformations.end()) { + // actualize transformation all the time + item->second = transformation; + size_t index = item - m_transformations.begin(); + removed_transf[index] = false; + } else { + // add new transformation + m_transformations.emplace_back(key, transformation); + need_sort = true; + } } - //if (!closest.has_value()) return {}; - return closest; + // clean other transformation + ::erase(m_transformations, removed_transf); + + if (need_sort) + std::sort(m_transformations.begin(), m_transformations.end(), ::is_lower); +} + +std::optional RaycastManager::first_hit(const Vec3d& point, const Vec3d& direction, const ISkip *skip) const +{ + // Improve: it is not neccessaru to use AABBMesh and calc normal for every hit + + // Results + const AABBMesh *hit_mesh = nullptr; + double hit_squared_distance = 0.; + int hit_face = -1; + Vec3d hit_world; + const Transform3d *hit_tramsformation = nullptr; + const TrKey *hit_key = nullptr; + + for (const auto &[key, transformation]: m_transformations) { + size_t volume_id = key.second; + if (skip != nullptr && skip->skip(volume_id)) continue; + const AABBMesh *mesh = ::get_mesh(m_meshes, volume_id); + if (mesh == nullptr) continue; + Transform3d inv = transformation.inverse(); + + // transform input into mesh world + Vec3d point_ = inv * point; + Vec3d direction_= inv.linear() * direction; + + std::vector hits = mesh->query_ray_hits(point_, direction_); + if (hits.empty()) continue; // no intersection found + + const AABBMesh::hit_result &hit = hits.front(); + + // convert to world + Vec3d world = transformation * hit.position(); + double squared_distance = (point - world).squaredNorm(); + if (hit_mesh != nullptr && + hit_squared_distance < squared_distance) + continue; // exist closer one + + hit_mesh = mesh; + hit_squared_distance = squared_distance; + hit_face = hit.face(); + hit_world = world; + hit_tramsformation = &transformation; + hit_key = &key; + } + + if (hit_mesh == nullptr) + return {}; + + // Calculate normal from transformed triangle + // NOTE: Anisotropic transformation of normal is not perpendiculat to triangle + const Vec3i tri = hit_mesh->indices(hit_face); + std::array pts; + auto tr = hit_tramsformation->linear(); + for (int i = 0; i < 3; ++i) + pts[i] = tr * hit_mesh->vertices(tri[i]).cast(); + Vec3d normal_world = (pts[1] - pts[0]).cross(pts[2] - pts[1]); + if (has_reflection(*hit_tramsformation)) + normal_world *= -1; + normal_world.normalize(); + + SurfacePoint point_world{hit_world, normal_world}; + return RaycastManager::Hit{point_world, *hit_key, hit_squared_distance}; } -std::optional RaycastManager::unproject(const Vec3d &point, const Vec3d &direction, const ISkip *skip) const +std::optional RaycastManager::closest_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip) const { std::optional closest; - for (const auto &item : m_transformations) { - const TrKey &key = item.first; - size_t volume_id = key.second; + for (const auto &[key, transformation] : m_transformations) { + size_t volume_id = key.second; if (skip != nullptr && skip->skip(volume_id)) continue; - const Transform3d &transformation = item.second; - auto raycaster_it = - std::find_if(m_raycasters.begin(), m_raycasters.end(), - [volume_id](const RaycastManager::Raycaster &it) - -> bool { return volume_id == it.first; }); - if (raycaster_it == m_raycasters.end()) continue; - const MeshRaycaster &raycaster = *(raycaster_it->second); - const AABBMesh& mesh = raycaster.get_aabb_mesh(); + const AABBMesh *mesh = ::get_mesh(m_meshes, volume_id); + if (mesh == nullptr) continue; Transform3d tr_inv = transformation.inverse(); Vec3d mesh_point = tr_inv * point; Vec3d mesh_direction = tr_inv.linear() * direction; @@ -130,54 +178,185 @@ std::optional RaycastManager::unproject(const Vec3d &point, Vec3d point_negative = mesh_point + mesh_direction; // Throw ray to both directions of ray - std::vector hits = mesh.query_ray_hits(point_positive, mesh_direction); - std::vector hits_neg = mesh.query_ray_hits(point_negative, -mesh_direction); + std::vector hits = mesh->query_ray_hits(point_positive, mesh_direction); + std::vector hits_neg = mesh->query_ray_hits(point_negative, -mesh_direction); hits.insert(hits.end(), std::make_move_iterator(hits_neg.begin()), std::make_move_iterator(hits_neg.end())); for (const AABBMesh::hit_result &hit : hits) { double squared_distance = (mesh_point - hit.position()).squaredNorm(); if (closest.has_value() && closest->squared_distance < squared_distance) continue; - SurfacePoint surface_point(hit.position().cast(), hit.normal().cast()); - closest = Hit(key, surface_point, squared_distance); + closest = Hit{{hit.position(), hit.normal()}, key, squared_distance}; } } return closest; } -std::optional RaycastManager::closest(const Vec3d &point, const ISkip *skip) const { - std::optional closest; - for (const auto &item : m_transformations) { - const TrKey &key = item.first; +std::optional RaycastManager::closest(const Vec3d &point, const ISkip *skip) const +{ + std::optional closest; + for (const auto &[key, transformation] : m_transformations) { size_t volume_id = key.second; if (skip != nullptr && skip->skip(volume_id)) continue; - auto raycaster_it = std::find_if(m_raycasters.begin(), m_raycasters.end(), - [volume_id](const RaycastManager::Raycaster &it) -> bool { return volume_id == it.first; }); - if (raycaster_it == m_raycasters.end()) - continue; - const MeshRaycaster &raycaster = *(raycaster_it->second); - const Transform3d &transformation = item.second; + const AABBMesh *mesh = ::get_mesh(m_meshes, volume_id); + if (mesh == nullptr) continue; Transform3d tr_inv = transformation.inverse(); - Vec3d mesh_point_d = tr_inv * point; - Vec3f mesh_point_f = mesh_point_d.cast(); - Vec3f n; - Vec3f p = raycaster.get_closest_point(mesh_point_f, &n); - double squared_distance = (mesh_point_f - p).squaredNorm(); + Vec3d mesh_point = tr_inv * point; + + int face_idx = 0; + Vec3d closest_point; + Vec3d pointd = point.cast(); + mesh->squared_distance(pointd, face_idx, closest_point); + + double squared_distance = (mesh_point - closest_point).squaredNorm(); if (closest.has_value() && closest->squared_distance < squared_distance) continue; - SurfacePoint surface_point(p,n); - closest = Hit(key, surface_point, squared_distance); + + closest = ClosePoint{key, closest_point, squared_distance}; } return closest; } Slic3r::Transform3d RaycastManager::get_transformation(const TrKey &tr_key) const { - auto item = std::find_if(m_transformations.begin(), - m_transformations.end(), - [&tr_key](const TrItem &it) -> bool { - return it.first == tr_key; - }); - if (item == m_transformations.end()) return Transform3d::Identity(); - return item->second; -} \ No newline at end of file + auto tr = ::find(m_transformations, tr_key); + if (tr == m_transformations.end()) + return Transform3d::Identity(); + return tr->second; +} + + +namespace { +void actualize(RaycastManager::Meshes &meshes, const ModelVolumePtrs &volumes, const RaycastManager::ISkip *skip, RaycastManager::Meshes* inputs) +{ + // check if volume was removed + std::vector removed_meshes(meshes.size(), {true}); + bool need_sort = false; + // actualize MeshRaycaster + for (const ModelVolume *volume : volumes) { + size_t oid = volume->id().id; + if (skip != nullptr && skip->skip(oid)) + continue; + auto is_oid = [oid](const RaycastManager::Mesh &it) { return oid == it.first; }; + if (auto item = std::find_if(meshes.begin(), meshes.end(), is_oid); + item != meshes.end()) { + size_t index = item - meshes.begin(); + removed_meshes[index] = false; + continue; + } + + // exist AABB in inputs ? + if (inputs != nullptr) { + auto input = std::find_if(inputs->begin(), inputs->end(), is_oid); + if (input != inputs->end()) { + meshes.emplace_back(std::move(*input)); + need_sort = true; + continue; + } + } + + // add new raycaster + bool calculate_epsilon = true; + auto mesh = std::make_unique(volume->mesh(), calculate_epsilon); + meshes.emplace_back(std::make_pair(oid, std::move(mesh))); + need_sort = true; + } + + // clean other raycasters + erase(meshes, removed_meshes); + + // All the time meshes must be sorted by volume id - for faster search + if (need_sort) { + auto is_lower = [](const RaycastManager::Mesh &m1, const RaycastManager::Mesh &m2) { return m1.first < m2.first; }; + std::sort(meshes.begin(), meshes.end(), is_lower); + } +} + +const Slic3r::AABBMesh *get_mesh(const RaycastManager::Meshes &meshes, size_t volume_id) +{ + auto is_lower_index = [](const RaycastManager::Mesh &m, size_t i) { return m.first < i; }; + auto it = std::lower_bound(meshes.begin(), meshes.end(), volume_id, is_lower_index); + if (it == meshes.end() || it->first != volume_id) + return nullptr; + return &(*(it->second)); +} + +RaycastManager::TrItems::iterator find(RaycastManager::TrItems &items, const RaycastManager::TrKey &key) { + auto fnc = [](const RaycastManager::TrItem &it, const RaycastManager::TrKey &l_key) { return is_lower_key(it.first, l_key); }; + auto it = std::lower_bound(items.begin(), items.end(), key, fnc); + if (it != items.end() && it->first != key) + return items.end(); + return it; +} + +RaycastManager::TrItems::const_iterator find(const RaycastManager::TrItems &items, const RaycastManager::TrKey &key) +{ + auto fnc = [](const RaycastManager::TrItem &it, const RaycastManager::TrKey &l_key) { return is_lower_key(it.first, l_key); }; + auto it = std::lower_bound(items.begin(), items.end(), key, fnc); + if (it != items.end() && it->first != key) + return items.end(); + return it; +} + +template inline void erase(std::vector &vec, const std::vector &flags) +{ + if (vec.size() < flags.size() || flags.empty()) + return; + + // reverse iteration over flags to erase indices from back to front. + for (int i = static_cast(flags.size()) - 1; i >= 0; --i) + if (flags[i]) + vec.erase(vec.begin() + i); +} + +} // namespace + +namespace Slic3r::GUI{ + +RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition) +{ + SceneRaycaster::EType type = SceneRaycaster::EType::Volume; + auto scene_casters = canvas.get_raycasters_for_picking(type); + if (scene_casters == nullptr) + return {}; + const std::vector> &casters = *scene_casters; + + const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; + const ModelObjectPtrs &objects = canvas.get_model()->objects; + + RaycastManager::Meshes meshes; + for (const std::shared_ptr &caster : casters) { + int index = SceneRaycaster::decode_id(type, caster->get_id()); + if (index < 0) + continue; + auto index_ = static_cast(index); + if(index_ >= gl_volumes.size()) + continue; + const GLVolume *gl_volume = gl_volumes[index_]; + if (gl_volume == nullptr) + continue; + const ModelVolume *volume = get_model_volume(*gl_volume, objects); + if (volume == nullptr) + continue; + size_t id = volume->id().id; + if (condition.skip(id)) + continue; + auto mesh = std::make_unique(caster->get_raycaster()->get_aabb_mesh()); + meshes.emplace_back(std::make_pair(id, std::move(mesh))); + } + return meshes; +} + + +std::optional ray_from_camera(const RaycastManager &raycaster, + const Vec2d &mouse_pos, + const Camera &camera, + const RaycastManager::ISkip *skip) +{ + Vec3d point; + Vec3d direction; + CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); + return raycaster.first_hit(point, direction, skip); +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/Utils/RaycastManager.hpp b/src/slic3r/Utils/RaycastManager.hpp index a185f6307..41ec82d6c 100644 --- a/src/slic3r/Utils/RaycastManager.hpp +++ b/src/slic3r/Utils/RaycastManager.hpp @@ -2,9 +2,8 @@ #define slic3r_RaycastManager_hpp_ #include // unique_ptr -#include // unique_ptr -#include -#include "slic3r/GUI/MeshUtils.hpp" // MeshRaycaster +#include +#include "libslic3r/AABBMesh.hpp" // Structure to cast rays #include "libslic3r/Point.hpp" // Transform3d #include "libslic3r/ObjectID.hpp" #include "libslic3r/Model.hpp" // ModelObjectPtrs, ModelObject, ModelInstance, ModelVolume @@ -17,19 +16,22 @@ namespace Slic3r::GUI{ /// class RaycastManager { - // ModelVolume.id - using Raycaster = std::pair >; - std::vector m_raycasters; +// Public structures used by RaycastManager +public: - // Key for transformation consist of unique volume and instance + // ModelVolume.id + using Mesh = std::pair >; + using Meshes = std::vector; + + // Key for transformation consist of unique volume and instance id ... ObjectId() // ModelInstance, ModelVolume using TrKey = std::pair; using TrItem = std::pair; - std::vector m_transformations; + using TrItems = std::vector; - // should contain shared pointer to camera but it is not shared pointer so it need it every time when casts rays - -public: + /// + /// Interface for identify allowed volumes to cast rays. + /// class ISkip{ public: virtual ~ISkip() = default; @@ -42,6 +44,39 @@ public: virtual bool skip(const size_t &model_volume_id) const { return false; } }; + // TODO: it is more general object move outside of this class + template + struct SurfacePoint { + using Vec3 = Eigen::Matrix; + Vec3 position = Vec3::Zero(); + Vec3 normal = Vec3::UnitZ(); + }; + + struct Hit : public SurfacePoint + { + TrKey tr_key; + double squared_distance; + }; + + struct ClosePoint + { + TrKey tr_key; + Vec3d point; + double squared_distance; + }; + +// Members +private: + // Keep structure to fast cast rays + // meshes are sorted by volume_id for faster search + Meshes m_meshes; + + // Keep transformation of meshes + TrItems m_transformations; + // Note: one mesh could have more transformations ... instances + +public: + /// /// Actualize raycasters + transformation /// Detection of removed object @@ -50,28 +85,9 @@ public: /// /// Model representation /// Condifiton for skip actualization - void actualize(const ModelObject *object, const ISkip *skip = nullptr); - - // TODO: it is more general object move outside of this class - struct SurfacePoint - { - Vec3f position = Vec3f::Zero(); - Vec3f normal = Vec3f::UnitZ(); - SurfacePoint() = default; - SurfacePoint(Vec3f position, Vec3f normal) - : position(position), normal(normal) - {} - }; - - struct Hit: public SurfacePoint - { - using Key = TrKey; - 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) - {} - }; + /// Speed up for already created AABBtrees + void actualize(const ModelObject &object, const ISkip *skip = nullptr, Meshes *meshes = nullptr); + void actualize(const ModelInstance &instance, const ISkip *skip = nullptr, Meshes* meshes = nullptr); class SkipVolume: public ISkip { @@ -93,26 +109,24 @@ public: }; /// - /// Unproject on mesh by Mesh raycasters - /// Note: Function use current camera position from wxGetApp() + /// Unproject on mesh and return closest hit to point in given direction /// - /// Position of mouse on screen - /// Projection params + /// Position in space + /// Casted ray direction /// Define which caster will be skipped, null mean no skip - /// Position on surface, normal direction and transformation key, which define hitted object instance - std::optional unproject(const Vec2d &mouse_pos, - const Camera &camera, - const ISkip *skip = nullptr) const; + /// Position on surface, normal direction in world coorinate + /// + key, to know hitted instance and volume + std::optional first_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const; /// - /// Unproject Ray(point direction) on mesh by MeshRaycasters + /// Unproject Ray(point direction) on mesh to find closest hit of surface in given direction /// NOTE: It inspect also oposit direction of ray !! /// /// Start point for ray - /// Direction of ray + /// Direction of ray, orientation doesn't matter, both are used /// Define which caster will be skipped, null mean no skip /// Position on surface, normal direction and transformation key, which define hitted object instance - std::optional unproject(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const; + std::optional closest_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const; /// /// Search of closest point @@ -120,16 +134,39 @@ public: /// Point /// Define which caster will be skipped, null mean no skip /// - std::optional closest(const Vec3d &point, const ISkip *skip = nullptr) const; + std::optional closest(const Vec3d &point, const ISkip *skip = nullptr) const; /// - /// Getter on transformation + /// Getter on transformation from hitted volume to world /// /// Define transformation /// Transformation for key Transform3d get_transformation(const TrKey &tr_key) const; }; +class GLCanvas3D; +/// +/// Use scene Raycasters and prepare data for actualize RaycasterManager +/// +/// contain Scene raycasters +/// Limit for scene casters +/// Meshes +RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition); + +struct Camera; +/// +/// Unproject on mesh by Mesh raycasters +/// +/// Position of mouse on screen +/// Projection params +/// Define which caster will be skipped, null mean no skip +/// Position on surface, normal direction in world coorinate +/// + key, to know hitted instance and volume +std::optional ray_from_camera(const RaycastManager &raycaster, + const Vec2d &mouse_pos, + const Camera &camera, + const RaycastManager::ISkip *skip); + } // namespace Slic3r::GUI #endif // slic3r_RaycastManager_hpp_ diff --git a/src/slic3r/Utils/WxFontUtils.cpp b/src/slic3r/Utils/WxFontUtils.cpp index a56f33141..1c191606d 100644 --- a/src/slic3r/Utils/WxFontUtils.cpp +++ b/src/slic3r/Utils/WxFontUtils.cpp @@ -55,13 +55,11 @@ static std::string get_file_path(const wxFont& font) { if (url == NULL) return {}; wxString file_uri; wxCFTypeRef(url).GetValue(file_uri); - std::string file_path(wxURI::Unescape(file_uri).c_str()); - size_t start = std::string("file://").size(); - if (file_path.empty() || file_path.size() <= start) - return {}; - // remove prefix file:// - file_path = file_path.substr(start, file_path.size() - start); - return file_path; + wxURI uri(file_uri); + const wxString &path = uri.GetPath(); + std::string path_str(wxURI::Unescape(path).c_str()); + BOOST_LOG_TRIVIAL(trace) << "input uri(" << file_uri.c_str() << ") convert to path(" << path.c_str() << ") string(" << path_str << ")."; + return path_str; } #endif // __APPLE__ } // namespace @@ -171,13 +169,23 @@ std::string WxFontUtils::store_wxFont(const wxFont &font) { // wxString os = wxPlatformInfo::Get().GetOperatingSystemIdName(); wxString font_descriptor = font.GetNativeFontInfoDesc(); + BOOST_LOG_TRIVIAL(trace) << "'" << font_descriptor << "' wx string get from GetNativeFontInfoDesc. wxFont " << + "IsOk(" << font.IsOk() << "), " << + "isNull(" << font.IsNull() << ")" << + // "IsFree(" << font.IsFree() << "), " << // on MacOs is no function is free + "IsFixedWidth(" << font.IsFixedWidth() << "), " << + "IsUsingSizeInPixels(" << font.IsUsingSizeInPixels() << "), " << + "Encoding(" << (int)font.GetEncoding() << "), " ; return std::string(font_descriptor.c_str()); } wxFont WxFontUtils::load_wxFont(const std::string &font_descriptor) { + BOOST_LOG_TRIVIAL(trace) << "'" << font_descriptor << "'font descriptor string param of load_wxFont()"; wxString font_descriptor_wx(font_descriptor); + BOOST_LOG_TRIVIAL(trace) << "'" << font_descriptor_wx.c_str() << "' wx string descriptor"; wxFont wx_font(font_descriptor_wx); + BOOST_LOG_TRIVIAL(trace) << "loaded font is '" << get_human_readable_name(wx_font) << "'."; return wx_font; } diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index 5248e089a..e40657d16 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -113,6 +113,18 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { SECTION("boolean expression parser: greater than or equal - false") { REQUIRE(! boolean_expression("12 >= 22")); } SECTION("boolean expression parser: lower than or equal (same values) - true") { REQUIRE(boolean_expression("12 <= 12")); } SECTION("boolean expression parser: greater than or equal (same values) - true") { REQUIRE(boolean_expression("12 >= 12")); } + SECTION("boolean expression parser: one_of(\"a\", \"a\", \"b\", \"c\")") { REQUIRE(boolean_expression("one_of(\"a\", \"a\", \"b\", \"c\")")); } + SECTION("boolean expression parser: one_of(\"b\", \"a\", \"b\", \"c\")") { REQUIRE(boolean_expression("one_of(\"b\", \"a\", \"b\", \"c\")")); } + SECTION("boolean expression parser: one_of(\"c\", \"a\", \"b\", \"c\")") { REQUIRE(boolean_expression("one_of(\"c\", \"a\", \"b\", \"c\")")); } + SECTION("boolean expression parser: one_of(\"d\", \"a\", \"b\", \"c\")") { REQUIRE(! boolean_expression("one_of(\"d\", \"a\", \"b\", \"c\")")); } + SECTION("boolean expression parser: one_of(\"a\")") { REQUIRE(! boolean_expression("one_of(\"a\")")); } + SECTION("boolean expression parser: one_of(\"a\", \"a\")") { REQUIRE(boolean_expression("one_of(\"a\", \"a\")")); } + SECTION("boolean expression parser: one_of(\"b\", \"a\")") { REQUIRE(! boolean_expression("one_of(\"b\", \"a\")")); } + SECTION("boolean expression parser: one_of(\"abcdef\", /.*c.*/)") { REQUIRE(boolean_expression("one_of(\"abcdef\", /.*c.*/)")); } + SECTION("boolean expression parser: one_of(\"abcdef\", /.*f.*/, /.*c.*/)") { REQUIRE(boolean_expression("one_of(\"abcdef\", /.*f.*/, /.*c.*/)")); } + SECTION("boolean expression parser: one_of(\"abcdef\", ~\".*f.*\", ~\".*c.*\")") { REQUIRE(boolean_expression("one_of(\"abcdef\", ~\".*f.*\", ~\".*c.*\")")); } + SECTION("boolean expression parser: one_of(\"ghij\", /.*f.*/, /.*c.*/)") { REQUIRE(! boolean_expression("one_of(\"ghij\", /.*f.*/, /.*c.*/)")); } + SECTION("boolean expression parser: one_of(\"ghij\", ~\".*f.*\", ~\".*c.*\")") { REQUIRE(! boolean_expression("one_of(\"ghij\", ~\".*f.*\", ~\".*c.*\")")); } SECTION("complex expression") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1")); } SECTION("complex expression2") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.6 and num_extruders>1)")); } SECTION("complex expression3") { REQUIRE(! boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.3 and num_extruders>1)")); }