Merge branch 'master' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_show_sla_supports
This commit is contained in:
commit
ddf636fe38
@ -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
|
||||
|
@ -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
|
||||
|
@ -165,9 +165,9 @@ inline std::vector<std::pair<VectorType, size_t>> 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<typename LineType>
|
||||
inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(const std::vector<LineType> &lines)
|
||||
inline AABBTreeIndirect::Tree<LineType::Dim, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(const std::vector<LineType> &lines)
|
||||
{
|
||||
using TreeType = AABBTreeIndirect::Tree<2, typename LineType::Scalar>;
|
||||
using TreeType = AABBTreeIndirect::Tree<LineType::Dim, typename LineType::Scalar>;
|
||||
// using CoordType = typename TreeType::CoordType;
|
||||
using VectorType = typename TreeType::VectorType;
|
||||
using BoundingBox = typename TreeType::BoundingBox;
|
||||
|
@ -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<float>& angle, const std::optional<float>& 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<Vec2d> 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<double>();
|
||||
// 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<float> 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<double>());
|
||||
transform.translate(position);
|
||||
transform.rotate(view_rot);
|
||||
transform.rotate(up_rot);
|
||||
return transform;
|
||||
|
@ -193,6 +193,7 @@ namespace Emboss
|
||||
/// Z-rotation as angle to Y axis(FontProp::angle)</param>
|
||||
/// <param name="transformation">In / Out transformation to modify by property</param>
|
||||
void apply_transformation(const FontProp &font_prop, Transform3d &transformation);
|
||||
void apply_transformation(const std::optional<float> &angle, const std::optional<float> &distance, Transform3d &transformation);
|
||||
|
||||
/// <summary>
|
||||
/// Read information from naming table of font file
|
||||
@ -273,7 +274,23 @@ namespace Emboss
|
||||
/// <param name="projection">Define transformation from 2d to 3d(orientation, position, scale, ...)</param>
|
||||
/// <returns>Projected shape into space</returns>
|
||||
indexed_triangle_set polygons2model(const ExPolygons &shape2d, const IProjection& projection);
|
||||
|
||||
/// <summary>
|
||||
/// Suggest wanted up vector of embossed text by emboss direction
|
||||
/// </summary>
|
||||
/// <param name="normal">Normalized vector of emboss direction in world</param>
|
||||
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
|
||||
/// <returns>Wanted up vector</returns>
|
||||
Vec3d suggest_up(const Vec3d normal, double up_limit = 0.9);
|
||||
|
||||
/// <summary>
|
||||
/// By transformation calculate angle between suggested and actual up vector
|
||||
/// </summary>
|
||||
/// <param name="tr">Transformation of embossed volume in world</param>
|
||||
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
|
||||
/// <returns>Rotation of suggested up-vector[in rad] in the range [-Pi, Pi], When rotation is not zero</returns>
|
||||
std::optional<float> calc_up(const Transform3d &tr, double up_limit = 0.9);
|
||||
|
||||
/// <summary>
|
||||
/// Create transformation for emboss text object to lay on surface point
|
||||
/// </summary>
|
||||
@ -282,7 +299,7 @@ namespace Emboss
|
||||
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
|
||||
/// <returns>Transformation onto surface point</returns>
|
||||
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
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -587,7 +587,7 @@ namespace client
|
||||
param1.set_s(buf);
|
||||
}
|
||||
|
||||
static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op)
|
||||
static void regex_op(const expr &lhs, boost::iterator_range<Iterator> &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<Iterator>(
|
||||
@ -609,8 +609,37 @@ namespace client
|
||||
}
|
||||
}
|
||||
|
||||
static void regex_matches (expr &lhs, boost::iterator_range<Iterator> &rhs) { return regex_op(lhs, rhs, '='); }
|
||||
static void regex_doesnt_match(expr &lhs, boost::iterator_range<Iterator> &rhs) { return regex_op(lhs, rhs, '!'); }
|
||||
static void regex_matches (expr &lhs, boost::iterator_range<Iterator> &rhs) { return regex_op(lhs, rhs, '=', lhs); }
|
||||
static void regex_doesnt_match(expr &lhs, boost::iterator_range<Iterator> &rhs) { return regex_op(lhs, rhs, '!', lhs); }
|
||||
|
||||
static void one_of_test_init(expr &out) {
|
||||
out.set_b(false);
|
||||
}
|
||||
template<bool RegEx>
|
||||
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<Iterator> &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<Iterator>::template digits<true>, _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<Iterator>::one_of_test_init, _val)] >
|
||||
( ',' > *(
|
||||
(
|
||||
unary_expression(_r1)[px::bind(&expr<Iterator>::template one_of_test<false>, _r2, _1, _val)]
|
||||
| (lit('~') > unary_expression(_r1))[px::bind(&expr<Iterator>::template one_of_test<true>, _r2, _1, _val)]
|
||||
| regular_expression[px::bind(&expr<Iterator>::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<Iterator, OptWithPos<Iterator>(const MyContext*), spirit_encoding::space_type> variable;
|
||||
// Evaluating whether a nullable variable is nil.
|
||||
qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit_encoding::space_type> is_nil_test;
|
||||
// Evaluating "one of" list of patterns.
|
||||
qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<expr<Iterator>>, spirit_encoding::space_type> one_of;
|
||||
qi::rule<Iterator, expr<Iterator>(const MyContext*, const expr<Iterator> ¶m), spirit_encoding::space_type> one_of_list;
|
||||
|
||||
qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool, bool>, spirit_encoding::space_type> if_else_output;
|
||||
qi::rule<Iterator, std::string(const MyContext*), qi::locals<OptWithPos<Iterator>, int>, spirit_encoding::space_type> assignment_statement;
|
||||
|
@ -49,10 +49,12 @@ struct FontProp
|
||||
// used for move over model surface
|
||||
// When not set value is zero and is not stored
|
||||
std::optional<float> 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<float> angle; // [in radians]
|
||||
std::optional<float> angle; // [in radians] form -Pi to Pi
|
||||
|
||||
// Parameter for True Type Font collections
|
||||
// Select index of font in collection
|
||||
|
@ -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
|
||||
|
@ -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<size_t>(v.volume_idx());
|
||||
if (volume_idx >= object.volumes.size())
|
||||
return nullptr;
|
||||
|
||||
return object.volumes[volume_idx];
|
||||
}
|
||||
|
||||
ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects)
|
||||
{
|
||||
if (v.object_idx() < 0)
|
||||
return nullptr;
|
||||
size_t objext_idx = static_cast<size_t>(v.object_idx());
|
||||
if (objext_idx >= objects.size())
|
||||
return nullptr;
|
||||
if (objects[objext_idx] == nullptr)
|
||||
return nullptr;
|
||||
return get_model_volume(v, *objects[objext_idx]);
|
||||
}
|
||||
|
||||
GLVolume *get_first_hovered_gl_volume(const GLCanvas3D &canvas) {
|
||||
int hovered_id_signed = canvas.get_first_hover_volume_idx();
|
||||
if (hovered_id_signed < 0)
|
||||
return nullptr;
|
||||
|
||||
size_t hovered_id = static_cast<size_t>(hovered_id_signed);
|
||||
const GLVolumePtrs &volumes = canvas.get_volumes().volumes;
|
||||
if (hovered_id >= volumes.size())
|
||||
return nullptr;
|
||||
|
||||
return volumes[hovered_id];
|
||||
}
|
||||
|
||||
GLVolume *get_selected_gl_volume(const GLCanvas3D &canvas) {
|
||||
const GLVolume *gl_volume = get_selected_gl_volume(canvas.get_selection());
|
||||
if (gl_volume == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes;
|
||||
for (GLVolume *v : gl_volumes)
|
||||
if (v->composite_id == gl_volume->composite_id)
|
||||
return v;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModelObject *get_model_object(const GLVolume &gl_volume, const Model &model) {
|
||||
return get_model_object(gl_volume, model.objects);
|
||||
}
|
||||
|
||||
ModelObject *get_model_object(const GLVolume &gl_volume, const ModelObjectPtrs &objects) {
|
||||
if (gl_volume.object_idx() < 0)
|
||||
return nullptr;
|
||||
size_t objext_idx = static_cast<size_t>(gl_volume.object_idx());
|
||||
if (objext_idx >= objects.size())
|
||||
return nullptr;
|
||||
return objects[objext_idx];
|
||||
}
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const Model& model) {
|
||||
return get_model_instance(gl_volume, model.objects);
|
||||
}
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObjectPtrs &objects) {
|
||||
if (gl_volume.instance_idx() < 0)
|
||||
return nullptr;
|
||||
ModelObject *object = get_model_object(gl_volume, objects);
|
||||
return get_model_instance(gl_volume, *object);
|
||||
}
|
||||
|
||||
ModelInstance *get_model_instance(const GLVolume &gl_volume, const ModelObject &object) {
|
||||
if (gl_volume.instance_idx() < 0)
|
||||
return nullptr;
|
||||
size_t instance_idx = static_cast<size_t>(gl_volume.instance_idx());
|
||||
if (instance_idx >= object.instances.size())
|
||||
return nullptr;
|
||||
return object.instances[instance_idx];
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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 <optional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#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<typename T, typename Draw>
|
||||
bool revertible(const std::string &name, T &value, const T *default_value, const std::string &undo_tooltip, float undo_offset, Draw draw);
|
||||
|
||||
bool m_should_set_minimal_windows_size = false;
|
||||
bool m_should_set_minimal_windows_size = false;
|
||||
void set_minimal_window_size(bool is_advance_edit_style);
|
||||
ImVec2 get_minimal_window_size() const;
|
||||
|
||||
@ -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<const GuiCfg> m_gui_cfg;
|
||||
std::optional<const GuiCfg> 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<ImVec2> 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<FaceName> faces = {};
|
||||
// Sorter set of Non valid face names in OS
|
||||
std::vector<wxString> 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<float> m_rotate_start_angle;
|
||||
|
||||
// when draging with text object hold screen offset of cursor from object center
|
||||
std::optional<Vec2d> m_dragging_mouse_offset;
|
||||
// Keep data about dragging only during drag&drop
|
||||
std::optional<SurfaceDrag> 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<Transform3d> m_temp_transformation;
|
||||
|
||||
// For text on scaled objects
|
||||
std::optional<float> m_scale_height;
|
||||
std::optional<float> 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:
|
||||
/// <summary>
|
||||
/// Check if text is last solid part of object
|
||||
/// TODO: move to emboss gui utils
|
||||
/// </summary>
|
||||
/// <param name="text">Model volume of Text</param>
|
||||
/// <returns>True when object otherwise False</returns>
|
||||
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
|
||||
|
204
src/slic3r/GUI/IconManager.cpp
Normal file
204
src/slic3r/GUI/IconManager.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
#include "IconManager.hpp"
|
||||
#include <cmath>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
namespace priv {
|
||||
// set shared pointer to point on bad texture
|
||||
static void clear(IconManager::Icons &icons);
|
||||
static const std::vector<std::pair<int, bool>>& 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::Icons> IconManager::init(const InitTypes &input)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Not implemented yet";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<IconManager::Icons> IconManager::init(const std::vector<std::string> &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<unsigned int>(std::abs(std::round(size.x)));
|
||||
assert(size.x == static_cast<float>(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<Icons> 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<float>(m_icons_texture.get_height());
|
||||
float tex_width = static_cast<float>(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<unsigned>(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<Icon>(def_icon);
|
||||
// NOTE: there are space between icons
|
||||
unsigned start_x = static_cast<unsigned>(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<std::pair<int, bool>> &priv::get_states(IconManager::RasterType type) {
|
||||
static std::vector<std::pair<int, bool>> color = {std::make_pair(0, false)};
|
||||
static std::vector<std::pair<int, bool>> white = {std::make_pair(1, false)};
|
||||
static std::vector<std::pair<int, bool>> gray = {std::make_pair(2, false)};
|
||||
static std::vector<std::pair<int, bool>> 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<intptr_t>(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
|
132
src/slic3r/GUI/IconManager.hpp
Normal file
132
src/slic3r/GUI/IconManager.hpp
Normal file
@ -0,0 +1,132 @@
|
||||
#ifndef slic3r_IconManager_hpp_
|
||||
#define slic3r_IconManager_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "imgui/imgui.h" // ImVec2
|
||||
#include "slic3r/GUI/GLTexture.hpp" // texture storage
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
/// <summary>
|
||||
/// Keep texture with icons for UI
|
||||
/// Manage texture live -> create and destruct texture
|
||||
/// by live of icon shared pointers.
|
||||
/// </summary>
|
||||
class IconManager
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Release texture
|
||||
/// Set shared pointers to invalid texture
|
||||
/// </summary>
|
||||
~IconManager();
|
||||
|
||||
/// <summary>
|
||||
/// Define way to convert svg data to raster
|
||||
/// </summary>
|
||||
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<InitType>;
|
||||
|
||||
/// <summary>
|
||||
/// Data for render texture with icon
|
||||
/// </summary>
|
||||
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<std::shared_ptr<Icon> >;
|
||||
// Vector of icons, each vector contain multiple use of a SVG render
|
||||
using VIcons = std::vector<Icons>;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize raster texture on GPU with given images
|
||||
/// NOTE: Have to be called after OpenGL initialization
|
||||
/// </summary>
|
||||
/// <param name="input">Define files and its </param>
|
||||
/// <returns>Rasterized icons stored on GPU,
|
||||
/// Same size and order as input, each item of vector is set of texture in order by RasterType</returns>
|
||||
VIcons init(const InitTypes &input);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize multiple icons with same settings for size and type
|
||||
/// NOTE: Have to be called after OpenGL initialization
|
||||
/// </summary>
|
||||
/// <param name="file_paths">Define files with icon</param>
|
||||
/// <param name="size">Size of stored texture[in px], float will be rounded</param>
|
||||
/// <param name="type">Define way to rasterize icon,
|
||||
/// together color, white and gray = RasterType::color | RasterType::white_only_data | RasterType::gray_only_data</param>
|
||||
/// <returns>Rasterized icons stored on GPU,
|
||||
/// Same size and order as file_paths, each item of vector is set of texture in order by RasterType</returns>
|
||||
VIcons init(const std::vector<std::string> &file_paths, const ImVec2 &size, RasterType type = RasterType::color);
|
||||
|
||||
/// <summary>
|
||||
/// Release icons which are hold only by this manager
|
||||
/// May change texture and position of icons.
|
||||
/// </summary>
|
||||
void release();
|
||||
|
||||
private:
|
||||
// keep data stored on GPU
|
||||
GLTexture m_icons_texture;
|
||||
Icons m_icons;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Draw imgui image with icon
|
||||
/// </summary>
|
||||
/// <param name="icon">Place in texture</param>
|
||||
/// <param name="size">[optional]Size of image, wen zero than use same size as stored texture</param>
|
||||
/// <param name="tint_col">viz ImGui::Image </param>
|
||||
/// <param name="border_col">viz ImGui::Image </param>
|
||||
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));
|
||||
|
||||
/// <summary>
|
||||
/// Draw icon which change on hover
|
||||
/// </summary>
|
||||
/// <param name="icon">Draw when no hover</param>
|
||||
/// <param name="icon_hover">Draw when hover</param>
|
||||
/// <returns>True when click, otherwise False</returns>
|
||||
bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover);
|
||||
|
||||
/// <summary>
|
||||
/// Use icon as button with 3 states activ hover and disabled
|
||||
/// </summary>
|
||||
/// <param name="activ">Not disabled not hovered image</param>
|
||||
/// <param name="hover">Hovered image</param>
|
||||
/// <param name="disable">Disabled image</param>
|
||||
/// <returns>True when click on enabled, otherwise False</returns>
|
||||
bool button(const IconManager::Icon &activ, const IconManager::Icon &hover, const IconManager::Icon &disable, bool disabled = false);
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
#endif // slic3r_IconManager_hpp_
|
@ -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)
|
||||
{
|
||||
|
@ -146,6 +146,12 @@ public:
|
||||
// Extended function ImGuiWrapper::slider_float to work with std::optional<int>, when value == def_val than optional release its value
|
||||
bool slider_optional_int(const char* label, std::optional<int> &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);
|
||||
|
||||
/// <summary>
|
||||
/// Use ImGui internals to unactivate (lose focus) in input.
|
||||
/// When input is activ it can't change value by application.
|
||||
/// </summary>
|
||||
static void left_inputs();
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
|
||||
/// <summary>
|
||||
/// Draw symbol of cross hair
|
||||
/// </summary>
|
||||
/// <param name="position">Center of cross hair</param>
|
||||
/// <param name="radius">Circle radius</param>
|
||||
/// <param name="color">Color of symbol</param>
|
||||
/// <param name="num_segments">Precission of circle</param>
|
||||
/// <param name="thickness">Thickness of Line in symbol</param>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Check that font ranges contain all chars in string
|
||||
/// (rendered Unicodes are stored in GlyphRanges)
|
||||
|
@ -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<ExPolygons> name_shapes(m_input.styles.size());
|
||||
std::vector<double> scales(m_input.styles.size());
|
||||
images = std::vector<StyleManager::StyleImage>(m_input.styles.size());
|
||||
m_images = std::vector<StyleManager::StyleImage>(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<unsigned char>(4*width * height, {255});
|
||||
m_pixels = std::vector<unsigned char>(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;
|
||||
|
@ -19,11 +19,11 @@ class CreateFontStyleImagesJob : public Job
|
||||
|
||||
// Output data
|
||||
// texture size
|
||||
int width, height;
|
||||
int m_width, m_height;
|
||||
// texture data
|
||||
std::vector<unsigned char> pixels;
|
||||
std::vector<unsigned char> m_pixels;
|
||||
// descriptors of sub textures
|
||||
std::vector<StyleManager::StyleImage> images;
|
||||
std::vector<StyleManager::StyleImage> m_images;
|
||||
|
||||
public:
|
||||
CreateFontStyleImagesJob(StyleManager::StyleImagesData &&input);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
348
src/slic3r/GUI/SurfaceDrag.cpp
Normal file
348
src/slic3r/GUI/SurfaceDrag.cpp
Normal file
@ -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 {
|
||||
|
||||
/// <summary>
|
||||
/// Calculate offset from mouse position to center of text
|
||||
/// </summary>
|
||||
/// <param name="screen_coor">Position on screen[in Px] e.g. mouse position</param>
|
||||
/// <param name="volume">Selected volume(text)</param>
|
||||
/// <param name="camera">Actual position and view direction of camera</param>
|
||||
/// <returns>Offset in screen coordinate</returns>
|
||||
static Vec2d calc_screen_offset_to_volume_center(const Vec2d &screen_coor, const ModelVolume &volume, const Camera &camera)
|
||||
{
|
||||
const Transform3d &volume_tr = volume.get_matrix();
|
||||
assert(volume.text_configuration.has_value());
|
||||
|
||||
auto calc_offset = [&screen_coor, &volume_tr, &camera, &volume](const Transform3d &instrance_tr) -> Vec2d {
|
||||
Transform3d to_world = instrance_tr * volume_tr;
|
||||
|
||||
// Use fix of .3mf loaded tranformation when exist
|
||||
if (volume.text_configuration->fix_3mf_tr.has_value())
|
||||
to_world = to_world * (*volume.text_configuration->fix_3mf_tr);
|
||||
// zero point of volume in world coordinate system
|
||||
Vec3d volume_center = to_world.translation();
|
||||
// screen coordinate of volume center
|
||||
Vec2i coor = CameraUtils::project(camera, volume_center);
|
||||
return coor.cast<double>() - screen_coor;
|
||||
};
|
||||
|
||||
auto object = volume.get_object();
|
||||
assert(!object->instances.empty());
|
||||
// Speed up for one instance
|
||||
if (object->instances.size() == 1)
|
||||
return calc_offset(object->instances.front()->get_matrix());
|
||||
|
||||
Vec2d nearest_offset;
|
||||
double nearest_offset_size = std::numeric_limits<double>::max();
|
||||
for (const ModelInstance *instance : object->instances) {
|
||||
Vec2d offset = calc_offset(instance->get_matrix());
|
||||
double offset_size = offset.norm();
|
||||
if (nearest_offset_size < offset_size)
|
||||
continue;
|
||||
nearest_offset_size = offset_size;
|
||||
nearest_offset = offset;
|
||||
}
|
||||
return nearest_offset;
|
||||
}
|
||||
|
||||
// Calculate scale in world for check in debug
|
||||
[[maybe_unused]] static std::optional<double> calc_scale(const Matrix3d &from, const Matrix3d &to, const Vec3d &dir)
|
||||
{
|
||||
Vec3d from_dir = from * dir;
|
||||
Vec3d to_dir = to * dir;
|
||||
double from_scale_sq = from_dir.squaredNorm();
|
||||
double to_scale_sq = to_dir.squaredNorm();
|
||||
if (is_approx(from_scale_sq, to_scale_sq, 1e-3))
|
||||
return {}; // no scale
|
||||
return sqrt(from_scale_sq / to_scale_sq);
|
||||
}
|
||||
|
||||
bool on_mouse_surface_drag(const wxMouseEvent &mouse_event,
|
||||
const Camera &camera,
|
||||
std::optional<SurfaceDrag> &surface_drag,
|
||||
GLCanvas3D &canvas,
|
||||
RaycastManager &raycast_manager,
|
||||
std::optional<double> 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<size_t>(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<size_t> allowed_volumes_id;
|
||||
if (volumes.size() > 1) {
|
||||
allowed_volumes_id.reserve(volumes.size() - 1);
|
||||
for (auto &v : volumes) {
|
||||
// skip actual selected object
|
||||
if (v->id() == volume->id())
|
||||
continue;
|
||||
// drag only above part not modifiers or negative surface
|
||||
if (!v->is_model_part())
|
||||
continue;
|
||||
allowed_volumes_id.emplace_back(v->id().id);
|
||||
}
|
||||
}
|
||||
RaycastManager::AllowVolumes condition(std::move(allowed_volumes_id));
|
||||
RaycastManager::Meshes meshes = create_meshes(canvas, condition);
|
||||
// initialize raycasters
|
||||
// INFO: It could slows down for big objects
|
||||
// (may be move to thread and do not show drag until it finish)
|
||||
raycast_manager.actualize(*instance, &condition, &meshes);
|
||||
|
||||
// wxCoord == int --> wx/types.h
|
||||
Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
|
||||
Vec2d mouse_pos = mouse_coord.cast<double>();
|
||||
Vec2d mouse_offset = calc_screen_offset_to_volume_center(mouse_pos, *volume, camera);
|
||||
|
||||
Transform3d volume_tr = gl_volume->get_volume_transformation().get_matrix();
|
||||
|
||||
if (volume->text_configuration.has_value()) {
|
||||
const TextConfiguration &tc = *volume->text_configuration;
|
||||
// fix baked transformation from .3mf store process
|
||||
if (tc.fix_3mf_tr.has_value())
|
||||
volume_tr = volume_tr * tc.fix_3mf_tr->inverse();
|
||||
}
|
||||
|
||||
Transform3d instance_tr = instance->get_matrix();
|
||||
Transform3d instance_tr_inv = instance_tr.inverse();
|
||||
Transform3d world_tr = instance_tr * volume_tr;
|
||||
std::optional<float> 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<double>();
|
||||
Vec2d offseted_mouse = mouse_pos + surface_drag->mouse_offset;
|
||||
|
||||
std::optional<RaycastManager::Hit> hit = ray_from_camera(
|
||||
raycast_manager, offseted_mouse, camera, &surface_drag->condition);
|
||||
|
||||
surface_drag->exist_hit = hit.has_value();
|
||||
if (!hit.has_value()) {
|
||||
// cross hair need redraw
|
||||
canvas.set_as_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto world_linear = surface_drag->world.linear();
|
||||
// Calculate offset: transformation to wanted position
|
||||
{
|
||||
// Reset skew of the text Z axis:
|
||||
// Project the old Z axis into a new Z axis, which is perpendicular to the old XY plane.
|
||||
Vec3d old_z = world_linear.col(2);
|
||||
Vec3d new_z = world_linear.col(0).cross(world_linear.col(1));
|
||||
world_linear.col(2) = new_z * (old_z.dot(new_z) / new_z.squaredNorm());
|
||||
}
|
||||
|
||||
Vec3d text_z_world = world_linear.col(2); // world_linear * Vec3d::UnitZ()
|
||||
auto z_rotation = Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(text_z_world, hit->normal);
|
||||
Transform3d world_new = z_rotation * surface_drag->world;
|
||||
auto world_new_linear = world_new.linear();
|
||||
|
||||
// Fix direction of up vector 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<double, Eigen::DontAlign>::FromTwoVectors(y_world, wanted_up);
|
||||
|
||||
world_new = y_rotation * world_new;
|
||||
world_new_linear = world_new.linear();
|
||||
}
|
||||
|
||||
// Edit position from right
|
||||
Transform3d volume_new{Eigen::Translation<double, 3>(surface_drag->instance_inv * hit->position)};
|
||||
volume_new.linear() = surface_drag->instance_inv.linear() * world_new_linear;
|
||||
|
||||
// Check that transformation matrix is valid transformation
|
||||
assert(volume_new.matrix()(0, 0) == volume_new.matrix()(0, 0)); // Check valid transformation not a NAN
|
||||
if (volume_new.matrix()(0, 0) != volume_new.matrix()(0, 0))
|
||||
return true;
|
||||
|
||||
// Check that scale in world did not changed
|
||||
assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitY()).has_value());
|
||||
assert(!calc_scale(world_linear, world_new_linear, Vec3d::UnitZ()).has_value());
|
||||
|
||||
const ModelVolume *volume = get_model_volume(*surface_drag->gl_volume, canvas.get_model()->objects);
|
||||
if (volume != nullptr && volume->text_configuration.has_value()) {
|
||||
const TextConfiguration &tc = *volume->text_configuration;
|
||||
// fix baked transformation from .3mf store process
|
||||
if (tc.fix_3mf_tr.has_value())
|
||||
volume_new = volume_new * (*tc.fix_3mf_tr);
|
||||
|
||||
// apply move in Z direction and rotation by up vector
|
||||
Emboss::apply_transformation(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<Vec3d> 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<RaycastManager::Hit> 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<RaycastManager::ClosePoint> 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<TextConfiguration> &tc = mv->text_configuration;
|
||||
if (!tc.has_value())
|
||||
return res;
|
||||
|
||||
const std::optional<Transform3d> &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
|
90
src/slic3r/GUI/SurfaceDrag.hpp
Normal file
90
src/slic3r/GUI/SurfaceDrag.hpp
Normal file
@ -0,0 +1,90 @@
|
||||
#ifndef slic3r_SurfaceDrag_hpp_
|
||||
#define slic3r_SurfaceDrag_hpp_
|
||||
|
||||
#include <optional>
|
||||
#include "libslic3r/Point.hpp" // Vec2d, Transform3d
|
||||
#include "slic3r/Utils/RaycastManager.hpp"
|
||||
#include "wx/event.h" // wxMouseEvent
|
||||
|
||||
namespace Slic3r {
|
||||
class GLVolume;
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
class GLCanvas3D;
|
||||
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<float> start_angle;
|
||||
|
||||
// Flag whether coordinate hit some volume
|
||||
bool exist_hit = true;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Contain type of event and mouse position</param>
|
||||
/// <param name="camera">Actual viewport of camera</param>
|
||||
/// <param name="surface_drag">Structure which keep information about dragging</param>
|
||||
/// <param name="canvas">Contain gl_volumes and selection</param>
|
||||
/// <param name="raycast_manager">AABB trees for raycast in object
|
||||
/// Refresh state inside of function </param>
|
||||
/// <param name="up_limit">When set than use correction of up vector</param>
|
||||
/// <returns>True when event is processed otherwise false</returns>
|
||||
bool on_mouse_surface_drag(const wxMouseEvent &mouse_event,
|
||||
const Camera &camera,
|
||||
std::optional<SurfaceDrag> &surface_drag,
|
||||
GLCanvas3D &canvas,
|
||||
RaycastManager &raycast_manager,
|
||||
std::optional<double> up_limit = {});
|
||||
|
||||
/// <summary>
|
||||
/// Calculate translation of volume onto surface of model
|
||||
/// </summary>
|
||||
/// <param name="selection">Must contain only one selected volume, Transformation of current instance</param>
|
||||
/// <param name="raycast_manager">AABB trees of object. Actualize object</param>
|
||||
/// <returns>Offset of volume in volume coordinate</returns>
|
||||
std::optional<Vec3d> calc_surface_offset(const Selection &selection, RaycastManager &raycast_manager);
|
||||
|
||||
/// <summary>
|
||||
/// Get transformation to world
|
||||
/// - use fix after store to 3mf when exists
|
||||
/// </summary>
|
||||
/// <param name="gl_volume">Scene volume</param>
|
||||
/// <param name="objects">To identify Model volume with fix transformation</param>
|
||||
/// <returns>Fixed Transformation of gl_volume</returns>
|
||||
Transform3d world_matrix_fixed(const GLVolume &gl_volume, const ModelObjectPtrs& objects);
|
||||
|
||||
/// <summary>
|
||||
/// Get transformation to world
|
||||
/// - use fix after store to 3mf when exists
|
||||
/// NOTE: when not one volume selected return identity
|
||||
/// </summary>
|
||||
/// <param name="selection">Selected volume</param>
|
||||
/// <returns>Fixed Transformation of selected volume in selection</returns>
|
||||
Transform3d world_matrix_fixed(const Selection &selection);
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
#endif // slic3r_SurfaceDrag_hpp_
|
@ -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<float> &skew = prop.skew;
|
||||
bool is_italic = skew.has_value() || WxFontUtils::is_italic(wx_font);
|
||||
const std::optional<float> &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<float> &boldness = prop.boldness;
|
||||
bool is_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font);
|
||||
const std::optional<float> &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<CreateFontStyleImagesJob>(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<FontFile> 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));
|
||||
|
||||
|
@ -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<wxFont> &get_wx_font() const { return m_style_cache.wx_font; }
|
||||
const std::optional<wxFont> &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<size_t>::max(); }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// check whether current style differ to selected
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool is_font_changed() const;
|
||||
|
||||
/// <summary>
|
||||
/// Setter on wx_font when changed
|
||||
/// </summary>
|
||||
@ -221,7 +227,7 @@ private:
|
||||
ImFontAtlas atlas = {};
|
||||
|
||||
// wx widget font
|
||||
std::optional<wxFont> 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<wxFont> stored_wx_font;
|
||||
wxFont stored_wx_font = {};
|
||||
|
||||
// index into m_style_items
|
||||
size_t style_index = std::numeric_limits<size_t>::max();
|
||||
@ -277,6 +283,9 @@ private:
|
||||
|
||||
// place to store result in main thread in Finalize
|
||||
std::shared_ptr<StyleImages> result;
|
||||
|
||||
// pixel per milimeter (scaled DPI)
|
||||
double ppm;
|
||||
};
|
||||
std::shared_ptr<StyleImagesData::StyleImages> m_temp_style_images;
|
||||
bool m_exist_style_images;
|
||||
|
@ -1,54 +1,45 @@
|
||||
#include "RaycastManager.hpp"
|
||||
#include <utility>
|
||||
|
||||
// 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<typename VecType> inline void erase(std::vector<VecType> &vec, const std::vector<bool> &flags);
|
||||
}
|
||||
|
||||
void RaycastManager::actualize(const ModelObject &object, const ISkip *skip, Meshes *meshes)
|
||||
{
|
||||
// check if volume was removed
|
||||
std::vector<bool> removed_casters(m_raycasters.size(), {true});
|
||||
// actualize MeshRaycaster
|
||||
::actualize(m_meshes, object.volumes, skip, meshes);
|
||||
|
||||
// check if inscance was removed
|
||||
std::vector<bool> 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<MeshRaycaster>(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::Hit> 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<Hit> 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>();
|
||||
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<bool> 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::Hit> 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<AABBMesh::hit_result> 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<Vec3d,3> pts;
|
||||
auto tr = hit_tramsformation->linear();
|
||||
for (int i = 0; i < 3; ++i)
|
||||
pts[i] = tr * hit_mesh->vertices(tri[i]).cast<double>();
|
||||
Vec3d normal_world = (pts[1] - pts[0]).cross(pts[2] - pts[1]);
|
||||
if (has_reflection(*hit_tramsformation))
|
||||
normal_world *= -1;
|
||||
normal_world.normalize();
|
||||
|
||||
SurfacePoint<double> point_world{hit_world, normal_world};
|
||||
return RaycastManager::Hit{point_world, *hit_key, hit_squared_distance};
|
||||
}
|
||||
|
||||
std::optional<RaycastManager::Hit> RaycastManager::unproject(const Vec3d &point, const Vec3d &direction, const ISkip *skip) const
|
||||
std::optional<RaycastManager::Hit> RaycastManager::closest_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip) const
|
||||
{
|
||||
std::optional<Hit> 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::Hit> RaycastManager::unproject(const Vec3d &point,
|
||||
Vec3d point_negative = mesh_point + mesh_direction;
|
||||
|
||||
// Throw ray to both directions of ray
|
||||
std::vector<AABBMesh::hit_result> hits = mesh.query_ray_hits(point_positive, mesh_direction);
|
||||
std::vector<AABBMesh::hit_result> hits_neg = mesh.query_ray_hits(point_negative, -mesh_direction);
|
||||
std::vector<AABBMesh::hit_result> hits = mesh->query_ray_hits(point_positive, mesh_direction);
|
||||
std::vector<AABBMesh::hit_result> 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<float>(), hit.normal().cast<float>());
|
||||
closest = Hit(key, surface_point, squared_distance);
|
||||
closest = Hit{{hit.position(), hit.normal()}, key, squared_distance};
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
std::optional<RaycastManager::Hit> RaycastManager::closest(const Vec3d &point, const ISkip *skip) const {
|
||||
std::optional<Hit> closest;
|
||||
for (const auto &item : m_transformations) {
|
||||
const TrKey &key = item.first;
|
||||
std::optional<RaycastManager::ClosePoint> RaycastManager::closest(const Vec3d &point, const ISkip *skip) const
|
||||
{
|
||||
std::optional<ClosePoint> 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<float>();
|
||||
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<double>();
|
||||
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;
|
||||
}
|
||||
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<bool> 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<AABBMesh>(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<typename VecType> inline void erase(std::vector<VecType> &vec, const std::vector<bool> &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<int>(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<std::shared_ptr<SceneRaycasterItem>> &casters = *scene_casters;
|
||||
|
||||
const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes;
|
||||
const ModelObjectPtrs &objects = canvas.get_model()->objects;
|
||||
|
||||
RaycastManager::Meshes meshes;
|
||||
for (const std::shared_ptr<SceneRaycasterItem> &caster : casters) {
|
||||
int index = SceneRaycaster::decode_id(type, caster->get_id());
|
||||
if (index < 0)
|
||||
continue;
|
||||
auto index_ = static_cast<size_t>(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<AABBMesh>(caster->get_raycaster()->get_aabb_mesh());
|
||||
meshes.emplace_back(std::make_pair(id, std::move(mesh)));
|
||||
}
|
||||
return meshes;
|
||||
}
|
||||
|
||||
|
||||
std::optional<RaycastManager::Hit> ray_from_camera(const RaycastManager &raycaster,
|
||||
const Vec2d &mouse_pos,
|
||||
const Camera &camera,
|
||||
const RaycastManager::ISkip *skip)
|
||||
{
|
||||
Vec3d point;
|
||||
Vec3d direction;
|
||||
CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction);
|
||||
return raycaster.first_hit(point, direction, skip);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
@ -2,9 +2,8 @@
|
||||
#define slic3r_RaycastManager_hpp_
|
||||
|
||||
#include <memory> // unique_ptr
|
||||
#include <optional> // unique_ptr
|
||||
#include <map>
|
||||
#include "slic3r/GUI/MeshUtils.hpp" // MeshRaycaster
|
||||
#include <optional>
|
||||
#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{
|
||||
/// </summary>
|
||||
class RaycastManager
|
||||
{
|
||||
// ModelVolume.id
|
||||
using Raycaster = std::pair<size_t, std::unique_ptr<MeshRaycaster> >;
|
||||
std::vector<Raycaster> m_raycasters;
|
||||
// Public structures used by RaycastManager
|
||||
public:
|
||||
|
||||
// Key for transformation consist of unique volume and instance
|
||||
// ModelVolume.id
|
||||
using Mesh = std::pair<size_t, std::unique_ptr<AABBMesh> >;
|
||||
using Meshes = std::vector<Mesh>;
|
||||
|
||||
// Key for transformation consist of unique volume and instance id ... ObjectId()
|
||||
// ModelInstance, ModelVolume
|
||||
using TrKey = std::pair<size_t, size_t>;
|
||||
using TrItem = std::pair<TrKey, Transform3d>;
|
||||
std::vector<TrItem> m_transformations;
|
||||
using TrItems = std::vector<TrItem>;
|
||||
|
||||
// should contain shared pointer to camera but it is not shared pointer so it need it every time when casts rays
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Interface for identify allowed volumes to cast rays.
|
||||
/// </summary>
|
||||
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<typename T>
|
||||
struct SurfacePoint {
|
||||
using Vec3 = Eigen::Matrix<T, 3, 1, Eigen::DontAlign>;
|
||||
Vec3 position = Vec3::Zero();
|
||||
Vec3 normal = Vec3::UnitZ();
|
||||
};
|
||||
|
||||
struct Hit : public SurfacePoint<double>
|
||||
{
|
||||
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:
|
||||
|
||||
/// <summary>
|
||||
/// Actualize raycasters + transformation
|
||||
/// Detection of removed object
|
||||
@ -50,28 +85,9 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="object">Model representation</param>
|
||||
/// <param name="skip">Condifiton for skip actualization</param>
|
||||
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)
|
||||
{}
|
||||
};
|
||||
/// <param name="meshes">Speed up for already created AABBtrees</param>
|
||||
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:
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="mouse_pos">Position of mouse on screen</param>
|
||||
/// <param name="camera">Projection params</param>
|
||||
/// <param name="point">Position in space</param>
|
||||
/// <param name="direction">Casted ray direction</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns>Position on surface, normal direction and transformation key, which define hitted object instance</returns>
|
||||
std::optional<Hit> unproject(const Vec2d &mouse_pos,
|
||||
const Camera &camera,
|
||||
const ISkip *skip = nullptr) const;
|
||||
/// <returns>Position on surface, normal direction in world coorinate
|
||||
/// + key, to know hitted instance and volume</returns>
|
||||
std::optional<Hit> first_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// 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 !!
|
||||
/// </summary>
|
||||
/// <param name="point">Start point for ray</param>
|
||||
/// <param name="direction">Direction of ray</param>
|
||||
/// <param name="direction">Direction of ray, orientation doesn't matter, both are used</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns>Position on surface, normal direction and transformation key, which define hitted object instance</returns>
|
||||
std::optional<Hit> unproject(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const;
|
||||
std::optional<Hit> closest_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Search of closest point
|
||||
@ -120,16 +134,39 @@ public:
|
||||
/// <param name="point">Point</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns></returns>
|
||||
std::optional<Hit> closest(const Vec3d &point, const ISkip *skip = nullptr) const;
|
||||
std::optional<ClosePoint> closest(const Vec3d &point, const ISkip *skip = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Getter on transformation
|
||||
/// Getter on transformation from hitted volume to world
|
||||
/// </summary>
|
||||
/// <param name="tr_key">Define transformation</param>
|
||||
/// <returns>Transformation for key</returns>
|
||||
Transform3d get_transformation(const TrKey &tr_key) const;
|
||||
};
|
||||
|
||||
class GLCanvas3D;
|
||||
/// <summary>
|
||||
/// Use scene Raycasters and prepare data for actualize RaycasterManager
|
||||
/// </summary>
|
||||
/// <param name="canvas">contain Scene raycasters</param>
|
||||
/// <param name="condition">Limit for scene casters</param>
|
||||
/// <returns>Meshes</returns>
|
||||
RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition);
|
||||
|
||||
struct Camera;
|
||||
/// <summary>
|
||||
/// Unproject on mesh by Mesh raycasters
|
||||
/// </summary>
|
||||
/// <param name="mouse_pos">Position of mouse on screen</param>
|
||||
/// <param name="camera">Projection params</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns>Position on surface, normal direction in world coorinate
|
||||
/// + key, to know hitted instance and volume</returns>
|
||||
std::optional<RaycastManager::Hit> ray_from_camera(const RaycastManager &raycaster,
|
||||
const Vec2d &mouse_pos,
|
||||
const Camera &camera,
|
||||
const RaycastManager::ISkip *skip);
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_RaycastManager_hpp_
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)")); }
|
||||
|
Loading…
Reference in New Issue
Block a user