Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorenricoturri1966 <enricoturri@seznam.cz>2022-06-06 15:59:03 +0300
committerenricoturri1966 <enricoturri@seznam.cz>2022-06-06 15:59:03 +0300
commitdb8a120953bf695ffef2324adb0a58475d6b3c23 (patch)
tree4b9461693f547ea4f634cfd4e7531f9df114055c
parent0b617e2cf6b7fb5aa3f4c8e0098f0dfa509bb8b6 (diff)
parent904e3a874eb8369d9bac2d575febcc19e3a7a1d9 (diff)
Merge remote-tracking branch 'origin/et_trafo_matrix_rebase'
-rw-r--r--src/libslic3r/Geometry.cpp247
-rw-r--r--src/libslic3r/Geometry.hpp138
-rw-r--r--src/libslic3r/Model.cpp71
-rw-r--r--src/libslic3r/Model.hpp2465
-rw-r--r--src/libslic3r/Point.hpp1134
-rw-r--r--src/libslic3r/PrintApply.cpp2897
-rw-r--r--src/libslic3r/Technologies.hpp2
-rw-r--r--src/slic3r/CMakeLists.txt4
-rw-r--r--src/slic3r/GUI/3DBed.cpp14
-rw-r--r--src/slic3r/GUI/3DBed.hpp10
-rw-r--r--src/slic3r/GUI/3DScene.hpp1596
-rw-r--r--src/slic3r/GUI/CoordAxes.cpp104
-rw-r--r--src/slic3r/GUI/CoordAxes.hpp64
-rw-r--r--src/slic3r/GUI/GLCanvas3D.cpp104
-rw-r--r--src/slic3r/GUI/GLCanvas3D.hpp10
-rw-r--r--src/slic3r/GUI/GUI_Geometry.cpp9
-rw-r--r--src/slic3r/GUI/GUI_Geometry.hpp81
-rw-r--r--src/slic3r/GUI/GUI_ObjectList.cpp38
-rw-r--r--src/slic3r/GUI/GUI_ObjectManipulation.cpp458
-rw-r--r--src/slic3r/GUI/GUI_ObjectManipulation.hpp56
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoBase.cpp35
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoBase.hpp301
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoCut.cpp832
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp878
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp954
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp1980
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoMove.cpp258
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoMove.hpp23
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp2790
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp172
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp10
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoScale.cpp440
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoScale.hpp48
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp2744
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp1134
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmosManager.cpp20
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmosManager.hpp2
-rw-r--r--src/slic3r/GUI/MeshUtils.cpp748
-rw-r--r--src/slic3r/GUI/Plater.cpp33
-rw-r--r--src/slic3r/GUI/Selection.cpp838
-rw-r--r--src/slic3r/GUI/Selection.hpp139
-rw-r--r--src/slic3r/GUI/wxExtensions.cpp4
42 files changed, 13356 insertions, 10529 deletions
diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp
index 6eae83277..115d21693 100644
--- a/src/libslic3r/Geometry.cpp
+++ b/src/libslic3r/Geometry.cpp
@@ -313,9 +313,60 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation,
return transform;
}
+void assemble_transform(Transform3d& transform, const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror)
+{
+ transform = translation * rotation * scale * mirror;
+}
+
+Transform3d assemble_transform(const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror)
+{
+ Transform3d transform;
+ assemble_transform(transform, translation, rotation, scale, mirror);
+ return transform;
+}
+
+void translation_transform(Transform3d& transform, const Vec3d& translation)
+{
+ transform = Transform3d::Identity();
+ transform.translate(translation);
+}
+
+Transform3d translation_transform(const Vec3d& translation)
+{
+ Transform3d transform;
+ translation_transform(transform, translation);
+ return transform;
+}
+
+void rotation_transform(Transform3d& transform, const Vec3d& rotation)
+{
+ transform = Transform3d::Identity();
+ transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX()));
+}
+
+Transform3d rotation_transform(const Vec3d& rotation)
+{
+ Transform3d transform;
+ rotation_transform(transform, rotation);
+ return transform;
+}
+
+void scale_transform(Transform3d& transform, const Vec3d& scale)
+{
+ transform = Transform3d::Identity();
+ transform.scale(scale);
+}
+
+Transform3d scale_transform(const Vec3d& scale)
+{
+ Transform3d transform;
+ scale_transform(transform, scale);
+ return transform;
+}
+
Vec3d extract_euler_angles(const Eigen::Matrix<double, 3, 3, Eigen::DontAlign>& rotation_matrix)
{
- // reference: http://www.gregslabaugh.net/publications/euler.pdf
+ // reference: http://eecs.qmul.ac.uk/~gslabaugh/publications/euler.pdf
Vec3d angles1 = Vec3d::Zero();
Vec3d angles2 = Vec3d::Zero();
if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5) {
@@ -363,6 +414,54 @@ Vec3d extract_euler_angles(const Transform3d& transform)
return extract_euler_angles(m);
}
+#if ENABLE_WORLD_COORDINATE
+Transform3d Transformation::get_offset_matrix() const
+{
+ return assemble_transform(get_offset());
+}
+
+static Transform3d extract_rotation(const Transform3d& trafo)
+{
+ Matrix3d rotation;
+ Matrix3d scale;
+ trafo.computeRotationScaling(&rotation, &scale);
+ return Transform3d(rotation);
+}
+
+static Transform3d extract_scale(const Transform3d& trafo)
+{
+ Matrix3d rotation;
+ Matrix3d scale;
+ trafo.computeRotationScaling(&rotation, &scale);
+ return Transform3d(scale);
+}
+
+static std::pair<Transform3d, Transform3d> extract_rotation_scale(const Transform3d& trafo)
+{
+ Matrix3d rotation;
+ Matrix3d scale;
+ trafo.computeRotationScaling(&rotation, &scale);
+ return { Transform3d(rotation), Transform3d(scale) };
+}
+
+static bool contains_skew(const Transform3d& trafo)
+{
+ Matrix3d rotation;
+ Matrix3d scale;
+ trafo.computeRotationScaling(&rotation, &scale);
+ return !scale.isDiagonal();
+}
+
+Vec3d Transformation::get_rotation() const
+{
+ return extract_euler_angles(extract_rotation(m_matrix));
+}
+
+Transform3d Transformation::get_rotation_matrix() const
+{
+ return extract_rotation(m_matrix);
+}
+#else
bool Transformation::Flags::needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const
{
return (this->dont_translate != dont_translate) || (this->dont_rotate != dont_rotate) || (this->dont_scale != dont_scale) || (this->dont_mirror != dont_mirror);
@@ -400,12 +499,19 @@ void Transformation::set_offset(Axis axis, double offset)
m_dirty = true;
}
}
+#endif // ENABLE_WORLD_COORDINATE
void Transformation::set_rotation(const Vec3d& rotation)
{
+#if ENABLE_WORLD_COORDINATE
+ const Vec3d offset = get_offset();
+ m_matrix = rotation_transform(rotation) * extract_scale(m_matrix);
+ m_matrix.translation() = offset;
+#else
set_rotation(X, rotation.x());
set_rotation(Y, rotation.y());
set_rotation(Z, rotation.z());
+#endif // ENABLE_WORLD_COORDINATE
}
void Transformation::set_rotation(Axis axis, double rotation)
@@ -414,32 +520,106 @@ void Transformation::set_rotation(Axis axis, double rotation)
if (is_approx(std::abs(rotation), 2.0 * double(PI)))
rotation = 0.0;
+#if ENABLE_WORLD_COORDINATE
+ auto [curr_rotation, scale] = extract_rotation_scale(m_matrix);
+ Vec3d angles = extract_euler_angles(curr_rotation);
+ angles[axis] = rotation;
+
+ const Vec3d offset = get_offset();
+ m_matrix = rotation_transform(angles) * scale;
+ m_matrix.translation() = offset;
+#else
if (m_rotation(axis) != rotation) {
m_rotation(axis) = rotation;
m_dirty = true;
}
+#endif // ENABLE_WORLD_COORDINATE
}
+#if ENABLE_WORLD_COORDINATE
+Vec3d Transformation::get_scaling_factor() const
+{
+ const Transform3d scale = extract_scale(m_matrix);
+ return { scale(0, 0), scale(1, 1), scale(2, 2) };
+}
+
+Transform3d Transformation::get_scaling_factor_matrix() const
+{
+ return extract_scale(m_matrix);
+}
+#endif // ENABLE_WORLD_COORDINATE
+
void Transformation::set_scaling_factor(const Vec3d& scaling_factor)
{
+#if ENABLE_WORLD_COORDINATE
+ assert(scaling_factor.x() > 0.0 && scaling_factor.y() > 0.0 && scaling_factor.z() > 0.0);
+
+ const Vec3d offset = get_offset();
+ m_matrix = extract_rotation(m_matrix) * scale_transform(scaling_factor);
+ m_matrix.translation() = offset;
+#else
set_scaling_factor(X, scaling_factor.x());
set_scaling_factor(Y, scaling_factor.y());
set_scaling_factor(Z, scaling_factor.z());
+#endif // ENABLE_WORLD_COORDINATE
}
void Transformation::set_scaling_factor(Axis axis, double scaling_factor)
{
+#if ENABLE_WORLD_COORDINATE
+ assert(scaling_factor > 0.0);
+ auto [rotation, scale] = extract_rotation_scale(m_matrix);
+ scale(axis, axis) = scaling_factor;
+
+ const Vec3d offset = get_offset();
+ m_matrix = rotation * scale;
+ m_matrix.translation() = offset;
+#else
if (m_scaling_factor(axis) != std::abs(scaling_factor)) {
m_scaling_factor(axis) = std::abs(scaling_factor);
m_dirty = true;
}
+#endif // ENABLE_WORLD_COORDINATE
+}
+
+#if ENABLE_WORLD_COORDINATE
+Vec3d Transformation::get_mirror() const
+{
+ const Transform3d scale = extract_scale(m_matrix);
+ return { scale(0, 0) / std::abs(scale(0, 0)), scale(1, 1) / std::abs(scale(1, 1)), scale(2, 2) / std::abs(scale(2, 2)) };
}
+Transform3d Transformation::get_mirror_matrix() const
+{
+ const Vec3d scale = get_scaling_factor();
+ return scale_transform({ scale.x() / std::abs(scale.x()), scale.y() / std::abs(scale.y()), scale.z() / std::abs(scale.z()) });
+}
+#endif // ENABLE_WORLD_COORDINATE
+
void Transformation::set_mirror(const Vec3d& mirror)
{
+#if ENABLE_WORLD_COORDINATE
+ Vec3d copy(mirror);
+ const Vec3d abs_mirror = copy.cwiseAbs();
+ for (int i = 0; i < 3; ++i) {
+ if (abs_mirror(i) == 0.0)
+ copy(i) = 1.0;
+ else if (abs_mirror(i) != 1.0)
+ copy(i) /= abs_mirror(i);
+ }
+
+ const Vec3d curr_scale = get_scaling_factor();
+ const Vec3d signs = curr_scale.cwiseProduct(copy);
+ set_scaling_factor({
+ signs.x() < 0.0 ? std::abs(curr_scale.x()) * copy.x() : curr_scale.x(),
+ signs.y() < 0.0 ? std::abs(curr_scale.y()) * copy.y() : curr_scale.y(),
+ signs.z() < 0.0 ? std::abs(curr_scale.z()) * copy.z() : curr_scale.z()
+ });
+#else
set_mirror(X, mirror.x());
set_mirror(Y, mirror.y());
set_mirror(Z, mirror.z());
+#endif // ENABLE_WORLD_COORDINATE
}
void Transformation::set_mirror(Axis axis, double mirror)
@@ -450,12 +630,24 @@ void Transformation::set_mirror(Axis axis, double mirror)
else if (abs_mirror != 1.0)
mirror /= abs_mirror;
+#if ENABLE_WORLD_COORDINATE
+ const double curr_scale = get_scaling_factor(axis);
+ const double sign = curr_scale * mirror;
+ set_scaling_factor(axis, sign < 0.0 ? std::abs(curr_scale) * mirror : curr_scale);
+#else
if (m_mirror(axis) != mirror) {
m_mirror(axis) = mirror;
m_dirty = true;
}
+#endif // ENABLE_WORLD_COORDINATE
}
+#if ENABLE_WORLD_COORDINATE
+bool Transformation::has_skew() const
+{
+ return contains_skew(m_matrix);
+}
+#else
void Transformation::set_from_transform(const Transform3d& transform)
{
// offset
@@ -493,17 +685,62 @@ void Transformation::set_from_transform(const Transform3d& transform)
// if (!m_matrix.isApprox(transform))
// std::cout << "something went wrong in extracting data from matrix" << std::endl;
}
+#endif // ENABLE_WORLD_COORDINATE
void Transformation::reset()
{
+#if !ENABLE_WORLD_COORDINATE
m_offset = Vec3d::Zero();
m_rotation = Vec3d::Zero();
m_scaling_factor = Vec3d::Ones();
m_mirror = Vec3d::Ones();
+#endif // !ENABLE_WORLD_COORDINATE
m_matrix = Transform3d::Identity();
+#if !ENABLE_WORLD_COORDINATE
m_dirty = false;
+#endif // !ENABLE_WORLD_COORDINATE
+}
+
+#if ENABLE_WORLD_COORDINATE
+void Transformation::reset_skew()
+{
+ Matrix3d rotation;
+ Matrix3d scale;
+ m_matrix.computeRotationScaling(&rotation, &scale);
+
+ const double average_scale = std::cbrt(scale(0, 0) * scale(1, 1) * scale(2, 2));
+
+ scale(0, 0) = average_scale;
+ scale(1, 1) = average_scale;
+ scale(2, 2) = average_scale;
+
+ scale(0, 1) = 0.0;
+ scale(0, 2) = 0.0;
+ scale(1, 0) = 0.0;
+ scale(1, 2) = 0.0;
+ scale(2, 0) = 0.0;
+ scale(2, 1) = 0.0;
+
+ const Vec3d offset = get_offset();
+ m_matrix = rotation * scale;
+ m_matrix.translation() = offset;
+}
+
+Transform3d Transformation::get_matrix_no_offset() const
+{
+ Transformation copy(*this);
+ copy.reset_offset();
+ return copy.get_matrix();
}
+Transform3d Transformation::get_matrix_no_scaling_factor() const
+{
+ Transformation copy(*this);
+ copy.reset_scaling_factor();
+ copy.reset_mirror();
+ return copy.get_matrix();
+}
+#else
const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const
{
if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) {
@@ -520,12 +757,14 @@ const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rot
return m_matrix;
}
+#endif // ENABLE_WORLD_COORDINATE
Transformation Transformation::operator * (const Transformation& other) const
{
return Transformation(get_matrix() * other.get_matrix());
}
+#if !ENABLE_WORLD_COORDINATE
Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox)
{
Transformation out;
@@ -571,8 +810,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
out.set_scaling_factor(Vec3d(std::abs(scale.x()), std::abs(scale.y()), std::abs(scale.z())));
out.set_mirror(Vec3d(scale.x() > 0 ? 1. : -1, scale.y() > 0 ? 1. : -1, scale.z() > 0 ? 1. : -1));
}
- else
- {
+ else {
// General anisotropic scaling, general rotation.
// Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world.
// Scale it to get the required size.
@@ -581,6 +819,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
return out;
}
+#endif // !ENABLE_WORLD_COORDINATE
// For parsing a transformation matrix from 3MF / AMF.
Transform3d transform3d_from_string(const std::string& transform_str)
@@ -619,7 +858,7 @@ Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot
double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
{
const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to));
- const Vec3d axis = angle_axis.axis();
+ const Vec3d& axis = angle_axis.axis();
const double angle = angle_axis.angle();
#ifndef NDEBUG
if (std::abs(angle) > 1e-8) {
diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp
index 2ca4ef884..aa09a0d3e 100644
--- a/src/libslic3r/Geometry.hpp
+++ b/src/libslic3r/Geometry.hpp
@@ -323,7 +323,8 @@ bool arrange(
// 4) rotate Y
// 5) rotate Z
// 6) translate
-void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
+void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(),
+ const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
// Returns the transform obtained by assembling the given transformations in the following order:
// 1) mirror
@@ -332,7 +333,43 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d
// 4) rotate Y
// 5) rotate Z
// 6) translate
-Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
+Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(),
+ const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
+
+// Sets the given transform by multiplying the given transformations in the following order:
+// T = translation * rotation * scale * mirror
+void assemble_transform(Transform3d& transform, const Transform3d& translation = Transform3d::Identity(),
+ const Transform3d& rotation = Transform3d::Identity(), const Transform3d& scale = Transform3d::Identity(),
+ const Transform3d& mirror = Transform3d::Identity());
+
+// Returns the transform obtained by multiplying the given transformations in the following order:
+// T = translation * rotation * scale * mirror
+Transform3d assemble_transform(const Transform3d& translation = Transform3d::Identity(), const Transform3d& rotation = Transform3d::Identity(),
+ const Transform3d& scale = Transform3d::Identity(), const Transform3d& mirror = Transform3d::Identity());
+
+// Sets the given transform by assembling the given translation
+void translation_transform(Transform3d& transform, const Vec3d& translation);
+
+// Returns the transform obtained by assembling the given translation
+Transform3d translation_transform(const Vec3d& translation);
+
+// Sets the given transform by assembling the given rotations in the following order:
+// 1) rotate X
+// 2) rotate Y
+// 3) rotate Z
+void rotation_transform(Transform3d& transform, const Vec3d& rotation);
+
+// Returns the transform obtained by assembling the given rotations in the following order:
+// 1) rotate X
+// 2) rotate Y
+// 3) rotate Z
+Transform3d rotation_transform(const Vec3d& rotation);
+
+// Sets the given transform by assembling the given scale factors
+void scale_transform(Transform3d& transform, const Vec3d& scale);
+
+// Returns the transform obtained by assembling the given scale factors
+Transform3d scale_transform(const Vec3d& scale);
// Returns the euler angles extracted from the given rotation matrix
// Warning -> The matrix should not contain any scale or shear !!!
@@ -344,6 +381,9 @@ Vec3d extract_euler_angles(const Transform3d& transform);
class Transformation
{
+#if ENABLE_WORLD_COORDINATE
+ Transform3d m_matrix{ Transform3d::Identity() };
+#else
struct Flags
{
bool dont_translate{ true };
@@ -363,8 +403,26 @@ class Transformation
mutable Transform3d m_matrix{ Transform3d::Identity() };
mutable Flags m_flags;
mutable bool m_dirty{ false };
+#endif // ENABLE_WORLD_COORDINATE
public:
+#if ENABLE_WORLD_COORDINATE
+ Transformation() = default;
+ explicit Transformation(const Transform3d& transform) : m_matrix(transform) {}
+
+ Vec3d get_offset() const { return m_matrix.translation(); }
+ double get_offset(Axis axis) const { return get_offset()[axis]; }
+
+ Transform3d get_offset_matrix() const;
+
+ void set_offset(const Vec3d& offset) { m_matrix.translation() = offset; }
+ void set_offset(Axis axis, double offset) { m_matrix.translation()[axis] = offset; }
+
+ Vec3d get_rotation() const;
+ double get_rotation(Axis axis) const { return get_rotation()[axis]; }
+
+ Transform3d get_rotation_matrix() const;
+#else
Transformation();
explicit Transformation(const Transform3d& transform);
@@ -376,47 +434,103 @@ public:
const Vec3d& get_rotation() const { return m_rotation; }
double get_rotation(Axis axis) const { return m_rotation(axis); }
+#endif // ENABLE_WORLD_COORDINATE
void set_rotation(const Vec3d& rotation);
void set_rotation(Axis axis, double rotation);
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_scaling_factor() const;
+ double get_scaling_factor(Axis axis) const { return get_scaling_factor()[axis]; }
+
+ Transform3d get_scaling_factor_matrix() const;
+
+ bool is_scaling_uniform() const {
+ const Vec3d scale = get_scaling_factor();
+ return std::abs(scale.x() - scale.y()) < 1e-8 && std::abs(scale.x() - scale.z()) < 1e-8;
+ }
+#else
const Vec3d& get_scaling_factor() const { return m_scaling_factor; }
double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); }
+#endif // ENABLE_WORLD_COORDINATE
void set_scaling_factor(const Vec3d& scaling_factor);
void set_scaling_factor(Axis axis, double scaling_factor);
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_mirror() const;
+ double get_mirror(Axis axis) const { return get_mirror()[axis]; }
+
+ Transform3d get_mirror_matrix() const;
+
+ bool is_left_handed() const {
+ const Vec3d mirror = get_mirror();
+ return mirror.x() * mirror.y() * mirror.z() < 0.0;
+ }
+#else
bool is_scaling_uniform() const { return std::abs(m_scaling_factor.x() - m_scaling_factor.y()) < 1e-8 && std::abs(m_scaling_factor.x() - m_scaling_factor.z()) < 1e-8; }
const Vec3d& get_mirror() const { return m_mirror; }
double get_mirror(Axis axis) const { return m_mirror(axis); }
bool is_left_handed() const { return m_mirror.x() * m_mirror.y() * m_mirror.z() < 0.; }
+#endif // ENABLE_WORLD_COORDINATE
void set_mirror(const Vec3d& mirror);
void set_mirror(Axis axis, double mirror);
+#if ENABLE_WORLD_COORDINATE
+ bool has_skew() const;
+#else
void set_from_transform(const Transform3d& transform);
+#endif // ENABLE_WORLD_COORDINATE
void reset();
-
+#if ENABLE_WORLD_COORDINATE
+ void reset_offset() { set_offset(Vec3d::Zero()); }
+ void reset_rotation() { set_rotation(Vec3d::Zero()); }
+ void reset_scaling_factor() { set_scaling_factor(Vec3d::Ones()); }
+ void reset_mirror() { set_mirror(Vec3d::Ones()); }
+ void reset_skew();
+
+ const Transform3d& get_matrix() const { return m_matrix; }
+ Transform3d get_matrix_no_offset() const;
+ Transform3d get_matrix_no_scaling_factor() const;
+
+ void set_matrix(const Transform3d& transform) { m_matrix = transform; }
+#else
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const;
+#endif // ENABLE_WORLD_COORDINATE
Transformation operator * (const Transformation& other) const;
+#if !ENABLE_WORLD_COORDINATE
// Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity
// as possible in least squares norm in regard to the 8 corners of bbox.
// Bounding box is expected to be centered around zero in all axes.
static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox);
+#endif // !ENABLE_WORLD_COORDINATE
private:
- friend class cereal::access;
- template<class Archive> void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); }
- explicit Transformation(int) : m_dirty(true) {}
- template <class Archive> static void load_and_construct(Archive &ar, cereal::construct<Transformation> &construct)
- {
- // Calling a private constructor with special "int" parameter to indicate that no construction is necessary.
- construct(1);
- ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror);
- }
+ friend class cereal::access;
+#if ENABLE_WORLD_COORDINATE
+ template<class Archive> void serialize(Archive& ar) { ar(m_matrix); }
+ explicit Transformation(int) {}
+ template <class Archive> static void load_and_construct(Archive& ar, cereal::construct<Transformation>& construct)
+ {
+ // Calling a private constructor with special "int" parameter to indicate that no construction is necessary.
+ construct(1);
+ ar(construct.ptr()->m_matrix);
+ }
+#else
+ template<class Archive> void serialize(Archive& ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); }
+ explicit Transformation(int) : m_dirty(true) {}
+ template <class Archive> static void load_and_construct(Archive& ar, cereal::construct<Transformation>& construct)
+ {
+ // Calling a private constructor with special "int" parameter to indicate that no construction is necessary.
+ construct(1);
+ ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror);
+ }
+#endif // ENABLE_WORLD_COORDINATE
};
// For parsing a transformation matrix from 3MF / AMF.
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 3376cc888..1f8083aca 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -945,7 +945,11 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const
if (this->instances.empty())
throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances");
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d inst_matrix = this->instances.front()->get_transformation().get_matrix_no_offset();
+#else
const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true);
+#endif // ENABLE_WORLD_COORDINATE
for (const ModelVolume *v : this->volumes)
if (v->is_model_part())
m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
@@ -957,9 +961,15 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const
BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_translate) const
{
BoundingBoxf3 bb;
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d inst_matrix = dont_translate ?
+ this->instances[instance_idx]->get_transformation().get_matrix_no_offset() :
+ this->instances[instance_idx]->get_transformation().get_matrix();
+
+#else
const Transform3d& inst_matrix = this->instances[instance_idx]->get_transformation().get_matrix(dont_translate);
- for (ModelVolume *v : this->volumes)
- {
+#endif // ENABLE_WORLD_COORDINATE
+ for (ModelVolume *v : this->volumes) {
if (v->is_model_part())
bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
}
@@ -1368,9 +1378,12 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
new_object->add_instance(*model_instance);
ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh));
- for (ModelInstance* model_instance : new_object->instances)
- {
+ for (ModelInstance* model_instance : new_object->instances) {
+#if ENABLE_WORLD_COORDINATE
+ Vec3d shift = model_instance->get_transformation().get_matrix_no_offset() * new_vol->get_offset();
+#else
Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset();
+#endif // ENABLE_WORLD_COORDINATE
model_instance->set_offset(model_instance->get_offset() + shift);
}
@@ -1412,9 +1425,11 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
assert(instance_idx < this->instances.size());
const Geometry::Transformation reference_trafo = this->instances[instance_idx]->get_transformation();
+#if !ENABLE_WORLD_COORDINATE
if (Geometry::is_rotation_ninety_degrees(reference_trafo.get_rotation()))
// nothing to do, scaling in the world coordinate space is possible in the representation of Geometry::Transformation.
return;
+#endif // !ENABLE_WORLD_COORDINATE
bool left_handed = reference_trafo.is_left_handed();
bool has_mirrorring = ! reference_trafo.get_mirror().isApprox(Vec3d(1., 1., 1.));
@@ -1432,8 +1447,18 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
// Adjust the meshes.
// Transformation to be applied to the meshes.
+#if ENABLE_WORLD_COORDINATE
+ Geometry::Transformation reference_trafo_mod = reference_trafo;
+ reference_trafo_mod.reset_offset();
+ if (uniform_scaling)
+ reference_trafo_mod.reset_scaling_factor();
+ if (!has_mirrorring)
+ reference_trafo_mod.reset_mirror();
+ Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0);
+#else
Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0);
- Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix();
+#endif // ENABLE_WORLD_COORDINATE
+ Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix();
for (ModelVolume *model_volume : this->volumes) {
const Geometry::Transformation volume_trafo = model_volume->get_transformation();
bool volume_left_handed = volume_trafo.is_left_handed();
@@ -1442,7 +1467,17 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
std::abs(volume_trafo.get_scaling_factor().x() - volume_trafo.get_scaling_factor().z()) < EPSILON;
double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.;
// Transform the mesh.
- Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0);
+#if ENABLE_WORLD_COORDINATE
+ Geometry::Transformation volume_trafo_mod = volume_trafo;
+ volume_trafo_mod.reset_offset();
+ if (volume_uniform_scaling)
+ volume_trafo_mod.reset_scaling_factor();
+ if (!volume_has_mirrorring)
+ volume_trafo_mod.reset_mirror();
+ Eigen::Matrix3d volume_trafo_3x3 = volume_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0);
+#else
+ Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0);
+#endif // ENABLE_WORLD_COORDINATE
// Following method creates a new shared_ptr<TriangleMesh>
model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed);
// Reset the rotation, scaling and mirroring.
@@ -1489,7 +1524,11 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const
double min_z = DBL_MAX;
const ModelInstance* inst = instances[instance_idx];
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d mi = inst->get_matrix_no_offset();
+#else
const Transform3d& mi = inst->get_matrix(true);
+#endif // ENABLE_WORLD_COORDINATE
for (const ModelVolume* v : volumes) {
if (!v->is_model_part())
@@ -1510,7 +1549,11 @@ double ModelObject::get_instance_max_z(size_t instance_idx) const
double max_z = -DBL_MAX;
const ModelInstance* inst = instances[instance_idx];
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d mi = inst->get_matrix_no_offset();
+#else
const Transform3d& mi = inst->get_matrix(true);
+#endif // ENABLE_WORLD_COORDINATE
for (const ModelVolume* v : volumes) {
if (!v->is_model_part())
@@ -1936,14 +1979,22 @@ void ModelVolume::convert_from_meters()
void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const
{
+#if ENABLE_WORLD_COORDINATE
+ mesh->transform(dont_translate ? get_matrix_no_offset() : get_matrix());
+#else
mesh->transform(get_matrix(dont_translate));
+#endif // ENABLE_WORLD_COORDINATE
}
BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const
{
// Rotate around mesh origin.
TriangleMesh copy(mesh);
+#if ENABLE_WORLD_COORDINATE
+ copy.transform(get_transformation().get_rotation_matrix());
+#else
copy.transform(get_matrix(true, false, true, true));
+#endif // ENABLE_WORLD_COORDINATE
BoundingBoxf3 bbox = copy.bounding_box();
if (!empty(bbox)) {
@@ -1968,12 +2019,20 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mes
BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const
{
+#if ENABLE_WORLD_COORDINATE
+ return bbox.transformed(dont_translate ? get_matrix_no_offset() : get_matrix());
+#else
return bbox.transformed(get_matrix(dont_translate));
+#endif // ENABLE_WORLD_COORDINATE
}
Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const
{
+#if ENABLE_WORLD_COORDINATE
+ return dont_translate ? get_matrix_no_offset() * v : get_matrix() * v;
+#else
return get_matrix(dont_translate) * v;
+#endif // ENABLE_WORLD_COORDINATE
}
void ModelInstance::transform_polygon(Polygon* polygon) const
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 0c95d98c0..828358419 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -1,1212 +1,1253 @@
-#ifndef slic3r_Model_hpp_
-#define slic3r_Model_hpp_
-
-#include "libslic3r.h"
-#include "enum_bitmask.hpp"
-#include "Geometry.hpp"
-#include "ObjectID.hpp"
-#include "Point.hpp"
-#include "PrintConfig.hpp"
-#include "Slicing.hpp"
-#include "SLA/SupportPoint.hpp"
-#include "SLA/Hollowing.hpp"
-#include "TriangleMesh.hpp"
-#include "Arrange.hpp"
-#include "CustomGCode.hpp"
-#include "enum_bitmask.hpp"
-
-#include <map>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-namespace cereal {
- class BinaryInputArchive;
- class BinaryOutputArchive;
- template <class T> void load_optional(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr);
- template <class T> void save_optional(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr);
- template <class T> void load_by_value(BinaryInputArchive &ar, T &obj);
- template <class T> void save_by_value(BinaryOutputArchive &ar, const T &obj);
-}
-
-namespace Slic3r {
-enum class ConversionType;
-
-class BuildVolume;
-class Model;
-class ModelInstance;
-class ModelMaterial;
-class ModelObject;
-class ModelVolume;
-class ModelWipeTower;
-class Print;
-class SLAPrint;
-class TriangleSelector;
-
-namespace UndoRedo {
- class StackImpl;
-}
-
-class ModelConfigObject : public ObjectBase, public ModelConfig
-{
-private:
- friend class cereal::access;
- friend class UndoRedo::StackImpl;
- friend class ModelObject;
- friend class ModelVolume;
- friend class ModelMaterial;
-
- // Constructors to be only called by derived classes.
- // Default constructor to assign a unique ID.
- explicit ModelConfigObject() = default;
- // Constructor with ignored int parameter to assign an invalid ID, to be replaced
- // by an existing ID copied from elsewhere.
- explicit ModelConfigObject(int) : ObjectBase(-1) {}
- // Copy constructor copies the ID.
- explicit ModelConfigObject(const ModelConfigObject &cfg) = default;
- // Move constructor copies the ID.
- explicit ModelConfigObject(ModelConfigObject &&cfg) = default;
-
- Timestamp timestamp() const throw() override { return this->ModelConfig::timestamp(); }
- bool object_id_and_timestamp_match(const ModelConfigObject &rhs) const throw() { return this->id() == rhs.id() && this->timestamp() == rhs.timestamp(); }
-
- // called by ModelObject::assign_copy()
- ModelConfigObject& operator=(const ModelConfigObject &rhs) = default;
- ModelConfigObject& operator=(ModelConfigObject &&rhs) = default;
-
- template<class Archive> void serialize(Archive &ar) {
- ar(cereal::base_class<ModelConfig>(this));
- }
-};
-
-namespace Internal {
- template<typename T>
- class StaticSerializationWrapper
- {
- public:
- StaticSerializationWrapper(T &wrap) : wrapped(wrap) {}
- private:
- friend class cereal::access;
- friend class UndoRedo::StackImpl;
- template<class Archive> void load(Archive &ar) { cereal::load_by_value(ar, wrapped); }
- template<class Archive> void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); }
- T& wrapped;
- };
-}
-
-typedef std::string t_model_material_id;
-typedef std::string t_model_material_attribute;
-typedef std::map<t_model_material_attribute, std::string> t_model_material_attributes;
-
-typedef std::map<t_model_material_id, ModelMaterial*> ModelMaterialMap;
-typedef std::vector<ModelObject*> ModelObjectPtrs;
-typedef std::vector<ModelVolume*> ModelVolumePtrs;
-typedef std::vector<ModelInstance*> ModelInstancePtrs;
-
-#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \
- /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \
- /* to make a private copy for background processing. */ \
- static TYPE* new_copy(const TYPE &rhs) { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \
- static TYPE* new_copy(TYPE &&rhs) { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \
- static TYPE make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \
- static TYPE make_copy(TYPE &&rhs) { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \
- TYPE& assign_copy(const TYPE &rhs); \
- TYPE& assign_copy(TYPE &&rhs); \
- /* Copy a TYPE, generate new IDs. The front end will use this call. */ \
- static TYPE* new_clone(const TYPE &rhs) { \
- /* Default constructor assigning an invalid ID. */ \
- auto obj = new TYPE(-1); \
- obj->assign_clone(rhs); \
- assert(obj->id().valid() && obj->id() != rhs.id()); \
- return obj; \
- } \
- TYPE make_clone(const TYPE &rhs) { \
- /* Default constructor assigning an invalid ID. */ \
- TYPE obj(-1); \
- obj.assign_clone(rhs); \
- assert(obj.id().valid() && obj.id() != rhs.id()); \
- return obj; \
- } \
- TYPE& assign_clone(const TYPE &rhs) { \
- this->assign_copy(rhs); \
- assert(this->id().valid() && this->id() == rhs.id()); \
- this->assign_new_unique_ids_recursive(); \
- assert(this->id().valid() && this->id() != rhs.id()); \
- return *this; \
- }
-
-// Material, which may be shared across multiple ModelObjects of a single Model.
-class ModelMaterial final : public ObjectBase
-{
-public:
- // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose.
- t_model_material_attributes attributes;
- // Dynamic configuration storage for the object specific configuration values, overriding the global configuration.
- ModelConfigObject config;
-
- Model* get_model() const { return m_model; }
- void apply(const t_model_material_attributes &attributes)
- { this->attributes.insert(attributes.begin(), attributes.end()); }
-
-private:
- // Parent, owning this material.
- Model *m_model;
-
- // To be accessed by the Model.
- friend class Model;
- // Constructor, which assigns a new unique ID to the material and to its config.
- ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); }
- // Copy constructor copies the IDs of the ModelMaterial and its config, and m_model!
- ModelMaterial(const ModelMaterial &rhs) = default;
- void set_model(Model *model) { m_model = model; }
- void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
-
- // To be accessed by the serialization and Undo/Redo code.
- friend class cereal::access;
- friend class UndoRedo::StackImpl;
- // Create an object for deserialization, don't allocate IDs for ModelMaterial and its config.
- ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); }
- template<class Archive> void serialize(Archive &ar) {
- assert(this->id().invalid()); assert(this->config.id().invalid());
- Internal::StaticSerializationWrapper<ModelConfigObject> config_wrapper(config);
- ar(attributes, config_wrapper);
- // assert(this->id().valid()); assert(this->config.id().valid());
- }
-
- // Disabled methods.
- ModelMaterial(ModelMaterial &&rhs) = delete;
- ModelMaterial& operator=(const ModelMaterial &rhs) = delete;
- ModelMaterial& operator=(ModelMaterial &&rhs) = delete;
-};
-
-class LayerHeightProfile final : public ObjectWithTimestamp {
-public:
- // Assign the content if the timestamp differs, don't assign an ObjectID.
- void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } }
- void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
-
- std::vector<coordf_t> get() const throw() { return m_data; }
- bool empty() const throw() { return m_data.empty(); }
- void set(const std::vector<coordf_t> &data) { if (m_data != data) { m_data = data; this->touch(); } }
- void set(std::vector<coordf_t> &&data) { if (m_data != data) { m_data = std::move(data); this->touch(); } }
- void clear() { m_data.clear(); this->touch(); }
-
- template<class Archive> void serialize(Archive &ar)
- {
- ar(cereal::base_class<ObjectWithTimestamp>(this), m_data);
- }
-
-private:
- // Constructors to be only called by derived classes.
- // Default constructor to assign a unique ID.
- explicit LayerHeightProfile() = default;
- // Constructor with ignored int parameter to assign an invalid ID, to be replaced
- // by an existing ID copied from elsewhere.
- explicit LayerHeightProfile(int) : ObjectWithTimestamp(-1) {}
- // Copy constructor copies the ID.
- explicit LayerHeightProfile(const LayerHeightProfile &rhs) = default;
- // Move constructor copies the ID.
- explicit LayerHeightProfile(LayerHeightProfile &&rhs) = default;
-
- // called by ModelObject::assign_copy()
- LayerHeightProfile& operator=(const LayerHeightProfile &rhs) = default;
- LayerHeightProfile& operator=(LayerHeightProfile &&rhs) = default;
-
- std::vector<coordf_t> m_data;
-
- // to access set_new_unique_id() when copy / pasting an object
- friend class ModelObject;
-};
-
-// Declared outside of ModelVolume, so it could be forward declared.
-enum class ModelVolumeType : int {
- INVALID = -1,
- MODEL_PART = 0,
- NEGATIVE_VOLUME,
- PARAMETER_MODIFIER,
- SUPPORT_BLOCKER,
- SUPPORT_ENFORCER,
-};
-
-enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower };
-using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
-ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
-
-// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
-// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
-// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
-// different rotation and different uniform scaling.
-class ModelObject final : public ObjectBase
-{
-public:
- std::string name;
- std::string input_file; // XXX: consider fs::path
- // Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling.
- // Instances are owned by this ModelObject.
- ModelInstancePtrs instances;
- // Printable and modifier volumes, each with its material ID and a set of override parameters.
- // ModelVolumes are owned by this ModelObject.
- ModelVolumePtrs volumes;
- // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings.
- ModelConfigObject config;
- // Variation of a layer thickness for spans of Z coordinates + optional parameter overrides.
- t_layer_config_ranges layer_config_ranges;
- // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers.
- // The pairs of <z, layer_height> are packed into a 1D array.
- LayerHeightProfile layer_height_profile;
- // Whether or not this object is printable
- bool printable;
-
- // This vector holds position of selected support points for SLA. The data are
- // saved in mesh coordinates to allow using them for several instances.
- // The format is (x, y, z, point_size, supports_island)
- sla::SupportPoints sla_support_points;
- // To keep track of where the points came from (used for synchronization between
- // the SLA gizmo and the backend).
- sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
-
- // Holes to be drilled into the object so resin can flow out
- sla::DrainHoles sla_drain_holes;
-
- /* This vector accumulates the total translation applied to the object by the
- center_around_origin() method. Callers might want to apply the same translation
- to new volumes before adding them to this object in order to preserve alignment
- when user expects that. */
- Vec3d origin_translation;
-
- Model* get_model() { return m_model; }
- const Model* get_model() const { return m_model; }
-
- ModelVolume* add_volume(const TriangleMesh &mesh);
- ModelVolume* add_volume(TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART);
- ModelVolume* add_volume(const ModelVolume &volume, ModelVolumeType type = ModelVolumeType::INVALID);
- ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh);
- void delete_volume(size_t idx);
- void clear_volumes();
- void sort_volumes(bool full_sort);
- bool is_multiparts() const { return volumes.size() > 1; }
- // Checks if any of object volume is painted using the fdm support painting gizmo.
- bool is_fdm_support_painted() const;
- // Checks if any of object volume is painted using the seam painting gizmo.
- bool is_seam_painted() const;
- // Checks if any of object volume is painted using the multi-material painting gizmo.
- bool is_mm_painted() const;
-
- ModelInstance* add_instance();
- ModelInstance* add_instance(const ModelInstance &instance);
- ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation, const Vec3d &mirror);
- void delete_instance(size_t idx);
- void delete_last_instance();
- void clear_instances();
-
- // Returns the bounding box of the transformed instances.
- // This bounding box is approximate and not snug.
- // This bounding box is being cached.
- const BoundingBoxf3& bounding_box() const;
- void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; }
-
- // A mesh containing all transformed instances of this object.
- TriangleMesh mesh() const;
- // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
- // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater.
- TriangleMesh raw_mesh() const;
- // The same as above, but producing a lightweight indexed_triangle_set.
- indexed_triangle_set raw_indexed_triangle_set() const;
- // A transformed snug bounding box around the non-modifier object volumes, without the translation applied.
- // This bounding box is only used for the actual slicing.
- const BoundingBoxf3& raw_bounding_box() const;
- // A snug bounding box around the transformed non-modifier object volumes.
- BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const;
- // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
- const BoundingBoxf3& raw_mesh_bounding_box() const;
- // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes.
- BoundingBoxf3 full_raw_mesh_bounding_box() const;
-
- // Calculate 2D convex hull of of a projection of the transformed printable volumes into the XY plane.
- // This method is cheap in that it does not make any unnecessary copy of the volume meshes.
- // This method is used by the auto arrange function.
- Polygon convex_hull_2d(const Transform3d &trafo_instance) const;
-
- void center_around_origin(bool include_modifiers = true);
- void ensure_on_bed(bool allow_negative_z = false);
-
- void translate_instances(const Vec3d& vector);
- void translate_instance(size_t instance_idx, const Vec3d& vector);
- void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); }
- void translate(double x, double y, double z);
- void scale(const Vec3d &versor);
- void scale(const double s) { this->scale(Vec3d(s, s, s)); }
- void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); }
- /// Scale the current ModelObject to fit by altering the scaling factor of ModelInstances.
- /// It operates on the total size by duplicating the object according to all the instances.
- /// \param size Sizef3 the size vector
- void scale_to_fit(const Vec3d &size);
- void rotate(double angle, Axis axis);
- void rotate(double angle, const Vec3d& axis);
- void mirror(Axis axis);
-
- // This method could only be called before the meshes of this ModelVolumes are not shared!
- void scale_mesh_after_creation(const float scale);
- void convert_units(ModelObjectPtrs&new_objects, ConversionType conv_type, std::vector<int> volume_idxs);
-
- size_t materials_count() const;
- size_t facets_count() const;
- size_t parts_count() const;
- ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes);
- void split(ModelObjectPtrs* new_objects);
- void merge();
- // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
- // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
- // This situation is solved by baking in the instance transformation into the mesh vertices.
- // Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well.
- void bake_xy_rotation_into_meshes(size_t instance_idx);
-
- double get_min_z() const;
- double get_max_z() const;
- double get_instance_min_z(size_t instance_idx) const;
- double get_instance_max_z(size_t instance_idx) const;
-
- // Print object statistics to console.
- void print_info() const;
-
- std::string get_export_filename() const;
-
- // Get full stl statistics for all object's meshes
- TriangleMeshStats get_object_stl_stats() const;
- // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
- int get_repaired_errors_count(const int vol_idx = -1) const;
-
-private:
- friend class Model;
- // This constructor assigns new ID to this ModelObject and its config.
- explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()),
- m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
- {
- assert(this->id().valid());
- assert(this->config.id().valid());
- assert(this->layer_height_profile.id().valid());
- }
- explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
- {
- assert(this->id().invalid());
- assert(this->config.id().invalid());
- assert(this->layer_height_profile.id().invalid());
- }
- ~ModelObject();
- void assign_new_unique_ids_recursive() override;
-
- // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision"
- // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics).
- ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(rhs.m_model) {
- assert(this->id().invalid());
- assert(this->config.id().invalid());
- assert(this->layer_height_profile.id().invalid());
- assert(rhs.id() != rhs.config.id());
- assert(rhs.id() != rhs.layer_height_profile.id());
- this->assign_copy(rhs);
- assert(this->id().valid());
- assert(this->config.id().valid());
- assert(this->layer_height_profile.id().valid());
- assert(this->id() != this->config.id());
- assert(this->id() != this->layer_height_profile.id());
- assert(this->id() == rhs.id());
- assert(this->config.id() == rhs.config.id());
- assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
- }
- explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1) {
- assert(this->id().invalid());
- assert(this->config.id().invalid());
- assert(this->layer_height_profile.id().invalid());
- assert(rhs.id() != rhs.config.id());
- assert(rhs.id() != rhs.layer_height_profile.id());
- this->assign_copy(std::move(rhs));
- assert(this->id().valid());
- assert(this->config.id().valid());
- assert(this->layer_height_profile.id().valid());
- assert(this->id() != this->config.id());
- assert(this->id() != this->layer_height_profile.id());
- assert(this->id() == rhs.id());
- assert(this->config.id() == rhs.config.id());
- assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
- }
- ModelObject& operator=(const ModelObject &rhs) {
- this->assign_copy(rhs);
- m_model = rhs.m_model;
- assert(this->id().valid());
- assert(this->config.id().valid());
- assert(this->layer_height_profile.id().valid());
- assert(this->id() != this->config.id());
- assert(this->id() != this->layer_height_profile.id());
- assert(this->id() == rhs.id());
- assert(this->config.id() == rhs.config.id());
- assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
- return *this;
- }
- ModelObject& operator=(ModelObject &&rhs) {
- this->assign_copy(std::move(rhs));
- m_model = rhs.m_model;
- assert(this->id().valid());
- assert(this->config.id().valid());
- assert(this->layer_height_profile.id().valid());
- assert(this->id() != this->config.id());
- assert(this->id() != this->layer_height_profile.id());
- assert(this->id() == rhs.id());
- assert(this->config.id() == rhs.config.id());
- assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
- return *this;
- }
- void set_new_unique_id() {
- ObjectBase::set_new_unique_id();
- this->config.set_new_unique_id();
- this->layer_height_profile.set_new_unique_id();
- }
-
- OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
-
- // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized.
- Model *m_model = nullptr;
-
- // Bounding box, cached.
- mutable BoundingBoxf3 m_bounding_box;
- mutable bool m_bounding_box_valid;
- mutable BoundingBoxf3 m_raw_bounding_box;
- mutable bool m_raw_bounding_box_valid;
- mutable BoundingBoxf3 m_raw_mesh_bounding_box;
- mutable bool m_raw_mesh_bounding_box_valid;
-
- // Called by Print::apply() to set the model pointer after making a copy.
- friend class Print;
- friend class SLAPrint;
- void set_model(Model *model) { m_model = model; }
-
- // Undo / Redo through the cereal serialization library
- friend class cereal::access;
- friend class UndoRedo::StackImpl;
- // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config.
- ModelObject() :
- ObjectBase(-1), config(-1), layer_height_profile(-1),
- m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {
- assert(this->id().invalid());
- assert(this->config.id().invalid());
- assert(this->layer_height_profile.id().invalid());
- }
- template<class Archive> void serialize(Archive &ar) {
- ar(cereal::base_class<ObjectBase>(this));
- Internal::StaticSerializationWrapper<ModelConfigObject> config_wrapper(config);
- Internal::StaticSerializationWrapper<LayerHeightProfile> layer_heigth_profile_wrapper(layer_height_profile);
- ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
- sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
- m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
- }
-
- // Called by Print::validate() from the UI thread.
- unsigned int update_instances_print_volume_state(const BuildVolume &build_volume);
-};
-
-enum class EnforcerBlockerType : int8_t {
- // Maximum is 3. The value is serialized in TriangleSelector into 2 bits.
- NONE = 0,
- ENFORCER = 1,
- BLOCKER = 2,
- // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code.
- Extruder1 = ENFORCER,
- Extruder2 = BLOCKER,
- Extruder3,
- Extruder4,
- Extruder5,
- Extruder6,
- Extruder7,
- Extruder8,
- Extruder9,
- Extruder10,
- Extruder11,
- Extruder12,
- Extruder13,
- Extruder14,
- Extruder15,
-};
-
-enum class ConversionType : int {
- CONV_TO_INCH,
- CONV_FROM_INCH,
- CONV_TO_METER,
- CONV_FROM_METER,
-};
-
-class FacetsAnnotation final : public ObjectWithTimestamp {
-public:
- // Assign the content if the timestamp differs, don't assign an ObjectID.
- void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } }
- void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
- const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>>& get_data() const throw() { return m_data; }
- bool set(const TriangleSelector& selector);
- indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
- indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const;
- bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
- bool empty() const { return m_data.first.empty(); }
-
- // Following method clears the config and increases its timestamp, so the deleted
- // state is considered changed from perspective of the undo/redo stack.
- void reset();
-
- // Serialize triangle into string, for serialization into 3MF/AMF.
- std::string get_triangle_as_string(int i) const;
-
- // Before deserialization, reserve space for n_triangles.
- void reserve(int n_triangles) { m_data.first.reserve(n_triangles); }
- // Deserialize triangles one by one, with strictly increasing triangle_id.
- void set_triangle_from_string(int triangle_id, const std::string& str);
- // After deserializing the last triangle, shrink data to fit.
- void shrink_to_fit() { m_data.first.shrink_to_fit(); m_data.second.shrink_to_fit(); }
-
-private:
- // Constructors to be only called by derived classes.
- // Default constructor to assign a unique ID.
- explicit FacetsAnnotation() = default;
- // Constructor with ignored int parameter to assign an invalid ID, to be replaced
- // by an existing ID copied from elsewhere.
- explicit FacetsAnnotation(int) : ObjectWithTimestamp(-1) {}
- // Copy constructor copies the ID.
- explicit FacetsAnnotation(const FacetsAnnotation &rhs) = default;
- // Move constructor copies the ID.
- explicit FacetsAnnotation(FacetsAnnotation &&rhs) = default;
-
- // called by ModelVolume::assign_copy()
- FacetsAnnotation& operator=(const FacetsAnnotation &rhs) = default;
- FacetsAnnotation& operator=(FacetsAnnotation &&rhs) = default;
-
- friend class cereal::access;
- friend class UndoRedo::StackImpl;
-
- template<class Archive> void serialize(Archive &ar)
- {
- ar(cereal::base_class<ObjectWithTimestamp>(this), m_data);
- }
-
- std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> m_data;
-
- // To access set_new_unique_id() when copy / pasting a ModelVolume.
- friend class ModelVolume;
-};
-
-// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
-// ModelVolume instances are owned by a ModelObject.
-class ModelVolume final : public ObjectBase
-{
-public:
- std::string name;
- // struct used by reload from disk command to recover data from disk
- struct Source
- {
- std::string input_file;
- int object_idx{ -1 };
- int volume_idx{ -1 };
- Vec3d mesh_offset{ Vec3d::Zero() };
- Geometry::Transformation transform;
- bool is_converted_from_inches{ false };
- bool is_converted_from_meters{ false };
- bool is_from_builtin_objects{ false };
-
- template<class Archive> void serialize(Archive& ar) {
- //FIXME Vojtech: Serialize / deserialize only if the Source is set.
- // likely testing input_file or object_idx would be sufficient.
- ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects);
- }
- };
- Source source;
-
- // The triangular model.
- const TriangleMesh& mesh() const { return *m_mesh.get(); }
- void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
- void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
- void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
- void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
- void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
- void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
- void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
- // Configuration parameters specific to an object model geometry or a modifier volume,
- // overriding the global Slic3r settings and the ModelObject settings.
- ModelConfigObject config;
-
- // List of mesh facets to be supported/unsupported.
- FacetsAnnotation supported_facets;
-
- // List of seam enforcers/blockers.
- FacetsAnnotation seam_facets;
-
- // List of mesh facets painted for MMU segmentation.
- FacetsAnnotation mmu_segmentation_facets;
-
- // A parent object owning this modifier volume.
- ModelObject* get_object() const { return this->object; }
- ModelVolumeType type() const { return m_type; }
- void set_type(const ModelVolumeType t) { m_type = t; }
- bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; }
- bool is_negative_volume() const { return m_type == ModelVolumeType::NEGATIVE_VOLUME; }
- bool is_modifier() const { return m_type == ModelVolumeType::PARAMETER_MODIFIER; }
- bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; }
- 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; }
- t_model_material_id material_id() const { return m_material_id; }
- void set_material_id(t_model_material_id material_id);
- ModelMaterial* material() const;
- void set_material(t_model_material_id material_id, const ModelMaterial &material);
- // Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config.
- // Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes).
- int extruder_id() const;
-
- bool is_splittable() const;
-
- // Split this volume, append the result to the object owning this volume.
- // Return the number of volumes created from this one.
- // This is useful to assign different materials to different volumes of an object.
- size_t split(unsigned int max_extruders);
- void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); }
- void translate(const Vec3d& displacement);
- void scale(const Vec3d& scaling_factors);
- void scale(double x, double y, double z) { scale(Vec3d(x, y, z)); }
- void scale(double s) { scale(Vec3d(s, s, s)); }
- void rotate(double angle, Axis axis);
- void rotate(double angle, const Vec3d& axis);
- void mirror(Axis axis);
-
- // This method could only be called before the meshes of this ModelVolumes are not shared!
- void scale_geometry_after_creation(const Vec3f &versor);
- void scale_geometry_after_creation(const float scale) { this->scale_geometry_after_creation(Vec3f(scale, scale, scale)); }
-
- // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box.
- // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared!
- void center_geometry_after_creation(bool update_source_offset = true);
-
- void calculate_convex_hull();
- const TriangleMesh& get_convex_hull() const;
- const std::shared_ptr<const TriangleMesh>& get_convex_hull_shared_ptr() const { return m_convex_hull; }
- // Get count of errors in the mesh
- int get_repaired_errors_count() const;
-
- // Helpers for loading / storing into AMF / 3MF files.
- static ModelVolumeType type_from_string(const std::string &s);
- static std::string type_to_string(const ModelVolumeType t);
-
- const Geometry::Transformation& get_transformation() const { return m_transformation; }
- void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
- void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); }
-
- const Vec3d& get_offset() const { return m_transformation.get_offset(); }
- double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
-
- void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
- void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
-
- const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
- double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
-
- void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
- void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); }
-
- Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
- double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); }
-
- void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
- void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
-
- const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
- double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
- bool is_left_handed() const { return m_transformation.is_left_handed(); }
-
- void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
- void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
- void convert_from_imperial_units();
- void convert_from_meters();
-
- const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
-
- void set_new_unique_id() {
- ObjectBase::set_new_unique_id();
- this->config.set_new_unique_id();
- this->supported_facets.set_new_unique_id();
- this->seam_facets.set_new_unique_id();
- this->mmu_segmentation_facets.set_new_unique_id();
- }
-
- bool is_fdm_support_painted() const { return !this->supported_facets.empty(); }
- bool is_seam_painted() const { return !this->seam_facets.empty(); }
- bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); }
-
-protected:
- friend class Print;
- friend class SLAPrint;
- friend class Model;
- friend class ModelObject;
- friend void model_volume_list_update_supports(ModelObject& model_object_dst, const ModelObject& model_object_new);
-
- // Copies IDs of both the ModelVolume and its config.
- explicit ModelVolume(const ModelVolume &rhs) = default;
- void set_model_object(ModelObject *model_object) { object = model_object; }
- void assign_new_unique_ids_recursive() override;
- void transform_this_mesh(const Transform3d& t, bool fix_left_handed);
- void transform_this_mesh(const Matrix3d& m, bool fix_left_handed);
-
-private:
- // Parent object owning this ModelVolume.
- ModelObject* object;
- // The triangular model.
- std::shared_ptr<const TriangleMesh> m_mesh;
- // Is it an object to be printed, or a modifier volume?
- ModelVolumeType m_type;
- t_model_material_id m_material_id;
- // The convex hull of this model's mesh.
- std::shared_ptr<const TriangleMesh> m_convex_hull;
- Geometry::Transformation m_transformation;
-
- // flag to optimize the checking if the volume is splittable
- // -1 -> is unknown value (before first cheking)
- // 0 -> is not splittable
- // 1 -> is splittable
- mutable int m_is_splittable{ -1 };
-
- ModelVolume(ModelObject *object, const TriangleMesh &mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART) : m_mesh(new TriangleMesh(mesh)), m_type(type), object(object)
- {
- assert(this->id().valid());
- assert(this->config.id().valid());
- assert(this->supported_facets.id().valid());
- assert(this->seam_facets.id().valid());
- assert(this->mmu_segmentation_facets.id().valid());
- assert(this->id() != this->config.id());
- assert(this->id() != this->supported_facets.id());
- assert(this->id() != this->seam_facets.id());
- assert(this->id() != this->mmu_segmentation_facets.id());
- if (mesh.facets_count() > 1)
- calculate_convex_hull();
- }
- ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) :
- m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) {
- assert(this->id().valid());
- assert(this->config.id().valid());
- assert(this->supported_facets.id().valid());
- assert(this->seam_facets.id().valid());
- assert(this->mmu_segmentation_facets.id().valid());
- assert(this->id() != this->config.id());
- assert(this->id() != this->supported_facets.id());
- assert(this->id() != this->seam_facets.id());
- assert(this->id() != this->mmu_segmentation_facets.id());
- }
-
- // Copying an existing volume, therefore this volume will get a copy of the ID assigned.
- ModelVolume(ModelObject *object, const ModelVolume &other) :
- ObjectBase(other),
- name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
- config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
- supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets)
- {
- assert(this->id().valid());
- assert(this->config.id().valid());
- assert(this->supported_facets.id().valid());
- assert(this->seam_facets.id().valid());
- assert(this->mmu_segmentation_facets.id().valid());
- assert(this->id() != this->config.id());
- assert(this->id() != this->supported_facets.id());
- assert(this->id() != this->seam_facets.id());
- assert(this->id() != this->mmu_segmentation_facets.id());
- assert(this->id() == other.id());
- assert(this->config.id() == other.config.id());
- assert(this->supported_facets.id() == other.supported_facets.id());
- assert(this->seam_facets.id() == other.seam_facets.id());
- assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id());
- this->set_material_id(other.material_id());
- }
- // Providing a new mesh, therefore this volume will get a new unique ID assigned.
- ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) :
- name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation)
- {
- assert(this->id().valid());
- assert(this->config.id().valid());
- assert(this->supported_facets.id().valid());
- assert(this->seam_facets.id().valid());
- assert(this->mmu_segmentation_facets.id().valid());
- assert(this->id() != this->config.id());
- assert(this->id() != this->supported_facets.id());
- assert(this->id() != this->seam_facets.id());
- assert(this->id() != this->mmu_segmentation_facets.id());
- assert(this->id() != other.id());
- assert(this->config.id() == other.config.id());
- this->set_material_id(other.material_id());
- this->config.set_new_unique_id();
- if (m_mesh->facets_count() > 1)
- calculate_convex_hull();
- assert(this->config.id().valid());
- assert(this->config.id() != other.config.id());
- assert(this->supported_facets.id() != other.supported_facets.id());
- assert(this->seam_facets.id() != other.seam_facets.id());
- assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id());
- assert(this->id() != this->config.id());
- assert(this->supported_facets.empty());
- assert(this->seam_facets.empty());
- assert(this->mmu_segmentation_facets.empty());
- }
-
- ModelVolume& operator=(ModelVolume &rhs) = delete;
-
- friend class cereal::access;
- friend class UndoRedo::StackImpl;
- // Used for deserialization, therefore no IDs are allocated.
- ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) {
- assert(this->id().invalid());
- assert(this->config.id().invalid());
- assert(this->supported_facets.id().invalid());
- assert(this->seam_facets.id().invalid());
- assert(this->mmu_segmentation_facets.id().invalid());
- }
- template<class Archive> void load(Archive &ar) {
- bool has_convex_hull;
- ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
- cereal::load_by_value(ar, supported_facets);
- cereal::load_by_value(ar, seam_facets);
- cereal::load_by_value(ar, mmu_segmentation_facets);
- cereal::load_by_value(ar, config);
- assert(m_mesh);
- if (has_convex_hull) {
- cereal::load_optional(ar, m_convex_hull);
- if (! m_convex_hull && ! m_mesh->empty())
- // The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it.
- this->calculate_convex_hull();
- } else
- m_convex_hull.reset();
- }
- template<class Archive> void save(Archive &ar) const {
- bool has_convex_hull = m_convex_hull.get() != nullptr;
- ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
- cereal::save_by_value(ar, supported_facets);
- cereal::save_by_value(ar, seam_facets);
- cereal::save_by_value(ar, mmu_segmentation_facets);
- cereal::save_by_value(ar, config);
- if (has_convex_hull)
- cereal::save_optional(ar, m_convex_hull);
- }
-};
-
-inline void model_volumes_sort_by_id(ModelVolumePtrs &model_volumes)
-{
- std::sort(model_volumes.begin(), model_volumes.end(), [](const ModelVolume *l, const ModelVolume *r) { return l->id() < r->id(); });
-}
-
-inline const ModelVolume* model_volume_find_by_id(const ModelVolumePtrs &model_volumes, const ObjectID id)
-{
- auto it = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [id](const ModelVolume *mv) { return mv->id() < id; });
- return it != model_volumes.end() && (*it)->id() == id ? *it : nullptr;
-}
-
-enum ModelInstanceEPrintVolumeState : unsigned char
-{
- ModelInstancePVS_Inside,
- ModelInstancePVS_Partly_Outside,
- ModelInstancePVS_Fully_Outside,
- ModelInstanceNum_BedStates
-};
-
-// A single instance of a ModelObject.
-// Knows the affine transformation of an object.
-class ModelInstance final : public ObjectBase
-{
-private:
- Geometry::Transformation m_transformation;
-
-public:
- // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state())
- ModelInstanceEPrintVolumeState print_volume_state;
- // Whether or not this instance is printable
- bool printable;
-
- ModelObject* get_object() const { return this->object; }
-
- const Geometry::Transformation& get_transformation() const { return m_transformation; }
- void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
-
- const Vec3d& get_offset() const { return m_transformation.get_offset(); }
- double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
-
- void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
- void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
-
- const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
- double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
-
- void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
- void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); }
-
- const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
- double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); }
-
- void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
- void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
-
- const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
- double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
- bool is_left_handed() const { return m_transformation.is_left_handed(); }
-
- void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
- void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
-
- // To be called on an external mesh
- void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
- // Calculate a bounding box of a transformed mesh. To be called on an external mesh.
- BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const;
- // Transform an external bounding box.
- BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const;
- // Transform an external vector.
- Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const;
- // To be called on an external polygon. It does not translate the polygon, only rotates and scales.
- void transform_polygon(Polygon* polygon) const;
-
- const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
-
- bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); }
-
- // Getting the input polygon for arrange
- arrangement::ArrangePolygon get_arrange_polygon() const;
-
- // Apply the arrange result on the ModelInstance
- void apply_arrange_result(const Vec2d& offs, double rotation)
- {
- // write the transformation data into the model instance
- set_rotation(Z, rotation);
- set_offset(X, unscale<double>(offs(X)));
- set_offset(Y, unscale<double>(offs(Y)));
- this->object->invalidate_bounding_box();
- }
-
-protected:
- friend class Print;
- friend class SLAPrint;
- friend class Model;
- friend class ModelObject;
-
- explicit ModelInstance(const ModelInstance &rhs) = default;
- void set_model_object(ModelObject *model_object) { object = model_object; }
-
-private:
- // Parent object, owning this instance.
- ModelObject* object;
-
- // Constructor, which assigns a new unique ID.
- explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object) { assert(this->id().valid()); }
- // Constructor, which assigns a new unique ID.
- explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
- m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); }
-
- explicit ModelInstance(ModelInstance &&rhs) = delete;
- ModelInstance& operator=(const ModelInstance &rhs) = delete;
- ModelInstance& operator=(ModelInstance &&rhs) = delete;
-
- friend class cereal::access;
- friend class UndoRedo::StackImpl;
- // Used for deserialization, therefore no IDs are allocated.
- ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); }
- template<class Archive> void serialize(Archive &ar) {
- ar(m_transformation, print_volume_state, printable);
- }
-};
-
-
-class ModelWipeTower final : public ObjectBase
-{
-public:
- Vec2d position;
- double rotation;
-
-private:
- friend class cereal::access;
- friend class UndoRedo::StackImpl;
- friend class Model;
-
- // Constructors to be only called by derived classes.
- // Default constructor to assign a unique ID.
- explicit ModelWipeTower() {}
- // Constructor with ignored int parameter to assign an invalid ID, to be replaced
- // by an existing ID copied from elsewhere.
- explicit ModelWipeTower(int) : ObjectBase(-1) {}
- // Copy constructor copies the ID.
- explicit ModelWipeTower(const ModelWipeTower &cfg) = default;
-
- // Disabled methods.
- ModelWipeTower(ModelWipeTower &&rhs) = delete;
- ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete;
- ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete;
-
- // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object.
- template<typename Archive> void serialize(Archive &ar) { ar(position, rotation); }
-};
-
-// The print bed content.
-// Description of a triangular model with multiple materials, multiple instances with various affine transformations
-// and with multiple modifier meshes.
-// A model groups multiple objects, each object having possibly multiple instances,
-// all objects may share mutliple materials.
-class Model final : public ObjectBase
-{
-public:
- // Materials are owned by a model and referenced by objects through t_model_material_id.
- // Single material may be shared by multiple models.
- ModelMaterialMap materials;
- // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation).
- ModelObjectPtrs objects;
- // Wipe tower object.
- ModelWipeTower wipe_tower;
-
- // Extensions for color print
- CustomGCode::Info custom_gcode_per_print_z;
-
- // Default constructor assigns a new ID to the model.
- Model() { assert(this->id().valid()); }
- ~Model() { this->clear_objects(); this->clear_materials(); }
-
- /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */
- /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */
- Model(const Model &rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); }
- explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); }
- Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; }
- Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; }
-
- OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
-
- enum class LoadAttribute : int {
- AddDefaultInstances,
- CheckVersion
- };
- using LoadAttributes = enum_bitmask<LoadAttribute>;
-
- static Model read_from_file(
- const std::string& input_file,
- DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr,
- LoadAttributes options = LoadAttribute::AddDefaultInstances);
- static Model read_from_archive(
- const std::string& input_file,
- DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions,
- LoadAttributes options = LoadAttribute::AddDefaultInstances);
-
- // Add a new ModelObject to this Model, generate a new ID for this ModelObject.
- ModelObject* add_object();
- ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh);
- ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh);
- ModelObject* add_object(const ModelObject &other);
- void delete_object(size_t idx);
- bool delete_object(ObjectID id);
- bool delete_object(ModelObject* object);
- void clear_objects();
-
- ModelMaterial* add_material(t_model_material_id material_id);
- ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other);
- ModelMaterial* get_material(t_model_material_id material_id) {
- ModelMaterialMap::iterator i = this->materials.find(material_id);
- return (i == this->materials.end()) ? nullptr : i->second;
- }
-
- void delete_material(t_model_material_id material_id);
- void clear_materials();
- bool add_default_instances();
- // Returns approximate axis aligned bounding box of this model
- BoundingBoxf3 bounding_box() const;
- // Set the print_volume_state of PrintObject::instances,
- // return total number of printable objects.
- unsigned int update_print_volume_state(const BuildVolume &build_volume);
- // Returns true if any ModelObject was modified.
- bool center_instances_around_point(const Vec2d &point);
- void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
- TriangleMesh mesh() const;
-
- // Croaks if the duplicated objects do not fit the print bed.
- void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
-
- bool looks_like_multipart_object() const;
- void convert_multipart_object(unsigned int max_extruders);
- bool looks_like_imperial_units() const;
- void convert_from_imperial_units(bool only_small_volumes);
- bool looks_like_saved_in_meters() const;
- void convert_from_meters(bool only_small_volumes);
- int removed_objects_with_zero_volume();
-
- // Ensures that the min z of the model is not negative
- void adjust_min_z();
-
- void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); }
-
- // Propose an output file name & path based on the first printable object's name and source input file's path.
- std::string propose_export_file_name_and_path() const;
- // Propose an output path, replace extension. The new_extension shall contain the initial dot.
- std::string propose_export_file_name_and_path(const std::string &new_extension) const;
-
- // Checks if any of objects is painted using the fdm support painting gizmo.
- bool is_fdm_support_painted() const;
- // Checks if any of objects is painted using the seam painting gizmo.
- bool is_seam_painted() const;
- // Checks if any of objects is painted using the multi-material painting gizmo.
- bool is_mm_painted() const;
-
-private:
- explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }
- void assign_new_unique_ids_recursive();
- void update_links_bottom_up_recursive();
-
- friend class cereal::access;
- friend class UndoRedo::StackImpl;
- template<class Archive> void serialize(Archive &ar) {
- Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower);
- ar(materials, objects, wipe_tower_wrapper);
- }
-};
-
-ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
-
-#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
-#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
-
-// Test whether the two models contain the same number of ModelObjects with the same set of IDs
-// ordered in the same order. In that case it is not necessary to kill the background processing.
-bool model_object_list_equal(const Model &model_old, const Model &model_new);
-
-// Test whether the new model is just an extension of the old model (new objects were added
-// to the end of the original list. In that case it is not necessary to kill the background processing.
-bool model_object_list_extended(const Model &model_old, const Model &model_new);
-
-// Test whether the new ModelObject contains a different set of volumes (or sorted in a different order)
-// than the old ModelObject.
-bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type);
-bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const std::initializer_list<ModelVolumeType> &types);
-
-// Test whether the now ModelObject has newer custom supports data than the old one.
-// The function assumes that volumes list is synchronized.
-bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new);
-
-// Test whether the now ModelObject has newer custom seam data than the old one.
-// The function assumes that volumes list is synchronized.
-bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new);
-
-// Test whether the now ModelObject has newer MMU segmentation data than the old one.
-// The function assumes that volumes list is synchronized.
-extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new);
-
-// If the model has multi-part objects, then it is currently not supported by the SLA mode.
-// Either the model cannot be loaded, or a SLA printer has to be activated.
-bool model_has_multi_part_objects(const Model &model);
-// If the model has advanced features, then it cannot be processed in simple mode.
-bool model_has_advanced_features(const Model &model);
-
-#ifndef NDEBUG
-// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
-void check_model_ids_validity(const Model &model);
-void check_model_ids_equal(const Model &model1, const Model &model2);
-#endif /* NDEBUG */
-
-static const float SINKING_Z_THRESHOLD = -0.001f;
-static const double SINKING_MIN_Z_THRESHOLD = 0.05;
-
-} // namespace Slic3r
-
-namespace cereal
-{
- template <class Archive> struct specialize<Archive, Slic3r::ModelVolume, cereal::specialization::member_load_save> {};
- template <class Archive> struct specialize<Archive, Slic3r::ModelConfigObject, cereal::specialization::member_serialize> {};
-}
-
-#endif /* slic3r_Model_hpp_ */
+#ifndef slic3r_Model_hpp_
+#define slic3r_Model_hpp_
+
+#include "libslic3r.h"
+#include "enum_bitmask.hpp"
+#include "Geometry.hpp"
+#include "ObjectID.hpp"
+#include "Point.hpp"
+#include "PrintConfig.hpp"
+#include "Slicing.hpp"
+#include "SLA/SupportPoint.hpp"
+#include "SLA/Hollowing.hpp"
+#include "TriangleMesh.hpp"
+#include "Arrange.hpp"
+#include "CustomGCode.hpp"
+#include "enum_bitmask.hpp"
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace cereal {
+ class BinaryInputArchive;
+ class BinaryOutputArchive;
+ template <class T> void load_optional(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr);
+ template <class T> void save_optional(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr);
+ template <class T> void load_by_value(BinaryInputArchive &ar, T &obj);
+ template <class T> void save_by_value(BinaryOutputArchive &ar, const T &obj);
+}
+
+namespace Slic3r {
+enum class ConversionType;
+
+class BuildVolume;
+class Model;
+class ModelInstance;
+class ModelMaterial;
+class ModelObject;
+class ModelVolume;
+class ModelWipeTower;
+class Print;
+class SLAPrint;
+class TriangleSelector;
+
+namespace UndoRedo {
+ class StackImpl;
+}
+
+class ModelConfigObject : public ObjectBase, public ModelConfig
+{
+private:
+ friend class cereal::access;
+ friend class UndoRedo::StackImpl;
+ friend class ModelObject;
+ friend class ModelVolume;
+ friend class ModelMaterial;
+
+ // Constructors to be only called by derived classes.
+ // Default constructor to assign a unique ID.
+ explicit ModelConfigObject() = default;
+ // Constructor with ignored int parameter to assign an invalid ID, to be replaced
+ // by an existing ID copied from elsewhere.
+ explicit ModelConfigObject(int) : ObjectBase(-1) {}
+ // Copy constructor copies the ID.
+ explicit ModelConfigObject(const ModelConfigObject &cfg) = default;
+ // Move constructor copies the ID.
+ explicit ModelConfigObject(ModelConfigObject &&cfg) = default;
+
+ Timestamp timestamp() const throw() override { return this->ModelConfig::timestamp(); }
+ bool object_id_and_timestamp_match(const ModelConfigObject &rhs) const throw() { return this->id() == rhs.id() && this->timestamp() == rhs.timestamp(); }
+
+ // called by ModelObject::assign_copy()
+ ModelConfigObject& operator=(const ModelConfigObject &rhs) = default;
+ ModelConfigObject& operator=(ModelConfigObject &&rhs) = default;
+
+ template<class Archive> void serialize(Archive &ar) {
+ ar(cereal::base_class<ModelConfig>(this));
+ }
+};
+
+namespace Internal {
+ template<typename T>
+ class StaticSerializationWrapper
+ {
+ public:
+ StaticSerializationWrapper(T &wrap) : wrapped(wrap) {}
+ private:
+ friend class cereal::access;
+ friend class UndoRedo::StackImpl;
+ template<class Archive> void load(Archive &ar) { cereal::load_by_value(ar, wrapped); }
+ template<class Archive> void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); }
+ T& wrapped;
+ };
+}
+
+typedef std::string t_model_material_id;
+typedef std::string t_model_material_attribute;
+typedef std::map<t_model_material_attribute, std::string> t_model_material_attributes;
+
+typedef std::map<t_model_material_id, ModelMaterial*> ModelMaterialMap;
+typedef std::vector<ModelObject*> ModelObjectPtrs;
+typedef std::vector<ModelVolume*> ModelVolumePtrs;
+typedef std::vector<ModelInstance*> ModelInstancePtrs;
+
+#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \
+ /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \
+ /* to make a private copy for background processing. */ \
+ static TYPE* new_copy(const TYPE &rhs) { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \
+ static TYPE* new_copy(TYPE &&rhs) { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \
+ static TYPE make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \
+ static TYPE make_copy(TYPE &&rhs) { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \
+ TYPE& assign_copy(const TYPE &rhs); \
+ TYPE& assign_copy(TYPE &&rhs); \
+ /* Copy a TYPE, generate new IDs. The front end will use this call. */ \
+ static TYPE* new_clone(const TYPE &rhs) { \
+ /* Default constructor assigning an invalid ID. */ \
+ auto obj = new TYPE(-1); \
+ obj->assign_clone(rhs); \
+ assert(obj->id().valid() && obj->id() != rhs.id()); \
+ return obj; \
+ } \
+ TYPE make_clone(const TYPE &rhs) { \
+ /* Default constructor assigning an invalid ID. */ \
+ TYPE obj(-1); \
+ obj.assign_clone(rhs); \
+ assert(obj.id().valid() && obj.id() != rhs.id()); \
+ return obj; \
+ } \
+ TYPE& assign_clone(const TYPE &rhs) { \
+ this->assign_copy(rhs); \
+ assert(this->id().valid() && this->id() == rhs.id()); \
+ this->assign_new_unique_ids_recursive(); \
+ assert(this->id().valid() && this->id() != rhs.id()); \
+ return *this; \
+ }
+
+// Material, which may be shared across multiple ModelObjects of a single Model.
+class ModelMaterial final : public ObjectBase
+{
+public:
+ // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose.
+ t_model_material_attributes attributes;
+ // Dynamic configuration storage for the object specific configuration values, overriding the global configuration.
+ ModelConfigObject config;
+
+ Model* get_model() const { return m_model; }
+ void apply(const t_model_material_attributes &attributes)
+ { this->attributes.insert(attributes.begin(), attributes.end()); }
+
+private:
+ // Parent, owning this material.
+ Model *m_model;
+
+ // To be accessed by the Model.
+ friend class Model;
+ // Constructor, which assigns a new unique ID to the material and to its config.
+ ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); }
+ // Copy constructor copies the IDs of the ModelMaterial and its config, and m_model!
+ ModelMaterial(const ModelMaterial &rhs) = default;
+ void set_model(Model *model) { m_model = model; }
+ void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
+
+ // To be accessed by the serialization and Undo/Redo code.
+ friend class cereal::access;
+ friend class UndoRedo::StackImpl;
+ // Create an object for deserialization, don't allocate IDs for ModelMaterial and its config.
+ ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); }
+ template<class Archive> void serialize(Archive &ar) {
+ assert(this->id().invalid()); assert(this->config.id().invalid());
+ Internal::StaticSerializationWrapper<ModelConfigObject> config_wrapper(config);
+ ar(attributes, config_wrapper);
+ // assert(this->id().valid()); assert(this->config.id().valid());
+ }
+
+ // Disabled methods.
+ ModelMaterial(ModelMaterial &&rhs) = delete;
+ ModelMaterial& operator=(const ModelMaterial &rhs) = delete;
+ ModelMaterial& operator=(ModelMaterial &&rhs) = delete;
+};
+
+class LayerHeightProfile final : public ObjectWithTimestamp {
+public:
+ // Assign the content if the timestamp differs, don't assign an ObjectID.
+ void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } }
+ void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
+
+ std::vector<coordf_t> get() const throw() { return m_data; }
+ bool empty() const throw() { return m_data.empty(); }
+ void set(const std::vector<coordf_t> &data) { if (m_data != data) { m_data = data; this->touch(); } }
+ void set(std::vector<coordf_t> &&data) { if (m_data != data) { m_data = std::move(data); this->touch(); } }
+ void clear() { m_data.clear(); this->touch(); }
+
+ template<class Archive> void serialize(Archive &ar)
+ {
+ ar(cereal::base_class<ObjectWithTimestamp>(this), m_data);
+ }
+
+private:
+ // Constructors to be only called by derived classes.
+ // Default constructor to assign a unique ID.
+ explicit LayerHeightProfile() = default;
+ // Constructor with ignored int parameter to assign an invalid ID, to be replaced
+ // by an existing ID copied from elsewhere.
+ explicit LayerHeightProfile(int) : ObjectWithTimestamp(-1) {}
+ // Copy constructor copies the ID.
+ explicit LayerHeightProfile(const LayerHeightProfile &rhs) = default;
+ // Move constructor copies the ID.
+ explicit LayerHeightProfile(LayerHeightProfile &&rhs) = default;
+
+ // called by ModelObject::assign_copy()
+ LayerHeightProfile& operator=(const LayerHeightProfile &rhs) = default;
+ LayerHeightProfile& operator=(LayerHeightProfile &&rhs) = default;
+
+ std::vector<coordf_t> m_data;
+
+ // to access set_new_unique_id() when copy / pasting an object
+ friend class ModelObject;
+};
+
+// Declared outside of ModelVolume, so it could be forward declared.
+enum class ModelVolumeType : int {
+ INVALID = -1,
+ MODEL_PART = 0,
+ NEGATIVE_VOLUME,
+ PARAMETER_MODIFIER,
+ SUPPORT_BLOCKER,
+ SUPPORT_ENFORCER,
+};
+
+enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower };
+using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
+ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
+
+// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
+// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
+// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
+// different rotation and different uniform scaling.
+class ModelObject final : public ObjectBase
+{
+public:
+ std::string name;
+ std::string input_file; // XXX: consider fs::path
+ // Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling.
+ // Instances are owned by this ModelObject.
+ ModelInstancePtrs instances;
+ // Printable and modifier volumes, each with its material ID and a set of override parameters.
+ // ModelVolumes are owned by this ModelObject.
+ ModelVolumePtrs volumes;
+ // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings.
+ ModelConfigObject config;
+ // Variation of a layer thickness for spans of Z coordinates + optional parameter overrides.
+ t_layer_config_ranges layer_config_ranges;
+ // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers.
+ // The pairs of <z, layer_height> are packed into a 1D array.
+ LayerHeightProfile layer_height_profile;
+ // Whether or not this object is printable
+ bool printable;
+
+ // This vector holds position of selected support points for SLA. The data are
+ // saved in mesh coordinates to allow using them for several instances.
+ // The format is (x, y, z, point_size, supports_island)
+ sla::SupportPoints sla_support_points;
+ // To keep track of where the points came from (used for synchronization between
+ // the SLA gizmo and the backend).
+ sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
+
+ // Holes to be drilled into the object so resin can flow out
+ sla::DrainHoles sla_drain_holes;
+
+ /* This vector accumulates the total translation applied to the object by the
+ center_around_origin() method. Callers might want to apply the same translation
+ to new volumes before adding them to this object in order to preserve alignment
+ when user expects that. */
+ Vec3d origin_translation;
+
+ Model* get_model() { return m_model; }
+ const Model* get_model() const { return m_model; }
+
+ ModelVolume* add_volume(const TriangleMesh &mesh);
+ ModelVolume* add_volume(TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART);
+ ModelVolume* add_volume(const ModelVolume &volume, ModelVolumeType type = ModelVolumeType::INVALID);
+ ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh);
+ void delete_volume(size_t idx);
+ void clear_volumes();
+ void sort_volumes(bool full_sort);
+ bool is_multiparts() const { return volumes.size() > 1; }
+ // Checks if any of object volume is painted using the fdm support painting gizmo.
+ bool is_fdm_support_painted() const;
+ // Checks if any of object volume is painted using the seam painting gizmo.
+ bool is_seam_painted() const;
+ // Checks if any of object volume is painted using the multi-material painting gizmo.
+ bool is_mm_painted() const;
+
+ ModelInstance* add_instance();
+ ModelInstance* add_instance(const ModelInstance &instance);
+ ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation, const Vec3d &mirror);
+ void delete_instance(size_t idx);
+ void delete_last_instance();
+ void clear_instances();
+
+ // Returns the bounding box of the transformed instances.
+ // This bounding box is approximate and not snug.
+ // This bounding box is being cached.
+ const BoundingBoxf3& bounding_box() const;
+ void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; }
+
+ // A mesh containing all transformed instances of this object.
+ TriangleMesh mesh() const;
+ // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
+ // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater.
+ TriangleMesh raw_mesh() const;
+ // The same as above, but producing a lightweight indexed_triangle_set.
+ indexed_triangle_set raw_indexed_triangle_set() const;
+ // A transformed snug bounding box around the non-modifier object volumes, without the translation applied.
+ // This bounding box is only used for the actual slicing.
+ const BoundingBoxf3& raw_bounding_box() const;
+ // A snug bounding box around the transformed non-modifier object volumes.
+ BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const;
+ // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
+ const BoundingBoxf3& raw_mesh_bounding_box() const;
+ // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes.
+ BoundingBoxf3 full_raw_mesh_bounding_box() const;
+
+ // Calculate 2D convex hull of of a projection of the transformed printable volumes into the XY plane.
+ // This method is cheap in that it does not make any unnecessary copy of the volume meshes.
+ // This method is used by the auto arrange function.
+ Polygon convex_hull_2d(const Transform3d &trafo_instance) const;
+
+ void center_around_origin(bool include_modifiers = true);
+ void ensure_on_bed(bool allow_negative_z = false);
+
+ void translate_instances(const Vec3d& vector);
+ void translate_instance(size_t instance_idx, const Vec3d& vector);
+ void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); }
+ void translate(double x, double y, double z);
+ void scale(const Vec3d &versor);
+ void scale(const double s) { this->scale(Vec3d(s, s, s)); }
+ void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); }
+ /// Scale the current ModelObject to fit by altering the scaling factor of ModelInstances.
+ /// It operates on the total size by duplicating the object according to all the instances.
+ /// \param size Sizef3 the size vector
+ void scale_to_fit(const Vec3d &size);
+ void rotate(double angle, Axis axis);
+ void rotate(double angle, const Vec3d& axis);
+ void mirror(Axis axis);
+
+ // This method could only be called before the meshes of this ModelVolumes are not shared!
+ void scale_mesh_after_creation(const float scale);
+ void convert_units(ModelObjectPtrs&new_objects, ConversionType conv_type, std::vector<int> volume_idxs);
+
+ size_t materials_count() const;
+ size_t facets_count() const;
+ size_t parts_count() const;
+ ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes);
+ void split(ModelObjectPtrs* new_objects);
+ void merge();
+ // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
+ // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
+ // This situation is solved by baking in the instance transformation into the mesh vertices.
+ // Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well.
+ void bake_xy_rotation_into_meshes(size_t instance_idx);
+
+ double get_min_z() const;
+ double get_max_z() const;
+ double get_instance_min_z(size_t instance_idx) const;
+ double get_instance_max_z(size_t instance_idx) const;
+
+ // Print object statistics to console.
+ void print_info() const;
+
+ std::string get_export_filename() const;
+
+ // Get full stl statistics for all object's meshes
+ TriangleMeshStats get_object_stl_stats() const;
+ // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
+ int get_repaired_errors_count(const int vol_idx = -1) const;
+
+private:
+ friend class Model;
+ // This constructor assigns new ID to this ModelObject and its config.
+ explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()),
+ m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
+ {
+ assert(this->id().valid());
+ assert(this->config.id().valid());
+ assert(this->layer_height_profile.id().valid());
+ }
+ explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
+ {
+ assert(this->id().invalid());
+ assert(this->config.id().invalid());
+ assert(this->layer_height_profile.id().invalid());
+ }
+ ~ModelObject();
+ void assign_new_unique_ids_recursive() override;
+
+ // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision"
+ // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics).
+ ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(rhs.m_model) {
+ assert(this->id().invalid());
+ assert(this->config.id().invalid());
+ assert(this->layer_height_profile.id().invalid());
+ assert(rhs.id() != rhs.config.id());
+ assert(rhs.id() != rhs.layer_height_profile.id());
+ this->assign_copy(rhs);
+ assert(this->id().valid());
+ assert(this->config.id().valid());
+ assert(this->layer_height_profile.id().valid());
+ assert(this->id() != this->config.id());
+ assert(this->id() != this->layer_height_profile.id());
+ assert(this->id() == rhs.id());
+ assert(this->config.id() == rhs.config.id());
+ assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
+ }
+ explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1) {
+ assert(this->id().invalid());
+ assert(this->config.id().invalid());
+ assert(this->layer_height_profile.id().invalid());
+ assert(rhs.id() != rhs.config.id());
+ assert(rhs.id() != rhs.layer_height_profile.id());
+ this->assign_copy(std::move(rhs));
+ assert(this->id().valid());
+ assert(this->config.id().valid());
+ assert(this->layer_height_profile.id().valid());
+ assert(this->id() != this->config.id());
+ assert(this->id() != this->layer_height_profile.id());
+ assert(this->id() == rhs.id());
+ assert(this->config.id() == rhs.config.id());
+ assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
+ }
+ ModelObject& operator=(const ModelObject &rhs) {
+ this->assign_copy(rhs);
+ m_model = rhs.m_model;
+ assert(this->id().valid());
+ assert(this->config.id().valid());
+ assert(this->layer_height_profile.id().valid());
+ assert(this->id() != this->config.id());
+ assert(this->id() != this->layer_height_profile.id());
+ assert(this->id() == rhs.id());
+ assert(this->config.id() == rhs.config.id());
+ assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
+ return *this;
+ }
+ ModelObject& operator=(ModelObject &&rhs) {
+ this->assign_copy(std::move(rhs));
+ m_model = rhs.m_model;
+ assert(this->id().valid());
+ assert(this->config.id().valid());
+ assert(this->layer_height_profile.id().valid());
+ assert(this->id() != this->config.id());
+ assert(this->id() != this->layer_height_profile.id());
+ assert(this->id() == rhs.id());
+ assert(this->config.id() == rhs.config.id());
+ assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
+ return *this;
+ }
+ void set_new_unique_id() {
+ ObjectBase::set_new_unique_id();
+ this->config.set_new_unique_id();
+ this->layer_height_profile.set_new_unique_id();
+ }
+
+ OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
+
+ // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized.
+ Model *m_model = nullptr;
+
+ // Bounding box, cached.
+ mutable BoundingBoxf3 m_bounding_box;
+ mutable bool m_bounding_box_valid;
+ mutable BoundingBoxf3 m_raw_bounding_box;
+ mutable bool m_raw_bounding_box_valid;
+ mutable BoundingBoxf3 m_raw_mesh_bounding_box;
+ mutable bool m_raw_mesh_bounding_box_valid;
+
+ // Called by Print::apply() to set the model pointer after making a copy.
+ friend class Print;
+ friend class SLAPrint;
+ void set_model(Model *model) { m_model = model; }
+
+ // Undo / Redo through the cereal serialization library
+ friend class cereal::access;
+ friend class UndoRedo::StackImpl;
+ // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config.
+ ModelObject() :
+ ObjectBase(-1), config(-1), layer_height_profile(-1),
+ m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {
+ assert(this->id().invalid());
+ assert(this->config.id().invalid());
+ assert(this->layer_height_profile.id().invalid());
+ }
+ template<class Archive> void serialize(Archive &ar) {
+ ar(cereal::base_class<ObjectBase>(this));
+ Internal::StaticSerializationWrapper<ModelConfigObject> config_wrapper(config);
+ Internal::StaticSerializationWrapper<LayerHeightProfile> layer_heigth_profile_wrapper(layer_height_profile);
+ ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
+ sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
+ m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
+ }
+
+ // Called by Print::validate() from the UI thread.
+ unsigned int update_instances_print_volume_state(const BuildVolume &build_volume);
+};
+
+enum class EnforcerBlockerType : int8_t {
+ // Maximum is 3. The value is serialized in TriangleSelector into 2 bits.
+ NONE = 0,
+ ENFORCER = 1,
+ BLOCKER = 2,
+ // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code.
+ Extruder1 = ENFORCER,
+ Extruder2 = BLOCKER,
+ Extruder3,
+ Extruder4,
+ Extruder5,
+ Extruder6,
+ Extruder7,
+ Extruder8,
+ Extruder9,
+ Extruder10,
+ Extruder11,
+ Extruder12,
+ Extruder13,
+ Extruder14,
+ Extruder15,
+};
+
+enum class ConversionType : int {
+ CONV_TO_INCH,
+ CONV_FROM_INCH,
+ CONV_TO_METER,
+ CONV_FROM_METER,
+};
+
+class FacetsAnnotation final : public ObjectWithTimestamp {
+public:
+ // Assign the content if the timestamp differs, don't assign an ObjectID.
+ void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } }
+ void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
+ const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>>& get_data() const throw() { return m_data; }
+ bool set(const TriangleSelector& selector);
+ indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
+ indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const;
+ bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
+ bool empty() const { return m_data.first.empty(); }
+
+ // Following method clears the config and increases its timestamp, so the deleted
+ // state is considered changed from perspective of the undo/redo stack.
+ void reset();
+
+ // Serialize triangle into string, for serialization into 3MF/AMF.
+ std::string get_triangle_as_string(int i) const;
+
+ // Before deserialization, reserve space for n_triangles.
+ void reserve(int n_triangles) { m_data.first.reserve(n_triangles); }
+ // Deserialize triangles one by one, with strictly increasing triangle_id.
+ void set_triangle_from_string(int triangle_id, const std::string& str);
+ // After deserializing the last triangle, shrink data to fit.
+ void shrink_to_fit() { m_data.first.shrink_to_fit(); m_data.second.shrink_to_fit(); }
+
+private:
+ // Constructors to be only called by derived classes.
+ // Default constructor to assign a unique ID.
+ explicit FacetsAnnotation() = default;
+ // Constructor with ignored int parameter to assign an invalid ID, to be replaced
+ // by an existing ID copied from elsewhere.
+ explicit FacetsAnnotation(int) : ObjectWithTimestamp(-1) {}
+ // Copy constructor copies the ID.
+ explicit FacetsAnnotation(const FacetsAnnotation &rhs) = default;
+ // Move constructor copies the ID.
+ explicit FacetsAnnotation(FacetsAnnotation &&rhs) = default;
+
+ // called by ModelVolume::assign_copy()
+ FacetsAnnotation& operator=(const FacetsAnnotation &rhs) = default;
+ FacetsAnnotation& operator=(FacetsAnnotation &&rhs) = default;
+
+ friend class cereal::access;
+ friend class UndoRedo::StackImpl;
+
+ template<class Archive> void serialize(Archive &ar)
+ {
+ ar(cereal::base_class<ObjectWithTimestamp>(this), m_data);
+ }
+
+ std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> m_data;
+
+ // To access set_new_unique_id() when copy / pasting a ModelVolume.
+ friend class ModelVolume;
+};
+
+// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
+// ModelVolume instances are owned by a ModelObject.
+class ModelVolume final : public ObjectBase
+{
+public:
+ std::string name;
+ // struct used by reload from disk command to recover data from disk
+ struct Source
+ {
+ std::string input_file;
+ int object_idx{ -1 };
+ int volume_idx{ -1 };
+ Vec3d mesh_offset{ Vec3d::Zero() };
+ Geometry::Transformation transform;
+ bool is_converted_from_inches{ false };
+ bool is_converted_from_meters{ false };
+ bool is_from_builtin_objects{ false };
+
+ template<class Archive> void serialize(Archive& ar) {
+ //FIXME Vojtech: Serialize / deserialize only if the Source is set.
+ // likely testing input_file or object_idx would be sufficient.
+ ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects);
+ }
+ };
+ Source source;
+
+ // The triangular model.
+ const TriangleMesh& mesh() const { return *m_mesh.get(); }
+ void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
+ void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
+ void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
+ void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
+ void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
+ void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
+ void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
+ // Configuration parameters specific to an object model geometry or a modifier volume,
+ // overriding the global Slic3r settings and the ModelObject settings.
+ ModelConfigObject config;
+
+ // List of mesh facets to be supported/unsupported.
+ FacetsAnnotation supported_facets;
+
+ // List of seam enforcers/blockers.
+ FacetsAnnotation seam_facets;
+
+ // List of mesh facets painted for MMU segmentation.
+ FacetsAnnotation mmu_segmentation_facets;
+
+ // A parent object owning this modifier volume.
+ ModelObject* get_object() const { return this->object; }
+ ModelVolumeType type() const { return m_type; }
+ void set_type(const ModelVolumeType t) { m_type = t; }
+ bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; }
+ bool is_negative_volume() const { return m_type == ModelVolumeType::NEGATIVE_VOLUME; }
+ bool is_modifier() const { return m_type == ModelVolumeType::PARAMETER_MODIFIER; }
+ bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; }
+ 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; }
+ t_model_material_id material_id() const { return m_material_id; }
+ void set_material_id(t_model_material_id material_id);
+ ModelMaterial* material() const;
+ void set_material(t_model_material_id material_id, const ModelMaterial &material);
+ // Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config.
+ // Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes).
+ int extruder_id() const;
+
+ bool is_splittable() const;
+
+ // Split this volume, append the result to the object owning this volume.
+ // Return the number of volumes created from this one.
+ // This is useful to assign different materials to different volumes of an object.
+ size_t split(unsigned int max_extruders);
+ void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); }
+ void translate(const Vec3d& displacement);
+ void scale(const Vec3d& scaling_factors);
+ void scale(double x, double y, double z) { scale(Vec3d(x, y, z)); }
+ void scale(double s) { scale(Vec3d(s, s, s)); }
+ void rotate(double angle, Axis axis);
+ void rotate(double angle, const Vec3d& axis);
+ void mirror(Axis axis);
+
+ // This method could only be called before the meshes of this ModelVolumes are not shared!
+ void scale_geometry_after_creation(const Vec3f &versor);
+ void scale_geometry_after_creation(const float scale) { this->scale_geometry_after_creation(Vec3f(scale, scale, scale)); }
+
+ // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box.
+ // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared!
+ void center_geometry_after_creation(bool update_source_offset = true);
+
+ void calculate_convex_hull();
+ const TriangleMesh& get_convex_hull() const;
+ const std::shared_ptr<const TriangleMesh>& get_convex_hull_shared_ptr() const { return m_convex_hull; }
+ // Get count of errors in the mesh
+ int get_repaired_errors_count() const;
+
+ // Helpers for loading / storing into AMF / 3MF files.
+ static ModelVolumeType type_from_string(const std::string &s);
+ static std::string type_to_string(const ModelVolumeType t);
+
+ const Geometry::Transformation& get_transformation() const { return m_transformation; }
+ void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
+#if ENABLE_WORLD_COORDINATE
+ void set_transformation(const Transform3d& trafo) { m_transformation.set_matrix(trafo); }
+
+ Vec3d get_offset() const { return m_transformation.get_offset(); }
+#else
+ void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); }
+
+ const Vec3d& get_offset() const { return m_transformation.get_offset(); }
+#endif // ENABLE_WORLD_COORDINATE
+
+ double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
+
+ void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
+ void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_rotation() const { return m_transformation.get_rotation(); }
+#else
+ const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
+
+ void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
+ void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); }
+
+ Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
+ double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); }
+
+ void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
+ void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_mirror() const { return m_transformation.get_mirror(); }
+#else
+ const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
+ bool is_left_handed() const { return m_transformation.is_left_handed(); }
+
+ void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
+ void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
+ void convert_from_imperial_units();
+ void convert_from_meters();
+
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d& get_matrix() const { return m_transformation.get_matrix(); }
+ Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); }
+#else
+ const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
+#endif // ENABLE_WORLD_COORDINATE
+
+ void set_new_unique_id() {
+ ObjectBase::set_new_unique_id();
+ this->config.set_new_unique_id();
+ this->supported_facets.set_new_unique_id();
+ this->seam_facets.set_new_unique_id();
+ this->mmu_segmentation_facets.set_new_unique_id();
+ }
+
+ bool is_fdm_support_painted() const { return !this->supported_facets.empty(); }
+ bool is_seam_painted() const { return !this->seam_facets.empty(); }
+ bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); }
+
+protected:
+ friend class Print;
+ friend class SLAPrint;
+ friend class Model;
+ friend class ModelObject;
+ friend void model_volume_list_update_supports(ModelObject& model_object_dst, const ModelObject& model_object_new);
+
+ // Copies IDs of both the ModelVolume and its config.
+ explicit ModelVolume(const ModelVolume &rhs) = default;
+ void set_model_object(ModelObject *model_object) { object = model_object; }
+ void assign_new_unique_ids_recursive() override;
+ void transform_this_mesh(const Transform3d& t, bool fix_left_handed);
+ void transform_this_mesh(const Matrix3d& m, bool fix_left_handed);
+
+private:
+ // Parent object owning this ModelVolume.
+ ModelObject* object;
+ // The triangular model.
+ std::shared_ptr<const TriangleMesh> m_mesh;
+ // Is it an object to be printed, or a modifier volume?
+ ModelVolumeType m_type;
+ t_model_material_id m_material_id;
+ // The convex hull of this model's mesh.
+ std::shared_ptr<const TriangleMesh> m_convex_hull;
+ Geometry::Transformation m_transformation;
+
+ // flag to optimize the checking if the volume is splittable
+ // -1 -> is unknown value (before first cheking)
+ // 0 -> is not splittable
+ // 1 -> is splittable
+ mutable int m_is_splittable{ -1 };
+
+ ModelVolume(ModelObject *object, const TriangleMesh &mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART) : m_mesh(new TriangleMesh(mesh)), m_type(type), object(object)
+ {
+ assert(this->id().valid());
+ assert(this->config.id().valid());
+ assert(this->supported_facets.id().valid());
+ assert(this->seam_facets.id().valid());
+ assert(this->mmu_segmentation_facets.id().valid());
+ assert(this->id() != this->config.id());
+ assert(this->id() != this->supported_facets.id());
+ assert(this->id() != this->seam_facets.id());
+ assert(this->id() != this->mmu_segmentation_facets.id());
+ if (mesh.facets_count() > 1)
+ calculate_convex_hull();
+ }
+ ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) :
+ m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) {
+ assert(this->id().valid());
+ assert(this->config.id().valid());
+ assert(this->supported_facets.id().valid());
+ assert(this->seam_facets.id().valid());
+ assert(this->mmu_segmentation_facets.id().valid());
+ assert(this->id() != this->config.id());
+ assert(this->id() != this->supported_facets.id());
+ assert(this->id() != this->seam_facets.id());
+ assert(this->id() != this->mmu_segmentation_facets.id());
+ }
+
+ // Copying an existing volume, therefore this volume will get a copy of the ID assigned.
+ ModelVolume(ModelObject *object, const ModelVolume &other) :
+ ObjectBase(other),
+ name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
+ config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
+ supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets)
+ {
+ assert(this->id().valid());
+ assert(this->config.id().valid());
+ assert(this->supported_facets.id().valid());
+ assert(this->seam_facets.id().valid());
+ assert(this->mmu_segmentation_facets.id().valid());
+ assert(this->id() != this->config.id());
+ assert(this->id() != this->supported_facets.id());
+ assert(this->id() != this->seam_facets.id());
+ assert(this->id() != this->mmu_segmentation_facets.id());
+ assert(this->id() == other.id());
+ assert(this->config.id() == other.config.id());
+ assert(this->supported_facets.id() == other.supported_facets.id());
+ assert(this->seam_facets.id() == other.seam_facets.id());
+ assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id());
+ this->set_material_id(other.material_id());
+ }
+ // Providing a new mesh, therefore this volume will get a new unique ID assigned.
+ ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) :
+ name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation)
+ {
+ assert(this->id().valid());
+ assert(this->config.id().valid());
+ assert(this->supported_facets.id().valid());
+ assert(this->seam_facets.id().valid());
+ assert(this->mmu_segmentation_facets.id().valid());
+ assert(this->id() != this->config.id());
+ assert(this->id() != this->supported_facets.id());
+ assert(this->id() != this->seam_facets.id());
+ assert(this->id() != this->mmu_segmentation_facets.id());
+ assert(this->id() != other.id());
+ assert(this->config.id() == other.config.id());
+ this->set_material_id(other.material_id());
+ this->config.set_new_unique_id();
+ if (m_mesh->facets_count() > 1)
+ calculate_convex_hull();
+ assert(this->config.id().valid());
+ assert(this->config.id() != other.config.id());
+ assert(this->supported_facets.id() != other.supported_facets.id());
+ assert(this->seam_facets.id() != other.seam_facets.id());
+ assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id());
+ assert(this->id() != this->config.id());
+ assert(this->supported_facets.empty());
+ assert(this->seam_facets.empty());
+ assert(this->mmu_segmentation_facets.empty());
+ }
+
+ ModelVolume& operator=(ModelVolume &rhs) = delete;
+
+ friend class cereal::access;
+ friend class UndoRedo::StackImpl;
+ // Used for deserialization, therefore no IDs are allocated.
+ ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) {
+ assert(this->id().invalid());
+ assert(this->config.id().invalid());
+ assert(this->supported_facets.id().invalid());
+ assert(this->seam_facets.id().invalid());
+ assert(this->mmu_segmentation_facets.id().invalid());
+ }
+ template<class Archive> void load(Archive &ar) {
+ bool has_convex_hull;
+ ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
+ cereal::load_by_value(ar, supported_facets);
+ cereal::load_by_value(ar, seam_facets);
+ cereal::load_by_value(ar, mmu_segmentation_facets);
+ cereal::load_by_value(ar, config);
+ assert(m_mesh);
+ if (has_convex_hull) {
+ cereal::load_optional(ar, m_convex_hull);
+ if (! m_convex_hull && ! m_mesh->empty())
+ // The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it.
+ this->calculate_convex_hull();
+ } else
+ m_convex_hull.reset();
+ }
+ template<class Archive> void save(Archive &ar) const {
+ bool has_convex_hull = m_convex_hull.get() != nullptr;
+ ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
+ cereal::save_by_value(ar, supported_facets);
+ cereal::save_by_value(ar, seam_facets);
+ cereal::save_by_value(ar, mmu_segmentation_facets);
+ cereal::save_by_value(ar, config);
+ if (has_convex_hull)
+ cereal::save_optional(ar, m_convex_hull);
+ }
+};
+
+inline void model_volumes_sort_by_id(ModelVolumePtrs &model_volumes)
+{
+ std::sort(model_volumes.begin(), model_volumes.end(), [](const ModelVolume *l, const ModelVolume *r) { return l->id() < r->id(); });
+}
+
+inline const ModelVolume* model_volume_find_by_id(const ModelVolumePtrs &model_volumes, const ObjectID id)
+{
+ auto it = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [id](const ModelVolume *mv) { return mv->id() < id; });
+ return it != model_volumes.end() && (*it)->id() == id ? *it : nullptr;
+}
+
+enum ModelInstanceEPrintVolumeState : unsigned char
+{
+ ModelInstancePVS_Inside,
+ ModelInstancePVS_Partly_Outside,
+ ModelInstancePVS_Fully_Outside,
+ ModelInstanceNum_BedStates
+};
+
+// A single instance of a ModelObject.
+// Knows the affine transformation of an object.
+class ModelInstance final : public ObjectBase
+{
+private:
+ Geometry::Transformation m_transformation;
+
+public:
+ // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state())
+ ModelInstanceEPrintVolumeState print_volume_state;
+ // Whether or not this instance is printable
+ bool printable;
+
+ ModelObject* get_object() const { return this->object; }
+
+ const Geometry::Transformation& get_transformation() const { return m_transformation; }
+ void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_offset() const { return m_transformation.get_offset(); }
+#else
+ const Vec3d& get_offset() const { return m_transformation.get_offset(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
+
+ void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
+ void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_rotation() const { return m_transformation.get_rotation(); }
+#else
+ const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
+
+ void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
+ void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
+#else
+ const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); }
+
+ void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
+ void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_mirror() const { return m_transformation.get_mirror(); }
+#else
+ const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
+ bool is_left_handed() const { return m_transformation.is_left_handed(); }
+
+ void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
+ void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
+
+ // To be called on an external mesh
+ void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
+ // Calculate a bounding box of a transformed mesh. To be called on an external mesh.
+ BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const;
+ // Transform an external bounding box.
+ BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const;
+ // Transform an external vector.
+ Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const;
+ // To be called on an external polygon. It does not translate the polygon, only rotates and scales.
+ void transform_polygon(Polygon* polygon) const;
+
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d& get_matrix() const { return m_transformation.get_matrix(); }
+ Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); }
+#else
+ const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
+#endif // ENABLE_WORLD_COORDINATE
+
+ bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); }
+
+ // Getting the input polygon for arrange
+ arrangement::ArrangePolygon get_arrange_polygon() const;
+
+ // Apply the arrange result on the ModelInstance
+ void apply_arrange_result(const Vec2d& offs, double rotation)
+ {
+ // write the transformation data into the model instance
+ set_rotation(Z, rotation);
+ set_offset(X, unscale<double>(offs(X)));
+ set_offset(Y, unscale<double>(offs(Y)));
+ this->object->invalidate_bounding_box();
+ }
+
+protected:
+ friend class Print;
+ friend class SLAPrint;
+ friend class Model;
+ friend class ModelObject;
+
+ explicit ModelInstance(const ModelInstance &rhs) = default;
+ void set_model_object(ModelObject *model_object) { object = model_object; }
+
+private:
+ // Parent object, owning this instance.
+ ModelObject* object;
+
+ // Constructor, which assigns a new unique ID.
+ explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object) { assert(this->id().valid()); }
+ // Constructor, which assigns a new unique ID.
+ explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
+ m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); }
+
+ explicit ModelInstance(ModelInstance &&rhs) = delete;
+ ModelInstance& operator=(const ModelInstance &rhs) = delete;
+ ModelInstance& operator=(ModelInstance &&rhs) = delete;
+
+ friend class cereal::access;
+ friend class UndoRedo::StackImpl;
+ // Used for deserialization, therefore no IDs are allocated.
+ ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); }
+ template<class Archive> void serialize(Archive &ar) {
+ ar(m_transformation, print_volume_state, printable);
+ }
+};
+
+
+class ModelWipeTower final : public ObjectBase
+{
+public:
+ Vec2d position;
+ double rotation;
+
+private:
+ friend class cereal::access;
+ friend class UndoRedo::StackImpl;
+ friend class Model;
+
+ // Constructors to be only called by derived classes.
+ // Default constructor to assign a unique ID.
+ explicit ModelWipeTower() {}
+ // Constructor with ignored int parameter to assign an invalid ID, to be replaced
+ // by an existing ID copied from elsewhere.
+ explicit ModelWipeTower(int) : ObjectBase(-1) {}
+ // Copy constructor copies the ID.
+ explicit ModelWipeTower(const ModelWipeTower &cfg) = default;
+
+ // Disabled methods.
+ ModelWipeTower(ModelWipeTower &&rhs) = delete;
+ ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete;
+ ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete;
+
+ // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object.
+ template<typename Archive> void serialize(Archive &ar) { ar(position, rotation); }
+};
+
+// The print bed content.
+// Description of a triangular model with multiple materials, multiple instances with various affine transformations
+// and with multiple modifier meshes.
+// A model groups multiple objects, each object having possibly multiple instances,
+// all objects may share mutliple materials.
+class Model final : public ObjectBase
+{
+public:
+ // Materials are owned by a model and referenced by objects through t_model_material_id.
+ // Single material may be shared by multiple models.
+ ModelMaterialMap materials;
+ // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation).
+ ModelObjectPtrs objects;
+ // Wipe tower object.
+ ModelWipeTower wipe_tower;
+
+ // Extensions for color print
+ CustomGCode::Info custom_gcode_per_print_z;
+
+ // Default constructor assigns a new ID to the model.
+ Model() { assert(this->id().valid()); }
+ ~Model() { this->clear_objects(); this->clear_materials(); }
+
+ /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */
+ /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */
+ Model(const Model &rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); }
+ explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); }
+ Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; }
+ Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; }
+
+ OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
+
+ enum class LoadAttribute : int {
+ AddDefaultInstances,
+ CheckVersion
+ };
+ using LoadAttributes = enum_bitmask<LoadAttribute>;
+
+ static Model read_from_file(
+ const std::string& input_file,
+ DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr,
+ LoadAttributes options = LoadAttribute::AddDefaultInstances);
+ static Model read_from_archive(
+ const std::string& input_file,
+ DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions,
+ LoadAttributes options = LoadAttribute::AddDefaultInstances);
+
+ // Add a new ModelObject to this Model, generate a new ID for this ModelObject.
+ ModelObject* add_object();
+ ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh);
+ ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh);
+ ModelObject* add_object(const ModelObject &other);
+ void delete_object(size_t idx);
+ bool delete_object(ObjectID id);
+ bool delete_object(ModelObject* object);
+ void clear_objects();
+
+ ModelMaterial* add_material(t_model_material_id material_id);
+ ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other);
+ ModelMaterial* get_material(t_model_material_id material_id) {
+ ModelMaterialMap::iterator i = this->materials.find(material_id);
+ return (i == this->materials.end()) ? nullptr : i->second;
+ }
+
+ void delete_material(t_model_material_id material_id);
+ void clear_materials();
+ bool add_default_instances();
+ // Returns approximate axis aligned bounding box of this model
+ BoundingBoxf3 bounding_box() const;
+ // Set the print_volume_state of PrintObject::instances,
+ // return total number of printable objects.
+ unsigned int update_print_volume_state(const BuildVolume &build_volume);
+ // Returns true if any ModelObject was modified.
+ bool center_instances_around_point(const Vec2d &point);
+ void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
+ TriangleMesh mesh() const;
+
+ // Croaks if the duplicated objects do not fit the print bed.
+ void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
+
+ bool looks_like_multipart_object() const;
+ void convert_multipart_object(unsigned int max_extruders);
+ bool looks_like_imperial_units() const;
+ void convert_from_imperial_units(bool only_small_volumes);
+ bool looks_like_saved_in_meters() const;
+ void convert_from_meters(bool only_small_volumes);
+ int removed_objects_with_zero_volume();
+
+ // Ensures that the min z of the model is not negative
+ void adjust_min_z();
+
+ void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); }
+
+ // Propose an output file name & path based on the first printable object's name and source input file's path.
+ std::string propose_export_file_name_and_path() const;
+ // Propose an output path, replace extension. The new_extension shall contain the initial dot.
+ std::string propose_export_file_name_and_path(const std::string &new_extension) const;
+
+ // Checks if any of objects is painted using the fdm support painting gizmo.
+ bool is_fdm_support_painted() const;
+ // Checks if any of objects is painted using the seam painting gizmo.
+ bool is_seam_painted() const;
+ // Checks if any of objects is painted using the multi-material painting gizmo.
+ bool is_mm_painted() const;
+
+private:
+ explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }
+ void assign_new_unique_ids_recursive();
+ void update_links_bottom_up_recursive();
+
+ friend class cereal::access;
+ friend class UndoRedo::StackImpl;
+ template<class Archive> void serialize(Archive &ar) {
+ Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower);
+ ar(materials, objects, wipe_tower_wrapper);
+ }
+};
+
+ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
+
+#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
+#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
+
+// Test whether the two models contain the same number of ModelObjects with the same set of IDs
+// ordered in the same order. In that case it is not necessary to kill the background processing.
+bool model_object_list_equal(const Model &model_old, const Model &model_new);
+
+// Test whether the new model is just an extension of the old model (new objects were added
+// to the end of the original list. In that case it is not necessary to kill the background processing.
+bool model_object_list_extended(const Model &model_old, const Model &model_new);
+
+// Test whether the new ModelObject contains a different set of volumes (or sorted in a different order)
+// than the old ModelObject.
+bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type);
+bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const std::initializer_list<ModelVolumeType> &types);
+
+// Test whether the now ModelObject has newer custom supports data than the old one.
+// The function assumes that volumes list is synchronized.
+bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new);
+
+// Test whether the now ModelObject has newer custom seam data than the old one.
+// The function assumes that volumes list is synchronized.
+bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new);
+
+// Test whether the now ModelObject has newer MMU segmentation data than the old one.
+// The function assumes that volumes list is synchronized.
+extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new);
+
+// If the model has multi-part objects, then it is currently not supported by the SLA mode.
+// Either the model cannot be loaded, or a SLA printer has to be activated.
+bool model_has_multi_part_objects(const Model &model);
+// If the model has advanced features, then it cannot be processed in simple mode.
+bool model_has_advanced_features(const Model &model);
+
+#ifndef NDEBUG
+// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
+void check_model_ids_validity(const Model &model);
+void check_model_ids_equal(const Model &model1, const Model &model2);
+#endif /* NDEBUG */
+
+static const float SINKING_Z_THRESHOLD = -0.001f;
+static const double SINKING_MIN_Z_THRESHOLD = 0.05;
+
+} // namespace Slic3r
+
+namespace cereal
+{
+ template <class Archive> struct specialize<Archive, Slic3r::ModelVolume, cereal::specialization::member_load_save> {};
+ template <class Archive> struct specialize<Archive, Slic3r::ModelConfigObject, cereal::specialization::member_serialize> {};
+}
+
+#endif /* slic3r_Model_hpp_ */
diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp
index 84152ee9c..ec071673b 100644
--- a/src/libslic3r/Point.hpp
+++ b/src/libslic3r/Point.hpp
@@ -1,565 +1,569 @@
-#ifndef slic3r_Point_hpp_
-#define slic3r_Point_hpp_
-
-#include "libslic3r.h"
-#include <cstddef>
-#include <vector>
-#include <cmath>
-#include <string>
-#include <sstream>
-#include <unordered_map>
-
-#include <Eigen/Geometry>
-
-#include "LocalesUtils.hpp"
-
-namespace Slic3r {
-
-class BoundingBox;
-class BoundingBoxf;
-class Line;
-class MultiPoint;
-class Point;
-using Vector = Point;
-
-// Base template for eigen derived vectors
-template<int N, int M, class T>
-using Mat = Eigen::Matrix<T, N, M, Eigen::DontAlign, N, M>;
-
-template<int N, class T> using Vec = Mat<N, 1, T>;
-
-template<typename NumberType>
-using DynVec = Eigen::Matrix<NumberType, Eigen::Dynamic, 1>;
-
-// Eigen types, to replace the Slic3r's own types in the future.
-// Vector types with a fixed point coordinate base type.
-using Vec2crd = Eigen::Matrix<coord_t, 2, 1, Eigen::DontAlign>;
-using Vec3crd = Eigen::Matrix<coord_t, 3, 1, Eigen::DontAlign>;
-using Vec2i = Eigen::Matrix<int, 2, 1, Eigen::DontAlign>;
-using Vec3i = Eigen::Matrix<int, 3, 1, Eigen::DontAlign>;
-using Vec4i = Eigen::Matrix<int, 4, 1, Eigen::DontAlign>;
-using Vec2i32 = Eigen::Matrix<int32_t, 2, 1, Eigen::DontAlign>;
-using Vec2i64 = Eigen::Matrix<int64_t, 2, 1, Eigen::DontAlign>;
-using Vec3i32 = Eigen::Matrix<int32_t, 3, 1, Eigen::DontAlign>;
-using Vec3i64 = Eigen::Matrix<int64_t, 3, 1, Eigen::DontAlign>;
-
-// Vector types with a double coordinate base type.
-using Vec2f = Eigen::Matrix<float, 2, 1, Eigen::DontAlign>;
-using Vec3f = Eigen::Matrix<float, 3, 1, Eigen::DontAlign>;
-using Vec2d = Eigen::Matrix<double, 2, 1, Eigen::DontAlign>;
-using Vec3d = Eigen::Matrix<double, 3, 1, Eigen::DontAlign>;
-
-using Points = std::vector<Point>;
-using PointPtrs = std::vector<Point*>;
-using PointConstPtrs = std::vector<const Point*>;
-using Points3 = std::vector<Vec3crd>;
-using Pointfs = std::vector<Vec2d>;
-using Vec2ds = std::vector<Vec2d>;
-using Pointf3s = std::vector<Vec3d>;
-
-using Matrix2f = Eigen::Matrix<float, 2, 2, Eigen::DontAlign>;
-using Matrix2d = Eigen::Matrix<double, 2, 2, Eigen::DontAlign>;
-using Matrix3f = Eigen::Matrix<float, 3, 3, Eigen::DontAlign>;
-using Matrix3d = Eigen::Matrix<double, 3, 3, Eigen::DontAlign>;
-using Matrix4f = Eigen::Matrix<float, 4, 4, Eigen::DontAlign>;
-using Matrix4d = Eigen::Matrix<double, 4, 4, Eigen::DontAlign>;
-
-template<int N, class T>
-using Transform = Eigen::Transform<float, N, Eigen::Affine, Eigen::DontAlign>;
-
-using Transform2f = Eigen::Transform<float, 2, Eigen::Affine, Eigen::DontAlign>;
-using Transform2d = Eigen::Transform<double, 2, Eigen::Affine, Eigen::DontAlign>;
-using Transform3f = Eigen::Transform<float, 3, Eigen::Affine, Eigen::DontAlign>;
-using Transform3d = Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAlign>;
-
-// I don't know why Eigen::Transform::Identity() return a const object...
-template<int N, class T> Transform<N, T> identity() { return Transform<N, T>::Identity(); }
-inline const auto &identity3f = identity<3, float>;
-inline const auto &identity3d = identity<3, double>;
-
-inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y()); }
-
-// Cross product of two 2D vectors.
-// None of the vectors may be of int32_t type as the result would overflow.
-template<typename Derived, typename Derived2>
-inline typename Derived::Scalar cross2(const Eigen::MatrixBase<Derived> &v1, const Eigen::MatrixBase<Derived2> &v2)
-{
- static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector");
- static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector");
- static_assert(! std::is_same<typename Derived::Scalar, int32_t>::value, "cross2(): Scalar type must not be int32_t, otherwise the cross product would overflow.");
- static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "cross2(): Scalar types of 1st and 2nd operand must be equal.");
- return v1.x() * v2.y() - v1.y() * v2.x();
-}
-
-// 2D vector perpendicular to the argument.
-template<typename Derived>
-inline Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> perp(const Eigen::MatrixBase<Derived> &v)
-{
- static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "perp(): parameter is not a 2D vector");
- return { - v.y(), v.x() };
-}
-
-// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>.
-template<typename Derived, typename Derived2>
-inline double angle(const Eigen::MatrixBase<Derived> &v1, const Eigen::MatrixBase<Derived2> &v2) {
- static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector");
- static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector");
- auto v1d = v1.template cast<double>();
- auto v2d = v2.template cast<double>();
- return atan2(cross2(v1d, v2d), v1d.dot(v2d));
-}
-
-template<class T, int N, int Options>
-Eigen::Matrix<T, 2, 1, Eigen::DontAlign> to_2d(const Eigen::MatrixBase<Eigen::Matrix<T, N, 1, Options>> &ptN) { return { ptN.x(), ptN.y() }; }
-
-template<class T, int Options>
-Eigen::Matrix<T, 3, 1, Eigen::DontAlign> to_3d(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> & pt, const T z) { return { pt.x(), pt.y(), z }; }
-
-inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale<double>(x), unscale<double>(y)); }
-inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale<double>(pt.x()), unscale<double>(pt.y())); }
-inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale<double>(pt.x()), unscale<double>(pt.y())); }
-inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale<double>(x), unscale<double>(y), unscale<double>(z)); }
-inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale<double>(pt.x()), unscale<double>(pt.y()), unscale<double>(pt.z())); }
-inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale<double>(pt.x()), unscale<double>(pt.y()), unscale<double>(pt.z())); }
-
-inline std::string to_string(const Vec2crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; }
-inline std::string to_string(const Vec2d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; }
-inline std::string to_string(const Vec3crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; }
-inline std::string to_string(const Vec3d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; }
-
-std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t);
-Pointf3s transform(const Pointf3s& points, const Transform3d& t);
-
-template<int N, class T> using Vec = Eigen::Matrix<T, N, 1, Eigen::DontAlign, N, 1>;
-
-class Point : public Vec2crd
-{
-public:
- using coord_type = coord_t;
-
- Point() : Vec2crd(0, 0) {}
- Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {}
- Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {}
- Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {}
- Point(const Point &rhs) { *this = rhs; }
- explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {}
- // This constructor allows you to construct Point from Eigen expressions
- template<typename OtherDerived>
- Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
- static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
- static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
- static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
-
- // This method allows you to assign Eigen expressions to MyVectorType
- template<typename OtherDerived>
- Point& operator=(const Eigen::MatrixBase<OtherDerived> &other)
- {
- this->Vec2crd::operator=(other);
- return *this;
- }
-
- Point& operator+=(const Point& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); return *this; }
- Point& operator-=(const Point& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); return *this; }
- Point& operator*=(const double &rhs) { this->x() = coord_t(this->x() * rhs); this->y() = coord_t(this->y() * rhs); return *this; }
- Point operator*(const double &rhs) { return Point(this->x() * rhs, this->y() * rhs); }
-
- void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); }
- void rotate(double cos_a, double sin_a) {
- double cur_x = (double)this->x();
- double cur_y = (double)this->y();
- this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y);
- this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x);
- }
-
- void rotate(double angle, const Point &center);
- Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; }
- Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; }
- Point rotated(double angle, const Point &center) const { Point res(*this); res.rotate(angle, center); return res; }
- int nearest_point_index(const Points &points) const;
- int nearest_point_index(const PointConstPtrs &points) const;
- int nearest_point_index(const PointPtrs &points) const;
- bool nearest_point(const Points &points, Point* point) const;
- Point projection_onto(const MultiPoint &poly) const;
- Point projection_onto(const Line &line) const;
-};
-
-inline bool operator<(const Point &l, const Point &r)
-{
- return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y());
-}
-
-inline Point operator* (const Point& l, const double &r)
-{
- return {coord_t(l.x() * r), coord_t(l.y() * r)};
-}
-
-inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON))
-{
- Point d = (p2 - p1).cwiseAbs();
- return d.x() < epsilon && d.y() < epsilon;
-}
-
-inline bool is_approx(const Vec2f &p1, const Vec2f &p2, float epsilon = float(EPSILON))
-{
- Vec2f d = (p2 - p1).cwiseAbs();
- return d.x() < epsilon && d.y() < epsilon;
-}
-
-inline bool is_approx(const Vec2d &p1, const Vec2d &p2, double epsilon = EPSILON)
-{
- Vec2d d = (p2 - p1).cwiseAbs();
- return d.x() < epsilon && d.y() < epsilon;
-}
-
-inline bool is_approx(const Vec3f &p1, const Vec3f &p2, float epsilon = float(EPSILON))
-{
- Vec3f d = (p2 - p1).cwiseAbs();
- return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon;
-}
-
-inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON)
-{
- Vec3d d = (p2 - p1).cwiseAbs();
- return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon;
-}
-
-inline Point lerp(const Point &a, const Point &b, double t)
-{
- assert((t >= -EPSILON) && (t <= 1. + EPSILON));
- return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
-}
-
-BoundingBox get_extents(const Points &pts);
-BoundingBox get_extents(const std::vector<Points> &pts);
-BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
-
-// Test for duplicate points in a vector of points.
-// The points are copied, sorted and checked for duplicates globally.
-bool has_duplicate_points(std::vector<Point> &&pts);
-inline bool has_duplicate_points(const std::vector<Point> &pts)
-{
- std::vector<Point> cpy = pts;
- return has_duplicate_points(std::move(cpy));
-}
-
-// Test for duplicate points in a vector of points.
-// Only successive points are checked for equality.
-inline bool has_duplicate_successive_points(const std::vector<Point> &pts)
-{
- for (size_t i = 1; i < pts.size(); ++ i)
- if (pts[i - 1] == pts[i])
- return true;
- return false;
-}
-
-// Test for duplicate points in a vector of points.
-// Only successive points are checked for equality. Additionally, first and last points are compared for equality.
-inline bool has_duplicate_successive_points_closed(const std::vector<Point> &pts)
-{
- return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back());
-}
-
-namespace int128 {
- // Exact orientation predicate,
- // returns +1: CCW, 0: collinear, -1: CW.
- int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3);
- // Exact orientation predicate,
- // returns +1: CCW, 0: collinear, -1: CW.
- int cross(const Vec2crd &v1, const Vec2crd &v2);
-}
-
-// To be used by std::unordered_map, std::unordered_multimap and friends.
-struct PointHash {
- size_t operator()(const Vec2crd &pt) const {
- return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y());
- }
-};
-
-// A generic class to search for a closest Point in a given radius.
-// It uses std::unordered_multimap to implement an efficient 2D spatial hashing.
-// The PointAccessor has to return const Point*.
-// If a nullptr is returned, it is ignored by the query.
-template<typename ValueType, typename PointAccessor> class ClosestPointInRadiusLookup
-{
-public:
- ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) :
- m_search_radius(search_radius), m_point_accessor(point_accessor), m_grid_log2(0)
- {
- // Resolution of a grid, twice the search radius + some epsilon.
- coord_t gridres = 2 * m_search_radius + 4;
- m_grid_resolution = gridres;
- assert(m_grid_resolution > 0);
- assert(m_grid_resolution < (coord_t(1) << 30));
- // Compute m_grid_log2 = log2(m_grid_resolution)
- if (m_grid_resolution > 32767) {
- m_grid_resolution >>= 16;
- m_grid_log2 += 16;
- }
- if (m_grid_resolution > 127) {
- m_grid_resolution >>= 8;
- m_grid_log2 += 8;
- }
- if (m_grid_resolution > 7) {
- m_grid_resolution >>= 4;
- m_grid_log2 += 4;
- }
- if (m_grid_resolution > 1) {
- m_grid_resolution >>= 2;
- m_grid_log2 += 2;
- }
- if (m_grid_resolution > 0)
- ++ m_grid_log2;
- m_grid_resolution = 1 << m_grid_log2;
- assert(m_grid_resolution >= gridres);
- assert(gridres > m_grid_resolution / 2);
- }
-
- void insert(const ValueType &value) {
- const Vec2crd *pt = m_point_accessor(value);
- if (pt != nullptr)
- m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), value));
- }
-
- void insert(ValueType &&value) {
- const Vec2crd *pt = m_point_accessor(value);
- if (pt != nullptr)
- m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value)));
- }
-
- // Erase a data point equal to value. (ValueType has to declare the operator==).
- // Returns true if the data point equal to value was found and removed.
- bool erase(const ValueType &value) {
- const Point *pt = m_point_accessor(value);
- if (pt != nullptr) {
- // Range of fragment starts around grid_corner, close to pt.
- auto range = m_map.equal_range(Point((*pt).x()>>m_grid_log2, (*pt).y()>>m_grid_log2));
- // Remove the first item.
- for (auto it = range.first; it != range.second; ++ it) {
- if (it->second == value) {
- m_map.erase(it);
- return true;
- }
- }
- }
- return false;
- }
-
- // Return a pair of <ValueType*, distance_squared>
- std::pair<const ValueType*, double> find(const Vec2crd &pt) {
- // Iterate over 4 closest grid cells around pt,
- // find the closest start point inside these cells to pt.
- const ValueType *value_min = nullptr;
- double dist_min = std::numeric_limits<double>::max();
- // Round pt to a closest grid_cell corner.
- Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2);
- // For four neighbors of grid_corner:
- for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) {
- for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) {
- // Range of fragment starts around grid_corner, close to pt.
- auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y));
- // Find the map entry closest to pt.
- for (auto it = range.first; it != range.second; ++it) {
- const ValueType &value = it->second;
- const Vec2crd *pt2 = m_point_accessor(value);
- if (pt2 != nullptr) {
- const double d2 = (pt - *pt2).cast<double>().squaredNorm();
- if (d2 < dist_min) {
- dist_min = d2;
- value_min = &value;
- }
- }
- }
- }
- }
- return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ?
- std::make_pair(value_min, dist_min) :
- std::make_pair(nullptr, std::numeric_limits<double>::max());
- }
-
- // Returns all pairs of values and squared distances.
- std::vector<std::pair<const ValueType*, double>> find_all(const Vec2crd &pt) {
- // Iterate over 4 closest grid cells around pt,
- // Round pt to a closest grid_cell corner.
- Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2);
- // For four neighbors of grid_corner:
- std::vector<std::pair<const ValueType*, double>> out;
- const double r2 = double(m_search_radius) * m_search_radius;
- for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) {
- for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) {
- // Range of fragment starts around grid_corner, close to pt.
- auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y));
- // Find the map entry closest to pt.
- for (auto it = range.first; it != range.second; ++it) {
- const ValueType &value = it->second;
- const Vec2crd *pt2 = m_point_accessor(value);
- if (pt2 != nullptr) {
- const double d2 = (pt - *pt2).cast<double>().squaredNorm();
- if (d2 <= r2)
- out.emplace_back(&value, d2);
- }
- }
- }
- }
- return out;
- }
-
-private:
- using map_type = typename std::unordered_multimap<Vec2crd, ValueType, PointHash>;
- PointAccessor m_point_accessor;
- map_type m_map;
- coord_t m_search_radius;
- coord_t m_grid_resolution;
- coord_t m_grid_log2;
-};
-
-std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf);
-
-
-// /////////////////////////////////////////////////////////////////////////////
-// Type safe conversions to and from scaled and unscaled coordinates
-// /////////////////////////////////////////////////////////////////////////////
-
-// Semantics are the following:
-// Upscaling (scaled()): only from floating point types (or Vec) to either
-// floating point or integer 'scaled coord' coordinates.
-// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only
-
-// Conversion definition from unscaled to floating point scaled
-template<class Tout,
- class Tin,
- class = FloatingOnly<Tin>>
-inline constexpr FloatingOnly<Tout> scaled(const Tin &v) noexcept
-{
- return Tout(v / Tin(SCALING_FACTOR));
-}
-
-// Conversion definition from unscaled to integer 'scaled coord'.
-// TODO: is the rounding necessary? Here it is commented out to show that
-// it can be different for integers but it does not have to be. Using
-// std::round means loosing noexcept and constexpr modifiers
-template<class Tout = coord_t, class Tin, class = FloatingOnly<Tin>>
-inline constexpr ScaledCoordOnly<Tout> scaled(const Tin &v) noexcept
-{
- //return static_cast<Tout>(std::round(v / SCALING_FACTOR));
- return Tout(v / Tin(SCALING_FACTOR));
-}
-
-// Conversion for Eigen vectors (N dimensional points)
-template<class Tout = coord_t,
- class Tin,
- int N,
- class = FloatingOnly<Tin>,
- int...EigenArgs>
-inline Eigen::Matrix<ArithmeticOnly<Tout>, N, EigenArgs...>
-scaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v)
-{
- return (v / SCALING_FACTOR).template cast<Tout>();
-}
-
-// Conversion from arithmetic scaled type to floating point unscaled
-template<class Tout = double,
- class Tin,
- class = ArithmeticOnly<Tin>,
- class = FloatingOnly<Tout>>
-inline constexpr Tout unscaled(const Tin &v) noexcept
-{
- return Tout(v) * Tout(SCALING_FACTOR);
-}
-
-// Unscaling for Eigen vectors. Input base type can be arithmetic, output base
-// type can only be floating point.
-template<class Tout = double,
- class Tin,
- int N,
- class = ArithmeticOnly<Tin>,
- class = FloatingOnly<Tout>,
- int...EigenArgs>
-inline constexpr Eigen::Matrix<Tout, N, EigenArgs...>
-unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept
-{
- return v.template cast<Tout>() * Tout(SCALING_FACTOR);
-}
-
-// Align a coordinate to a grid. The coordinate may be negative,
-// the aligned value will never be bigger than the original one.
-inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) {
- // Current C++ standard defines the result of integer division to be rounded to zero,
- // for both positive and negative numbers. Here we want to round down for negative
- // numbers as well.
- coord_t aligned = (coord < 0) ?
- ((coord - spacing + 1) / spacing) * spacing :
- (coord / spacing) * spacing;
- assert(aligned <= coord);
- return aligned;
-}
-inline Point align_to_grid(Point coord, Point spacing)
- { return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); }
-inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base)
- { return base + align_to_grid(coord - base, spacing); }
-inline Point align_to_grid(Point coord, Point spacing, Point base)
- { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); }
-
-} // namespace Slic3r
-
-// start Boost
-#include <boost/version.hpp>
-#include <boost/polygon/polygon.hpp>
-namespace boost { namespace polygon {
- template <>
- struct geometry_concept<Slic3r::Point> { using type = point_concept; };
-
- template <>
- struct point_traits<Slic3r::Point> {
- using coordinate_type = coord_t;
-
- static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) {
- return static_cast<coordinate_type>(point((orient == HORIZONTAL) ? 0 : 1));
- }
- };
-
- template <>
- struct point_mutable_traits<Slic3r::Point> {
- using coordinate_type = coord_t;
- static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) {
- point((orient == HORIZONTAL) ? 0 : 1) = value;
- }
- static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) {
- return Slic3r::Point(x_value, y_value);
- }
- };
-} }
-// end Boost
-
-// Serialization through the Cereal library
-namespace cereal {
-// template<class Archive> void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); }
-// template<class Archive> void serialize(Archive& archive, Slic3r::Vec3crd &v) { archive(v.x(), v.y(), v.z()); }
- template<class Archive> void serialize(Archive& archive, Slic3r::Vec2i &v) { archive(v.x(), v.y()); }
- template<class Archive> void serialize(Archive& archive, Slic3r::Vec3i &v) { archive(v.x(), v.y(), v.z()); }
-// template<class Archive> void serialize(Archive& archive, Slic3r::Vec2i64 &v) { archive(v.x(), v.y()); }
-// template<class Archive> void serialize(Archive& archive, Slic3r::Vec3i64 &v) { archive(v.x(), v.y(), v.z()); }
- template<class Archive> void serialize(Archive& archive, Slic3r::Vec2f &v) { archive(v.x(), v.y()); }
- template<class Archive> void serialize(Archive& archive, Slic3r::Vec3f &v) { archive(v.x(), v.y(), v.z()); }
- template<class Archive> void serialize(Archive& archive, Slic3r::Vec2d &v) { archive(v.x(), v.y()); }
- template<class Archive> void serialize(Archive& archive, Slic3r::Vec3d &v) { archive(v.x(), v.y(), v.z()); }
-
- template<class Archive> void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); }
- template<class Archive> void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); }
-}
-
-// To be able to use Vec<> and Mat<> in range based for loops:
-namespace Eigen {
-template<class T, int N, int M>
-T* begin(Slic3r::Mat<N, M, T> &mat) { return mat.data(); }
-
-template<class T, int N, int M>
-T* end(Slic3r::Mat<N, M, T> &mat) { return mat.data() + N * M; }
-
-template<class T, int N, int M>
-const T* begin(const Slic3r::Mat<N, M, T> &mat) { return mat.data(); }
-
-template<class T, int N, int M>
-const T* end(const Slic3r::Mat<N, M, T> &mat) { return mat.data() + N * M; }
-} // namespace Eigen
-
-#endif
+#ifndef slic3r_Point_hpp_
+#define slic3r_Point_hpp_
+
+#include "libslic3r.h"
+#include <cstddef>
+#include <vector>
+#include <cmath>
+#include <string>
+#include <sstream>
+#include <unordered_map>
+
+#include <Eigen/Geometry>
+
+#include "LocalesUtils.hpp"
+
+namespace Slic3r {
+
+class BoundingBox;
+class BoundingBoxf;
+class Line;
+class MultiPoint;
+class Point;
+using Vector = Point;
+
+// Base template for eigen derived vectors
+template<int N, int M, class T>
+using Mat = Eigen::Matrix<T, N, M, Eigen::DontAlign, N, M>;
+
+template<int N, class T> using Vec = Mat<N, 1, T>;
+
+template<typename NumberType>
+using DynVec = Eigen::Matrix<NumberType, Eigen::Dynamic, 1>;
+
+// Eigen types, to replace the Slic3r's own types in the future.
+// Vector types with a fixed point coordinate base type.
+using Vec2crd = Eigen::Matrix<coord_t, 2, 1, Eigen::DontAlign>;
+using Vec3crd = Eigen::Matrix<coord_t, 3, 1, Eigen::DontAlign>;
+using Vec2i = Eigen::Matrix<int, 2, 1, Eigen::DontAlign>;
+using Vec3i = Eigen::Matrix<int, 3, 1, Eigen::DontAlign>;
+using Vec4i = Eigen::Matrix<int, 4, 1, Eigen::DontAlign>;
+using Vec2i32 = Eigen::Matrix<int32_t, 2, 1, Eigen::DontAlign>;
+using Vec2i64 = Eigen::Matrix<int64_t, 2, 1, Eigen::DontAlign>;
+using Vec3i32 = Eigen::Matrix<int32_t, 3, 1, Eigen::DontAlign>;
+using Vec3i64 = Eigen::Matrix<int64_t, 3, 1, Eigen::DontAlign>;
+
+// Vector types with a double coordinate base type.
+using Vec2f = Eigen::Matrix<float, 2, 1, Eigen::DontAlign>;
+using Vec3f = Eigen::Matrix<float, 3, 1, Eigen::DontAlign>;
+using Vec2d = Eigen::Matrix<double, 2, 1, Eigen::DontAlign>;
+using Vec3d = Eigen::Matrix<double, 3, 1, Eigen::DontAlign>;
+
+using Points = std::vector<Point>;
+using PointPtrs = std::vector<Point*>;
+using PointConstPtrs = std::vector<const Point*>;
+using Points3 = std::vector<Vec3crd>;
+using Pointfs = std::vector<Vec2d>;
+using Vec2ds = std::vector<Vec2d>;
+using Pointf3s = std::vector<Vec3d>;
+
+using Matrix2f = Eigen::Matrix<float, 2, 2, Eigen::DontAlign>;
+using Matrix2d = Eigen::Matrix<double, 2, 2, Eigen::DontAlign>;
+using Matrix3f = Eigen::Matrix<float, 3, 3, Eigen::DontAlign>;
+using Matrix3d = Eigen::Matrix<double, 3, 3, Eigen::DontAlign>;
+using Matrix4f = Eigen::Matrix<float, 4, 4, Eigen::DontAlign>;
+using Matrix4d = Eigen::Matrix<double, 4, 4, Eigen::DontAlign>;
+
+template<int N, class T>
+using Transform = Eigen::Transform<float, N, Eigen::Affine, Eigen::DontAlign>;
+
+using Transform2f = Eigen::Transform<float, 2, Eigen::Affine, Eigen::DontAlign>;
+using Transform2d = Eigen::Transform<double, 2, Eigen::Affine, Eigen::DontAlign>;
+using Transform3f = Eigen::Transform<float, 3, Eigen::Affine, Eigen::DontAlign>;
+using Transform3d = Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAlign>;
+
+// I don't know why Eigen::Transform::Identity() return a const object...
+template<int N, class T> Transform<N, T> identity() { return Transform<N, T>::Identity(); }
+inline const auto &identity3f = identity<3, float>;
+inline const auto &identity3d = identity<3, double>;
+
+inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y()); }
+
+// Cross product of two 2D vectors.
+// None of the vectors may be of int32_t type as the result would overflow.
+template<typename Derived, typename Derived2>
+inline typename Derived::Scalar cross2(const Eigen::MatrixBase<Derived> &v1, const Eigen::MatrixBase<Derived2> &v2)
+{
+ static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector");
+ static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector");
+ static_assert(! std::is_same<typename Derived::Scalar, int32_t>::value, "cross2(): Scalar type must not be int32_t, otherwise the cross product would overflow.");
+ static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "cross2(): Scalar types of 1st and 2nd operand must be equal.");
+ return v1.x() * v2.y() - v1.y() * v2.x();
+}
+
+// 2D vector perpendicular to the argument.
+template<typename Derived>
+inline Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> perp(const Eigen::MatrixBase<Derived> &v)
+{
+ static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "perp(): parameter is not a 2D vector");
+ return { - v.y(), v.x() };
+}
+
+// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>.
+template<typename Derived, typename Derived2>
+inline double angle(const Eigen::MatrixBase<Derived> &v1, const Eigen::MatrixBase<Derived2> &v2) {
+ static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector");
+ static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector");
+ auto v1d = v1.template cast<double>();
+ auto v2d = v2.template cast<double>();
+ return atan2(cross2(v1d, v2d), v1d.dot(v2d));
+}
+
+template<class T, int N, int Options>
+Eigen::Matrix<T, 2, 1, Eigen::DontAlign> to_2d(const Eigen::MatrixBase<Eigen::Matrix<T, N, 1, Options>> &ptN) { return { ptN.x(), ptN.y() }; }
+
+template<class T, int Options>
+Eigen::Matrix<T, 3, 1, Eigen::DontAlign> to_3d(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> & pt, const T z) { return { pt.x(), pt.y(), z }; }
+
+inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale<double>(x), unscale<double>(y)); }
+inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale<double>(pt.x()), unscale<double>(pt.y())); }
+inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale<double>(pt.x()), unscale<double>(pt.y())); }
+inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale<double>(x), unscale<double>(y), unscale<double>(z)); }
+inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale<double>(pt.x()), unscale<double>(pt.y()), unscale<double>(pt.z())); }
+inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale<double>(pt.x()), unscale<double>(pt.y()), unscale<double>(pt.z())); }
+
+inline std::string to_string(const Vec2crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; }
+inline std::string to_string(const Vec2d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; }
+inline std::string to_string(const Vec3crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; }
+inline std::string to_string(const Vec3d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; }
+
+std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t);
+Pointf3s transform(const Pointf3s& points, const Transform3d& t);
+
+template<int N, class T> using Vec = Eigen::Matrix<T, N, 1, Eigen::DontAlign, N, 1>;
+
+class Point : public Vec2crd
+{
+public:
+ using coord_type = coord_t;
+
+ Point() : Vec2crd(0, 0) {}
+ Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {}
+ Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {}
+ Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {}
+ Point(const Point &rhs) { *this = rhs; }
+ explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {}
+ // This constructor allows you to construct Point from Eigen expressions
+ template<typename OtherDerived>
+ Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
+ static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
+ static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
+ static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
+
+ // This method allows you to assign Eigen expressions to MyVectorType
+ template<typename OtherDerived>
+ Point& operator=(const Eigen::MatrixBase<OtherDerived> &other)
+ {
+ this->Vec2crd::operator=(other);
+ return *this;
+ }
+
+ Point& operator+=(const Point& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); return *this; }
+ Point& operator-=(const Point& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); return *this; }
+ Point& operator*=(const double &rhs) { this->x() = coord_t(this->x() * rhs); this->y() = coord_t(this->y() * rhs); return *this; }
+ Point operator*(const double &rhs) { return Point(this->x() * rhs, this->y() * rhs); }
+
+ void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); }
+ void rotate(double cos_a, double sin_a) {
+ double cur_x = (double)this->x();
+ double cur_y = (double)this->y();
+ this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y);
+ this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x);
+ }
+
+ void rotate(double angle, const Point &center);
+ Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; }
+ Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; }
+ Point rotated(double angle, const Point &center) const { Point res(*this); res.rotate(angle, center); return res; }
+ int nearest_point_index(const Points &points) const;
+ int nearest_point_index(const PointConstPtrs &points) const;
+ int nearest_point_index(const PointPtrs &points) const;
+ bool nearest_point(const Points &points, Point* point) const;
+ Point projection_onto(const MultiPoint &poly) const;
+ Point projection_onto(const Line &line) const;
+};
+
+inline bool operator<(const Point &l, const Point &r)
+{
+ return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y());
+}
+
+inline Point operator* (const Point& l, const double &r)
+{
+ return {coord_t(l.x() * r), coord_t(l.y() * r)};
+}
+
+inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON))
+{
+ Point d = (p2 - p1).cwiseAbs();
+ return d.x() < epsilon && d.y() < epsilon;
+}
+
+inline bool is_approx(const Vec2f &p1, const Vec2f &p2, float epsilon = float(EPSILON))
+{
+ Vec2f d = (p2 - p1).cwiseAbs();
+ return d.x() < epsilon && d.y() < epsilon;
+}
+
+inline bool is_approx(const Vec2d &p1, const Vec2d &p2, double epsilon = EPSILON)
+{
+ Vec2d d = (p2 - p1).cwiseAbs();
+ return d.x() < epsilon && d.y() < epsilon;
+}
+
+inline bool is_approx(const Vec3f &p1, const Vec3f &p2, float epsilon = float(EPSILON))
+{
+ Vec3f d = (p2 - p1).cwiseAbs();
+ return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon;
+}
+
+inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON)
+{
+ Vec3d d = (p2 - p1).cwiseAbs();
+ return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon;
+}
+
+inline Point lerp(const Point &a, const Point &b, double t)
+{
+ assert((t >= -EPSILON) && (t <= 1. + EPSILON));
+ return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
+}
+
+BoundingBox get_extents(const Points &pts);
+BoundingBox get_extents(const std::vector<Points> &pts);
+BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
+
+// Test for duplicate points in a vector of points.
+// The points are copied, sorted and checked for duplicates globally.
+bool has_duplicate_points(std::vector<Point> &&pts);
+inline bool has_duplicate_points(const std::vector<Point> &pts)
+{
+ std::vector<Point> cpy = pts;
+ return has_duplicate_points(std::move(cpy));
+}
+
+// Test for duplicate points in a vector of points.
+// Only successive points are checked for equality.
+inline bool has_duplicate_successive_points(const std::vector<Point> &pts)
+{
+ for (size_t i = 1; i < pts.size(); ++ i)
+ if (pts[i - 1] == pts[i])
+ return true;
+ return false;
+}
+
+// Test for duplicate points in a vector of points.
+// Only successive points are checked for equality. Additionally, first and last points are compared for equality.
+inline bool has_duplicate_successive_points_closed(const std::vector<Point> &pts)
+{
+ return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back());
+}
+
+namespace int128 {
+ // Exact orientation predicate,
+ // returns +1: CCW, 0: collinear, -1: CW.
+ int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3);
+ // Exact orientation predicate,
+ // returns +1: CCW, 0: collinear, -1: CW.
+ int cross(const Vec2crd &v1, const Vec2crd &v2);
+}
+
+// To be used by std::unordered_map, std::unordered_multimap and friends.
+struct PointHash {
+ size_t operator()(const Vec2crd &pt) const {
+ return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y());
+ }
+};
+
+// A generic class to search for a closest Point in a given radius.
+// It uses std::unordered_multimap to implement an efficient 2D spatial hashing.
+// The PointAccessor has to return const Point*.
+// If a nullptr is returned, it is ignored by the query.
+template<typename ValueType, typename PointAccessor> class ClosestPointInRadiusLookup
+{
+public:
+ ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) :
+ m_search_radius(search_radius), m_point_accessor(point_accessor), m_grid_log2(0)
+ {
+ // Resolution of a grid, twice the search radius + some epsilon.
+ coord_t gridres = 2 * m_search_radius + 4;
+ m_grid_resolution = gridres;
+ assert(m_grid_resolution > 0);
+ assert(m_grid_resolution < (coord_t(1) << 30));
+ // Compute m_grid_log2 = log2(m_grid_resolution)
+ if (m_grid_resolution > 32767) {
+ m_grid_resolution >>= 16;
+ m_grid_log2 += 16;
+ }
+ if (m_grid_resolution > 127) {
+ m_grid_resolution >>= 8;
+ m_grid_log2 += 8;
+ }
+ if (m_grid_resolution > 7) {
+ m_grid_resolution >>= 4;
+ m_grid_log2 += 4;
+ }
+ if (m_grid_resolution > 1) {
+ m_grid_resolution >>= 2;
+ m_grid_log2 += 2;
+ }
+ if (m_grid_resolution > 0)
+ ++ m_grid_log2;
+ m_grid_resolution = 1 << m_grid_log2;
+ assert(m_grid_resolution >= gridres);
+ assert(gridres > m_grid_resolution / 2);
+ }
+
+ void insert(const ValueType &value) {
+ const Vec2crd *pt = m_point_accessor(value);
+ if (pt != nullptr)
+ m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), value));
+ }
+
+ void insert(ValueType &&value) {
+ const Vec2crd *pt = m_point_accessor(value);
+ if (pt != nullptr)
+ m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value)));
+ }
+
+ // Erase a data point equal to value. (ValueType has to declare the operator==).
+ // Returns true if the data point equal to value was found and removed.
+ bool erase(const ValueType &value) {
+ const Point *pt = m_point_accessor(value);
+ if (pt != nullptr) {
+ // Range of fragment starts around grid_corner, close to pt.
+ auto range = m_map.equal_range(Point((*pt).x()>>m_grid_log2, (*pt).y()>>m_grid_log2));
+ // Remove the first item.
+ for (auto it = range.first; it != range.second; ++ it) {
+ if (it->second == value) {
+ m_map.erase(it);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // Return a pair of <ValueType*, distance_squared>
+ std::pair<const ValueType*, double> find(const Vec2crd &pt) {
+ // Iterate over 4 closest grid cells around pt,
+ // find the closest start point inside these cells to pt.
+ const ValueType *value_min = nullptr;
+ double dist_min = std::numeric_limits<double>::max();
+ // Round pt to a closest grid_cell corner.
+ Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2);
+ // For four neighbors of grid_corner:
+ for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) {
+ for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) {
+ // Range of fragment starts around grid_corner, close to pt.
+ auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y));
+ // Find the map entry closest to pt.
+ for (auto it = range.first; it != range.second; ++it) {
+ const ValueType &value = it->second;
+ const Vec2crd *pt2 = m_point_accessor(value);
+ if (pt2 != nullptr) {
+ const double d2 = (pt - *pt2).cast<double>().squaredNorm();
+ if (d2 < dist_min) {
+ dist_min = d2;
+ value_min = &value;
+ }
+ }
+ }
+ }
+ }
+ return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ?
+ std::make_pair(value_min, dist_min) :
+ std::make_pair(nullptr, std::numeric_limits<double>::max());
+ }
+
+ // Returns all pairs of values and squared distances.
+ std::vector<std::pair<const ValueType*, double>> find_all(const Vec2crd &pt) {
+ // Iterate over 4 closest grid cells around pt,
+ // Round pt to a closest grid_cell corner.
+ Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2);
+ // For four neighbors of grid_corner:
+ std::vector<std::pair<const ValueType*, double>> out;
+ const double r2 = double(m_search_radius) * m_search_radius;
+ for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) {
+ for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) {
+ // Range of fragment starts around grid_corner, close to pt.
+ auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y));
+ // Find the map entry closest to pt.
+ for (auto it = range.first; it != range.second; ++it) {
+ const ValueType &value = it->second;
+ const Vec2crd *pt2 = m_point_accessor(value);
+ if (pt2 != nullptr) {
+ const double d2 = (pt - *pt2).cast<double>().squaredNorm();
+ if (d2 <= r2)
+ out.emplace_back(&value, d2);
+ }
+ }
+ }
+ }
+ return out;
+ }
+
+private:
+ using map_type = typename std::unordered_multimap<Vec2crd, ValueType, PointHash>;
+ PointAccessor m_point_accessor;
+ map_type m_map;
+ coord_t m_search_radius;
+ coord_t m_grid_resolution;
+ coord_t m_grid_log2;
+};
+
+std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf);
+
+
+// /////////////////////////////////////////////////////////////////////////////
+// Type safe conversions to and from scaled and unscaled coordinates
+// /////////////////////////////////////////////////////////////////////////////
+
+// Semantics are the following:
+// Upscaling (scaled()): only from floating point types (or Vec) to either
+// floating point or integer 'scaled coord' coordinates.
+// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only
+
+// Conversion definition from unscaled to floating point scaled
+template<class Tout,
+ class Tin,
+ class = FloatingOnly<Tin>>
+inline constexpr FloatingOnly<Tout> scaled(const Tin &v) noexcept
+{
+ return Tout(v / Tin(SCALING_FACTOR));
+}
+
+// Conversion definition from unscaled to integer 'scaled coord'.
+// TODO: is the rounding necessary? Here it is commented out to show that
+// it can be different for integers but it does not have to be. Using
+// std::round means loosing noexcept and constexpr modifiers
+template<class Tout = coord_t, class Tin, class = FloatingOnly<Tin>>
+inline constexpr ScaledCoordOnly<Tout> scaled(const Tin &v) noexcept
+{
+ //return static_cast<Tout>(std::round(v / SCALING_FACTOR));
+ return Tout(v / Tin(SCALING_FACTOR));
+}
+
+// Conversion for Eigen vectors (N dimensional points)
+template<class Tout = coord_t,
+ class Tin,
+ int N,
+ class = FloatingOnly<Tin>,
+ int...EigenArgs>
+inline Eigen::Matrix<ArithmeticOnly<Tout>, N, EigenArgs...>
+scaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v)
+{
+ return (v / SCALING_FACTOR).template cast<Tout>();
+}
+
+// Conversion from arithmetic scaled type to floating point unscaled
+template<class Tout = double,
+ class Tin,
+ class = ArithmeticOnly<Tin>,
+ class = FloatingOnly<Tout>>
+inline constexpr Tout unscaled(const Tin &v) noexcept
+{
+ return Tout(v) * Tout(SCALING_FACTOR);
+}
+
+// Unscaling for Eigen vectors. Input base type can be arithmetic, output base
+// type can only be floating point.
+template<class Tout = double,
+ class Tin,
+ int N,
+ class = ArithmeticOnly<Tin>,
+ class = FloatingOnly<Tout>,
+ int...EigenArgs>
+inline constexpr Eigen::Matrix<Tout, N, EigenArgs...>
+unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept
+{
+ return v.template cast<Tout>() * Tout(SCALING_FACTOR);
+}
+
+// Align a coordinate to a grid. The coordinate may be negative,
+// the aligned value will never be bigger than the original one.
+inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) {
+ // Current C++ standard defines the result of integer division to be rounded to zero,
+ // for both positive and negative numbers. Here we want to round down for negative
+ // numbers as well.
+ coord_t aligned = (coord < 0) ?
+ ((coord - spacing + 1) / spacing) * spacing :
+ (coord / spacing) * spacing;
+ assert(aligned <= coord);
+ return aligned;
+}
+inline Point align_to_grid(Point coord, Point spacing)
+ { return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); }
+inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base)
+ { return base + align_to_grid(coord - base, spacing); }
+inline Point align_to_grid(Point coord, Point spacing, Point base)
+ { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); }
+
+} // namespace Slic3r
+
+// start Boost
+#include <boost/version.hpp>
+#include <boost/polygon/polygon.hpp>
+namespace boost { namespace polygon {
+ template <>
+ struct geometry_concept<Slic3r::Point> { using type = point_concept; };
+
+ template <>
+ struct point_traits<Slic3r::Point> {
+ using coordinate_type = coord_t;
+
+ static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) {
+ return static_cast<coordinate_type>(point((orient == HORIZONTAL) ? 0 : 1));
+ }
+ };
+
+ template <>
+ struct point_mutable_traits<Slic3r::Point> {
+ using coordinate_type = coord_t;
+ static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) {
+ point((orient == HORIZONTAL) ? 0 : 1) = value;
+ }
+ static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) {
+ return Slic3r::Point(x_value, y_value);
+ }
+ };
+} }
+// end Boost
+
+// Serialization through the Cereal library
+namespace cereal {
+// template<class Archive> void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); }
+// template<class Archive> void serialize(Archive& archive, Slic3r::Vec3crd &v) { archive(v.x(), v.y(), v.z()); }
+ template<class Archive> void serialize(Archive& archive, Slic3r::Vec2i &v) { archive(v.x(), v.y()); }
+ template<class Archive> void serialize(Archive& archive, Slic3r::Vec3i &v) { archive(v.x(), v.y(), v.z()); }
+// template<class Archive> void serialize(Archive& archive, Slic3r::Vec2i64 &v) { archive(v.x(), v.y()); }
+// template<class Archive> void serialize(Archive& archive, Slic3r::Vec3i64 &v) { archive(v.x(), v.y(), v.z()); }
+ template<class Archive> void serialize(Archive& archive, Slic3r::Vec2f &v) { archive(v.x(), v.y()); }
+ template<class Archive> void serialize(Archive& archive, Slic3r::Vec3f &v) { archive(v.x(), v.y(), v.z()); }
+ template<class Archive> void serialize(Archive& archive, Slic3r::Vec2d &v) { archive(v.x(), v.y()); }
+ template<class Archive> void serialize(Archive& archive, Slic3r::Vec3d &v) { archive(v.x(), v.y(), v.z()); }
+
+ template<class Archive> void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); }
+ template<class Archive> void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); }
+#if ENABLE_WORLD_COORDINATE
+ template<class Archive> void load(Archive& archive, Slic3r::Transform3d& m) { archive.loadBinary((char*)m.data(), sizeof(double) * 16); }
+ template<class Archive> void save(Archive& archive, const Slic3r::Transform3d& m) { archive.saveBinary((char*)m.data(), sizeof(double) * 16); }
+#endif // ENABLE_WORLD_COORDINATE
+}
+
+// To be able to use Vec<> and Mat<> in range based for loops:
+namespace Eigen {
+template<class T, int N, int M>
+T* begin(Slic3r::Mat<N, M, T> &mat) { return mat.data(); }
+
+template<class T, int N, int M>
+T* end(Slic3r::Mat<N, M, T> &mat) { return mat.data() + N * M; }
+
+template<class T, int N, int M>
+const T* begin(const Slic3r::Mat<N, M, T> &mat) { return mat.data(); }
+
+template<class T, int N, int M>
+const T* end(const Slic3r::Mat<N, M, T> &mat) { return mat.data() + N * M; }
+} // namespace Eigen
+
+#endif
diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp
index 34e3129d9..4c085728c 100644
--- a/src/libslic3r/PrintApply.cpp
+++ b/src/libslic3r/PrintApply.cpp
@@ -1,1444 +1,1453 @@
-#include "Model.hpp"
-#include "Print.hpp"
-
-#include <cfloat>
-
-namespace Slic3r {
-
-// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new
-// in the exact order and with the same IDs.
-// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order.
-// Friend to ModelVolume to allow copying.
-// static is not accepted by gcc if declared as a friend of ModelObject.
-/* static */ void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_new)
-{
- typedef std::pair<const ModelVolume*, bool> ModelVolumeWithStatus;
- std::vector<ModelVolumeWithStatus> old_volumes;
- old_volumes.reserve(model_object_dst.volumes.size());
- for (const ModelVolume *model_volume : model_object_dst.volumes)
- old_volumes.emplace_back(ModelVolumeWithStatus(model_volume, false));
- auto model_volume_lower = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() < mv2.first->id(); };
- auto model_volume_equal = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() == mv2.first->id(); };
- std::sort(old_volumes.begin(), old_volumes.end(), model_volume_lower);
- model_object_dst.volumes.clear();
- model_object_dst.volumes.reserve(model_object_new.volumes.size());
- for (const ModelVolume *model_volume_src : model_object_new.volumes) {
- ModelVolumeWithStatus key(model_volume_src, false);
- auto it = std::lower_bound(old_volumes.begin(), old_volumes.end(), key, model_volume_lower);
- if (it != old_volumes.end() && model_volume_equal(*it, key)) {
- // The volume was found in the old list. Just copy it.
- assert(! it->second); // not consumed yet
- it->second = true;
- ModelVolume *model_volume_dst = const_cast<ModelVolume*>(it->first);
- // For support modifiers, the type may have been switched from blocker to enforcer and vice versa.
- assert((model_volume_dst->is_support_modifier() && model_volume_src->is_support_modifier()) || model_volume_dst->type() == model_volume_src->type());
- model_object_dst.volumes.emplace_back(model_volume_dst);
- if (model_volume_dst->is_support_modifier()) {
- // For support modifiers, the type may have been switched from blocker to enforcer and vice versa.
- model_volume_dst->set_type(model_volume_src->type());
- model_volume_dst->set_transformation(model_volume_src->get_transformation());
- }
- assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix()));
- } else {
- // The volume was not found in the old list. Create a new copy.
- assert(model_volume_src->is_support_modifier());
- model_object_dst.volumes.emplace_back(new ModelVolume(*model_volume_src));
- model_object_dst.volumes.back()->set_model_object(&model_object_dst);
- }
- }
- // Release the non-consumed old volumes (those were deleted from the new list).
- for (ModelVolumeWithStatus &mv_with_status : old_volumes)
- if (! mv_with_status.second)
- delete mv_with_status.first;
-}
-
-static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolumeType type)
-{
- size_t i_src, i_dst;
- for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) {
- const ModelVolume &mv_src = *model_object_src.volumes[i_src];
- ModelVolume &mv_dst = *model_object_dst.volumes[i_dst];
- if (mv_src.type() != type) {
- ++ i_src;
- continue;
- }
- if (mv_dst.type() != type) {
- ++ i_dst;
- continue;
- }
- assert(mv_src.id() == mv_dst.id());
- // Copy the ModelVolume data.
- mv_dst.name = mv_src.name;
- mv_dst.config.assign_config(mv_src.config);
- assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id());
- mv_dst.supported_facets.assign(mv_src.supported_facets);
- assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id());
- mv_dst.seam_facets.assign(mv_src.seam_facets);
- assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id());
- mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets);
- //FIXME what to do with the materials?
- // mv_dst.m_material_id = mv_src.m_material_id;
- ++ i_src;
- ++ i_dst;
- }
-}
-
-static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src)
-{
- assert(lr_dst.size() == lr_src.size());
- auto it_src = lr_src.cbegin();
- for (auto &kvp_dst : lr_dst) {
- const auto &kvp_src = *it_src ++;
- assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON);
- assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON);
- // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile.
- // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON);
- kvp_dst.second = kvp_src.second;
- }
-}
-
-static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs)
-{
- typedef Transform3d::Scalar T;
- const T *lv = lhs.data();
- const T *rv = rhs.data();
- for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) {
- if (*lv < *rv)
- return true;
- else if (*lv > *rv)
- return false;
- }
- return false;
-}
-
-static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &rhs)
-{
- typedef Transform3d::Scalar T;
- const T *lv = lhs.data();
- const T *rv = rhs.data();
- for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv)
- if (*lv != *rv)
- return false;
- return true;
-}
-
-struct PrintObjectTrafoAndInstances
-{
- Transform3d trafo;
- PrintInstances instances;
- bool operator<(const PrintObjectTrafoAndInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); }
-};
-
-// Generate a list of trafos and XY offsets for instances of a ModelObject
-static std::vector<PrintObjectTrafoAndInstances> print_objects_from_model_object(const ModelObject &model_object)
-{
- std::set<PrintObjectTrafoAndInstances> trafos;
- PrintObjectTrafoAndInstances trafo;
- for (ModelInstance *model_instance : model_object.instances)
- if (model_instance->is_printable()) {
- trafo.trafo = model_instance->get_matrix();
- auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]);
- // Reset the XY axes of the transformation.
- trafo.trafo.data()[12] = 0;
- trafo.trafo.data()[13] = 0;
- // Search or insert a trafo.
- auto it = trafos.emplace(trafo).first;
- const_cast<PrintObjectTrafoAndInstances&>(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift });
- }
- return std::vector<PrintObjectTrafoAndInstances>(trafos.begin(), trafos.end());
-}
-
-// Compare just the layer ranges and their layer heights, not the associated configs.
-// Ignore the layer heights if check_layer_heights is false.
-static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height)
-{
- if (lr1.size() != lr2.size())
- return false;
- auto it2 = lr2.begin();
- for (const auto &kvp1 : lr1) {
- const auto &kvp2 = *it2 ++;
- if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON ||
- std::abs(kvp1.first.second - kvp2.first.second) > EPSILON ||
- (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON))
- return false;
- }
- return true;
-}
-
-// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored.
-static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector<CustomGCode::Item> &va, const std::vector<CustomGCode::Item> &vb)
-{
- auto it_a = va.begin();
- auto it_b = vb.begin();
- while (it_a != va.end() || it_b != vb.end()) {
- if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) {
- // Skip any CustomGCode items, which are not tool changes.
- ++ it_a;
- continue;
- }
- if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) {
- // Skip any CustomGCode items, which are not tool changes.
- ++ it_b;
- continue;
- }
- if (it_a == va.end() || it_b == vb.end())
- // va or vb contains more Tool Changes than the other.
- return true;
- assert(it_a->type == CustomGCode::ToolChange);
- assert(it_b->type == CustomGCode::ToolChange);
- if (*it_a != *it_b)
- // The two Tool Changes differ.
- return true;
- ++ it_a;
- ++ it_b;
- }
- // There is no change in custom Tool Changes.
- return false;
-}
-
-// Collect changes to print config, account for overrides of extruder retract values by filament presets.
-static t_config_option_keys print_config_diffs(
- const PrintConfig &current_config,
- const DynamicPrintConfig &new_full_config,
- DynamicPrintConfig &filament_overrides)
-{
- const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
- const std::string filament_prefix = "filament_";
- t_config_option_keys print_diff;
- for (const t_config_option_key &opt_key : current_config.keys()) {
- const ConfigOption *opt_old = current_config.option(opt_key);
- assert(opt_old != nullptr);
- const ConfigOption *opt_new = new_full_config.option(opt_key);
- // assert(opt_new != nullptr);
- if (opt_new == nullptr)
- //FIXME This may happen when executing some test cases.
- continue;
- const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr;
- if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) {
- // An extruder retract override is available at some of the filament presets.
- bool overriden = opt_new->overriden_by(opt_new_filament);
- if (overriden || *opt_old != *opt_new) {
- auto opt_copy = opt_new->clone();
- opt_copy->apply_override(opt_new_filament);
- bool changed = *opt_old != *opt_copy;
- if (changed)
- print_diff.emplace_back(opt_key);
- if (changed || overriden) {
- // filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config.
- filament_overrides.set_key_value(opt_key, opt_copy);
- } else
- delete opt_copy;
- }
- } else if (*opt_new != *opt_old)
- print_diff.emplace_back(opt_key);
- }
-
- return print_diff;
-}
-
-// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser.
-static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig &current_full_config, const DynamicPrintConfig &new_full_config)
-{
- t_config_option_keys full_config_diff;
- for (const t_config_option_key &opt_key : new_full_config.keys()) {
- const ConfigOption *opt_old = current_full_config.option(opt_key);
- const ConfigOption *opt_new = new_full_config.option(opt_key);
- if (opt_old == nullptr || *opt_new != *opt_old)
- full_config_diff.emplace_back(opt_key);
- }
- return full_config_diff;
-}
-
-// Repository for solving partial overlaps of ModelObject::layer_config_ranges.
-// Here the const DynamicPrintConfig* point to the config in ModelObject::layer_config_ranges.
-class LayerRanges
-{
-public:
- struct LayerRange {
- t_layer_height_range layer_height_range;
- // Config is owned by the associated ModelObject.
- const DynamicPrintConfig* config { nullptr };
-
- bool operator<(const LayerRange &rhs) const throw() { return this->layer_height_range < rhs.layer_height_range; }
- };
-
- LayerRanges() = default;
- LayerRanges(const t_layer_config_ranges &in) { this->assign(in); }
-
- // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs.
- void assign(const t_layer_config_ranges &in) {
- m_ranges.clear();
- m_ranges.reserve(in.size());
- // Input ranges are sorted lexicographically. First range trims the other ranges.
- coordf_t last_z = 0;
- for (const std::pair<const t_layer_height_range, ModelConfig> &range : in)
- if (range.first.second > last_z) {
- coordf_t min_z = std::max(range.first.first, 0.);
- if (min_z > last_z + EPSILON) {
- m_ranges.push_back({ t_layer_height_range(last_z, min_z) });
- last_z = min_z;
- }
- if (range.first.second > last_z + EPSILON) {
- const DynamicPrintConfig *cfg = &range.second.get();
- m_ranges.push_back({ t_layer_height_range(last_z, range.first.second), cfg });
- last_z = range.first.second;
- }
- }
- if (m_ranges.empty())
- m_ranges.push_back({ t_layer_height_range(0, DBL_MAX) });
- else if (m_ranges.back().config == nullptr)
- m_ranges.back().layer_height_range.second = DBL_MAX;
- else
- m_ranges.push_back({ t_layer_height_range(m_ranges.back().layer_height_range.second, DBL_MAX) });
- }
-
- const DynamicPrintConfig* config(const t_layer_height_range &range) const {
- auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), LayerRange{ { range.first - EPSILON, range.second - EPSILON } });
- // #ys_FIXME_COLOR
- // assert(it != m_ranges.end());
- // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON);
- // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON);
- if (it == m_ranges.end() ||
- std::abs(it->layer_height_range.first - range.first) > EPSILON ||
- std::abs(it->layer_height_range.second - range.second) > EPSILON )
- return nullptr; // desired range doesn't found
- return it == m_ranges.end() ? nullptr : it->config;
- }
-
- std::vector<LayerRange>::const_iterator begin() const { return m_ranges.cbegin(); }
- std::vector<LayerRange>::const_iterator end () const { return m_ranges.cend(); }
- size_t size () const { return m_ranges.size(); }
-
-private:
- // Layer ranges with their config overrides and list of volumes with their snug bounding boxes in a given layer range.
- std::vector<LayerRange> m_ranges;
-};
-
-// To track Model / ModelObject updates between the front end and back end, including layer height ranges, their configs,
-// and snug bounding boxes of ModelVolumes.
-struct ModelObjectStatus {
- enum Status {
- Unknown,
- Old,
- New,
- Moved,
- Deleted,
- };
-
- enum class PrintObjectRegionsStatus {
- Invalid,
- Valid,
- PartiallyValid,
- };
-
- ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {}
- ~ModelObjectStatus() { if (print_object_regions) print_object_regions->ref_cnt_dec(); }
-
- // Key of the set.
- ObjectID id;
- // Status of this ModelObject with id on apply().
- Status status;
- // PrintObjects to be generated for this ModelObject including their base transformation.
- std::vector<PrintObjectTrafoAndInstances> print_instances;
- // Regions shared by the associated PrintObjects.
- PrintObjectRegions *print_object_regions { nullptr };
- // Status of the above.
- PrintObjectRegionsStatus print_object_regions_status { PrintObjectRegionsStatus::Invalid };
-
- // Search by id.
- bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; }
-};
-
-struct ModelObjectStatusDB
-{
- void add(const ModelObject &model_object, const ModelObjectStatus::Status status) {
- assert(db.find(ModelObjectStatus(model_object.id())) == db.end());
- db.emplace(model_object.id(), status);
- }
-
- bool add_if_new(const ModelObject &model_object, const ModelObjectStatus::Status status) {
- auto it = db.find(ModelObjectStatus(model_object.id()));
- if (it == db.end()) {
- db.emplace_hint(it, model_object.id(), status);
- return true;
- }
- return false;
- }
-
- const ModelObjectStatus& get(const ModelObject &model_object) {
- auto it = db.find(ModelObjectStatus(model_object.id()));
- assert(it != db.end());
- return *it;
- }
-
- const ModelObjectStatus& reuse(const ModelObject &model_object) {
- const ModelObjectStatus &result = this->get(model_object);
- assert(result.status != ModelObjectStatus::Deleted);
- return result;
- }
-
- std::set<ModelObjectStatus> db;
-};
-
-struct PrintObjectStatus {
- enum Status {
- Unknown,
- Deleted,
- Reused,
- New
- };
-
- PrintObjectStatus(PrintObject *print_object, Status status = Unknown) :
- id(print_object->model_object()->id()),
- print_object(print_object),
- trafo(print_object->trafo()),
- status(status) {}
- PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {}
-
- // ID of the ModelObject & PrintObject
- ObjectID id;
- // Pointer to the old PrintObject
- PrintObject *print_object;
- // Trafo generated with model_object->world_matrix(true)
- Transform3d trafo;
- Status status;
-
- // Search by id.
- bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; }
-};
-
-class PrintObjectStatusDB {
-public:
- using iterator = std::multiset<PrintObjectStatus>::iterator;
- using const_iterator = std::multiset<PrintObjectStatus>::const_iterator;
-
- PrintObjectStatusDB(const PrintObjectPtrs &print_objects) {
- for (PrintObject *print_object : print_objects)
- m_db.emplace(PrintObjectStatus(print_object));
- }
-
- struct iterator_range : std::pair<const_iterator, const_iterator>
- {
- using std::pair<const_iterator, const_iterator>::pair;
- iterator_range(const std::pair<const_iterator, const_iterator> in) : std::pair<const_iterator, const_iterator>(in) {}
-
- const_iterator begin() throw() { return this->first; }
- const_iterator end() throw() { return this->second; }
- };
-
- iterator_range get_range(const ModelObject &model_object) const {
- return m_db.equal_range(PrintObjectStatus(model_object.id()));
- }
-
- iterator_range get_range(const ModelObjectStatus &model_object_status) const {
- return m_db.equal_range(PrintObjectStatus(model_object_status.id));
- }
-
- size_t count(const ModelObject &model_object) {
- return m_db.count(PrintObjectStatus(model_object.id()));
- }
-
- std::multiset<PrintObjectStatus>::iterator begin() { return m_db.begin(); }
- std::multiset<PrintObjectStatus>::iterator end() { return m_db.end(); }
-
- void clear() {
- m_db.clear();
- }
-
-private:
- std::multiset<PrintObjectStatus> m_db;
-};
-
-static inline bool model_volume_solid_or_modifier(const ModelVolume &mv)
-{
- ModelVolumeType type = mv.type();
- return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER;
-}
-
-static inline Transform3f trafo_for_bbox(const Transform3d &object_trafo, const Transform3d &volume_trafo)
-{
- Transform3d m = object_trafo * volume_trafo;
- m.translation().x() = 0.;
- m.translation().y() = 0.;
- return m.cast<float>();
-}
-
-static inline bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d &t1, const Transform3d &t2)
-{
- if (std::abs(t1.translation().z() - t2.translation().z()) > EPSILON)
- // One of the object is higher than the other above the build plate (or below the build plate).
- return false;
- Matrix3d m1 = t1.matrix().block<3, 3>(0, 0);
- Matrix3d m2 = t2.matrix().block<3, 3>(0, 0);
- Matrix3d m = m2.inverse() * m1;
- Vec3d z = m.block<3, 1>(0, 2);
- if (std::abs(z.x()) > EPSILON || std::abs(z.y()) > EPSILON || std::abs(z.z() - 1.) > EPSILON)
- // Z direction or length changed.
- return false;
- // Z still points in the same direction and it has the same length.
- Vec3d x = m.block<3, 1>(0, 0);
- Vec3d y = m.block<3, 1>(0, 1);
- if (std::abs(x.z()) > EPSILON || std::abs(y.z()) > EPSILON)
- return false;
- double lx2 = x.squaredNorm();
- double ly2 = y.squaredNorm();
- if (lx2 - 1. > EPSILON * EPSILON || ly2 - 1. > EPSILON * EPSILON)
- return false;
- // Verify whether the vectors x, y are still perpendicular.
- double d = x.dot(y);
- return std::abs(d * d) < EPSILON * lx2 * ly2;
-}
-
-static PrintObjectRegions::BoundingBox transformed_its_bbox2d(const indexed_triangle_set &its, const Transform3f &m, float offset)
-{
- assert(! its.indices.empty());
-
- PrintObjectRegions::BoundingBox bbox(m * its.vertices[its.indices.front()(0)]);
- for (const stl_triangle_vertex_indices &tri : its.indices)
- for (int i = 0; i < 3; ++ i)
- bbox.extend(m * its.vertices[tri(i)]);
- bbox.min() -= Vec3f(offset, offset, float(EPSILON));
- bbox.max() += Vec3f(offset, offset, float(EPSILON));
- return bbox;
-}
-
-static void transformed_its_bboxes_in_z_ranges(
- const indexed_triangle_set &its,
- const Transform3f &m,
- const std::vector<t_layer_height_range> &z_ranges,
- std::vector<std::pair<PrintObjectRegions::BoundingBox, bool>> &bboxes,
- const float offset)
-{
- bboxes.assign(z_ranges.size(), std::make_pair(PrintObjectRegions::BoundingBox(), false));
- for (const stl_triangle_vertex_indices &tri : its.indices) {
- const Vec3f pts[3] = { m * its.vertices[tri(0)], m * its.vertices[tri(1)], m * its.vertices[tri(2)] };
- for (size_t irange = 0; irange < z_ranges.size(); ++ irange) {
- const t_layer_height_range &z_range = z_ranges[irange];
- std::pair<PrintObjectRegions::BoundingBox, bool> &bbox = bboxes[irange];
- auto bbox_extend = [&bbox](const Vec3f& p) {
- if (bbox.second) {
- bbox.first.extend(p);
- } else {
- bbox.first.min() = bbox.first.max() = p;
- bbox.second = true;
- }
- };
- int iprev = 2;
- for (int iedge = 0; iedge < 3; ++ iedge) {
- const Vec3f *p1 = &pts[iprev];
- const Vec3f *p2 = &pts[iedge];
- // Sort the edge points by Z.
- if (p1->z() > p2->z())
- std::swap(p1, p2);
- if (p2->z() <= z_range.first || p1->z() >= z_range.second) {
- // Out of this slab.
- } else if (p1->z() < z_range.first) {
- if (p1->z() > z_range.second) {
- // Two intersections.
- float zspan = p2->z() - p1->z();
- float t1 = (z_range.first - p1->z()) / zspan;
- float t2 = (z_range.second - p1->z()) / zspan;
- Vec2f p = to_2d(*p1);
- Vec2f v(p2->x() - p1->x(), p2->y() - p1->y());
- bbox_extend(to_3d((p + v * t1).eval(), float(z_range.first)));
- bbox_extend(to_3d((p + v * t2).eval(), float(z_range.second)));
- } else {
- // Single intersection with the lower limit.
- float t = (z_range.first - p1->z()) / (p2->z() - p1->z());
- Vec2f v(p2->x() - p1->x(), p2->y() - p1->y());
- bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.first)));
- bbox_extend(*p2);
- }
- } else if (p2->z() > z_range.second) {
- // Single intersection with the upper limit.
- float t = (z_range.second - p1->z()) / (p2->z() - p1->z());
- Vec2f v(p2->x() - p1->x(), p2->y() - p1->y());
- bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.second)));
- bbox_extend(*p1);
- } else {
- // Both points are inside.
- bbox_extend(*p1);
- bbox_extend(*p2);
- }
- iprev = iedge;
- }
- }
- }
-
- for (std::pair<PrintObjectRegions::BoundingBox, bool> &bbox : bboxes) {
- bbox.first.min() -= Vec3f(offset, offset, float(EPSILON));
- bbox.first.max() += Vec3f(offset, offset, float(EPSILON));
- }
-}
-
-// Last PrintObject for this print_object_regions has been fully invalidated (deleted).
-// Keep print_object_regions, but delete those volumes, which were either removed from new_volumes, or which rotated or scaled, so they need
-// their bounding boxes to be recalculated.
-void print_objects_regions_invalidate_keep_some_volumes(PrintObjectRegions &print_object_regions, ModelVolumePtrs old_volumes, ModelVolumePtrs new_volumes)
-{
- print_object_regions.all_regions.clear();
-
- model_volumes_sort_by_id(old_volumes);
- model_volumes_sort_by_id(new_volumes);
-
- size_t i_cached_volume = 0;
- size_t last_cached_volume = 0;
- size_t i_old = 0;
- for (size_t i_new = 0; i_new < new_volumes.size(); ++ i_new)
- if (model_volume_solid_or_modifier(*new_volumes[i_new])) {
- for (; i_old < old_volumes.size(); ++ i_old)
- if (old_volumes[i_old]->id() >= new_volumes[i_new]->id())
- break;
- if (i_old != old_volumes.size() && old_volumes[i_old]->id() == new_volumes[i_new]->id()) {
- if (old_volumes[i_old]->get_matrix().isApprox(new_volumes[i_new]->get_matrix())) {
- // Reuse the volume.
- for (; print_object_regions.cached_volume_ids[i_cached_volume] < old_volumes[i_old]->id(); ++ i_cached_volume)
- assert(i_cached_volume < print_object_regions.cached_volume_ids.size());
- assert(i_cached_volume < print_object_regions.cached_volume_ids.size() && print_object_regions.cached_volume_ids[i_cached_volume] == old_volumes[i_old]->id());
- print_object_regions.cached_volume_ids[last_cached_volume ++] = print_object_regions.cached_volume_ids[i_cached_volume ++];
- } else {
- // Don't reuse the volume.
- }
- }
- }
- print_object_regions.cached_volume_ids.erase(print_object_regions.cached_volume_ids.begin() + last_cached_volume, print_object_regions.cached_volume_ids.end());
-}
-
-// Find a bounding box of a volume's part intersecting layer_range. Such a bounding box will likely be smaller in XY than the full bounding box,
-// thus it will intersect with lower number of other volumes.
-const PrintObjectRegions::BoundingBox* find_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const ModelVolume &volume)
-{
- auto it = lower_bound_by_predicate(layer_range.volumes.begin(), layer_range.volumes.end(), [&volume](const PrintObjectRegions::VolumeExtents &l){ return l.volume_id < volume.id(); });
- return it != layer_range.volumes.end() && it->volume_id == volume.id() ? &it->bbox : nullptr;
-}
-
-// Find a bounding box of a topmost printable volume referenced by this modifier given this_region_id.
-PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const int this_region_id)
-{
- // Find the top-most printable volume of this modifier, or the printable volume itself.
- const PrintObjectRegions::VolumeRegion &this_region = layer_range.volume_regions[this_region_id];
- const PrintObjectRegions::BoundingBox *this_extents = find_volume_extents(layer_range, *this_region.model_volume);
- assert(this_extents);
- PrintObjectRegions::BoundingBox out { *this_extents };
- if (! this_region.model_volume->is_model_part())
- for (int parent_region_id = this_region.parent;;) {
- assert(parent_region_id >= 0);
- const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
- const PrintObjectRegions::BoundingBox *parent_extents = find_volume_extents(layer_range, *parent_region.model_volume);
- assert(parent_extents);
- out.extend(*parent_extents);
- if (parent_region.model_volume->is_model_part())
- break;
- parent_region_id = parent_region.parent;
- }
- return out;
-}
-
-PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders);
-
-void print_region_ref_inc(PrintRegion &r) { ++ r.m_ref_cnt; }
-void print_region_ref_reset(PrintRegion &r) { r.m_ref_cnt = 0; }
-int print_region_ref_cnt(const PrintRegion &r) { return r.m_ref_cnt; }
-
-// Verify whether the PrintRegions of a PrintObject are still valid, possibly after updating the region configs.
-// Before region configs are updated, callback_invalidate() is called to possibly stop background processing.
-// Returns false if this object needs to be resliced because regions were merged or split.
-bool verify_update_print_object_regions(
- ModelVolumePtrs model_volumes,
- const PrintRegionConfig &default_region_config,
- size_t num_extruders,
- const std::vector<unsigned int> &painting_extruders,
- PrintObjectRegions &print_object_regions,
- const std::function<void(const PrintRegionConfig&, const PrintRegionConfig&, const t_config_option_keys&)> &callback_invalidate)
-{
- // Sort by ModelVolume ID.
- model_volumes_sort_by_id(model_volumes);
-
- for (std::unique_ptr<PrintRegion> &region : print_object_regions.all_regions)
- print_region_ref_reset(*region);
-
- // Verify and / or update PrintRegions produced by ModelVolumes, layer range modifiers, modifier volumes.
- for (PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) {
- // Each modifier ModelVolume intersecting this layer_range shall be referenced here at least once if it intersects some
- // printable ModelVolume at this layer_range even if it does not modify its overlapping printable ModelVolume configuration yet.
- // VolumeRegions reference ModelVolumes in layer_range.volume_regions the order they are stored in ModelObject volumes.
- // Remember whether a given modifier ModelVolume was visited already.
- auto it_model_volume_modifier_last = model_volumes.end();
- for (PrintObjectRegions::VolumeRegion &region : layer_range.volume_regions)
- if (region.model_volume->is_model_part() || region.model_volume->is_modifier()) {
- auto it_model_volume = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [&region](const ModelVolume *l){ return l->id() < region.model_volume->id(); });
- assert(it_model_volume != model_volumes.end() && (*it_model_volume)->id() == region.model_volume->id());
- if (region.model_volume->is_modifier() && it_model_volume != it_model_volume_modifier_last) {
- // A modifier ModelVolume is visited for the first time.
- // A visited modifier may not have had parent volume_regions created overlapping with some model parts or modifiers,
- // if the visited modifier did not modify their properties. Now the visited modifier's configuration may have changed,
- // which may require new regions to be created.
- it_model_volume_modifier_last = it_model_volume;
- int next_region_id = int(&region - layer_range.volume_regions.data());
- const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, *region.model_volume);
- assert(bbox);
- for (int parent_region_id = next_region_id - 1; parent_region_id >= 0; -- parent_region_id) {
- const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
- assert(parent_region.model_volume != region.model_volume);
- if (parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) {
- // volume_regions are produced in decreasing order of parent volume_regions ids.
- // Some regions may not have been generated the last time by generate_print_object_regions().
- assert(next_region_id == int(layer_range.volume_regions.size()) ||
- layer_range.volume_regions[next_region_id].model_volume != region.model_volume ||
- layer_range.volume_regions[next_region_id].parent <= parent_region_id);
- if (next_region_id < int(layer_range.volume_regions.size()) &&
- layer_range.volume_regions[next_region_id].model_volume == region.model_volume &&
- layer_range.volume_regions[next_region_id].parent == parent_region_id) {
- // A parent region is already overridden.
- ++ next_region_id;
- } else if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox))
- // Such parent region does not exist. If it is needed, then we need to reslice.
- // Only create new region for a modifier, which actually modifies config of it's parent.
- if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders);
- config != parent_region.region->config())
- // This modifier newly overrides a region, which it did not before. We need to reslice.
- return false;
- }
- }
- }
- PrintRegionConfig cfg = region.parent == -1 ?
- region_config_from_model_volume(default_region_config, layer_range.config, **it_model_volume, num_extruders) :
- region_config_from_model_volume(layer_range.volume_regions[region.parent].region->config(), nullptr, **it_model_volume, num_extruders);
- if (cfg != region.region->config()) {
- // Region configuration changed.
- if (print_region_ref_cnt(*region.region) == 0) {
- // Region is referenced for the first time. Just change its parameters.
- // Stop the background process before assigning new configuration to the regions.
- t_config_option_keys diff = region.region->config().diff(cfg);
- callback_invalidate(region.region->config(), cfg, diff);
- region.region->config_apply_only(cfg, diff, false);
- } else {
- // Region is referenced multiple times, thus the region is being split. We need to reslice.
- return false;
- }
- }
- print_region_ref_inc(*region.region);
- }
- }
-
- // Verify and / or update PrintRegions produced by color painting.
- for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges)
- for (const PrintObjectRegions::PaintedRegion &region : layer_range.painted_regions) {
- const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent];
- PrintRegionConfig cfg = parent_region.region->config();
- cfg.perimeter_extruder.value = region.extruder_id;
- cfg.solid_infill_extruder.value = region.extruder_id;
- cfg.infill_extruder.value = region.extruder_id;
- if (cfg != region.region->config()) {
- // Region configuration changed.
- if (print_region_ref_cnt(*region.region) == 0) {
- // Region is referenced for the first time. Just change its parameters.
- // Stop the background process before assigning new configuration to the regions.
- t_config_option_keys diff = region.region->config().diff(cfg);
- callback_invalidate(region.region->config(), cfg, diff);
- region.region->config_apply_only(cfg, diff, false);
- } else {
- // Region is referenced multiple times, thus the region is being split. We need to reslice.
- return false;
- }
- }
- print_region_ref_inc(*region.region);
- }
-
- // Lastly verify, whether some regions were not merged.
- {
- std::vector<const PrintRegion*> regions;
- regions.reserve(print_object_regions.all_regions.size());
- for (std::unique_ptr<PrintRegion> &region : print_object_regions.all_regions) {
- assert(print_region_ref_cnt(*region) > 0);
- regions.emplace_back(&(*region.get()));
- }
- std::sort(regions.begin(), regions.end(), [](const PrintRegion *l, const PrintRegion *r){ return l->config_hash() < r->config_hash(); });
- for (size_t i = 0; i < regions.size(); ++ i) {
- size_t hash = regions[i]->config_hash();
- size_t j = i;
- for (++ j; j < regions.size() && regions[j]->config_hash() == hash; ++ j)
- if (regions[i]->config() == regions[j]->config()) {
- // Regions were merged. We need to reslice.
- return false;
- }
- }
- }
-
- return true;
-}
-
-// Update caches of volume bounding boxes.
-void update_volume_bboxes(
- std::vector<PrintObjectRegions::LayerRangeRegions> &layer_ranges,
- std::vector<ObjectID> &cached_volume_ids,
- ModelVolumePtrs model_volumes,
- const Transform3d &object_trafo,
- const float offset)
-{
- // output will be sorted by the order of model_volumes sorted by their ObjectIDs.
- model_volumes_sort_by_id(model_volumes);
-
- if (layer_ranges.size() == 1) {
- PrintObjectRegions::LayerRangeRegions &layer_range = layer_ranges.front();
- std::vector<PrintObjectRegions::VolumeExtents> volumes_old(std::move(layer_range.volumes));
- layer_range.volumes.reserve(model_volumes.size());
- for (const ModelVolume *model_volume : model_volumes)
- if (model_volume_solid_or_modifier(*model_volume)) {
- if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) {
- auto it = lower_bound_by_predicate(volumes_old.begin(), volumes_old.end(), [model_volume](PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); });
- if (it != volumes_old.end() && it->volume_id == model_volume->id())
- layer_range.volumes.emplace_back(*it);
- } else
- layer_range.volumes.push_back({ model_volume->id(),
- transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset) });
- }
- } else {
- std::vector<std::vector<PrintObjectRegions::VolumeExtents>> volumes_old;
- if (cached_volume_ids.empty())
- for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges)
- layer_range.volumes.clear();
- else {
- volumes_old.reserve(layer_ranges.size());
- for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges)
- volumes_old.emplace_back(std::move(layer_range.volumes));
- }
-
- std::vector<std::pair<PrintObjectRegions::BoundingBox, bool>> bboxes;
- std::vector<t_layer_height_range> ranges;
- ranges.reserve(layer_ranges.size());
- for (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) {
- t_layer_height_range r = layer_range.layer_height_range;
- r.first -= EPSILON;
- r.second += EPSILON;
- ranges.emplace_back(r);
- }
- for (const ModelVolume *model_volume : model_volumes)
- if (model_volume_solid_or_modifier(*model_volume)) {
- if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) {
- for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) {
- const auto &vold = volumes_old[&layer_range - layer_ranges.data()];
- auto it = lower_bound_by_predicate(vold.begin(), vold.end(), [model_volume](const PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); });
- if (it != vold.end() && it->volume_id == model_volume->id())
- layer_range.volumes.emplace_back(*it);
- }
- } else {
- transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), ranges, bboxes, offset);
- for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges)
- if (auto &bbox = bboxes[&layer_range - layer_ranges.data()]; bbox.second)
- layer_range.volumes.push_back({ model_volume->id(), bbox.first });
- }
- }
- }
-
- cached_volume_ids.clear();
- cached_volume_ids.reserve(model_volumes.size());
- for (const ModelVolume *v : model_volumes)
- if (model_volume_solid_or_modifier(*v))
- cached_volume_ids.emplace_back(v->id());
-}
-
-// Either a fresh PrintObject, or PrintObject regions were invalidated (merged, split).
-// Generate PrintRegions from scratch.
-static PrintObjectRegions* generate_print_object_regions(
- PrintObjectRegions *print_object_regions_old,
- const ModelVolumePtrs &model_volumes,
- const LayerRanges &model_layer_ranges,
- const PrintRegionConfig &default_region_config,
- const Transform3d &trafo,
- size_t num_extruders,
- const float xy_size_compensation,
- const std::vector<unsigned int> &painting_extruders)
-{
- // Reuse the old object or generate a new one.
- auto out = print_object_regions_old ? std::unique_ptr<PrintObjectRegions>(print_object_regions_old) : std::make_unique<PrintObjectRegions>();
- auto &all_regions = out->all_regions;
- auto &layer_ranges_regions = out->layer_ranges;
-
- all_regions.clear();
-
- bool reuse_old = print_object_regions_old && !print_object_regions_old->layer_ranges.empty();
-
- if (reuse_old) {
- // Reuse old bounding boxes of some ModelVolumes and their ranges.
- // Verify that the old ranges match the new ranges.
- assert(model_layer_ranges.size() == layer_ranges_regions.size());
- for (const auto &range : model_layer_ranges) {
- PrintObjectRegions::LayerRangeRegions &r = layer_ranges_regions[&range - &*model_layer_ranges.begin()];
- assert(range.layer_height_range == r.layer_height_range);
- // If model::assign_copy() is called, layer_ranges_regions is copied thus the pointers to configs are lost.
- r.config = range.config;
- r.volume_regions.clear();
- r.painted_regions.clear();
- }
- } else {
- out->trafo_bboxes = trafo;
- layer_ranges_regions.reserve(model_layer_ranges.size());
- for (const auto &range : model_layer_ranges)
- layer_ranges_regions.push_back({ range.layer_height_range, range.config });
- }
-
- const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
- update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation));
-
- std::vector<PrintRegion*> region_set;
- auto get_create_region = [&region_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* {
- size_t hash = config.hash();
- auto it = Slic3r::lower_bound_by_predicate(region_set.begin(), region_set.end(), [&config, hash](const PrintRegion* l) {
- return l->config_hash() < hash || (l->config_hash() == hash && l->config() < config); });
- if (it != region_set.end() && (*it)->config_hash() == hash && (*it)->config() == config)
- return *it;
- // Insert into a sorted array, it has O(n) complexity, but the calling algorithm has an O(n^2*log(n)) complexity anyways.
- all_regions.emplace_back(std::make_unique<PrintRegion>(std::move(config), hash, int(all_regions.size())));
- PrintRegion *region = all_regions.back().get();
- region_set.emplace(it, region);
- return region;
- };
-
- // Chain the regions in the order they are stored in the volumes list.
- for (int volume_id = 0; volume_id < int(model_volumes.size()); ++ volume_id) {
- const ModelVolume &volume = *model_volumes[volume_id];
- if (model_volume_solid_or_modifier(volume)) {
- for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions)
- if (const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, volume); bbox) {
- if (volume.is_model_part()) {
- // Add a model volume, assign an existing region or generate a new one.
- layer_range.volume_regions.push_back({
- &volume, -1,
- get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders)),
- bbox
- });
- } else if (volume.is_negative_volume()) {
- // Add a negative (subtractor) volume. Such volume has neither region nor parent volume assigned.
- layer_range.volume_regions.push_back({ &volume, -1, nullptr, bbox });
- } else {
- assert(volume.is_modifier());
- // Modifiers may be chained one over the other. Check for overlap, merge DynamicPrintConfigs.
- bool added = false;
- int parent_model_part_id = -1;
- for (int parent_region_id = int(layer_range.volume_regions.size()) - 1; parent_region_id >= 0; -- parent_region_id) {
- const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
- const ModelVolume &parent_volume = *parent_region.model_volume;
- if (parent_volume.is_model_part() || parent_volume.is_modifier())
- if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) {
- // Only create new region for a modifier, which actually modifies config of it's parent.
- if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders);
- config != parent_region.region->config()) {
- added = true;
- layer_range.volume_regions.push_back({ &volume, parent_region_id, get_create_region(std::move(config)), bbox });
- } else if (parent_model_part_id == -1 && parent_volume.is_model_part())
- parent_model_part_id = parent_region_id;
- }
- }
- if (! added && parent_model_part_id >= 0)
- // This modifier does not override any printable volume's configuration, however it may in the future.
- // Store it so that verify_update_print_object_regions() will handle this modifier correctly if its configuration changes.
- layer_range.volume_regions.push_back({ &volume, parent_model_part_id, layer_range.volume_regions[parent_model_part_id].region, bbox });
- }
- }
- }
- }
-
- // Finally add painting regions.
- for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) {
- for (unsigned int painted_extruder_id : painting_extruders)
- for (int parent_region_id = 0; parent_region_id < int(layer_range.volume_regions.size()); ++ parent_region_id)
- if (const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
- parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) {
- PrintRegionConfig cfg = parent_region.region->config();
- cfg.perimeter_extruder.value = painted_extruder_id;
- cfg.solid_infill_extruder.value = painted_extruder_id;
- cfg.infill_extruder.value = painted_extruder_id;
- layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))});
- }
- // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MMU segmentation.
- std::sort(layer_range.painted_regions.begin(), layer_range.painted_regions.end(), [&layer_range](auto &l, auto &r) {
- int lid = layer_range.volume_regions[l.parent].region->print_object_region_id();
- int rid = layer_range.volume_regions[r.parent].region->print_object_region_id();
- return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); });
- }
-
- return out.release();
-}
-
-Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config)
-{
-#ifdef _DEBUG
- check_model_ids_validity(model);
-#endif /* _DEBUG */
-
- // Normalize the config.
- new_full_config.option("print_settings_id", true);
- new_full_config.option("filament_settings_id", true);
- new_full_config.option("printer_settings_id", true);
- new_full_config.option("physical_printer_settings_id", true);
- new_full_config.normalize_fdm();
-
- // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
- DynamicPrintConfig filament_overrides;
- t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides);
- t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config);
- // Collect changes to object and region configs.
- t_config_option_keys object_diff = m_default_object_config.diff(new_full_config);
- t_config_option_keys region_diff = m_default_region_config.diff(new_full_config);
-
- // Do not use the ApplyStatus as we will use the max function when updating apply_status.
- unsigned int apply_status = APPLY_STATUS_UNCHANGED;
- auto update_apply_status = [&apply_status](bool invalidated)
- { apply_status = std::max<unsigned int>(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); };
- if (! (print_diff.empty() && object_diff.empty() && region_diff.empty()))
- update_apply_status(false);
-
- // Grab the lock for the Print / PrintObject milestones.
- std::scoped_lock<std::mutex> lock(this->state_mutex());
-
- // The following call may stop the background processing.
- if (! print_diff.empty())
- update_apply_status(this->invalidate_state_by_config_options(new_full_config, print_diff));
-
- // Apply variables to placeholder parser. The placeholder parser is used by G-code export,
- // which should be stopped if print_diff is not empty.
- size_t num_extruders = m_config.nozzle_diameter.size();
- bool num_extruders_changed = false;
- if (! full_config_diff.empty()) {
- update_apply_status(this->invalidate_step(psGCodeExport));
- m_placeholder_parser.clear_config();
- // Set the profile aliases for the PrintBase::output_filename()
- m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone());
- m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone());
- m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone());
- m_placeholder_parser.set("physical_printer_preset", new_full_config.option("physical_printer_settings_id")->clone());
- // We want the filament overrides to be applied over their respective extruder parameters by the PlaceholderParser.
- // see "Placeholders do not respect filament overrides." GH issue #3649
- m_placeholder_parser.apply_config(filament_overrides);
- // It is also safe to change m_config now after this->invalidate_state_by_config_options() call.
- m_config.apply_only(new_full_config, print_diff, true);
- //FIXME use move semantics once ConfigBase supports it.
- // Some filament_overrides may contain values different from new_full_config, but equal to m_config.
- // As long as these config options don't reallocate memory when copying, we are safe overriding a value, which is in use by a worker thread.
- m_config.apply(filament_overrides);
- // Handle changes to object config defaults
- m_default_object_config.apply_only(new_full_config, object_diff, true);
- // Handle changes to regions config defaults
- m_default_region_config.apply_only(new_full_config, region_diff, true);
- m_full_print_config = std::move(new_full_config);
- if (num_extruders != m_config.nozzle_diameter.size()) {
- num_extruders = m_config.nozzle_diameter.size();
- num_extruders_changed = true;
- }
- }
-
- ModelObjectStatusDB model_object_status_db;
-
- // 1) Synchronize model objects.
- bool print_regions_reshuffled = false;
- if (model.id() != m_model.id()) {
- // Kill everything, initialize from scratch.
- // Stop background processing.
- this->call_cancel_callback();
- update_apply_status(this->invalidate_all_steps());
- for (PrintObject *object : m_objects) {
- model_object_status_db.add(*object->model_object(), ModelObjectStatus::Deleted);
- update_apply_status(object->invalidate_all_steps());
- delete object;
- }
- m_objects.clear();
- print_regions_reshuffled = true;
- m_model.assign_copy(model);
- for (const ModelObject *model_object : m_model.objects)
- model_object_status_db.add(*model_object, ModelObjectStatus::New);
- } else {
- if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) {
- update_apply_status(num_extruders_changed ||
- // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering.
- //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable
- // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same.
- (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ?
- // The Tool Ordering and the Wipe Tower are no more valid.
- this->invalidate_steps({ psWipeTower, psGCodeExport }) :
- // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering.
- this->invalidate_step(psGCodeExport));
- m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
- }
- if (model_object_list_equal(m_model, model)) {
- // The object list did not change.
- for (const ModelObject *model_object : m_model.objects)
- model_object_status_db.add(*model_object, ModelObjectStatus::Old);
- } else if (model_object_list_extended(m_model, model)) {
- // Add new objects. Their volumes and configs will be synchronized later.
- update_apply_status(this->invalidate_step(psGCodeExport));
- for (const ModelObject *model_object : m_model.objects)
- model_object_status_db.add(*model_object, ModelObjectStatus::Old);
- for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) {
- model_object_status_db.add(*model.objects[i], ModelObjectStatus::New);
- m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i]));
- m_model.objects.back()->set_model(&m_model);
- }
- } else {
- // Reorder the objects, add new objects.
- // First stop background processing before shuffling or deleting the PrintObjects in the object list.
- this->call_cancel_callback();
- update_apply_status(this->invalidate_step(psGCodeExport));
- // Second create a new list of objects.
- std::vector<ModelObject*> model_objects_old(std::move(m_model.objects));
- m_model.objects.clear();
- m_model.objects.reserve(model.objects.size());
- auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); };
- std::sort(model_objects_old.begin(), model_objects_old.end(), by_id_lower);
- for (const ModelObject *mobj : model.objects) {
- auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower);
- if (it == model_objects_old.end() || (*it)->id() != mobj->id()) {
- // New ModelObject added.
- m_model.objects.emplace_back(ModelObject::new_copy(*mobj));
- m_model.objects.back()->set_model(&m_model);
- model_object_status_db.add(*mobj, ModelObjectStatus::New);
- } else {
- // Existing ModelObject re-added (possibly moved in the list).
- m_model.objects.emplace_back(*it);
- model_object_status_db.add(*mobj, ModelObjectStatus::Moved);
- }
- }
- bool deleted_any = false;
- for (ModelObject *&model_object : model_objects_old)
- if (model_object_status_db.add_if_new(*model_object, ModelObjectStatus::Deleted))
- deleted_any = true;
- else
- // Do not delete this ModelObject instance.
- model_object = nullptr;
- if (deleted_any) {
- // Delete PrintObjects of the deleted ModelObjects.
- PrintObjectPtrs print_objects_old = std::move(m_objects);
- m_objects.clear();
- m_objects.reserve(print_objects_old.size());
- for (PrintObject *print_object : print_objects_old) {
- const ModelObjectStatus &status = model_object_status_db.get(*print_object->model_object());
- if (status.status == ModelObjectStatus::Deleted) {
- update_apply_status(print_object->invalidate_all_steps());
- delete print_object;
- } else
- m_objects.emplace_back(print_object);
- }
- for (ModelObject *model_object : model_objects_old)
- delete model_object;
- print_regions_reshuffled = true;
- }
- }
- }
-
- // 2) Map print objects including their transformation matrices.
- PrintObjectStatusDB print_object_status_db(m_objects);
-
- // 3) Synchronize ModelObjects & PrintObjects.
- const std::initializer_list<ModelVolumeType> solid_or_modifier_types { ModelVolumeType::MODEL_PART, ModelVolumeType::NEGATIVE_VOLUME, ModelVolumeType::PARAMETER_MODIFIER };
- for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) {
- ModelObject &model_object = *m_model.objects[idx_model_object];
- ModelObjectStatus &model_object_status = const_cast<ModelObjectStatus&>(model_object_status_db.reuse(model_object));
- const ModelObject &model_object_new = *model.objects[idx_model_object];
- if (model_object_status.status == ModelObjectStatus::New)
- // PrintObject instances will be added in the next loop.
- continue;
- // Update the ModelObject instance, possibly invalidate the linked PrintObjects.
- assert(model_object_status.status == ModelObjectStatus::Old || model_object_status.status == ModelObjectStatus::Moved);
- // Check whether a model part volume was added or removed, their transformations or order changed.
- // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked.
- bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) ||
- model_mmu_segmentation_data_changed(model_object, model_object_new) ||
- (model_object_new.is_mm_painted() && num_extruders_changed);
- bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) ||
- model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER);
- bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty());
- bool model_origin_translation_differ = model_object.origin_translation != model_object_new.origin_translation;
- auto print_objects_range = print_object_status_db.get_range(model_object);
- // The list actually can be empty if all instances are out of the print bed.
- //assert(print_objects_range.begin() != print_objects_range.end());
- // All PrintObjects in print_objects_range shall point to the same prints_objects_regions
- if (print_objects_range.begin() != print_objects_range.end()) {
- model_object_status.print_object_regions = print_objects_range.begin()->print_object->m_shared_regions;
- model_object_status.print_object_regions->ref_cnt_inc();
- }
- if (solid_or_modifier_differ || model_origin_translation_differ || layer_height_ranges_differ ||
- ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile)) {
- // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects.
- model_object_status.print_object_regions_status =
- model_object_status.print_object_regions == nullptr || model_origin_translation_differ || layer_height_ranges_differ ?
- // Drop print_objects_regions.
- ModelObjectStatus::PrintObjectRegionsStatus::Invalid :
- // Reuse bounding boxes of print_objects_regions for ModelVolumes with unmodified transformation.
- ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid;
- for (const PrintObjectStatus &print_object_status : print_objects_range) {
- update_apply_status(print_object_status.print_object->invalidate_all_steps());
- const_cast<PrintObjectStatus&>(print_object_status).status = PrintObjectStatus::Deleted;
- }
- if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid)
- // Drop everything from PrintObjectRegions but those VolumeExtents (of their particular ModelVolumes) that are still valid.
- print_objects_regions_invalidate_keep_some_volumes(*model_object_status.print_object_regions, model_object.volumes, model_object_new.volumes);
- else if (model_object_status.print_object_regions != nullptr)
- model_object_status.print_object_regions->clear();
- // Copy content of the ModelObject including its ID, do not change the parent.
- model_object.assign_copy(model_object_new);
- } else {
- model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Valid;
- if (supports_differ || model_custom_supports_data_changed(model_object, model_object_new)) {
- // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list.
- if (supports_differ) {
- this->call_cancel_callback();
- update_apply_status(false);
- }
- // Invalidate just the supports step.
- for (const PrintObjectStatus &print_object_status : print_objects_range)
- update_apply_status(print_object_status.print_object->invalidate_step(posSupportMaterial));
- if (supports_differ) {
- // Copy just the support volumes.
- model_volume_list_update_supports(model_object, model_object_new);
- }
- } else if (model_custom_seam_data_changed(model_object, model_object_new)) {
- update_apply_status(this->invalidate_step(psGCodeExport));
- }
- }
- if (! solid_or_modifier_differ) {
- // Synchronize Object's config.
- bool object_config_changed = ! model_object.config.timestamp_matches(model_object_new.config);
- if (object_config_changed)
- model_object.config.assign_config(model_object_new.config);
- if (! object_diff.empty() || object_config_changed || num_extruders_changed) {
- PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders);
- for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(model_object)) {
- t_config_option_keys diff = print_object_status.print_object->config().diff(new_config);
- if (! diff.empty()) {
- update_apply_status(print_object_status.print_object->invalidate_state_by_config_options(print_object_status.print_object->config(), new_config, diff));
- print_object_status.print_object->config_apply_only(new_config, diff, true);
- }
- }
- }
- // Synchronize (just copy) the remaining data of ModelVolumes (name, config, custom supports data).
- //FIXME What to do with m_material_id?
- model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART);
- model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER);
- layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */);
- // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step.
- model_object.name = model_object_new.name;
- model_object.input_file = model_object_new.input_file;
- // Only refresh ModelInstances if there is any change.
- if (model_object.instances.size() != model_object_new.instances.size() ||
- ! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), [](auto l, auto r){ return l->id() == r->id(); })) {
- // G-code generator accesses model_object.instances to generate sequential print ordering matching the Plater object list.
- update_apply_status(this->invalidate_step(psGCodeExport));
- model_object.clear_instances();
- model_object.instances.reserve(model_object_new.instances.size());
- for (const ModelInstance *model_instance : model_object_new.instances) {
- model_object.instances.emplace_back(new ModelInstance(*model_instance));
- model_object.instances.back()->set_model_object(&model_object);
- }
- } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(),
- [](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable &&
- l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) {
- // If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid.
- // This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only.
- model_object.invalidate_bounding_box();
- // Synchronize the content of instances.
- auto new_instance = model_object_new.instances.begin();
- for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) {
- (*old_instance)->set_transformation((*new_instance)->get_transformation());
- (*old_instance)->print_volume_state = (*new_instance)->print_volume_state;
- (*old_instance)->printable = (*new_instance)->printable;
- }
- }
- }
- }
-
- // 4) Generate PrintObjects from ModelObjects and their instances.
- {
- PrintObjectPtrs print_objects_new;
- print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size()));
- bool new_objects = false;
- // Walk over all new model objects and check, whether there are matching PrintObjects.
- for (ModelObject *model_object : m_model.objects) {
- ModelObjectStatus &model_object_status = const_cast<ModelObjectStatus&>(model_object_status_db.reuse(*model_object));
- model_object_status.print_instances = print_objects_from_model_object(*model_object);
- std::vector<const PrintObjectStatus*> old;
- old.reserve(print_object_status_db.count(*model_object));
- for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object))
- if (print_object_status.status != PrintObjectStatus::Deleted)
- old.emplace_back(&print_object_status);
- // Generate a list of trafos and XY offsets for instances of a ModelObject
- // Producing the config for PrintObject on demand, caching it at print_object_last.
- const PrintObject *print_object_last = nullptr;
- auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders](PrintObject *print_object) {
- print_object->config_apply(print_object_last ?
- print_object_last->config() :
- PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders));
- print_object_last = print_object;
- };
- if (old.empty()) {
- // Simple case, just generate new instances.
- for (PrintObjectTrafoAndInstances &print_instances : model_object_status.print_instances) {
- PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances));
- print_object_apply_config(print_object);
- print_objects_new.emplace_back(print_object);
- // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New));
- new_objects = true;
- }
- continue;
- }
- // Complex case, try to merge the two lists.
- // Sort the old lexicographically by their trafos.
- std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); });
- // Merge the old / new lists.
- auto it_old = old.begin();
- for (PrintObjectTrafoAndInstances &new_instances : model_object_status.print_instances) {
- for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old);
- if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) {
- // This is a new instance (or a set of instances with the same trafo). Just add it.
- PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances));
- print_object_apply_config(print_object);
- print_objects_new.emplace_back(print_object);
- // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New));
- new_objects = true;
- if (it_old != old.end())
- const_cast<PrintObjectStatus*>(*it_old)->status = PrintObjectStatus::Deleted;
- } else {
- // The PrintObject already exists and the copies differ.
- PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances));
- if (status != PrintBase::APPLY_STATUS_UNCHANGED)
- update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED);
- print_objects_new.emplace_back((*it_old)->print_object);
- const_cast<PrintObjectStatus*>(*it_old)->status = PrintObjectStatus::Reused;
- }
- }
- }
- if (m_objects != print_objects_new) {
- this->call_cancel_callback();
- update_apply_status(this->invalidate_all_steps());
- m_objects = print_objects_new;
- // Delete the PrintObjects marked as Unknown or Deleted.
- bool deleted_objects = false;
- for (const PrintObjectStatus &pos : print_object_status_db)
- if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) {
- update_apply_status(pos.print_object->invalidate_all_steps());
- delete pos.print_object;
- deleted_objects = true;
- }
- if (new_objects || deleted_objects)
- update_apply_status(this->invalidate_steps({ psSkirtBrim, psWipeTower, psGCodeExport }));
- if (new_objects)
- update_apply_status(false);
- print_regions_reshuffled = true;
- }
- print_object_status_db.clear();
- }
-
- // All regions now have distinct settings.
- // Check whether applying the new region config defaults we would get different regions,
- // update regions or create regions from scratch.
- for (auto it_print_object = m_objects.begin(); it_print_object != m_objects.end();) {
- // Find the range of PrintObjects sharing the same associated ModelObject.
- auto it_print_object_end = it_print_object;
- PrintObject &print_object = *(*it_print_object);
- const ModelObject &model_object = *print_object.model_object();
- ModelObjectStatus &model_object_status = const_cast<ModelObjectStatus&>(model_object_status_db.reuse(model_object));
- PrintObjectRegions *print_object_regions = model_object_status.print_object_regions;
- for (++ it_print_object_end; it_print_object_end != m_objects.end() && (*it_print_object)->model_object() == (*it_print_object_end)->model_object(); ++ it_print_object_end)
- assert((*it_print_object_end)->m_shared_regions == nullptr || (*it_print_object_end)->m_shared_regions == print_object_regions);
- if (print_object_regions == nullptr) {
- print_object_regions = new PrintObjectRegions{};
- model_object_status.print_object_regions = print_object_regions;
- print_object_regions->ref_cnt_inc();
- }
- std::vector<unsigned int> painting_extruders;
- if (const auto &volumes = print_object.model_object()->volumes;
- num_extruders > 1 &&
- std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
- //FIXME be more specific! Don't enumerate extruders that are not used for painting!
- painting_extruders.assign(num_extruders, 0);
- std::iota(painting_extruders.begin(), painting_extruders.end(), 1);
- }
- if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) {
- // Verify that the trafo for regions & volume bounding boxes thus for regions is still applicable.
- auto invalidate = [it_print_object, it_print_object_end, update_apply_status]() {
- for (auto it = it_print_object; it != it_print_object_end; ++ it)
- if ((*it)->m_shared_regions != nullptr)
- update_apply_status((*it)->invalidate_all_steps());
- };
- if (print_object_regions && ! trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(print_object_regions->trafo_bboxes, model_object_status.print_instances.front().trafo)) {
- invalidate();
- print_object_regions->clear();
- model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Invalid;
- print_regions_reshuffled = true;
- } else if (print_object_regions &&
- verify_update_print_object_regions(
- print_object.model_object()->volumes,
- m_default_region_config,
- num_extruders,
- painting_extruders,
- *print_object_regions,
- [it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) {
- for (auto it = it_print_object; it != it_print_object_end; ++it)
- if ((*it)->m_shared_regions != nullptr)
- update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys));
- })) {
- // Regions are valid, just keep them.
- } else {
- // Regions were reshuffled.
- invalidate();
- // At least reuse layer ranges and bounding boxes of ModelVolumes.
- model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid;
- print_regions_reshuffled = true;
- }
- }
- if (print_object_regions == nullptr || model_object_status.print_object_regions_status != ModelObjectStatus::PrintObjectRegionsStatus::Valid) {
- // Layer ranges with their associated configurations. Remove overlaps between the ranges
- // and create the regions from scratch.
- print_object_regions = generate_print_object_regions(
- print_object_regions,
- print_object.model_object()->volumes,
- LayerRanges(print_object.model_object()->layer_config_ranges),
- m_default_region_config,
- model_object_status.print_instances.front().trafo,
- num_extruders,
- print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value),
- painting_extruders);
- }
- for (auto it = it_print_object; it != it_print_object_end; ++it)
- if ((*it)->m_shared_regions) {
- assert((*it)->m_shared_regions == print_object_regions);
- } else {
- (*it)->m_shared_regions = print_object_regions;
- print_object_regions->ref_cnt_inc();
- }
- it_print_object = it_print_object_end;
- }
-
- if (print_regions_reshuffled) {
- // Update Print::m_print_regions from objects.
- struct cmp { bool operator() (const PrintRegion *l, const PrintRegion *r) const { return l->config_hash() == r->config_hash() && l->config() == r->config(); } };
- std::set<const PrintRegion*, cmp> region_set;
- m_print_regions.clear();
- PrintObjectRegions *print_object_regions = nullptr;
- for (PrintObject *print_object : m_objects)
- if (print_object_regions != print_object->m_shared_regions) {
- print_object_regions = print_object->m_shared_regions;
- for (std::unique_ptr<Slic3r::PrintRegion> &print_region : print_object_regions->all_regions)
- if (auto it = region_set.find(print_region.get()); it == region_set.end()) {
- int print_region_id = int(m_print_regions.size());
- m_print_regions.emplace_back(print_region.get());
- print_region->m_print_region_id = print_region_id;
- } else {
- print_region->m_print_region_id = (*it)->print_region_id();
- }
- }
- }
-
- // Update SlicingParameters for each object where the SlicingParameters is not valid.
- // If it is not valid, then it is ensured that PrintObject.m_slicing_params is not in use
- // (posSlicing and posSupportMaterial was invalidated).
- for (PrintObject *object : m_objects)
- object->update_slicing_parameters();
-
-#ifdef _DEBUG
- check_model_ids_equal(m_model, model);
-#endif /* _DEBUG */
-
- return static_cast<ApplyStatus>(apply_status);
-}
-
-} // namespace Slic3r
+#include "Model.hpp"
+#include "Print.hpp"
+
+#include <cfloat>
+
+namespace Slic3r {
+
+// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new
+// in the exact order and with the same IDs.
+// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order.
+// Friend to ModelVolume to allow copying.
+// static is not accepted by gcc if declared as a friend of ModelObject.
+/* static */ void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_new)
+{
+ typedef std::pair<const ModelVolume*, bool> ModelVolumeWithStatus;
+ std::vector<ModelVolumeWithStatus> old_volumes;
+ old_volumes.reserve(model_object_dst.volumes.size());
+ for (const ModelVolume *model_volume : model_object_dst.volumes)
+ old_volumes.emplace_back(ModelVolumeWithStatus(model_volume, false));
+ auto model_volume_lower = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() < mv2.first->id(); };
+ auto model_volume_equal = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() == mv2.first->id(); };
+ std::sort(old_volumes.begin(), old_volumes.end(), model_volume_lower);
+ model_object_dst.volumes.clear();
+ model_object_dst.volumes.reserve(model_object_new.volumes.size());
+ for (const ModelVolume *model_volume_src : model_object_new.volumes) {
+ ModelVolumeWithStatus key(model_volume_src, false);
+ auto it = std::lower_bound(old_volumes.begin(), old_volumes.end(), key, model_volume_lower);
+ if (it != old_volumes.end() && model_volume_equal(*it, key)) {
+ // The volume was found in the old list. Just copy it.
+ assert(! it->second); // not consumed yet
+ it->second = true;
+ ModelVolume *model_volume_dst = const_cast<ModelVolume*>(it->first);
+ // For support modifiers, the type may have been switched from blocker to enforcer and vice versa.
+ assert((model_volume_dst->is_support_modifier() && model_volume_src->is_support_modifier()) || model_volume_dst->type() == model_volume_src->type());
+ model_object_dst.volumes.emplace_back(model_volume_dst);
+ if (model_volume_dst->is_support_modifier()) {
+ // For support modifiers, the type may have been switched from blocker to enforcer and vice versa.
+ model_volume_dst->set_type(model_volume_src->type());
+ model_volume_dst->set_transformation(model_volume_src->get_transformation());
+ }
+ assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix()));
+ } else {
+ // The volume was not found in the old list. Create a new copy.
+ assert(model_volume_src->is_support_modifier());
+ model_object_dst.volumes.emplace_back(new ModelVolume(*model_volume_src));
+ model_object_dst.volumes.back()->set_model_object(&model_object_dst);
+ }
+ }
+ // Release the non-consumed old volumes (those were deleted from the new list).
+ for (ModelVolumeWithStatus &mv_with_status : old_volumes)
+ if (! mv_with_status.second)
+ delete mv_with_status.first;
+}
+
+static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolumeType type)
+{
+ size_t i_src, i_dst;
+ for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) {
+ const ModelVolume &mv_src = *model_object_src.volumes[i_src];
+ ModelVolume &mv_dst = *model_object_dst.volumes[i_dst];
+ if (mv_src.type() != type) {
+ ++ i_src;
+ continue;
+ }
+ if (mv_dst.type() != type) {
+ ++ i_dst;
+ continue;
+ }
+ assert(mv_src.id() == mv_dst.id());
+ // Copy the ModelVolume data.
+ mv_dst.name = mv_src.name;
+ mv_dst.config.assign_config(mv_src.config);
+ assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id());
+ mv_dst.supported_facets.assign(mv_src.supported_facets);
+ assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id());
+ mv_dst.seam_facets.assign(mv_src.seam_facets);
+ assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id());
+ mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets);
+ //FIXME what to do with the materials?
+ // mv_dst.m_material_id = mv_src.m_material_id;
+ ++ i_src;
+ ++ i_dst;
+ }
+}
+
+static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src)
+{
+ assert(lr_dst.size() == lr_src.size());
+ auto it_src = lr_src.cbegin();
+ for (auto &kvp_dst : lr_dst) {
+ const auto &kvp_src = *it_src ++;
+ assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON);
+ assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON);
+ // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile.
+ // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON);
+ kvp_dst.second = kvp_src.second;
+ }
+}
+
+static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs)
+{
+ typedef Transform3d::Scalar T;
+ const T *lv = lhs.data();
+ const T *rv = rhs.data();
+ for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) {
+ if (*lv < *rv)
+ return true;
+ else if (*lv > *rv)
+ return false;
+ }
+ return false;
+}
+
+static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &rhs)
+{
+ typedef Transform3d::Scalar T;
+ const T *lv = lhs.data();
+ const T *rv = rhs.data();
+ for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv)
+ if (*lv != *rv)
+ return false;
+ return true;
+}
+
+struct PrintObjectTrafoAndInstances
+{
+ Transform3d trafo;
+ PrintInstances instances;
+ bool operator<(const PrintObjectTrafoAndInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); }
+};
+
+// Generate a list of trafos and XY offsets for instances of a ModelObject
+static std::vector<PrintObjectTrafoAndInstances> print_objects_from_model_object(const ModelObject &model_object)
+{
+ std::set<PrintObjectTrafoAndInstances> trafos;
+ PrintObjectTrafoAndInstances trafo;
+ for (ModelInstance *model_instance : model_object.instances)
+ if (model_instance->is_printable()) {
+ trafo.trafo = model_instance->get_matrix();
+ auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]);
+ // Reset the XY axes of the transformation.
+ trafo.trafo.data()[12] = 0;
+ trafo.trafo.data()[13] = 0;
+ // Search or insert a trafo.
+ auto it = trafos.emplace(trafo).first;
+ const_cast<PrintObjectTrafoAndInstances&>(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift });
+ }
+ return std::vector<PrintObjectTrafoAndInstances>(trafos.begin(), trafos.end());
+}
+
+// Compare just the layer ranges and their layer heights, not the associated configs.
+// Ignore the layer heights if check_layer_heights is false.
+static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height)
+{
+ if (lr1.size() != lr2.size())
+ return false;
+ auto it2 = lr2.begin();
+ for (const auto &kvp1 : lr1) {
+ const auto &kvp2 = *it2 ++;
+ if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON ||
+ std::abs(kvp1.first.second - kvp2.first.second) > EPSILON ||
+ (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON))
+ return false;
+ }
+ return true;
+}
+
+// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored.
+static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector<CustomGCode::Item> &va, const std::vector<CustomGCode::Item> &vb)
+{
+ auto it_a = va.begin();
+ auto it_b = vb.begin();
+ while (it_a != va.end() || it_b != vb.end()) {
+ if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) {
+ // Skip any CustomGCode items, which are not tool changes.
+ ++ it_a;
+ continue;
+ }
+ if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) {
+ // Skip any CustomGCode items, which are not tool changes.
+ ++ it_b;
+ continue;
+ }
+ if (it_a == va.end() || it_b == vb.end())
+ // va or vb contains more Tool Changes than the other.
+ return true;
+ assert(it_a->type == CustomGCode::ToolChange);
+ assert(it_b->type == CustomGCode::ToolChange);
+ if (*it_a != *it_b)
+ // The two Tool Changes differ.
+ return true;
+ ++ it_a;
+ ++ it_b;
+ }
+ // There is no change in custom Tool Changes.
+ return false;
+}
+
+// Collect changes to print config, account for overrides of extruder retract values by filament presets.
+static t_config_option_keys print_config_diffs(
+ const PrintConfig &current_config,
+ const DynamicPrintConfig &new_full_config,
+ DynamicPrintConfig &filament_overrides)
+{
+ const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
+ const std::string filament_prefix = "filament_";
+ t_config_option_keys print_diff;
+ for (const t_config_option_key &opt_key : current_config.keys()) {
+ const ConfigOption *opt_old = current_config.option(opt_key);
+ assert(opt_old != nullptr);
+ const ConfigOption *opt_new = new_full_config.option(opt_key);
+ // assert(opt_new != nullptr);
+ if (opt_new == nullptr)
+ //FIXME This may happen when executing some test cases.
+ continue;
+ const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr;
+ if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) {
+ // An extruder retract override is available at some of the filament presets.
+ bool overriden = opt_new->overriden_by(opt_new_filament);
+ if (overriden || *opt_old != *opt_new) {
+ auto opt_copy = opt_new->clone();
+ opt_copy->apply_override(opt_new_filament);
+ bool changed = *opt_old != *opt_copy;
+ if (changed)
+ print_diff.emplace_back(opt_key);
+ if (changed || overriden) {
+ // filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config.
+ filament_overrides.set_key_value(opt_key, opt_copy);
+ } else
+ delete opt_copy;
+ }
+ } else if (*opt_new != *opt_old)
+ print_diff.emplace_back(opt_key);
+ }
+
+ return print_diff;
+}
+
+// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser.
+static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig &current_full_config, const DynamicPrintConfig &new_full_config)
+{
+ t_config_option_keys full_config_diff;
+ for (const t_config_option_key &opt_key : new_full_config.keys()) {
+ const ConfigOption *opt_old = current_full_config.option(opt_key);
+ const ConfigOption *opt_new = new_full_config.option(opt_key);
+ if (opt_old == nullptr || *opt_new != *opt_old)
+ full_config_diff.emplace_back(opt_key);
+ }
+ return full_config_diff;
+}
+
+// Repository for solving partial overlaps of ModelObject::layer_config_ranges.
+// Here the const DynamicPrintConfig* point to the config in ModelObject::layer_config_ranges.
+class LayerRanges
+{
+public:
+ struct LayerRange {
+ t_layer_height_range layer_height_range;
+ // Config is owned by the associated ModelObject.
+ const DynamicPrintConfig* config { nullptr };
+
+ bool operator<(const LayerRange &rhs) const throw() { return this->layer_height_range < rhs.layer_height_range; }
+ };
+
+ LayerRanges() = default;
+ LayerRanges(const t_layer_config_ranges &in) { this->assign(in); }
+
+ // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs.
+ void assign(const t_layer_config_ranges &in) {
+ m_ranges.clear();
+ m_ranges.reserve(in.size());
+ // Input ranges are sorted lexicographically. First range trims the other ranges.
+ coordf_t last_z = 0;
+ for (const std::pair<const t_layer_height_range, ModelConfig> &range : in)
+ if (range.first.second > last_z) {
+ coordf_t min_z = std::max(range.first.first, 0.);
+ if (min_z > last_z + EPSILON) {
+ m_ranges.push_back({ t_layer_height_range(last_z, min_z) });
+ last_z = min_z;
+ }
+ if (range.first.second > last_z + EPSILON) {
+ const DynamicPrintConfig *cfg = &range.second.get();
+ m_ranges.push_back({ t_layer_height_range(last_z, range.first.second), cfg });
+ last_z = range.first.second;
+ }
+ }
+ if (m_ranges.empty())
+ m_ranges.push_back({ t_layer_height_range(0, DBL_MAX) });
+ else if (m_ranges.back().config == nullptr)
+ m_ranges.back().layer_height_range.second = DBL_MAX;
+ else
+ m_ranges.push_back({ t_layer_height_range(m_ranges.back().layer_height_range.second, DBL_MAX) });
+ }
+
+ const DynamicPrintConfig* config(const t_layer_height_range &range) const {
+ auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), LayerRange{ { range.first - EPSILON, range.second - EPSILON } });
+ // #ys_FIXME_COLOR
+ // assert(it != m_ranges.end());
+ // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON);
+ // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON);
+ if (it == m_ranges.end() ||
+ std::abs(it->layer_height_range.first - range.first) > EPSILON ||
+ std::abs(it->layer_height_range.second - range.second) > EPSILON )
+ return nullptr; // desired range doesn't found
+ return it == m_ranges.end() ? nullptr : it->config;
+ }
+
+ std::vector<LayerRange>::const_iterator begin() const { return m_ranges.cbegin(); }
+ std::vector<LayerRange>::const_iterator end () const { return m_ranges.cend(); }
+ size_t size () const { return m_ranges.size(); }
+
+private:
+ // Layer ranges with their config overrides and list of volumes with their snug bounding boxes in a given layer range.
+ std::vector<LayerRange> m_ranges;
+};
+
+// To track Model / ModelObject updates between the front end and back end, including layer height ranges, their configs,
+// and snug bounding boxes of ModelVolumes.
+struct ModelObjectStatus {
+ enum Status {
+ Unknown,
+ Old,
+ New,
+ Moved,
+ Deleted,
+ };
+
+ enum class PrintObjectRegionsStatus {
+ Invalid,
+ Valid,
+ PartiallyValid,
+ };
+
+ ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {}
+ ~ModelObjectStatus() { if (print_object_regions) print_object_regions->ref_cnt_dec(); }
+
+ // Key of the set.
+ ObjectID id;
+ // Status of this ModelObject with id on apply().
+ Status status;
+ // PrintObjects to be generated for this ModelObject including their base transformation.
+ std::vector<PrintObjectTrafoAndInstances> print_instances;
+ // Regions shared by the associated PrintObjects.
+ PrintObjectRegions *print_object_regions { nullptr };
+ // Status of the above.
+ PrintObjectRegionsStatus print_object_regions_status { PrintObjectRegionsStatus::Invalid };
+
+ // Search by id.
+ bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; }
+};
+
+struct ModelObjectStatusDB
+{
+ void add(const ModelObject &model_object, const ModelObjectStatus::Status status) {
+ assert(db.find(ModelObjectStatus(model_object.id())) == db.end());
+ db.emplace(model_object.id(), status);
+ }
+
+ bool add_if_new(const ModelObject &model_object, const ModelObjectStatus::Status status) {
+ auto it = db.find(ModelObjectStatus(model_object.id()));
+ if (it == db.end()) {
+ db.emplace_hint(it, model_object.id(), status);
+ return true;
+ }
+ return false;
+ }
+
+ const ModelObjectStatus& get(const ModelObject &model_object) {
+ auto it = db.find(ModelObjectStatus(model_object.id()));
+ assert(it != db.end());
+ return *it;
+ }
+
+ const ModelObjectStatus& reuse(const ModelObject &model_object) {
+ const ModelObjectStatus &result = this->get(model_object);
+ assert(result.status != ModelObjectStatus::Deleted);
+ return result;
+ }
+
+ std::set<ModelObjectStatus> db;
+};
+
+struct PrintObjectStatus {
+ enum Status {
+ Unknown,
+ Deleted,
+ Reused,
+ New
+ };
+
+ PrintObjectStatus(PrintObject *print_object, Status status = Unknown) :
+ id(print_object->model_object()->id()),
+ print_object(print_object),
+ trafo(print_object->trafo()),
+ status(status) {}
+ PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {}
+
+ // ID of the ModelObject & PrintObject
+ ObjectID id;
+ // Pointer to the old PrintObject
+ PrintObject *print_object;
+ // Trafo generated with model_object->world_matrix(true)
+ Transform3d trafo;
+ Status status;
+
+ // Search by id.
+ bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; }
+};
+
+class PrintObjectStatusDB {
+public:
+ using iterator = std::multiset<PrintObjectStatus>::iterator;
+ using const_iterator = std::multiset<PrintObjectStatus>::const_iterator;
+
+ PrintObjectStatusDB(const PrintObjectPtrs &print_objects) {
+ for (PrintObject *print_object : print_objects)
+ m_db.emplace(PrintObjectStatus(print_object));
+ }
+
+ struct iterator_range : std::pair<const_iterator, const_iterator>
+ {
+ using std::pair<const_iterator, const_iterator>::pair;
+ iterator_range(const std::pair<const_iterator, const_iterator> in) : std::pair<const_iterator, const_iterator>(in) {}
+
+ const_iterator begin() throw() { return this->first; }
+ const_iterator end() throw() { return this->second; }
+ };
+
+ iterator_range get_range(const ModelObject &model_object) const {
+ return m_db.equal_range(PrintObjectStatus(model_object.id()));
+ }
+
+ iterator_range get_range(const ModelObjectStatus &model_object_status) const {
+ return m_db.equal_range(PrintObjectStatus(model_object_status.id));
+ }
+
+ size_t count(const ModelObject &model_object) {
+ return m_db.count(PrintObjectStatus(model_object.id()));
+ }
+
+ std::multiset<PrintObjectStatus>::iterator begin() { return m_db.begin(); }
+ std::multiset<PrintObjectStatus>::iterator end() { return m_db.end(); }
+
+ void clear() {
+ m_db.clear();
+ }
+
+private:
+ std::multiset<PrintObjectStatus> m_db;
+};
+
+static inline bool model_volume_solid_or_modifier(const ModelVolume &mv)
+{
+ ModelVolumeType type = mv.type();
+ return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER;
+}
+
+static inline Transform3f trafo_for_bbox(const Transform3d &object_trafo, const Transform3d &volume_trafo)
+{
+ Transform3d m = object_trafo * volume_trafo;
+ m.translation().x() = 0.;
+ m.translation().y() = 0.;
+ return m.cast<float>();
+}
+
+static inline bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d &t1, const Transform3d &t2)
+{
+ if (std::abs(t1.translation().z() - t2.translation().z()) > EPSILON)
+ // One of the object is higher than the other above the build plate (or below the build plate).
+ return false;
+ Matrix3d m1 = t1.matrix().block<3, 3>(0, 0);
+ Matrix3d m2 = t2.matrix().block<3, 3>(0, 0);
+ Matrix3d m = m2.inverse() * m1;
+ Vec3d z = m.block<3, 1>(0, 2);
+ if (std::abs(z.x()) > EPSILON || std::abs(z.y()) > EPSILON || std::abs(z.z() - 1.) > EPSILON)
+ // Z direction or length changed.
+ return false;
+ // Z still points in the same direction and it has the same length.
+ Vec3d x = m.block<3, 1>(0, 0);
+ Vec3d y = m.block<3, 1>(0, 1);
+ if (std::abs(x.z()) > EPSILON || std::abs(y.z()) > EPSILON)
+ return false;
+ double lx2 = x.squaredNorm();
+ double ly2 = y.squaredNorm();
+ if (lx2 - 1. > EPSILON * EPSILON || ly2 - 1. > EPSILON * EPSILON)
+ return false;
+ // Verify whether the vectors x, y are still perpendicular.
+ double d = x.dot(y);
+ return std::abs(d * d) < EPSILON * lx2 * ly2;
+}
+
+static PrintObjectRegions::BoundingBox transformed_its_bbox2d(const indexed_triangle_set &its, const Transform3f &m, float offset)
+{
+ assert(! its.indices.empty());
+
+ PrintObjectRegions::BoundingBox bbox(m * its.vertices[its.indices.front()(0)]);
+ for (const stl_triangle_vertex_indices &tri : its.indices)
+ for (int i = 0; i < 3; ++ i)
+ bbox.extend(m * its.vertices[tri(i)]);
+ bbox.min() -= Vec3f(offset, offset, float(EPSILON));
+ bbox.max() += Vec3f(offset, offset, float(EPSILON));
+ return bbox;
+}
+
+static void transformed_its_bboxes_in_z_ranges(
+ const indexed_triangle_set &its,
+ const Transform3f &m,
+ const std::vector<t_layer_height_range> &z_ranges,
+ std::vector<std::pair<PrintObjectRegions::BoundingBox, bool>> &bboxes,
+ const float offset)
+{
+ bboxes.assign(z_ranges.size(), std::make_pair(PrintObjectRegions::BoundingBox(), false));
+ for (const stl_triangle_vertex_indices &tri : its.indices) {
+ const Vec3f pts[3] = { m * its.vertices[tri(0)], m * its.vertices[tri(1)], m * its.vertices[tri(2)] };
+ for (size_t irange = 0; irange < z_ranges.size(); ++ irange) {
+ const t_layer_height_range &z_range = z_ranges[irange];
+ std::pair<PrintObjectRegions::BoundingBox, bool> &bbox = bboxes[irange];
+ auto bbox_extend = [&bbox](const Vec3f& p) {
+ if (bbox.second) {
+ bbox.first.extend(p);
+ } else {
+ bbox.first.min() = bbox.first.max() = p;
+ bbox.second = true;
+ }
+ };
+ int iprev = 2;
+ for (int iedge = 0; iedge < 3; ++ iedge) {
+ const Vec3f *p1 = &pts[iprev];
+ const Vec3f *p2 = &pts[iedge];
+ // Sort the edge points by Z.
+ if (p1->z() > p2->z())
+ std::swap(p1, p2);
+ if (p2->z() <= z_range.first || p1->z() >= z_range.second) {
+ // Out of this slab.
+ } else if (p1->z() < z_range.first) {
+ if (p1->z() > z_range.second) {
+ // Two intersections.
+ float zspan = p2->z() - p1->z();
+ float t1 = (z_range.first - p1->z()) / zspan;
+ float t2 = (z_range.second - p1->z()) / zspan;
+ Vec2f p = to_2d(*p1);
+ Vec2f v(p2->x() - p1->x(), p2->y() - p1->y());
+ bbox_extend(to_3d((p + v * t1).eval(), float(z_range.first)));
+ bbox_extend(to_3d((p + v * t2).eval(), float(z_range.second)));
+ } else {
+ // Single intersection with the lower limit.
+ float t = (z_range.first - p1->z()) / (p2->z() - p1->z());
+ Vec2f v(p2->x() - p1->x(), p2->y() - p1->y());
+ bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.first)));
+ bbox_extend(*p2);
+ }
+ } else if (p2->z() > z_range.second) {
+ // Single intersection with the upper limit.
+ float t = (z_range.second - p1->z()) / (p2->z() - p1->z());
+ Vec2f v(p2->x() - p1->x(), p2->y() - p1->y());
+ bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.second)));
+ bbox_extend(*p1);
+ } else {
+ // Both points are inside.
+ bbox_extend(*p1);
+ bbox_extend(*p2);
+ }
+ iprev = iedge;
+ }
+ }
+ }
+
+ for (std::pair<PrintObjectRegions::BoundingBox, bool> &bbox : bboxes) {
+ bbox.first.min() -= Vec3f(offset, offset, float(EPSILON));
+ bbox.first.max() += Vec3f(offset, offset, float(EPSILON));
+ }
+}
+
+// Last PrintObject for this print_object_regions has been fully invalidated (deleted).
+// Keep print_object_regions, but delete those volumes, which were either removed from new_volumes, or which rotated or scaled, so they need
+// their bounding boxes to be recalculated.
+void print_objects_regions_invalidate_keep_some_volumes(PrintObjectRegions &print_object_regions, ModelVolumePtrs old_volumes, ModelVolumePtrs new_volumes)
+{
+ print_object_regions.all_regions.clear();
+
+ model_volumes_sort_by_id(old_volumes);
+ model_volumes_sort_by_id(new_volumes);
+
+ size_t i_cached_volume = 0;
+ size_t last_cached_volume = 0;
+ size_t i_old = 0;
+ for (size_t i_new = 0; i_new < new_volumes.size(); ++ i_new)
+ if (model_volume_solid_or_modifier(*new_volumes[i_new])) {
+ for (; i_old < old_volumes.size(); ++ i_old)
+ if (old_volumes[i_old]->id() >= new_volumes[i_new]->id())
+ break;
+ if (i_old != old_volumes.size() && old_volumes[i_old]->id() == new_volumes[i_new]->id()) {
+ if (old_volumes[i_old]->get_matrix().isApprox(new_volumes[i_new]->get_matrix())) {
+ // Reuse the volume.
+ for (; print_object_regions.cached_volume_ids[i_cached_volume] < old_volumes[i_old]->id(); ++ i_cached_volume)
+ assert(i_cached_volume < print_object_regions.cached_volume_ids.size());
+ assert(i_cached_volume < print_object_regions.cached_volume_ids.size() && print_object_regions.cached_volume_ids[i_cached_volume] == old_volumes[i_old]->id());
+ print_object_regions.cached_volume_ids[last_cached_volume ++] = print_object_regions.cached_volume_ids[i_cached_volume ++];
+ } else {
+ // Don't reuse the volume.
+ }
+ }
+ }
+ print_object_regions.cached_volume_ids.erase(print_object_regions.cached_volume_ids.begin() + last_cached_volume, print_object_regions.cached_volume_ids.end());
+}
+
+// Find a bounding box of a volume's part intersecting layer_range. Such a bounding box will likely be smaller in XY than the full bounding box,
+// thus it will intersect with lower number of other volumes.
+const PrintObjectRegions::BoundingBox* find_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const ModelVolume &volume)
+{
+ auto it = lower_bound_by_predicate(layer_range.volumes.begin(), layer_range.volumes.end(), [&volume](const PrintObjectRegions::VolumeExtents &l){ return l.volume_id < volume.id(); });
+ return it != layer_range.volumes.end() && it->volume_id == volume.id() ? &it->bbox : nullptr;
+}
+
+// Find a bounding box of a topmost printable volume referenced by this modifier given this_region_id.
+PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const int this_region_id)
+{
+ // Find the top-most printable volume of this modifier, or the printable volume itself.
+ const PrintObjectRegions::VolumeRegion &this_region = layer_range.volume_regions[this_region_id];
+ const PrintObjectRegions::BoundingBox *this_extents = find_volume_extents(layer_range, *this_region.model_volume);
+ assert(this_extents);
+ PrintObjectRegions::BoundingBox out { *this_extents };
+ if (! this_region.model_volume->is_model_part())
+ for (int parent_region_id = this_region.parent;;) {
+ assert(parent_region_id >= 0);
+ const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
+ const PrintObjectRegions::BoundingBox *parent_extents = find_volume_extents(layer_range, *parent_region.model_volume);
+ assert(parent_extents);
+ out.extend(*parent_extents);
+ if (parent_region.model_volume->is_model_part())
+ break;
+ parent_region_id = parent_region.parent;
+ }
+ return out;
+}
+
+PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders);
+
+void print_region_ref_inc(PrintRegion &r) { ++ r.m_ref_cnt; }
+void print_region_ref_reset(PrintRegion &r) { r.m_ref_cnt = 0; }
+int print_region_ref_cnt(const PrintRegion &r) { return r.m_ref_cnt; }
+
+// Verify whether the PrintRegions of a PrintObject are still valid, possibly after updating the region configs.
+// Before region configs are updated, callback_invalidate() is called to possibly stop background processing.
+// Returns false if this object needs to be resliced because regions were merged or split.
+bool verify_update_print_object_regions(
+ ModelVolumePtrs model_volumes,
+ const PrintRegionConfig &default_region_config,
+ size_t num_extruders,
+ const std::vector<unsigned int> &painting_extruders,
+ PrintObjectRegions &print_object_regions,
+ const std::function<void(const PrintRegionConfig&, const PrintRegionConfig&, const t_config_option_keys&)> &callback_invalidate)
+{
+ // Sort by ModelVolume ID.
+ model_volumes_sort_by_id(model_volumes);
+
+ for (std::unique_ptr<PrintRegion> &region : print_object_regions.all_regions)
+ print_region_ref_reset(*region);
+
+ // Verify and / or update PrintRegions produced by ModelVolumes, layer range modifiers, modifier volumes.
+ for (PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) {
+ // Each modifier ModelVolume intersecting this layer_range shall be referenced here at least once if it intersects some
+ // printable ModelVolume at this layer_range even if it does not modify its overlapping printable ModelVolume configuration yet.
+ // VolumeRegions reference ModelVolumes in layer_range.volume_regions the order they are stored in ModelObject volumes.
+ // Remember whether a given modifier ModelVolume was visited already.
+ auto it_model_volume_modifier_last = model_volumes.end();
+ for (PrintObjectRegions::VolumeRegion &region : layer_range.volume_regions)
+ if (region.model_volume->is_model_part() || region.model_volume->is_modifier()) {
+ auto it_model_volume = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [&region](const ModelVolume *l){ return l->id() < region.model_volume->id(); });
+ assert(it_model_volume != model_volumes.end() && (*it_model_volume)->id() == region.model_volume->id());
+ if (region.model_volume->is_modifier() && it_model_volume != it_model_volume_modifier_last) {
+ // A modifier ModelVolume is visited for the first time.
+ // A visited modifier may not have had parent volume_regions created overlapping with some model parts or modifiers,
+ // if the visited modifier did not modify their properties. Now the visited modifier's configuration may have changed,
+ // which may require new regions to be created.
+ it_model_volume_modifier_last = it_model_volume;
+ int next_region_id = int(&region - layer_range.volume_regions.data());
+ const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, *region.model_volume);
+ assert(bbox);
+ for (int parent_region_id = next_region_id - 1; parent_region_id >= 0; -- parent_region_id) {
+ const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
+ assert(parent_region.model_volume != region.model_volume);
+ if (parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) {
+ // volume_regions are produced in decreasing order of parent volume_regions ids.
+ // Some regions may not have been generated the last time by generate_print_object_regions().
+ assert(next_region_id == int(layer_range.volume_regions.size()) ||
+ layer_range.volume_regions[next_region_id].model_volume != region.model_volume ||
+ layer_range.volume_regions[next_region_id].parent <= parent_region_id);
+ if (next_region_id < int(layer_range.volume_regions.size()) &&
+ layer_range.volume_regions[next_region_id].model_volume == region.model_volume &&
+ layer_range.volume_regions[next_region_id].parent == parent_region_id) {
+ // A parent region is already overridden.
+ ++ next_region_id;
+ } else if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox))
+ // Such parent region does not exist. If it is needed, then we need to reslice.
+ // Only create new region for a modifier, which actually modifies config of it's parent.
+ if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders);
+ config != parent_region.region->config())
+ // This modifier newly overrides a region, which it did not before. We need to reslice.
+ return false;
+ }
+ }
+ }
+ PrintRegionConfig cfg = region.parent == -1 ?
+ region_config_from_model_volume(default_region_config, layer_range.config, **it_model_volume, num_extruders) :
+ region_config_from_model_volume(layer_range.volume_regions[region.parent].region->config(), nullptr, **it_model_volume, num_extruders);
+ if (cfg != region.region->config()) {
+ // Region configuration changed.
+ if (print_region_ref_cnt(*region.region) == 0) {
+ // Region is referenced for the first time. Just change its parameters.
+ // Stop the background process before assigning new configuration to the regions.
+ t_config_option_keys diff = region.region->config().diff(cfg);
+ callback_invalidate(region.region->config(), cfg, diff);
+ region.region->config_apply_only(cfg, diff, false);
+ } else {
+ // Region is referenced multiple times, thus the region is being split. We need to reslice.
+ return false;
+ }
+ }
+ print_region_ref_inc(*region.region);
+ }
+ }
+
+ // Verify and / or update PrintRegions produced by color painting.
+ for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges)
+ for (const PrintObjectRegions::PaintedRegion &region : layer_range.painted_regions) {
+ const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent];
+ PrintRegionConfig cfg = parent_region.region->config();
+ cfg.perimeter_extruder.value = region.extruder_id;
+ cfg.solid_infill_extruder.value = region.extruder_id;
+ cfg.infill_extruder.value = region.extruder_id;
+ if (cfg != region.region->config()) {
+ // Region configuration changed.
+ if (print_region_ref_cnt(*region.region) == 0) {
+ // Region is referenced for the first time. Just change its parameters.
+ // Stop the background process before assigning new configuration to the regions.
+ t_config_option_keys diff = region.region->config().diff(cfg);
+ callback_invalidate(region.region->config(), cfg, diff);
+ region.region->config_apply_only(cfg, diff, false);
+ } else {
+ // Region is referenced multiple times, thus the region is being split. We need to reslice.
+ return false;
+ }
+ }
+ print_region_ref_inc(*region.region);
+ }
+
+ // Lastly verify, whether some regions were not merged.
+ {
+ std::vector<const PrintRegion*> regions;
+ regions.reserve(print_object_regions.all_regions.size());
+ for (std::unique_ptr<PrintRegion> &region : print_object_regions.all_regions) {
+ assert(print_region_ref_cnt(*region) > 0);
+ regions.emplace_back(&(*region.get()));
+ }
+ std::sort(regions.begin(), regions.end(), [](const PrintRegion *l, const PrintRegion *r){ return l->config_hash() < r->config_hash(); });
+ for (size_t i = 0; i < regions.size(); ++ i) {
+ size_t hash = regions[i]->config_hash();
+ size_t j = i;
+ for (++ j; j < regions.size() && regions[j]->config_hash() == hash; ++ j)
+ if (regions[i]->config() == regions[j]->config()) {
+ // Regions were merged. We need to reslice.
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Update caches of volume bounding boxes.
+void update_volume_bboxes(
+ std::vector<PrintObjectRegions::LayerRangeRegions> &layer_ranges,
+ std::vector<ObjectID> &cached_volume_ids,
+ ModelVolumePtrs model_volumes,
+ const Transform3d &object_trafo,
+ const float offset)
+{
+ // output will be sorted by the order of model_volumes sorted by their ObjectIDs.
+ model_volumes_sort_by_id(model_volumes);
+
+ if (layer_ranges.size() == 1) {
+ PrintObjectRegions::LayerRangeRegions &layer_range = layer_ranges.front();
+ std::vector<PrintObjectRegions::VolumeExtents> volumes_old(std::move(layer_range.volumes));
+ layer_range.volumes.reserve(model_volumes.size());
+ for (const ModelVolume *model_volume : model_volumes)
+ if (model_volume_solid_or_modifier(*model_volume)) {
+ if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) {
+ auto it = lower_bound_by_predicate(volumes_old.begin(), volumes_old.end(), [model_volume](PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); });
+ if (it != volumes_old.end() && it->volume_id == model_volume->id())
+ layer_range.volumes.emplace_back(*it);
+ } else
+#if ENABLE_WORLD_COORDINATE
+ layer_range.volumes.push_back({ model_volume->id(),
+ transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), offset) });
+#else
+ layer_range.volumes.push_back({ model_volume->id(),
+ transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset) });
+#endif // ENABLE_WORLD_COORDINATE
+ }
+ } else {
+ std::vector<std::vector<PrintObjectRegions::VolumeExtents>> volumes_old;
+ if (cached_volume_ids.empty())
+ for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges)
+ layer_range.volumes.clear();
+ else {
+ volumes_old.reserve(layer_ranges.size());
+ for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges)
+ volumes_old.emplace_back(std::move(layer_range.volumes));
+ }
+
+ std::vector<std::pair<PrintObjectRegions::BoundingBox, bool>> bboxes;
+ std::vector<t_layer_height_range> ranges;
+ ranges.reserve(layer_ranges.size());
+ for (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) {
+ t_layer_height_range r = layer_range.layer_height_range;
+ r.first -= EPSILON;
+ r.second += EPSILON;
+ ranges.emplace_back(r);
+ }
+ for (const ModelVolume *model_volume : model_volumes)
+ if (model_volume_solid_or_modifier(*model_volume)) {
+ if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) {
+ for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) {
+ const auto &vold = volumes_old[&layer_range - layer_ranges.data()];
+ auto it = lower_bound_by_predicate(vold.begin(), vold.end(), [model_volume](const PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); });
+ if (it != vold.end() && it->volume_id == model_volume->id())
+ layer_range.volumes.emplace_back(*it);
+ }
+ } else {
+#if ENABLE_WORLD_COORDINATE
+ transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), ranges, bboxes, offset);
+#else
+ transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), ranges, bboxes, offset);
+#endif // ENABLE_WORLD_COORDINATE
+ for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges)
+ if (auto &bbox = bboxes[&layer_range - layer_ranges.data()]; bbox.second)
+ layer_range.volumes.push_back({ model_volume->id(), bbox.first });
+ }
+ }
+ }
+
+ cached_volume_ids.clear();
+ cached_volume_ids.reserve(model_volumes.size());
+ for (const ModelVolume *v : model_volumes)
+ if (model_volume_solid_or_modifier(*v))
+ cached_volume_ids.emplace_back(v->id());
+}
+
+// Either a fresh PrintObject, or PrintObject regions were invalidated (merged, split).
+// Generate PrintRegions from scratch.
+static PrintObjectRegions* generate_print_object_regions(
+ PrintObjectRegions *print_object_regions_old,
+ const ModelVolumePtrs &model_volumes,
+ const LayerRanges &model_layer_ranges,
+ const PrintRegionConfig &default_region_config,
+ const Transform3d &trafo,
+ size_t num_extruders,
+ const float xy_size_compensation,
+ const std::vector<unsigned int> &painting_extruders)
+{
+ // Reuse the old object or generate a new one.
+ auto out = print_object_regions_old ? std::unique_ptr<PrintObjectRegions>(print_object_regions_old) : std::make_unique<PrintObjectRegions>();
+ auto &all_regions = out->all_regions;
+ auto &layer_ranges_regions = out->layer_ranges;
+
+ all_regions.clear();
+
+ bool reuse_old = print_object_regions_old && !print_object_regions_old->layer_ranges.empty();
+
+ if (reuse_old) {
+ // Reuse old bounding boxes of some ModelVolumes and their ranges.
+ // Verify that the old ranges match the new ranges.
+ assert(model_layer_ranges.size() == layer_ranges_regions.size());
+ for (const auto &range : model_layer_ranges) {
+ PrintObjectRegions::LayerRangeRegions &r = layer_ranges_regions[&range - &*model_layer_ranges.begin()];
+ assert(range.layer_height_range == r.layer_height_range);
+ // If model::assign_copy() is called, layer_ranges_regions is copied thus the pointers to configs are lost.
+ r.config = range.config;
+ r.volume_regions.clear();
+ r.painted_regions.clear();
+ }
+ } else {
+ out->trafo_bboxes = trafo;
+ layer_ranges_regions.reserve(model_layer_ranges.size());
+ for (const auto &range : model_layer_ranges)
+ layer_ranges_regions.push_back({ range.layer_height_range, range.config });
+ }
+
+ const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
+ update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation));
+
+ std::vector<PrintRegion*> region_set;
+ auto get_create_region = [&region_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* {
+ size_t hash = config.hash();
+ auto it = Slic3r::lower_bound_by_predicate(region_set.begin(), region_set.end(), [&config, hash](const PrintRegion* l) {
+ return l->config_hash() < hash || (l->config_hash() == hash && l->config() < config); });
+ if (it != region_set.end() && (*it)->config_hash() == hash && (*it)->config() == config)
+ return *it;
+ // Insert into a sorted array, it has O(n) complexity, but the calling algorithm has an O(n^2*log(n)) complexity anyways.
+ all_regions.emplace_back(std::make_unique<PrintRegion>(std::move(config), hash, int(all_regions.size())));
+ PrintRegion *region = all_regions.back().get();
+ region_set.emplace(it, region);
+ return region;
+ };
+
+ // Chain the regions in the order they are stored in the volumes list.
+ for (int volume_id = 0; volume_id < int(model_volumes.size()); ++ volume_id) {
+ const ModelVolume &volume = *model_volumes[volume_id];
+ if (model_volume_solid_or_modifier(volume)) {
+ for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions)
+ if (const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, volume); bbox) {
+ if (volume.is_model_part()) {
+ // Add a model volume, assign an existing region or generate a new one.
+ layer_range.volume_regions.push_back({
+ &volume, -1,
+ get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders)),
+ bbox
+ });
+ } else if (volume.is_negative_volume()) {
+ // Add a negative (subtractor) volume. Such volume has neither region nor parent volume assigned.
+ layer_range.volume_regions.push_back({ &volume, -1, nullptr, bbox });
+ } else {
+ assert(volume.is_modifier());
+ // Modifiers may be chained one over the other. Check for overlap, merge DynamicPrintConfigs.
+ bool added = false;
+ int parent_model_part_id = -1;
+ for (int parent_region_id = int(layer_range.volume_regions.size()) - 1; parent_region_id >= 0; -- parent_region_id) {
+ const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
+ const ModelVolume &parent_volume = *parent_region.model_volume;
+ if (parent_volume.is_model_part() || parent_volume.is_modifier())
+ if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) {
+ // Only create new region for a modifier, which actually modifies config of it's parent.
+ if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders);
+ config != parent_region.region->config()) {
+ added = true;
+ layer_range.volume_regions.push_back({ &volume, parent_region_id, get_create_region(std::move(config)), bbox });
+ } else if (parent_model_part_id == -1 && parent_volume.is_model_part())
+ parent_model_part_id = parent_region_id;
+ }
+ }
+ if (! added && parent_model_part_id >= 0)
+ // This modifier does not override any printable volume's configuration, however it may in the future.
+ // Store it so that verify_update_print_object_regions() will handle this modifier correctly if its configuration changes.
+ layer_range.volume_regions.push_back({ &volume, parent_model_part_id, layer_range.volume_regions[parent_model_part_id].region, bbox });
+ }
+ }
+ }
+ }
+
+ // Finally add painting regions.
+ for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) {
+ for (unsigned int painted_extruder_id : painting_extruders)
+ for (int parent_region_id = 0; parent_region_id < int(layer_range.volume_regions.size()); ++ parent_region_id)
+ if (const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
+ parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) {
+ PrintRegionConfig cfg = parent_region.region->config();
+ cfg.perimeter_extruder.value = painted_extruder_id;
+ cfg.solid_infill_extruder.value = painted_extruder_id;
+ cfg.infill_extruder.value = painted_extruder_id;
+ layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))});
+ }
+ // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MMU segmentation.
+ std::sort(layer_range.painted_regions.begin(), layer_range.painted_regions.end(), [&layer_range](auto &l, auto &r) {
+ int lid = layer_range.volume_regions[l.parent].region->print_object_region_id();
+ int rid = layer_range.volume_regions[r.parent].region->print_object_region_id();
+ return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); });
+ }
+
+ return out.release();
+}
+
+Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config)
+{
+#ifdef _DEBUG
+ check_model_ids_validity(model);
+#endif /* _DEBUG */
+
+ // Normalize the config.
+ new_full_config.option("print_settings_id", true);
+ new_full_config.option("filament_settings_id", true);
+ new_full_config.option("printer_settings_id", true);
+ new_full_config.option("physical_printer_settings_id", true);
+ new_full_config.normalize_fdm();
+
+ // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
+ DynamicPrintConfig filament_overrides;
+ t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides);
+ t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config);
+ // Collect changes to object and region configs.
+ t_config_option_keys object_diff = m_default_object_config.diff(new_full_config);
+ t_config_option_keys region_diff = m_default_region_config.diff(new_full_config);
+
+ // Do not use the ApplyStatus as we will use the max function when updating apply_status.
+ unsigned int apply_status = APPLY_STATUS_UNCHANGED;
+ auto update_apply_status = [&apply_status](bool invalidated)
+ { apply_status = std::max<unsigned int>(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); };
+ if (! (print_diff.empty() && object_diff.empty() && region_diff.empty()))
+ update_apply_status(false);
+
+ // Grab the lock for the Print / PrintObject milestones.
+ std::scoped_lock<std::mutex> lock(this->state_mutex());
+
+ // The following call may stop the background processing.
+ if (! print_diff.empty())
+ update_apply_status(this->invalidate_state_by_config_options(new_full_config, print_diff));
+
+ // Apply variables to placeholder parser. The placeholder parser is used by G-code export,
+ // which should be stopped if print_diff is not empty.
+ size_t num_extruders = m_config.nozzle_diameter.size();
+ bool num_extruders_changed = false;
+ if (! full_config_diff.empty()) {
+ update_apply_status(this->invalidate_step(psGCodeExport));
+ m_placeholder_parser.clear_config();
+ // Set the profile aliases for the PrintBase::output_filename()
+ m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone());
+ m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone());
+ m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone());
+ m_placeholder_parser.set("physical_printer_preset", new_full_config.option("physical_printer_settings_id")->clone());
+ // We want the filament overrides to be applied over their respective extruder parameters by the PlaceholderParser.
+ // see "Placeholders do not respect filament overrides." GH issue #3649
+ m_placeholder_parser.apply_config(filament_overrides);
+ // It is also safe to change m_config now after this->invalidate_state_by_config_options() call.
+ m_config.apply_only(new_full_config, print_diff, true);
+ //FIXME use move semantics once ConfigBase supports it.
+ // Some filament_overrides may contain values different from new_full_config, but equal to m_config.
+ // As long as these config options don't reallocate memory when copying, we are safe overriding a value, which is in use by a worker thread.
+ m_config.apply(filament_overrides);
+ // Handle changes to object config defaults
+ m_default_object_config.apply_only(new_full_config, object_diff, true);
+ // Handle changes to regions config defaults
+ m_default_region_config.apply_only(new_full_config, region_diff, true);
+ m_full_print_config = std::move(new_full_config);
+ if (num_extruders != m_config.nozzle_diameter.size()) {
+ num_extruders = m_config.nozzle_diameter.size();
+ num_extruders_changed = true;
+ }
+ }
+
+ ModelObjectStatusDB model_object_status_db;
+
+ // 1) Synchronize model objects.
+ bool print_regions_reshuffled = false;
+ if (model.id() != m_model.id()) {
+ // Kill everything, initialize from scratch.
+ // Stop background processing.
+ this->call_cancel_callback();
+ update_apply_status(this->invalidate_all_steps());
+ for (PrintObject *object : m_objects) {
+ model_object_status_db.add(*object->model_object(), ModelObjectStatus::Deleted);
+ update_apply_status(object->invalidate_all_steps());
+ delete object;
+ }
+ m_objects.clear();
+ print_regions_reshuffled = true;
+ m_model.assign_copy(model);
+ for (const ModelObject *model_object : m_model.objects)
+ model_object_status_db.add(*model_object, ModelObjectStatus::New);
+ } else {
+ if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) {
+ update_apply_status(num_extruders_changed ||
+ // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering.
+ //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable
+ // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same.
+ (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ?
+ // The Tool Ordering and the Wipe Tower are no more valid.
+ this->invalidate_steps({ psWipeTower, psGCodeExport }) :
+ // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering.
+ this->invalidate_step(psGCodeExport));
+ m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
+ }
+ if (model_object_list_equal(m_model, model)) {
+ // The object list did not change.
+ for (const ModelObject *model_object : m_model.objects)
+ model_object_status_db.add(*model_object, ModelObjectStatus::Old);
+ } else if (model_object_list_extended(m_model, model)) {
+ // Add new objects. Their volumes and configs will be synchronized later.
+ update_apply_status(this->invalidate_step(psGCodeExport));
+ for (const ModelObject *model_object : m_model.objects)
+ model_object_status_db.add(*model_object, ModelObjectStatus::Old);
+ for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) {
+ model_object_status_db.add(*model.objects[i], ModelObjectStatus::New);
+ m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i]));
+ m_model.objects.back()->set_model(&m_model);
+ }
+ } else {
+ // Reorder the objects, add new objects.
+ // First stop background processing before shuffling or deleting the PrintObjects in the object list.
+ this->call_cancel_callback();
+ update_apply_status(this->invalidate_step(psGCodeExport));
+ // Second create a new list of objects.
+ std::vector<ModelObject*> model_objects_old(std::move(m_model.objects));
+ m_model.objects.clear();
+ m_model.objects.reserve(model.objects.size());
+ auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); };
+ std::sort(model_objects_old.begin(), model_objects_old.end(), by_id_lower);
+ for (const ModelObject *mobj : model.objects) {
+ auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower);
+ if (it == model_objects_old.end() || (*it)->id() != mobj->id()) {
+ // New ModelObject added.
+ m_model.objects.emplace_back(ModelObject::new_copy(*mobj));
+ m_model.objects.back()->set_model(&m_model);
+ model_object_status_db.add(*mobj, ModelObjectStatus::New);
+ } else {
+ // Existing ModelObject re-added (possibly moved in the list).
+ m_model.objects.emplace_back(*it);
+ model_object_status_db.add(*mobj, ModelObjectStatus::Moved);
+ }
+ }
+ bool deleted_any = false;
+ for (ModelObject *&model_object : model_objects_old)
+ if (model_object_status_db.add_if_new(*model_object, ModelObjectStatus::Deleted))
+ deleted_any = true;
+ else
+ // Do not delete this ModelObject instance.
+ model_object = nullptr;
+ if (deleted_any) {
+ // Delete PrintObjects of the deleted ModelObjects.
+ PrintObjectPtrs print_objects_old = std::move(m_objects);
+ m_objects.clear();
+ m_objects.reserve(print_objects_old.size());
+ for (PrintObject *print_object : print_objects_old) {
+ const ModelObjectStatus &status = model_object_status_db.get(*print_object->model_object());
+ if (status.status == ModelObjectStatus::Deleted) {
+ update_apply_status(print_object->invalidate_all_steps());
+ delete print_object;
+ } else
+ m_objects.emplace_back(print_object);
+ }
+ for (ModelObject *model_object : model_objects_old)
+ delete model_object;
+ print_regions_reshuffled = true;
+ }
+ }
+ }
+
+ // 2) Map print objects including their transformation matrices.
+ PrintObjectStatusDB print_object_status_db(m_objects);
+
+ // 3) Synchronize ModelObjects & PrintObjects.
+ const std::initializer_list<ModelVolumeType> solid_or_modifier_types { ModelVolumeType::MODEL_PART, ModelVolumeType::NEGATIVE_VOLUME, ModelVolumeType::PARAMETER_MODIFIER };
+ for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) {
+ ModelObject &model_object = *m_model.objects[idx_model_object];
+ ModelObjectStatus &model_object_status = const_cast<ModelObjectStatus&>(model_object_status_db.reuse(model_object));
+ const ModelObject &model_object_new = *model.objects[idx_model_object];
+ if (model_object_status.status == ModelObjectStatus::New)
+ // PrintObject instances will be added in the next loop.
+ continue;
+ // Update the ModelObject instance, possibly invalidate the linked PrintObjects.
+ assert(model_object_status.status == ModelObjectStatus::Old || model_object_status.status == ModelObjectStatus::Moved);
+ // Check whether a model part volume was added or removed, their transformations or order changed.
+ // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked.
+ bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) ||
+ model_mmu_segmentation_data_changed(model_object, model_object_new) ||
+ (model_object_new.is_mm_painted() && num_extruders_changed);
+ bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) ||
+ model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER);
+ bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty());
+ bool model_origin_translation_differ = model_object.origin_translation != model_object_new.origin_translation;
+ auto print_objects_range = print_object_status_db.get_range(model_object);
+ // The list actually can be empty if all instances are out of the print bed.
+ //assert(print_objects_range.begin() != print_objects_range.end());
+ // All PrintObjects in print_objects_range shall point to the same prints_objects_regions
+ if (print_objects_range.begin() != print_objects_range.end()) {
+ model_object_status.print_object_regions = print_objects_range.begin()->print_object->m_shared_regions;
+ model_object_status.print_object_regions->ref_cnt_inc();
+ }
+ if (solid_or_modifier_differ || model_origin_translation_differ || layer_height_ranges_differ ||
+ ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile)) {
+ // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects.
+ model_object_status.print_object_regions_status =
+ model_object_status.print_object_regions == nullptr || model_origin_translation_differ || layer_height_ranges_differ ?
+ // Drop print_objects_regions.
+ ModelObjectStatus::PrintObjectRegionsStatus::Invalid :
+ // Reuse bounding boxes of print_objects_regions for ModelVolumes with unmodified transformation.
+ ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid;
+ for (const PrintObjectStatus &print_object_status : print_objects_range) {
+ update_apply_status(print_object_status.print_object->invalidate_all_steps());
+ const_cast<PrintObjectStatus&>(print_object_status).status = PrintObjectStatus::Deleted;
+ }
+ if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid)
+ // Drop everything from PrintObjectRegions but those VolumeExtents (of their particular ModelVolumes) that are still valid.
+ print_objects_regions_invalidate_keep_some_volumes(*model_object_status.print_object_regions, model_object.volumes, model_object_new.volumes);
+ else if (model_object_status.print_object_regions != nullptr)
+ model_object_status.print_object_regions->clear();
+ // Copy content of the ModelObject including its ID, do not change the parent.
+ model_object.assign_copy(model_object_new);
+ } else {
+ model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Valid;
+ if (supports_differ || model_custom_supports_data_changed(model_object, model_object_new)) {
+ // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list.
+ if (supports_differ) {
+ this->call_cancel_callback();
+ update_apply_status(false);
+ }
+ // Invalidate just the supports step.
+ for (const PrintObjectStatus &print_object_status : print_objects_range)
+ update_apply_status(print_object_status.print_object->invalidate_step(posSupportMaterial));
+ if (supports_differ) {
+ // Copy just the support volumes.
+ model_volume_list_update_supports(model_object, model_object_new);
+ }
+ } else if (model_custom_seam_data_changed(model_object, model_object_new)) {
+ update_apply_status(this->invalidate_step(psGCodeExport));
+ }
+ }
+ if (! solid_or_modifier_differ) {
+ // Synchronize Object's config.
+ bool object_config_changed = ! model_object.config.timestamp_matches(model_object_new.config);
+ if (object_config_changed)
+ model_object.config.assign_config(model_object_new.config);
+ if (! object_diff.empty() || object_config_changed || num_extruders_changed) {
+ PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders);
+ for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(model_object)) {
+ t_config_option_keys diff = print_object_status.print_object->config().diff(new_config);
+ if (! diff.empty()) {
+ update_apply_status(print_object_status.print_object->invalidate_state_by_config_options(print_object_status.print_object->config(), new_config, diff));
+ print_object_status.print_object->config_apply_only(new_config, diff, true);
+ }
+ }
+ }
+ // Synchronize (just copy) the remaining data of ModelVolumes (name, config, custom supports data).
+ //FIXME What to do with m_material_id?
+ model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART);
+ model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER);
+ layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */);
+ // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step.
+ model_object.name = model_object_new.name;
+ model_object.input_file = model_object_new.input_file;
+ // Only refresh ModelInstances if there is any change.
+ if (model_object.instances.size() != model_object_new.instances.size() ||
+ ! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), [](auto l, auto r){ return l->id() == r->id(); })) {
+ // G-code generator accesses model_object.instances to generate sequential print ordering matching the Plater object list.
+ update_apply_status(this->invalidate_step(psGCodeExport));
+ model_object.clear_instances();
+ model_object.instances.reserve(model_object_new.instances.size());
+ for (const ModelInstance *model_instance : model_object_new.instances) {
+ model_object.instances.emplace_back(new ModelInstance(*model_instance));
+ model_object.instances.back()->set_model_object(&model_object);
+ }
+ } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(),
+ [](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable &&
+ l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) {
+ // If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid.
+ // This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only.
+ model_object.invalidate_bounding_box();
+ // Synchronize the content of instances.
+ auto new_instance = model_object_new.instances.begin();
+ for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) {
+ (*old_instance)->set_transformation((*new_instance)->get_transformation());
+ (*old_instance)->print_volume_state = (*new_instance)->print_volume_state;
+ (*old_instance)->printable = (*new_instance)->printable;
+ }
+ }
+ }
+ }
+
+ // 4) Generate PrintObjects from ModelObjects and their instances.
+ {
+ PrintObjectPtrs print_objects_new;
+ print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size()));
+ bool new_objects = false;
+ // Walk over all new model objects and check, whether there are matching PrintObjects.
+ for (ModelObject *model_object : m_model.objects) {
+ ModelObjectStatus &model_object_status = const_cast<ModelObjectStatus&>(model_object_status_db.reuse(*model_object));
+ model_object_status.print_instances = print_objects_from_model_object(*model_object);
+ std::vector<const PrintObjectStatus*> old;
+ old.reserve(print_object_status_db.count(*model_object));
+ for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object))
+ if (print_object_status.status != PrintObjectStatus::Deleted)
+ old.emplace_back(&print_object_status);
+ // Generate a list of trafos and XY offsets for instances of a ModelObject
+ // Producing the config for PrintObject on demand, caching it at print_object_last.
+ const PrintObject *print_object_last = nullptr;
+ auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders](PrintObject *print_object) {
+ print_object->config_apply(print_object_last ?
+ print_object_last->config() :
+ PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders));
+ print_object_last = print_object;
+ };
+ if (old.empty()) {
+ // Simple case, just generate new instances.
+ for (PrintObjectTrafoAndInstances &print_instances : model_object_status.print_instances) {
+ PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances));
+ print_object_apply_config(print_object);
+ print_objects_new.emplace_back(print_object);
+ // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New));
+ new_objects = true;
+ }
+ continue;
+ }
+ // Complex case, try to merge the two lists.
+ // Sort the old lexicographically by their trafos.
+ std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); });
+ // Merge the old / new lists.
+ auto it_old = old.begin();
+ for (PrintObjectTrafoAndInstances &new_instances : model_object_status.print_instances) {
+ for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old);
+ if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) {
+ // This is a new instance (or a set of instances with the same trafo). Just add it.
+ PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances));
+ print_object_apply_config(print_object);
+ print_objects_new.emplace_back(print_object);
+ // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New));
+ new_objects = true;
+ if (it_old != old.end())
+ const_cast<PrintObjectStatus*>(*it_old)->status = PrintObjectStatus::Deleted;
+ } else {
+ // The PrintObject already exists and the copies differ.
+ PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances));
+ if (status != PrintBase::APPLY_STATUS_UNCHANGED)
+ update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED);
+ print_objects_new.emplace_back((*it_old)->print_object);
+ const_cast<PrintObjectStatus*>(*it_old)->status = PrintObjectStatus::Reused;
+ }
+ }
+ }
+ if (m_objects != print_objects_new) {
+ this->call_cancel_callback();
+ update_apply_status(this->invalidate_all_steps());
+ m_objects = print_objects_new;
+ // Delete the PrintObjects marked as Unknown or Deleted.
+ bool deleted_objects = false;
+ for (const PrintObjectStatus &pos : print_object_status_db)
+ if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) {
+ update_apply_status(pos.print_object->invalidate_all_steps());
+ delete pos.print_object;
+ deleted_objects = true;
+ }
+ if (new_objects || deleted_objects)
+ update_apply_status(this->invalidate_steps({ psSkirtBrim, psWipeTower, psGCodeExport }));
+ if (new_objects)
+ update_apply_status(false);
+ print_regions_reshuffled = true;
+ }
+ print_object_status_db.clear();
+ }
+
+ // All regions now have distinct settings.
+ // Check whether applying the new region config defaults we would get different regions,
+ // update regions or create regions from scratch.
+ for (auto it_print_object = m_objects.begin(); it_print_object != m_objects.end();) {
+ // Find the range of PrintObjects sharing the same associated ModelObject.
+ auto it_print_object_end = it_print_object;
+ PrintObject &print_object = *(*it_print_object);
+ const ModelObject &model_object = *print_object.model_object();
+ ModelObjectStatus &model_object_status = const_cast<ModelObjectStatus&>(model_object_status_db.reuse(model_object));
+ PrintObjectRegions *print_object_regions = model_object_status.print_object_regions;
+ for (++ it_print_object_end; it_print_object_end != m_objects.end() && (*it_print_object)->model_object() == (*it_print_object_end)->model_object(); ++ it_print_object_end)
+ assert((*it_print_object_end)->m_shared_regions == nullptr || (*it_print_object_end)->m_shared_regions == print_object_regions);
+ if (print_object_regions == nullptr) {
+ print_object_regions = new PrintObjectRegions{};
+ model_object_status.print_object_regions = print_object_regions;
+ print_object_regions->ref_cnt_inc();
+ }
+ std::vector<unsigned int> painting_extruders;
+ if (const auto &volumes = print_object.model_object()->volumes;
+ num_extruders > 1 &&
+ std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
+ //FIXME be more specific! Don't enumerate extruders that are not used for painting!
+ painting_extruders.assign(num_extruders, 0);
+ std::iota(painting_extruders.begin(), painting_extruders.end(), 1);
+ }
+ if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) {
+ // Verify that the trafo for regions & volume bounding boxes thus for regions is still applicable.
+ auto invalidate = [it_print_object, it_print_object_end, update_apply_status]() {
+ for (auto it = it_print_object; it != it_print_object_end; ++ it)
+ if ((*it)->m_shared_regions != nullptr)
+ update_apply_status((*it)->invalidate_all_steps());
+ };
+ if (print_object_regions && ! trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(print_object_regions->trafo_bboxes, model_object_status.print_instances.front().trafo)) {
+ invalidate();
+ print_object_regions->clear();
+ model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Invalid;
+ print_regions_reshuffled = true;
+ } else if (print_object_regions &&
+ verify_update_print_object_regions(
+ print_object.model_object()->volumes,
+ m_default_region_config,
+ num_extruders,
+ painting_extruders,
+ *print_object_regions,
+ [it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) {
+ for (auto it = it_print_object; it != it_print_object_end; ++it)
+ if ((*it)->m_shared_regions != nullptr)
+ update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys));
+ })) {
+ // Regions are valid, just keep them.
+ } else {
+ // Regions were reshuffled.
+ invalidate();
+ // At least reuse layer ranges and bounding boxes of ModelVolumes.
+ model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid;
+ print_regions_reshuffled = true;
+ }
+ }
+ if (print_object_regions == nullptr || model_object_status.print_object_regions_status != ModelObjectStatus::PrintObjectRegionsStatus::Valid) {
+ // Layer ranges with their associated configurations. Remove overlaps between the ranges
+ // and create the regions from scratch.
+ print_object_regions = generate_print_object_regions(
+ print_object_regions,
+ print_object.model_object()->volumes,
+ LayerRanges(print_object.model_object()->layer_config_ranges),
+ m_default_region_config,
+ model_object_status.print_instances.front().trafo,
+ num_extruders,
+ print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value),
+ painting_extruders);
+ }
+ for (auto it = it_print_object; it != it_print_object_end; ++it)
+ if ((*it)->m_shared_regions) {
+ assert((*it)->m_shared_regions == print_object_regions);
+ } else {
+ (*it)->m_shared_regions = print_object_regions;
+ print_object_regions->ref_cnt_inc();
+ }
+ it_print_object = it_print_object_end;
+ }
+
+ if (print_regions_reshuffled) {
+ // Update Print::m_print_regions from objects.
+ struct cmp { bool operator() (const PrintRegion *l, const PrintRegion *r) const { return l->config_hash() == r->config_hash() && l->config() == r->config(); } };
+ std::set<const PrintRegion*, cmp> region_set;
+ m_print_regions.clear();
+ PrintObjectRegions *print_object_regions = nullptr;
+ for (PrintObject *print_object : m_objects)
+ if (print_object_regions != print_object->m_shared_regions) {
+ print_object_regions = print_object->m_shared_regions;
+ for (std::unique_ptr<Slic3r::PrintRegion> &print_region : print_object_regions->all_regions)
+ if (auto it = region_set.find(print_region.get()); it == region_set.end()) {
+ int print_region_id = int(m_print_regions.size());
+ m_print_regions.emplace_back(print_region.get());
+ print_region->m_print_region_id = print_region_id;
+ } else {
+ print_region->m_print_region_id = (*it)->print_region_id();
+ }
+ }
+ }
+
+ // Update SlicingParameters for each object where the SlicingParameters is not valid.
+ // If it is not valid, then it is ensured that PrintObject.m_slicing_params is not in use
+ // (posSlicing and posSupportMaterial was invalidated).
+ for (PrintObject *object : m_objects)
+ object->update_slicing_parameters();
+
+#ifdef _DEBUG
+ check_model_ids_equal(m_model, model);
+#endif /* _DEBUG */
+
+ return static_cast<ApplyStatus>(apply_status);
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index 16b2fda0e..302c62879 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -69,6 +69,8 @@
#define ENABLE_SHOW_TOOLPATHS_COG (1 && ENABLE_2_5_0_ALPHA1)
// Enable recalculating toolpaths when switching to/from volumetric rate visualization
#define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1)
+// Enable editing volumes transformation in world coordinates and instances in local coordinates
+#define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1)
// Enable modified camera control using mouse
#define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1)
// Enable modified rectangle selection
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 78e73ba9a..29b8b7e73 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -85,6 +85,8 @@ set(SLIC3R_GUI_SOURCES
GUI/GUI_App.hpp
GUI/GUI_Utils.cpp
GUI/GUI_Utils.hpp
+ GUI/GUI_Geometry.cpp
+ GUI/GUI_Geometry.hpp
GUI/I18N.cpp
GUI/I18N.hpp
GUI/MainFrame.cpp
@@ -129,6 +131,8 @@ set(SLIC3R_GUI_SOURCES
GUI/2DBed.hpp
GUI/3DBed.cpp
GUI/3DBed.hpp
+ GUI/CoordAxes.cpp
+ GUI/CoordAxes.hpp
GUI/Camera.cpp
GUI/Camera.hpp
GUI/wxExtensions.cpp
diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp
index 1ce1af741..503c35a9b 100644
--- a/src/slic3r/GUI/3DBed.cpp
+++ b/src/slic3r/GUI/3DBed.cpp
@@ -102,6 +102,7 @@ const float* GeometryBuffer::get_vertices_data() const
}
#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
+#if !ENABLE_WORLD_COORDINATE
const float Bed3D::Axes::DefaultStemRadius = 0.5f;
const float Bed3D::Axes::DefaultStemLength = 25.0f;
const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius;
@@ -179,6 +180,7 @@ void Bed3D::Axes::render()
glsafe(::glDisable(GL_DEPTH_TEST));
}
+#endif // !ENABLE_WORLD_COORDINATE
bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom)
{
@@ -341,7 +343,11 @@ BoundingBoxf3 Bed3D::calc_extended_bounding_box() const
out.max.z() = 0.0;
// extend to contain axes
out.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones());
+#if ENABLE_WORLD_COORDINATE
+ out.merge(out.min + Vec3d(-m_axes.get_tip_radius(), -m_axes.get_tip_radius(), out.max.z()));
+#else
out.merge(out.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, out.max.z()));
+#endif // ENABLE_WORLD_COORDINATE
// extend to contain model, if any
BoundingBoxf3 model_bb = m_model.get_bounding_box();
if (model_bb.defined) {
@@ -539,7 +545,15 @@ std::tuple<Bed3D::Type, std::string, std::string> Bed3D::detect_type(const Point
void Bed3D::render_axes()
{
if (m_build_volume.valid())
+#if ENABLE_WORLD_COORDINATE
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ m_axes.render(Transform3d::Identity(), 0.25f);
+#else
+ m_axes.render(0.25f);
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#else
m_axes.render();
+#endif // ENABLE_WORLD_COORDINATE
}
#if ENABLE_GL_SHADERS_ATTRIBUTES
diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp
index 708d186a4..3c48a3901 100644
--- a/src/slic3r/GUI/3DBed.hpp
+++ b/src/slic3r/GUI/3DBed.hpp
@@ -3,7 +3,11 @@
#include "GLTexture.hpp"
#include "3DScene.hpp"
+#if ENABLE_WORLD_COORDINATE
+#include "CoordAxes.hpp"
+#else
#include "GLModel.hpp"
+#endif // ENABLE_WORLD_COORDINATE
#include "libslic3r/BuildVolume.hpp"
#if ENABLE_LEGACY_OPENGL_REMOVAL
@@ -44,6 +48,7 @@ public:
class Bed3D
{
+#if !ENABLE_WORLD_COORDINATE
class Axes
{
public:
@@ -67,6 +72,7 @@ class Bed3D
float get_total_length() const { return m_stem_length + DefaultTipLength; }
void render();
};
+#endif // !ENABLE_WORLD_COORDINATE
public:
enum class Type : unsigned char
@@ -107,7 +113,11 @@ private:
#if !ENABLE_LEGACY_OPENGL_REMOVAL
unsigned int m_vbo_id{ 0 };
#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_WORLD_COORDINATE
+ CoordAxes m_axes;
+#else
Axes m_axes;
+#endif // ENABLE_WORLD_COORDINATE
float m_scale_factor{ 1.0f };
diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp
index a85b8092a..4ef095223 100644
--- a/src/slic3r/GUI/3DScene.hpp
+++ b/src/slic3r/GUI/3DScene.hpp
@@ -1,783 +1,813 @@
-#ifndef slic3r_3DScene_hpp_
-#define slic3r_3DScene_hpp_
-
-#include "libslic3r/libslic3r.h"
-#include "libslic3r/Point.hpp"
-#include "libslic3r/Line.hpp"
-#include "libslic3r/TriangleMesh.hpp"
-#include "libslic3r/Utils.hpp"
-#include "libslic3r/Geometry.hpp"
-#include "libslic3r/Color.hpp"
-
-#include "GLModel.hpp"
-
-#include <functional>
-#include <optional>
-
-#ifndef NDEBUG
-#define HAS_GLSAFE
-#endif // NDEBUG
-
-#ifdef HAS_GLSAFE
- extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
- inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
- #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
- #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
-#else // HAS_GLSAFE
- inline void glAssertRecentCall() { }
- #define glsafe(cmd) cmd
- #define glcheck()
-#endif // HAS_GLSAFE
-
-namespace Slic3r {
-class SLAPrintObject;
-enum SLAPrintObjectStep : unsigned int;
-class BuildVolume;
-class DynamicPrintConfig;
-class ExtrusionPath;
-class ExtrusionMultiPath;
-class ExtrusionLoop;
-class ExtrusionEntity;
-class ExtrusionEntityCollection;
-class ModelObject;
-class ModelVolume;
-enum ModelInstanceEPrintVolumeState : unsigned char;
-
-// Return appropriate color based on the ModelVolume.
-extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume);
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
-// A container for interleaved arrays of 3D vertices and normals,
-// possibly indexed by triangles and / or quads.
-class GLIndexedVertexArray {
-public:
- // Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized.
- static_assert(sizeof(Eigen::AlignedBox<float, 3>) == 24, "Eigen::AlignedBox<float, 3> is not being vectorized, thus it does not need to be aligned");
- using BoundingBox = Eigen::AlignedBox<float, 3>;
-
- GLIndexedVertexArray() { m_bounding_box.setEmpty(); }
- GLIndexedVertexArray(const GLIndexedVertexArray &rhs) :
- vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved),
- triangle_indices(rhs.triangle_indices),
- quad_indices(rhs.quad_indices),
- m_bounding_box(rhs.m_bounding_box)
- { assert(! rhs.has_VBOs()); m_bounding_box.setEmpty(); }
- GLIndexedVertexArray(GLIndexedVertexArray &&rhs) :
- vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)),
- triangle_indices(std::move(rhs.triangle_indices)),
- quad_indices(std::move(rhs.quad_indices)),
- m_bounding_box(rhs.m_bounding_box)
- { assert(! rhs.has_VBOs()); }
-
- ~GLIndexedVertexArray() { release_geometry(); }
-
- GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs)
- {
- assert(vertices_and_normals_interleaved_VBO_id == 0);
- assert(triangle_indices_VBO_id == 0);
- assert(quad_indices_VBO_id == 0);
- assert(rhs.vertices_and_normals_interleaved_VBO_id == 0);
- assert(rhs.triangle_indices_VBO_id == 0);
- assert(rhs.quad_indices_VBO_id == 0);
- this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved;
- this->triangle_indices = rhs.triangle_indices;
- this->quad_indices = rhs.quad_indices;
- this->m_bounding_box = rhs.m_bounding_box;
- this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size;
- this->triangle_indices_size = rhs.triangle_indices_size;
- this->quad_indices_size = rhs.quad_indices_size;
- return *this;
- }
-
- GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs)
- {
- assert(vertices_and_normals_interleaved_VBO_id == 0);
- assert(triangle_indices_VBO_id == 0);
- assert(quad_indices_VBO_id == 0);
- assert(rhs.vertices_and_normals_interleaved_VBO_id == 0);
- assert(rhs.triangle_indices_VBO_id == 0);
- assert(rhs.quad_indices_VBO_id == 0);
- this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved);
- this->triangle_indices = std::move(rhs.triangle_indices);
- this->quad_indices = std::move(rhs.quad_indices);
- this->m_bounding_box = rhs.m_bounding_box;
- this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size;
- this->triangle_indices_size = rhs.triangle_indices_size;
- this->quad_indices_size = rhs.quad_indices_size;
- return *this;
- }
-
- // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x)
- std::vector<float> vertices_and_normals_interleaved;
- std::vector<int> triangle_indices;
- std::vector<int> quad_indices;
-
- // When the geometry data is loaded into the graphics card as Vertex Buffer Objects,
- // the above mentioned std::vectors are cleared and the following variables keep their original length.
- size_t vertices_and_normals_interleaved_size{ 0 };
- size_t triangle_indices_size{ 0 };
- size_t quad_indices_size{ 0 };
-
- // IDs of the Vertex Array Objects, into which the geometry has been loaded.
- // Zero if the VBOs are not sent to GPU yet.
- unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
- unsigned int triangle_indices_VBO_id{ 0 };
- unsigned int quad_indices_VBO_id{ 0 };
-
-#if ENABLE_SMOOTH_NORMALS
- void load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals = false);
- void load_mesh(const TriangleMesh& mesh, bool smooth_normals = false) { this->load_mesh_full_shading(mesh, smooth_normals); }
-#else
- void load_mesh_full_shading(const TriangleMesh& mesh);
- void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); }
-#endif // ENABLE_SMOOTH_NORMALS
-
- void load_its_flat_shading(const indexed_triangle_set &its);
-
- inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; }
-
- inline void reserve(size_t sz) {
- this->vertices_and_normals_interleaved.reserve(sz * 6);
- this->triangle_indices.reserve(sz * 3);
- this->quad_indices.reserve(sz * 4);
- }
-
- inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) {
- assert(this->vertices_and_normals_interleaved_VBO_id == 0);
- if (this->vertices_and_normals_interleaved_VBO_id != 0)
- return;
-
- if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
- this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
- this->vertices_and_normals_interleaved.emplace_back(nx);
- this->vertices_and_normals_interleaved.emplace_back(ny);
- this->vertices_and_normals_interleaved.emplace_back(nz);
- this->vertices_and_normals_interleaved.emplace_back(x);
- this->vertices_and_normals_interleaved.emplace_back(y);
- this->vertices_and_normals_interleaved.emplace_back(z);
-
- this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
- m_bounding_box.extend(Vec3f(x, y, z));
- };
-
- inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) {
- push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
- }
-
- template<typename Derived, typename Derived2>
- inline void push_geometry(const Eigen::MatrixBase<Derived>& p, const Eigen::MatrixBase<Derived2>& n) {
- push_geometry(float(p(0)), float(p(1)), float(p(2)), float(n(0)), float(n(1)), float(n(2)));
- }
-
- inline void push_triangle(int idx1, int idx2, int idx3) {
- assert(this->vertices_and_normals_interleaved_VBO_id == 0);
- if (this->vertices_and_normals_interleaved_VBO_id != 0)
- return;
-
- if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
- this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
- this->triangle_indices.emplace_back(idx1);
- this->triangle_indices.emplace_back(idx2);
- this->triangle_indices.emplace_back(idx3);
- this->triangle_indices_size = this->triangle_indices.size();
- };
-
- inline void push_quad(int idx1, int idx2, int idx3, int idx4) {
- assert(this->vertices_and_normals_interleaved_VBO_id == 0);
- if (this->vertices_and_normals_interleaved_VBO_id != 0)
- return;
-
- if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity())
- this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4));
- this->quad_indices.emplace_back(idx1);
- this->quad_indices.emplace_back(idx2);
- this->quad_indices.emplace_back(idx3);
- this->quad_indices.emplace_back(idx4);
- this->quad_indices_size = this->quad_indices.size();
- };
-
- // Finalize the initialization of the geometry & indices,
- // upload the geometry and indices to OpenGL VBO objects
- // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
- void finalize_geometry(bool opengl_initialized);
- // Release the geometry data, release OpenGL VBOs.
- void release_geometry();
-
- void render() const;
- void render(const std::pair<size_t, size_t>& tverts_range, const std::pair<size_t, size_t>& qverts_range) const;
-
- // Is there any geometry data stored?
- bool empty() const { return vertices_and_normals_interleaved_size == 0; }
-
- void clear() {
- this->vertices_and_normals_interleaved.clear();
- this->triangle_indices.clear();
- this->quad_indices.clear();
- vertices_and_normals_interleaved_size = 0;
- triangle_indices_size = 0;
- quad_indices_size = 0;
- m_bounding_box.setEmpty();
- }
-
- // Shrink the internal storage to tighly fit the data stored.
- void shrink_to_fit() {
- this->vertices_and_normals_interleaved.shrink_to_fit();
- this->triangle_indices.shrink_to_fit();
- this->quad_indices.shrink_to_fit();
- }
-
- const BoundingBox& bounding_box() const { return m_bounding_box; }
-
- // Return an estimate of the memory consumed by this class.
- size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); }
- // Return an estimate of the memory held by GPU vertex buffers.
- size_t gpu_memory_used() const
- {
- size_t memsize = 0;
- if (this->vertices_and_normals_interleaved_VBO_id != 0)
- memsize += this->vertices_and_normals_interleaved_size * 4;
- if (this->triangle_indices_VBO_id != 0)
- memsize += this->triangle_indices_size * 4;
- if (this->quad_indices_VBO_id != 0)
- memsize += this->quad_indices_size * 4;
- return memsize;
- }
- size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
-
-private:
- BoundingBox m_bounding_box;
-};
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
-class GLVolume {
-public:
- static const ColorRGBA SELECTED_COLOR;
- static const ColorRGBA HOVER_SELECT_COLOR;
- static const ColorRGBA HOVER_DESELECT_COLOR;
- static const ColorRGBA OUTSIDE_COLOR;
- static const ColorRGBA SELECTED_OUTSIDE_COLOR;
- static const ColorRGBA DISABLED_COLOR;
- static const ColorRGBA SLA_SUPPORT_COLOR;
- static const ColorRGBA SLA_PAD_COLOR;
- static const ColorRGBA NEUTRAL_COLOR;
- static const std::array<ColorRGBA, 4> MODEL_COLOR;
-
- enum EHoverState : unsigned char
- {
- HS_None,
- HS_Hover,
- HS_Select,
- HS_Deselect
- };
-
- GLVolume(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f);
- GLVolume(const ColorRGBA& color) : GLVolume(color.r(), color.g(), color.b(), color.a()) {}
-
-private:
- Geometry::Transformation m_instance_transformation;
- Geometry::Transformation m_volume_transformation;
-
- // Shift in z required by sla supports+pad
- double m_sla_shift_z;
- // Bounding box of this volume, in unscaled coordinates.
- std::optional<BoundingBoxf3> m_transformed_bounding_box;
- // Convex hull of the volume, if any.
- std::shared_ptr<const TriangleMesh> m_convex_hull;
- // Bounding box of this volume, in unscaled coordinates.
- std::optional<BoundingBoxf3> m_transformed_convex_hull_bounding_box;
- // Bounding box of the non sinking part of this volume, in unscaled coordinates.
- std::optional<BoundingBoxf3> m_transformed_non_sinking_bounding_box;
-
- class SinkingContours
- {
- static const float HalfWidth;
- GLVolume& m_parent;
- GUI::GLModel m_model;
- BoundingBoxf3 m_old_box;
- Vec3d m_shift{ Vec3d::Zero() };
-
- public:
- SinkingContours(GLVolume& volume) : m_parent(volume) {}
- void render();
-
- private:
- void update();
- };
-
- SinkingContours m_sinking_contours;
-
-#if ENABLE_SHOW_NON_MANIFOLD_EDGES
- class NonManifoldEdges
- {
- GLVolume& m_parent;
- GUI::GLModel m_model;
- bool m_update_needed{ true };
-
- public:
- NonManifoldEdges(GLVolume& volume) : m_parent(volume) {}
- void render();
- void set_as_dirty() { m_update_needed = true; }
-
- private:
- void update();
- };
-
- NonManifoldEdges m_non_manifold_edges;
-#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
-
-public:
- // Color of the triangles / quads held by this volume.
- ColorRGBA color;
- // Color used to render this volume.
- ColorRGBA render_color;
-
- struct CompositeID {
- CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {}
- CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {}
- // Object ID, which is equal to the index of the respective ModelObject in Model.objects array.
- int object_id;
- // Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array.
- // If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject,
- // and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports.
- // Volume with a negative volume_id cannot be picked independently, it will pick the associated instance.
- int volume_id;
- // Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array.
- int instance_id;
- bool operator==(const CompositeID &rhs) const { return object_id == rhs.object_id && volume_id == rhs.volume_id && instance_id == rhs.instance_id; }
- bool operator!=(const CompositeID &rhs) const { return ! (*this == rhs); }
- bool operator< (const CompositeID &rhs) const
- { return object_id < rhs.object_id || (object_id == rhs.object_id && (volume_id < rhs.volume_id || (volume_id == rhs.volume_id && instance_id < rhs.instance_id))); }
- };
- CompositeID composite_id;
- // Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID,
- // for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(),
- // and the associated ModelInstanceID.
- // Valid geometry_id should always be positive.
- std::pair<size_t, size_t> geometry_id;
- // An ID containing the extruder ID (used to select color).
- int extruder_id;
-
- // Various boolean flags.
- struct {
- // Is this object selected?
- bool selected : 1;
- // Is this object disabled from selection?
- bool disabled : 1;
- // Is this object printable?
- bool printable : 1;
- // Whether or not this volume is active for rendering
- bool is_active : 1;
- // Whether or not to use this volume when applying zoom_to_volumes()
- bool zoom_to_volumes : 1;
- // Wheter or not this volume is enabled for outside print volume detection in shader.
- bool shader_outside_printer_detection_enabled : 1;
- // Wheter or not this volume is outside print volume.
- bool is_outside : 1;
- // Wheter or not this volume has been generated from a modifier
- bool is_modifier : 1;
- // Wheter or not this volume has been generated from the wipe tower
- bool is_wipe_tower : 1;
- // Wheter or not this volume has been generated from an extrusion path
- bool is_extrusion_path : 1;
- // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE)
- bool force_native_color : 1;
- // Whether or not render this volume in neutral
- bool force_neutral_color : 1;
- // Whether or not to force rendering of sinking contours
- bool force_sinking_contours : 1;
- };
-
- // Is mouse or rectangle selection over this object to select/deselect it ?
- EHoverState hover;
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GUI::GLModel model;
-#else
- // Interleaved triangles & normals with indexed triangles & quads.
- GLIndexedVertexArray indexed_vertex_array;
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- // Ranges of triangle and quad indices to be rendered.
- std::pair<size_t, size_t> tverts_range;
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- std::pair<size_t, size_t> qverts_range;
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
- // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts
- // of the extrusions per layer.
- std::vector<coordf_t> print_zs;
- // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
- std::vector<size_t> offsets;
-
- // Bounding box of this volume, in unscaled coordinates.
- BoundingBoxf3 bounding_box() const {
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- return this->model.get_bounding_box();
-#else
- BoundingBoxf3 out;
- if (!this->indexed_vertex_array.bounding_box().isEmpty()) {
- out.min = this->indexed_vertex_array.bounding_box().min().cast<double>();
- out.max = this->indexed_vertex_array.bounding_box().max().cast<double>();
- out.defined = true;
- }
- return out;
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- }
-
- void set_color(const ColorRGBA& rgba) { color = rgba; }
- void set_render_color(const ColorRGBA& rgba) { render_color = rgba; }
- // Sets render color in dependence of current state
- void set_render_color(bool force_transparent);
- // set color according to model volume
- void set_color_from_model_volume(const ModelVolume& model_volume);
-
- const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; }
- void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); }
-
- const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); }
- double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); }
-
- void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); }
- void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); }
-
- const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); }
- double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); }
-
- void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); }
- void set_instance_rotation(Axis axis, double rotation) { m_instance_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); }
-
- Vec3d get_instance_scaling_factor() const { return m_instance_transformation.get_scaling_factor(); }
- double get_instance_scaling_factor(Axis axis) const { return m_instance_transformation.get_scaling_factor(axis); }
-
- void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); }
- void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); }
-
- const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); }
- double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); }
-
- void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); }
- void set_instance_mirror(Axis axis, double mirror) { m_instance_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); }
-
- const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; }
- void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); }
-
- const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); }
- double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); }
-
- void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); }
- void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); }
-
- const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); }
- double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); }
-
- void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); }
- void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); }
-
- const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); }
- double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); }
-
- void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); }
- void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); }
-
- const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); }
- double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); }
-
- void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); }
- void set_volume_mirror(Axis axis, double mirror) { m_volume_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); }
-
- double get_sla_shift_z() const { return m_sla_shift_z; }
- void set_sla_shift_z(double z) { m_sla_shift_z = z; }
-
- void set_convex_hull(std::shared_ptr<const TriangleMesh> convex_hull) { m_convex_hull = std::move(convex_hull); }
- void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(convex_hull); }
- void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(std::move(convex_hull)); }
-
- int object_idx() const { return this->composite_id.object_id; }
- int volume_idx() const { return this->composite_id.volume_id; }
- int instance_idx() const { return this->composite_id.instance_id; }
-
- Transform3d world_matrix() const;
- bool is_left_handed() const;
-
- const BoundingBoxf3& transformed_bounding_box() const;
- // non-caching variant
- BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const;
- // caching variant
- const BoundingBoxf3& transformed_convex_hull_bounding_box() const;
- // non-caching variant
- BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const;
- // caching variant
- const BoundingBoxf3& transformed_non_sinking_bounding_box() const;
- // convex hull
- const TriangleMesh* convex_hull() const { return m_convex_hull.get(); }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- bool empty() const { return this->model.is_empty(); }
-#else
- bool empty() const { return this->indexed_vertex_array.empty(); }
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- void set_range(double low, double high);
-
- void render();
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); }
- void release_geometry() { this->indexed_vertex_array.release_geometry(); }
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
- void set_bounding_boxes_as_dirty() {
- m_transformed_bounding_box.reset();
- m_transformed_convex_hull_bounding_box.reset();
- m_transformed_non_sinking_bounding_box.reset();
- }
-
- bool is_sla_support() const;
- bool is_sla_pad() const;
-
- bool is_sinking() const;
- bool is_below_printbed() const;
- void render_sinking_contours();
-#if ENABLE_SHOW_NON_MANIFOLD_EDGES
- void render_non_manifold_edges();
-#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
-
- // Return an estimate of the memory consumed by this class.
- size_t cpu_memory_used() const {
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- return sizeof(*this) + this->model.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) +
- this->offsets.capacity() * sizeof(size_t);
- }
- // Return an estimate of the memory held by GPU vertex buffers.
- size_t gpu_memory_used() const { return this->model.gpu_memory_used(); }
-#else
- //FIXME what to do wih m_convex_hull?
- return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t);
- }
- // Return an estimate of the memory held by GPU vertex buffers.
- size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); }
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
-};
-
-typedef std::vector<GLVolume*> GLVolumePtrs;
-typedef std::pair<GLVolume*, std::pair<unsigned int, double>> GLVolumeWithIdAndZ;
-typedef std::vector<GLVolumeWithIdAndZ> GLVolumeWithIdAndZList;
-
-class GLVolumeCollection
-{
-public:
- enum class ERenderType : unsigned char
- {
- Opaque,
- Transparent,
- All
- };
-
- struct PrintVolume
- {
- // see: Bed3D::EShapeType
- int type{ 0 };
- // data contains:
- // Rectangle:
- // [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y
- // Circle:
- // [0] = center.x, [1] = center.y, [3] = radius
- std::array<float, 4> data;
- // [0] = min z, [1] = max z
- std::array<float, 2> zs;
- };
-
-private:
- PrintVolume m_print_volume;
-
- // z range for clipping in shaders
- std::array<float, 2> m_z_range;
-
- // plane coeffs for clipping in shaders
- std::array<double, 4> m_clipping_plane;
-
- struct Slope
- {
- // toggle for slope rendering
- bool active{ false };
- float normal_z;
- };
-
- Slope m_slope;
- bool m_show_sinking_contours{ false };
-#if ENABLE_SHOW_NON_MANIFOLD_EDGES
- bool m_show_non_manifold_edges{ true };
-#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
-
-public:
- GLVolumePtrs volumes;
-
- GLVolumeCollection() { set_default_slope_normal_z(); }
- ~GLVolumeCollection() { clear(); }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- std::vector<int> load_object(
- const ModelObject* model_object,
- int obj_idx,
- const std::vector<int>& instance_idxs);
-
- int load_object_volume(
- const ModelObject* model_object,
- int obj_idx,
- int volume_idx,
- int instance_idx);
-
- // Load SLA auxiliary GLVolumes (for support trees or pad).
- void load_object_auxiliary(
- const SLAPrintObject* print_object,
- int obj_idx,
- // pairs of <instance_idx, print_instance_idx>
- const std::vector<std::pair<size_t, size_t>>& instances,
- SLAPrintObjectStep milestone,
- // Timestamp of the last change of the milestone
- size_t timestamp);
-
-#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
- int load_wipe_tower_preview(
- float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width);
-#else
- int load_wipe_tower_preview(
- int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width);
-#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
-#else
- std::vector<int> load_object(
- const ModelObject *model_object,
- int obj_idx,
- const std::vector<int> &instance_idxs,
- bool opengl_initialized);
-
- int load_object_volume(
- const ModelObject *model_object,
- int obj_idx,
- int volume_idx,
- int instance_idx,
- bool opengl_initialized);
-
- // Load SLA auxiliary GLVolumes (for support trees or pad).
- void load_object_auxiliary(
- const SLAPrintObject *print_object,
- int obj_idx,
- // pairs of <instance_idx, print_instance_idx>
- const std::vector<std::pair<size_t, size_t>>& instances,
- SLAPrintObjectStep milestone,
- // Timestamp of the last change of the milestone
- size_t timestamp,
- bool opengl_initialized);
-
-#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
- int load_wipe_tower_preview(
- float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized);
-#else
- int load_wipe_tower_preview(
- int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized);
-#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLVolume* new_toolpath_volume(const ColorRGBA& rgba);
- GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba);
-#else
- GLVolume* new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0);
- GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- // Render the volumes by OpenGL.
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, const Transform3d& projection_matrix,
- std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const;
-#else
- void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const;
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- // Finalize the initialization of the geometry & indices,
- // upload the geometry and indices to OpenGL VBO objects
- // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
- void finalize_geometry(bool opengl_initialized) { for (auto* v : volumes) v->finalize_geometry(opengl_initialized); }
- // Release the geometry data assigned to the volumes.
- // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them.
- void release_geometry() { for (auto *v : volumes) v->release_geometry(); }
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
- // Clear the geometry
- void clear() { for (auto *v : volumes) delete v; volumes.clear(); }
-
- bool empty() const { return volumes.empty(); }
- void set_range(double low, double high) { for (GLVolume* vol : this->volumes) vol->set_range(low, high); }
-
- void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; }
-
- void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; }
- void set_clipping_plane(const std::array<double, 4>& coeffs) { m_clipping_plane = coeffs; }
-
- const std::array<float, 2>& get_z_range() const { return m_z_range; }
- const std::array<double, 4>& get_clipping_plane() const { return m_clipping_plane; }
-
- bool is_slope_active() const { return m_slope.active; }
- void set_slope_active(bool active) { m_slope.active = active; }
-
- float get_slope_normal_z() const { return m_slope.normal_z; }
- void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; }
- void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); }
- void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; }
-#if ENABLE_SHOW_NON_MANIFOLD_EDGES
- void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; }
-#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
-
- // returns true if all the volumes are completely contained in the print volume
- // returns the containment state in the given out_state, if non-null
- bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const;
- void reset_outside_state();
-
- void update_colors_by_extruder(const DynamicPrintConfig* config);
-
- // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection
- std::vector<double> get_current_print_zs(bool active_only) const;
-
- // Return an estimate of the memory consumed by this class.
- size_t cpu_memory_used() const;
- // Return an estimate of the memory held by GPU vertex buffers.
- size_t gpu_memory_used() const;
- size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
- // Return CPU, GPU and total memory log line.
- std::string log_memory_info() const;
-
-private:
- GLVolumeCollection(const GLVolumeCollection &other);
- GLVolumeCollection& operator=(const GLVolumeCollection &);
-};
-
-GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = nullptr);
-
-struct _3DScene
-{
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GUI::GLModel::Geometry& geometry);
- static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GUI::GLModel::Geometry& geometry);
- static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
- static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
- static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
- static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
- static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
-#else
- static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
- static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
- static void extrusionentity_to_verts(const Polyline& polyline, float width, float height, float print_z, GLVolume& volume);
- static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
- static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
- static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
- static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume);
- static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume);
- static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
- static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
- static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-};
-
-}
-
-#endif
+#ifndef slic3r_3DScene_hpp_
+#define slic3r_3DScene_hpp_
+
+#include "libslic3r/libslic3r.h"
+#include "libslic3r/Point.hpp"
+#include "libslic3r/Line.hpp"
+#include "libslic3r/TriangleMesh.hpp"
+#include "libslic3r/Utils.hpp"
+#include "libslic3r/Geometry.hpp"
+#include "libslic3r/Color.hpp"
+
+#include "GLModel.hpp"
+
+#include <functional>
+#include <optional>
+
+#ifndef NDEBUG
+#define HAS_GLSAFE
+#endif // NDEBUG
+
+#ifdef HAS_GLSAFE
+ extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
+ inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
+ #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
+ #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
+#else // HAS_GLSAFE
+ inline void glAssertRecentCall() { }
+ #define glsafe(cmd) cmd
+ #define glcheck()
+#endif // HAS_GLSAFE
+
+namespace Slic3r {
+class SLAPrintObject;
+enum SLAPrintObjectStep : unsigned int;
+class BuildVolume;
+class DynamicPrintConfig;
+class ExtrusionPath;
+class ExtrusionMultiPath;
+class ExtrusionLoop;
+class ExtrusionEntity;
+class ExtrusionEntityCollection;
+class ModelObject;
+class ModelVolume;
+enum ModelInstanceEPrintVolumeState : unsigned char;
+
+// Return appropriate color based on the ModelVolume.
+extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume);
+
+#if !ENABLE_LEGACY_OPENGL_REMOVAL
+// A container for interleaved arrays of 3D vertices and normals,
+// possibly indexed by triangles and / or quads.
+class GLIndexedVertexArray {
+public:
+ // Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized.
+ static_assert(sizeof(Eigen::AlignedBox<float, 3>) == 24, "Eigen::AlignedBox<float, 3> is not being vectorized, thus it does not need to be aligned");
+ using BoundingBox = Eigen::AlignedBox<float, 3>;
+
+ GLIndexedVertexArray() { m_bounding_box.setEmpty(); }
+ GLIndexedVertexArray(const GLIndexedVertexArray &rhs) :
+ vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved),
+ triangle_indices(rhs.triangle_indices),
+ quad_indices(rhs.quad_indices),
+ m_bounding_box(rhs.m_bounding_box)
+ { assert(! rhs.has_VBOs()); m_bounding_box.setEmpty(); }
+ GLIndexedVertexArray(GLIndexedVertexArray &&rhs) :
+ vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)),
+ triangle_indices(std::move(rhs.triangle_indices)),
+ quad_indices(std::move(rhs.quad_indices)),
+ m_bounding_box(rhs.m_bounding_box)
+ { assert(! rhs.has_VBOs()); }
+
+ ~GLIndexedVertexArray() { release_geometry(); }
+
+ GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs)
+ {
+ assert(vertices_and_normals_interleaved_VBO_id == 0);
+ assert(triangle_indices_VBO_id == 0);
+ assert(quad_indices_VBO_id == 0);
+ assert(rhs.vertices_and_normals_interleaved_VBO_id == 0);
+ assert(rhs.triangle_indices_VBO_id == 0);
+ assert(rhs.quad_indices_VBO_id == 0);
+ this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved;
+ this->triangle_indices = rhs.triangle_indices;
+ this->quad_indices = rhs.quad_indices;
+ this->m_bounding_box = rhs.m_bounding_box;
+ this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size;
+ this->triangle_indices_size = rhs.triangle_indices_size;
+ this->quad_indices_size = rhs.quad_indices_size;
+ return *this;
+ }
+
+ GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs)
+ {
+ assert(vertices_and_normals_interleaved_VBO_id == 0);
+ assert(triangle_indices_VBO_id == 0);
+ assert(quad_indices_VBO_id == 0);
+ assert(rhs.vertices_and_normals_interleaved_VBO_id == 0);
+ assert(rhs.triangle_indices_VBO_id == 0);
+ assert(rhs.quad_indices_VBO_id == 0);
+ this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved);
+ this->triangle_indices = std::move(rhs.triangle_indices);
+ this->quad_indices = std::move(rhs.quad_indices);
+ this->m_bounding_box = rhs.m_bounding_box;
+ this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size;
+ this->triangle_indices_size = rhs.triangle_indices_size;
+ this->quad_indices_size = rhs.quad_indices_size;
+ return *this;
+ }
+
+ // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x)
+ std::vector<float> vertices_and_normals_interleaved;
+ std::vector<int> triangle_indices;
+ std::vector<int> quad_indices;
+
+ // When the geometry data is loaded into the graphics card as Vertex Buffer Objects,
+ // the above mentioned std::vectors are cleared and the following variables keep their original length.
+ size_t vertices_and_normals_interleaved_size{ 0 };
+ size_t triangle_indices_size{ 0 };
+ size_t quad_indices_size{ 0 };
+
+ // IDs of the Vertex Array Objects, into which the geometry has been loaded.
+ // Zero if the VBOs are not sent to GPU yet.
+ unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
+ unsigned int triangle_indices_VBO_id{ 0 };
+ unsigned int quad_indices_VBO_id{ 0 };
+
+#if ENABLE_SMOOTH_NORMALS
+ void load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals = false);
+ void load_mesh(const TriangleMesh& mesh, bool smooth_normals = false) { this->load_mesh_full_shading(mesh, smooth_normals); }
+#else
+ void load_mesh_full_shading(const TriangleMesh& mesh);
+ void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); }
+#endif // ENABLE_SMOOTH_NORMALS
+
+ void load_its_flat_shading(const indexed_triangle_set &its);
+
+ inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; }
+
+ inline void reserve(size_t sz) {
+ this->vertices_and_normals_interleaved.reserve(sz * 6);
+ this->triangle_indices.reserve(sz * 3);
+ this->quad_indices.reserve(sz * 4);
+ }
+
+ inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) {
+ assert(this->vertices_and_normals_interleaved_VBO_id == 0);
+ if (this->vertices_and_normals_interleaved_VBO_id != 0)
+ return;
+
+ if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
+ this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
+ this->vertices_and_normals_interleaved.emplace_back(nx);
+ this->vertices_and_normals_interleaved.emplace_back(ny);
+ this->vertices_and_normals_interleaved.emplace_back(nz);
+ this->vertices_and_normals_interleaved.emplace_back(x);
+ this->vertices_and_normals_interleaved.emplace_back(y);
+ this->vertices_and_normals_interleaved.emplace_back(z);
+
+ this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
+ m_bounding_box.extend(Vec3f(x, y, z));
+ };
+
+ inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) {
+ push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
+ }
+
+ template<typename Derived, typename Derived2>
+ inline void push_geometry(const Eigen::MatrixBase<Derived>& p, const Eigen::MatrixBase<Derived2>& n) {
+ push_geometry(float(p(0)), float(p(1)), float(p(2)), float(n(0)), float(n(1)), float(n(2)));
+ }
+
+ inline void push_triangle(int idx1, int idx2, int idx3) {
+ assert(this->vertices_and_normals_interleaved_VBO_id == 0);
+ if (this->vertices_and_normals_interleaved_VBO_id != 0)
+ return;
+
+ if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
+ this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
+ this->triangle_indices.emplace_back(idx1);
+ this->triangle_indices.emplace_back(idx2);
+ this->triangle_indices.emplace_back(idx3);
+ this->triangle_indices_size = this->triangle_indices.size();
+ };
+
+ inline void push_quad(int idx1, int idx2, int idx3, int idx4) {
+ assert(this->vertices_and_normals_interleaved_VBO_id == 0);
+ if (this->vertices_and_normals_interleaved_VBO_id != 0)
+ return;
+
+ if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity())
+ this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4));
+ this->quad_indices.emplace_back(idx1);
+ this->quad_indices.emplace_back(idx2);
+ this->quad_indices.emplace_back(idx3);
+ this->quad_indices.emplace_back(idx4);
+ this->quad_indices_size = this->quad_indices.size();
+ };
+
+ // Finalize the initialization of the geometry & indices,
+ // upload the geometry and indices to OpenGL VBO objects
+ // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
+ void finalize_geometry(bool opengl_initialized);
+ // Release the geometry data, release OpenGL VBOs.
+ void release_geometry();
+
+ void render() const;
+ void render(const std::pair<size_t, size_t>& tverts_range, const std::pair<size_t, size_t>& qverts_range) const;
+
+ // Is there any geometry data stored?
+ bool empty() const { return vertices_and_normals_interleaved_size == 0; }
+
+ void clear() {
+ this->vertices_and_normals_interleaved.clear();
+ this->triangle_indices.clear();
+ this->quad_indices.clear();
+ vertices_and_normals_interleaved_size = 0;
+ triangle_indices_size = 0;
+ quad_indices_size = 0;
+ m_bounding_box.setEmpty();
+ }
+
+ // Shrink the internal storage to tighly fit the data stored.
+ void shrink_to_fit() {
+ this->vertices_and_normals_interleaved.shrink_to_fit();
+ this->triangle_indices.shrink_to_fit();
+ this->quad_indices.shrink_to_fit();
+ }
+
+ const BoundingBox& bounding_box() const { return m_bounding_box; }
+
+ // Return an estimate of the memory consumed by this class.
+ size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); }
+ // Return an estimate of the memory held by GPU vertex buffers.
+ size_t gpu_memory_used() const
+ {
+ size_t memsize = 0;
+ if (this->vertices_and_normals_interleaved_VBO_id != 0)
+ memsize += this->vertices_and_normals_interleaved_size * 4;
+ if (this->triangle_indices_VBO_id != 0)
+ memsize += this->triangle_indices_size * 4;
+ if (this->quad_indices_VBO_id != 0)
+ memsize += this->quad_indices_size * 4;
+ return memsize;
+ }
+ size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
+
+private:
+ BoundingBox m_bounding_box;
+};
+#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
+
+class GLVolume {
+public:
+ static const ColorRGBA SELECTED_COLOR;
+ static const ColorRGBA HOVER_SELECT_COLOR;
+ static const ColorRGBA HOVER_DESELECT_COLOR;
+ static const ColorRGBA OUTSIDE_COLOR;
+ static const ColorRGBA SELECTED_OUTSIDE_COLOR;
+ static const ColorRGBA DISABLED_COLOR;
+ static const ColorRGBA SLA_SUPPORT_COLOR;
+ static const ColorRGBA SLA_PAD_COLOR;
+ static const ColorRGBA NEUTRAL_COLOR;
+ static const std::array<ColorRGBA, 4> MODEL_COLOR;
+
+ enum EHoverState : unsigned char
+ {
+ HS_None,
+ HS_Hover,
+ HS_Select,
+ HS_Deselect
+ };
+
+ GLVolume(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f);
+ GLVolume(const ColorRGBA& color) : GLVolume(color.r(), color.g(), color.b(), color.a()) {}
+
+private:
+ Geometry::Transformation m_instance_transformation;
+ Geometry::Transformation m_volume_transformation;
+
+ // Shift in z required by sla supports+pad
+ double m_sla_shift_z;
+ // Bounding box of this volume, in unscaled coordinates.
+ std::optional<BoundingBoxf3> m_transformed_bounding_box;
+ // Convex hull of the volume, if any.
+ std::shared_ptr<const TriangleMesh> m_convex_hull;
+ // Bounding box of this volume, in unscaled coordinates.
+ std::optional<BoundingBoxf3> m_transformed_convex_hull_bounding_box;
+ // Bounding box of the non sinking part of this volume, in unscaled coordinates.
+ std::optional<BoundingBoxf3> m_transformed_non_sinking_bounding_box;
+
+ class SinkingContours
+ {
+ static const float HalfWidth;
+ GLVolume& m_parent;
+ GUI::GLModel m_model;
+ BoundingBoxf3 m_old_box;
+ Vec3d m_shift{ Vec3d::Zero() };
+
+ public:
+ SinkingContours(GLVolume& volume) : m_parent(volume) {}
+ void render();
+
+ private:
+ void update();
+ };
+
+ SinkingContours m_sinking_contours;
+
+#if ENABLE_SHOW_NON_MANIFOLD_EDGES
+ class NonManifoldEdges
+ {
+ GLVolume& m_parent;
+ GUI::GLModel m_model;
+ bool m_update_needed{ true };
+
+ public:
+ NonManifoldEdges(GLVolume& volume) : m_parent(volume) {}
+ void render();
+ void set_as_dirty() { m_update_needed = true; }
+
+ private:
+ void update();
+ };
+
+ NonManifoldEdges m_non_manifold_edges;
+#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
+
+public:
+ // Color of the triangles / quads held by this volume.
+ ColorRGBA color;
+ // Color used to render this volume.
+ ColorRGBA render_color;
+
+ struct CompositeID {
+ CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {}
+ CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {}
+ // Object ID, which is equal to the index of the respective ModelObject in Model.objects array.
+ int object_id;
+ // Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array.
+ // If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject,
+ // and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports.
+ // Volume with a negative volume_id cannot be picked independently, it will pick the associated instance.
+ int volume_id;
+ // Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array.
+ int instance_id;
+ bool operator==(const CompositeID &rhs) const { return object_id == rhs.object_id && volume_id == rhs.volume_id && instance_id == rhs.instance_id; }
+ bool operator!=(const CompositeID &rhs) const { return ! (*this == rhs); }
+ bool operator< (const CompositeID &rhs) const
+ { return object_id < rhs.object_id || (object_id == rhs.object_id && (volume_id < rhs.volume_id || (volume_id == rhs.volume_id && instance_id < rhs.instance_id))); }
+ };
+ CompositeID composite_id;
+ // Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID,
+ // for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(),
+ // and the associated ModelInstanceID.
+ // Valid geometry_id should always be positive.
+ std::pair<size_t, size_t> geometry_id;
+ // An ID containing the extruder ID (used to select color).
+ int extruder_id;
+
+ // Various boolean flags.
+ struct {
+ // Is this object selected?
+ bool selected : 1;
+ // Is this object disabled from selection?
+ bool disabled : 1;
+ // Is this object printable?
+ bool printable : 1;
+ // Whether or not this volume is active for rendering
+ bool is_active : 1;
+ // Whether or not to use this volume when applying zoom_to_volumes()
+ bool zoom_to_volumes : 1;
+ // Wheter or not this volume is enabled for outside print volume detection in shader.
+ bool shader_outside_printer_detection_enabled : 1;
+ // Wheter or not this volume is outside print volume.
+ bool is_outside : 1;
+ // Wheter or not this volume has been generated from a modifier
+ bool is_modifier : 1;
+ // Wheter or not this volume has been generated from the wipe tower
+ bool is_wipe_tower : 1;
+ // Wheter or not this volume has been generated from an extrusion path
+ bool is_extrusion_path : 1;
+ // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE)
+ bool force_native_color : 1;
+ // Whether or not render this volume in neutral
+ bool force_neutral_color : 1;
+ // Whether or not to force rendering of sinking contours
+ bool force_sinking_contours : 1;
+ };
+
+ // Is mouse or rectangle selection over this object to select/deselect it ?
+ EHoverState hover;
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GUI::GLModel model;
+#else
+ // Interleaved triangles & normals with indexed triangles & quads.
+ GLIndexedVertexArray indexed_vertex_array;
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ // Ranges of triangle and quad indices to be rendered.
+ std::pair<size_t, size_t> tverts_range;
+#if !ENABLE_LEGACY_OPENGL_REMOVAL
+ std::pair<size_t, size_t> qverts_range;
+#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
+
+ // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts
+ // of the extrusions per layer.
+ std::vector<coordf_t> print_zs;
+ // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
+ std::vector<size_t> offsets;
+
+ // Bounding box of this volume, in unscaled coordinates.
+ BoundingBoxf3 bounding_box() const {
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ return this->model.get_bounding_box();
+#else
+ BoundingBoxf3 out;
+ if (!this->indexed_vertex_array.bounding_box().isEmpty()) {
+ out.min = this->indexed_vertex_array.bounding_box().min().cast<double>();
+ out.max = this->indexed_vertex_array.bounding_box().max().cast<double>();
+ out.defined = true;
+ }
+ return out;
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ }
+
+ void set_color(const ColorRGBA& rgba) { color = rgba; }
+ void set_render_color(const ColorRGBA& rgba) { render_color = rgba; }
+ // Sets render color in dependence of current state
+ void set_render_color(bool force_transparent);
+ // set color according to model volume
+ void set_color_from_model_volume(const ModelVolume& model_volume);
+
+ const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; }
+ void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); }
+#if ENABLE_WORLD_COORDINATE
+ void set_instance_transformation(const Transform3d& transform) { m_instance_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); }
+
+ Vec3d get_instance_offset() const { return m_instance_transformation.get_offset(); }
+#else
+ const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); }
+
+ void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); }
+ void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_instance_rotation() const { return m_instance_transformation.get_rotation(); }
+#else
+ const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); }
+
+ void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); }
+ void set_instance_rotation(Axis axis, double rotation) { m_instance_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); }
+
+ Vec3d get_instance_scaling_factor() const { return m_instance_transformation.get_scaling_factor(); }
+ double get_instance_scaling_factor(Axis axis) const { return m_instance_transformation.get_scaling_factor(axis); }
+
+ void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); }
+ void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_instance_mirror() const { return m_instance_transformation.get_mirror(); }
+#else
+ const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); }
+
+ void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); }
+ void set_instance_mirror(Axis axis, double mirror) { m_instance_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); }
+
+ const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; }
+ void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); }
+#if ENABLE_WORLD_COORDINATE
+ void set_volume_transformation(const Transform3d& transform) { m_volume_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); }
+
+ Vec3d get_volume_offset() const { return m_volume_transformation.get_offset(); }
+#else
+ const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); }
+
+ void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); }
+ void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_volume_rotation() const { return m_volume_transformation.get_rotation(); }
+#else
+ const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); }
+
+ void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); }
+ void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); }
+#else
+ const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); }
+
+ void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); }
+ void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); }
+
+#if ENABLE_WORLD_COORDINATE
+ Vec3d get_volume_mirror() const { return m_volume_transformation.get_mirror(); }
+#else
+ const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); }
+#endif // ENABLE_WORLD_COORDINATE
+ double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); }
+
+ void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); }
+ void set_volume_mirror(Axis axis, double mirror) { m_volume_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); }
+
+ double get_sla_shift_z() const { return m_sla_shift_z; }
+ void set_sla_shift_z(double z) { m_sla_shift_z = z; }
+
+ void set_convex_hull(std::shared_ptr<const TriangleMesh> convex_hull) { m_convex_hull = std::move(convex_hull); }
+ void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(convex_hull); }
+ void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(std::move(convex_hull)); }
+
+ int object_idx() const { return this->composite_id.object_id; }
+ int volume_idx() const { return this->composite_id.volume_id; }
+ int instance_idx() const { return this->composite_id.instance_id; }
+
+ Transform3d world_matrix() const;
+ bool is_left_handed() const;
+
+ const BoundingBoxf3& transformed_bounding_box() const;
+ // non-caching variant
+ BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const;
+ // caching variant
+ const BoundingBoxf3& transformed_convex_hull_bounding_box() const;
+ // non-caching variant
+ BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const;
+ // caching variant
+ const BoundingBoxf3& transformed_non_sinking_bounding_box() const;
+ // convex hull
+ const TriangleMesh* convex_hull() const { return m_convex_hull.get(); }
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ bool empty() const { return this->model.is_empty(); }
+#else
+ bool empty() const { return this->indexed_vertex_array.empty(); }
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ void set_range(double low, double high);
+
+ void render();
+
+#if !ENABLE_LEGACY_OPENGL_REMOVAL
+ void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); }
+ void release_geometry() { this->indexed_vertex_array.release_geometry(); }
+#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
+
+ void set_bounding_boxes_as_dirty() {
+ m_transformed_bounding_box.reset();
+ m_transformed_convex_hull_bounding_box.reset();
+ m_transformed_non_sinking_bounding_box.reset();
+ }
+
+ bool is_sla_support() const;
+ bool is_sla_pad() const;
+
+ bool is_sinking() const;
+ bool is_below_printbed() const;
+ void render_sinking_contours();
+#if ENABLE_SHOW_NON_MANIFOLD_EDGES
+ void render_non_manifold_edges();
+#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
+
+ // Return an estimate of the memory consumed by this class.
+ size_t cpu_memory_used() const {
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ return sizeof(*this) + this->model.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) +
+ this->offsets.capacity() * sizeof(size_t);
+ }
+ // Return an estimate of the memory held by GPU vertex buffers.
+ size_t gpu_memory_used() const { return this->model.gpu_memory_used(); }
+#else
+ //FIXME what to do wih m_convex_hull?
+ return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t);
+ }
+ // Return an estimate of the memory held by GPU vertex buffers.
+ size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); }
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
+};
+
+typedef std::vector<GLVolume*> GLVolumePtrs;
+typedef std::pair<GLVolume*, std::pair<unsigned int, double>> GLVolumeWithIdAndZ;
+typedef std::vector<GLVolumeWithIdAndZ> GLVolumeWithIdAndZList;
+
+class GLVolumeCollection
+{
+public:
+ enum class ERenderType : unsigned char
+ {
+ Opaque,
+ Transparent,
+ All
+ };
+
+ struct PrintVolume
+ {
+ // see: Bed3D::EShapeType
+ int type{ 0 };
+ // data contains:
+ // Rectangle:
+ // [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y
+ // Circle:
+ // [0] = center.x, [1] = center.y, [3] = radius
+ std::array<float, 4> data;
+ // [0] = min z, [1] = max z
+ std::array<float, 2> zs;
+ };
+
+private:
+ PrintVolume m_print_volume;
+
+ // z range for clipping in shaders
+ std::array<float, 2> m_z_range;
+
+ // plane coeffs for clipping in shaders
+ std::array<double, 4> m_clipping_plane;
+
+ struct Slope
+ {
+ // toggle for slope rendering
+ bool active{ false };
+ float normal_z;
+ };
+
+ Slope m_slope;
+ bool m_show_sinking_contours{ false };
+#if ENABLE_SHOW_NON_MANIFOLD_EDGES
+ bool m_show_non_manifold_edges{ true };
+#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
+
+public:
+ GLVolumePtrs volumes;
+
+ GLVolumeCollection() { set_default_slope_normal_z(); }
+ ~GLVolumeCollection() { clear(); }
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ std::vector<int> load_object(
+ const ModelObject* model_object,
+ int obj_idx,
+ const std::vector<int>& instance_idxs);
+
+ int load_object_volume(
+ const ModelObject* model_object,
+ int obj_idx,
+ int volume_idx,
+ int instance_idx);
+
+ // Load SLA auxiliary GLVolumes (for support trees or pad).
+ void load_object_auxiliary(
+ const SLAPrintObject* print_object,
+ int obj_idx,
+ // pairs of <instance_idx, print_instance_idx>
+ const std::vector<std::pair<size_t, size_t>>& instances,
+ SLAPrintObjectStep milestone,
+ // Timestamp of the last change of the milestone
+ size_t timestamp);
+
+#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
+ int load_wipe_tower_preview(
+ float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width);
+#else
+ int load_wipe_tower_preview(
+ int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width);
+#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
+#else
+ std::vector<int> load_object(
+ const ModelObject *model_object,
+ int obj_idx,
+ const std::vector<int> &instance_idxs,
+ bool opengl_initialized);
+
+ int load_object_volume(
+ const ModelObject *model_object,
+ int obj_idx,
+ int volume_idx,
+ int instance_idx,
+ bool opengl_initialized);
+
+ // Load SLA auxiliary GLVolumes (for support trees or pad).
+ void load_object_auxiliary(
+ const SLAPrintObject *print_object,
+ int obj_idx,
+ // pairs of <instance_idx, print_instance_idx>
+ const std::vector<std::pair<size_t, size_t>>& instances,
+ SLAPrintObjectStep milestone,
+ // Timestamp of the last change of the milestone
+ size_t timestamp,
+ bool opengl_initialized);
+
+#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
+ int load_wipe_tower_preview(
+ float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized);
+#else
+ int load_wipe_tower_preview(
+ int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized);
+#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLVolume* new_toolpath_volume(const ColorRGBA& rgba);
+ GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba);
+#else
+ GLVolume* new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0);
+ GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0);
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ // Render the volumes by OpenGL.
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, const Transform3d& projection_matrix,
+ std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const;
+#else
+ void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const;
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+#if !ENABLE_LEGACY_OPENGL_REMOVAL
+ // Finalize the initialization of the geometry & indices,
+ // upload the geometry and indices to OpenGL VBO objects
+ // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
+ void finalize_geometry(bool opengl_initialized) { for (auto* v : volumes) v->finalize_geometry(opengl_initialized); }
+ // Release the geometry data assigned to the volumes.
+ // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them.
+ void release_geometry() { for (auto *v : volumes) v->release_geometry(); }
+#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
+ // Clear the geometry
+ void clear() { for (auto *v : volumes) delete v; volumes.clear(); }
+
+ bool empty() const { return volumes.empty(); }
+ void set_range(double low, double high) { for (GLVolume* vol : this->volumes) vol->set_range(low, high); }
+
+ void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; }
+
+ void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; }
+ void set_clipping_plane(const std::array<double, 4>& coeffs) { m_clipping_plane = coeffs; }
+
+ const std::array<float, 2>& get_z_range() const { return m_z_range; }
+ const std::array<double, 4>& get_clipping_plane() const { return m_clipping_plane; }
+
+ bool is_slope_active() const { return m_slope.active; }
+ void set_slope_active(bool active) { m_slope.active = active; }
+
+ float get_slope_normal_z() const { return m_slope.normal_z; }
+ void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; }
+ void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); }
+ void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; }
+#if ENABLE_SHOW_NON_MANIFOLD_EDGES
+ void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; }
+#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
+
+ // returns true if all the volumes are completely contained in the print volume
+ // returns the containment state in the given out_state, if non-null
+ bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const;
+ void reset_outside_state();
+
+ void update_colors_by_extruder(const DynamicPrintConfig* config);
+
+ // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection
+ std::vector<double> get_current_print_zs(bool active_only) const;
+
+ // Return an estimate of the memory consumed by this class.
+ size_t cpu_memory_used() const;
+ // Return an estimate of the memory held by GPU vertex buffers.
+ size_t gpu_memory_used() const;
+ size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
+ // Return CPU, GPU and total memory log line.
+ std::string log_memory_info() const;
+
+private:
+ GLVolumeCollection(const GLVolumeCollection &other);
+ GLVolumeCollection& operator=(const GLVolumeCollection &);
+};
+
+GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = nullptr);
+
+struct _3DScene
+{
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GUI::GLModel::Geometry& geometry);
+ static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GUI::GLModel::Geometry& geometry);
+ static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
+ static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
+ static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
+ static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
+ static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
+#else
+ static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
+ static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
+ static void extrusionentity_to_verts(const Polyline& polyline, float width, float height, float print_z, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume);
+ static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
+ static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
+ static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume);
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+};
+
+}
+
+#endif
diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp
new file mode 100644
index 000000000..edd1d4f03
--- /dev/null
+++ b/src/slic3r/GUI/CoordAxes.cpp
@@ -0,0 +1,104 @@
+#include "libslic3r/libslic3r.h"
+
+#include "CoordAxes.hpp"
+#include "GUI_App.hpp"
+#include "3DScene.hpp"
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+#include "Plater.hpp"
+#include "Camera.hpp"
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+#include <GL/glew.h>
+
+#if ENABLE_WORLD_COORDINATE
+
+namespace Slic3r {
+namespace GUI {
+
+const float CoordAxes::DefaultStemRadius = 0.5f;
+const float CoordAxes::DefaultStemLength = 25.0f;
+const float CoordAxes::DefaultTipRadius = 2.5f * CoordAxes::DefaultStemRadius;
+const float CoordAxes::DefaultTipLength = 5.0f;
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+void CoordAxes::render(const Transform3d& trafo, float emission_factor)
+#else
+void CoordAxes::render(float emission_factor)
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+{
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ auto render_axis = [this](GLShaderProgram& shader, const Transform3d& transform) {
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ const Transform3d matrix = camera.get_view_matrix() * transform;
+ shader.set_uniform("view_model_matrix", matrix);
+ shader.set_uniform("projection_matrix", camera.get_projection_matrix());
+ shader.set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
+ m_arrow.render();
+#else
+ auto render_axis = [this](const Transform3f& transform) {
+ glsafe(::glPushMatrix());
+ glsafe(::glMultMatrixf(transform.data()));
+ m_arrow.render();
+ glsafe(::glPopMatrix());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ };
+
+ if (!m_arrow.is_initialized())
+ m_arrow.init_from(stilized_arrow(16, m_tip_radius, m_tip_length, m_stem_radius, m_stem_length));
+
+ GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
+ GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
+ if (shader == nullptr)
+ return;
+
+ if (curr_shader != nullptr)
+ curr_shader->stop_using();
+
+ shader->start_using();
+ shader->set_uniform("emission_factor", emission_factor);
+
+ // x axis
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_arrow.set_color(ColorRGBA::X());
+#else
+ m_arrow.set_color(-1, ColorRGBA::X());
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ render_axis(*shader, trafo * Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }));
+#else
+ render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast<float>());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ // y axis
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_arrow.set_color(ColorRGBA::Y());
+#else
+ m_arrow.set_color(-1, ColorRGBA::Y());
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ render_axis(*shader, trafo * Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }));
+#else
+ render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast<float>());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ // z axis
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_arrow.set_color(ColorRGBA::Z());
+#else
+ m_arrow.set_color(-1, ColorRGBA::Z());
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ render_axis(*shader, trafo * Geometry::assemble_transform(m_origin));
+#else
+ render_axis(Geometry::assemble_transform(m_origin).cast<float>());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ shader->stop_using();
+ if (curr_shader != nullptr)
+ curr_shader->start_using();
+}
+
+} // GUI
+} // Slic3r
+
+#endif // ENABLE_WORLD_COORDINATE
diff --git a/src/slic3r/GUI/CoordAxes.hpp b/src/slic3r/GUI/CoordAxes.hpp
new file mode 100644
index 000000000..9e49d1391
--- /dev/null
+++ b/src/slic3r/GUI/CoordAxes.hpp
@@ -0,0 +1,64 @@
+#ifndef slic3r_CoordAxes_hpp_
+#define slic3r_CoordAxes_hpp_
+
+#if ENABLE_WORLD_COORDINATE
+#include "GLModel.hpp"
+
+namespace Slic3r {
+namespace GUI {
+
+class CoordAxes
+{
+public:
+ static const float DefaultStemRadius;
+ static const float DefaultStemLength;
+ static const float DefaultTipRadius;
+ static const float DefaultTipLength;
+
+private:
+ Vec3d m_origin{ Vec3d::Zero() };
+ float m_stem_radius{ DefaultStemRadius };
+ float m_stem_length{ DefaultStemLength };
+ float m_tip_radius{ DefaultTipRadius };
+ float m_tip_length{ DefaultTipLength };
+ GLModel m_arrow;
+
+public:
+ const Vec3d& get_origin() const { return m_origin; }
+ void set_origin(const Vec3d& origin) { m_origin = origin; }
+ void set_stem_radius(float radius) {
+ m_stem_radius = radius;
+ m_arrow.reset();
+ }
+ void set_stem_length(float length) {
+ m_stem_length = length;
+ m_arrow.reset();
+ }
+ void set_tip_radius(float radius) {
+ m_tip_radius = radius;
+ m_arrow.reset();
+ }
+ void set_tip_length(float length) {
+ m_tip_length = length;
+ m_arrow.reset();
+ }
+
+ float get_stem_radius() const { return m_stem_radius; }
+ float get_stem_length() const { return m_stem_length; }
+ float get_tip_radius() const { return m_tip_radius; }
+ float get_tip_length() const { return m_tip_length; }
+ float get_total_length() const { return m_stem_length + m_tip_length; }
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ void render(const Transform3d& trafo, float emission_factor = 0.0f);
+#else
+ void render(float emission_factor = 0.0f);
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+};
+
+} // GUI
+} // Slic3r
+
+#endif // ENABLE_WORLD_COORDINATE
+
+#endif // slic3r_CoordAxes_hpp_
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index adab374f5..c44ba66a0 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1101,6 +1101,9 @@ wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>);
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
+#if ENABLE_WORLD_COORDINATE
+wxDEFINE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent);
+#endif // ENABLE_WORLD_COORDINATE
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
@@ -2914,7 +2917,13 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
else
displacement = multiplier * direction;
+#if ENABLE_WORLD_COORDINATE
+ TransformationType trafo_type;
+ trafo_type.set_relative();
+ m_selection.translate(displacement, trafo_type);
+#else
m_selection.translate(displacement);
+#endif // ENABLE_WORLD_COORDINATE
m_dirty = true;
}
);
@@ -3579,7 +3588,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
}
+#if ENABLE_WORLD_COORDINATE
+ TransformationType trafo_type;
+ trafo_type.set_relative();
+ m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type);
+#else
m_selection.translate(cur_pos - m_mouse.drag.start_position_3D);
+#endif // ENABLE_WORLD_COORDINATE
if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects)
update_sequential_clearance();
wxGetApp().obj_manipul()->set_dirty();
@@ -3825,9 +3840,17 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
ModelObject* model_object = m_model->objects[object_idx];
if (model_object != nullptr) {
if (selection_mode == Selection::Instance)
+#if ENABLE_WORLD_COORDINATE
+ model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
+#else
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
+#endif // ENABLE_WORLD_COORDINATE
else if (selection_mode == Selection::Volume)
+#if ENABLE_WORLD_COORDINATE
+ model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation());
+#else
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
+#endif // ENABLE_WORLD_COORDINATE
object_moved = true;
model_object->invalidate_bounding_box();
@@ -3907,8 +3930,8 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
int object_idx = v->object_idx();
if (object_idx == 1000) { // the wipe tower
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
- Vec3d offset = v->get_volume_offset();
- post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2))));
+ const Vec3d offset = v->get_volume_offset();
+ post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset.x(), offset.y(), v->get_volume_rotation().z())));
}
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
int object_idx = v->object_idx();
@@ -3916,8 +3939,8 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
continue;
- int instance_idx = v->instance_idx();
- int volume_idx = v->volume_idx();
+ const int instance_idx = v->instance_idx();
+ const int volume_idx = v->volume_idx();
done.insert(std::pair<int, int>(object_idx, instance_idx));
@@ -3925,12 +3948,20 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
ModelObject* model_object = m_model->objects[object_idx];
if (model_object != nullptr) {
if (selection_mode == Selection::Instance) {
+#if ENABLE_WORLD_COORDINATE
+ model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
+#else
model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation());
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
+#endif // ENABLE_WORLD_COORDINATE
}
else if (selection_mode == Selection::Volume) {
+#if ENABLE_WORLD_COORDINATE
+ model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation());
+#else
model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation());
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
+#endif // ENABLE_WORLD_COORDINATE
}
model_object->invalidate_bounding_box();
}
@@ -3980,12 +4011,12 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
Selection::EMode selection_mode = m_selection.get_mode();
for (const GLVolume* v : m_volumes.volumes) {
- int object_idx = v->object_idx();
+ const int object_idx = v->object_idx();
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
continue;
- int instance_idx = v->instance_idx();
- int volume_idx = v->volume_idx();
+ const int instance_idx = v->instance_idx();
+ const int volume_idx = v->volume_idx();
done.insert(std::pair<int, int>(object_idx, instance_idx));
@@ -3993,13 +4024,22 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
ModelObject* model_object = m_model->objects[object_idx];
if (model_object != nullptr) {
if (selection_mode == Selection::Instance) {
+#if ENABLE_WORLD_COORDINATE
+ model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
+#else
model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor());
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
+#endif // ENABLE_WORLD_COORDINATE
}
else if (selection_mode == Selection::Volume) {
+#if ENABLE_WORLD_COORDINATE
+ model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
+ model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation());
+#else
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor());
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
+#endif // ENABLE_WORLD_COORDINATE
}
model_object->invalidate_bounding_box();
}
@@ -4008,10 +4048,10 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
// Fixes sinking/flying instances
for (const std::pair<int, int>& i : done) {
ModelObject* m = m_model->objects[i.first];
- double shift_z = m->get_instance_min_z(i.second);
+ const double shift_z = m->get_instance_min_z(i.second);
// leave sinking instances as sinking
if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) {
- Vec3d shift(0.0, 0.0, -shift_z);
+ const Vec3d shift(0.0, 0.0, -shift_z);
m_selection.translate(i.first, i.second, shift);
m->translate_instance(i.second, shift);
}
@@ -4061,9 +4101,17 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type)
ModelObject* model_object = m_model->objects[object_idx];
if (model_object != nullptr) {
if (selection_mode == Selection::Instance)
+#if ENABLE_WORLD_COORDINATE
+ model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
+#else
model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror());
+#endif // ENABLE_WORLD_COORDINATE
else if (selection_mode == Selection::Volume)
+#if ENABLE_WORLD_COORDINATE
+ model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation());
+#else
model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror());
+#endif // ENABLE_WORLD_COORDINATE
model_object->invalidate_bounding_box();
}
@@ -4087,6 +4135,44 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type)
m_dirty = true;
}
+#if ENABLE_WORLD_COORDINATE
+void GLCanvas3D::do_reset_skew(const std::string& snapshot_type)
+{
+ if (m_model == nullptr)
+ return;
+
+ if (!snapshot_type.empty())
+ wxGetApp().plater()->take_snapshot(_(snapshot_type));
+
+ std::set<std::pair<int, int>> done; // keeps track of modified instances
+
+ const Selection::IndicesList& idxs = m_selection.get_volume_idxs();
+
+ for (unsigned int id : idxs) {
+ const GLVolume* v = m_volumes.volumes[id];
+ int object_idx = v->object_idx();
+ if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
+ continue;
+
+ int instance_idx = v->instance_idx();
+ int volume_idx = v->volume_idx();
+
+ done.insert(std::pair<int, int>(object_idx, instance_idx));
+
+ ModelObject* model_object = m_model->objects[object_idx];
+ if (model_object != nullptr) {
+ model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
+ model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation());
+ model_object->invalidate_bounding_box();
+ }
+ }
+
+ post_event(SimpleEvent(EVT_GLCANVAS_RESET_SKEW));
+
+ m_dirty = true;
+}
+#endif // ENABLE_WORLD_COORDINATE
+
void GLCanvas3D::update_gizmos_on_off_state()
{
set_as_dirty();
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 1876500e2..18ad42ae1 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -156,6 +156,9 @@ wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
+#if ENABLE_WORLD_COORDINATE
+wxDECLARE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent);
+#endif // ENABLE_WORLD_COORDINATE
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>);
@@ -739,7 +742,11 @@ public:
void update_volumes_colors_by_extruder();
+#if ENABLE_WORLD_COORDINATE
+ bool is_dragging() const { return m_gizmos.is_dragging() || (m_moving && !m_mouse.scene_position.isApprox(m_mouse.drag.start_position_3D)); }
+#else
bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; }
+#endif // ENABLE_WORLD_COORDINATE
void render();
// printable_only == false -> render also non printable volumes as grayed
@@ -807,6 +814,9 @@ public:
void do_rotate(const std::string& snapshot_type);
void do_scale(const std::string& snapshot_type);
void do_mirror(const std::string& snapshot_type);
+#if ENABLE_WORLD_COORDINATE
+ void do_reset_skew(const std::string& snapshot_type);
+#endif // ENABLE_WORLD_COORDINATE
void update_gizmos_on_off_state();
void reset_all_gizmos() { m_gizmos.reset_all_states(); }
diff --git a/src/slic3r/GUI/GUI_Geometry.cpp b/src/slic3r/GUI/GUI_Geometry.cpp
new file mode 100644
index 000000000..b0ed0e04f
--- /dev/null
+++ b/src/slic3r/GUI/GUI_Geometry.cpp
@@ -0,0 +1,9 @@
+#include "libslic3r/libslic3r.h"
+#include "GUI_Geometry.hpp"
+
+namespace Slic3r {
+namespace GUI {
+
+
+} // namespace Slic3r
+} // namespace GUI
diff --git a/src/slic3r/GUI/GUI_Geometry.hpp b/src/slic3r/GUI/GUI_Geometry.hpp
new file mode 100644
index 000000000..0d6cf7f4b
--- /dev/null
+++ b/src/slic3r/GUI/GUI_Geometry.hpp
@@ -0,0 +1,81 @@
+#ifndef slic3r_GUI_Geometry_hpp_
+#define slic3r_GUI_Geometry_hpp_
+
+namespace Slic3r {
+namespace GUI {
+
+#if ENABLE_WORLD_COORDINATE
+enum class ECoordinatesType : unsigned char
+{
+ World,
+ Instance,
+ Local
+};
+
+class TransformationType
+{
+public:
+ enum Enum {
+ // Transforming in a world coordinate system
+ World = 0,
+ // Transforming in a instance coordinate system
+ Instance = 1,
+ // Transforming in a local coordinate system
+ Local = 2,
+ // Absolute transformations, allowed in local coordinate system only.
+ Absolute = 0,
+ // Relative transformations, allowed in both local and world coordinate system.
+ Relative = 4,
+ // For group selection, the transformation is performed as if the group made a single solid body.
+ Joint = 0,
+ // For group selection, the transformation is performed on each object independently.
+ Independent = 8,
+
+ World_Relative_Joint = World | Relative | Joint,
+ World_Relative_Independent = World | Relative | Independent,
+ Instance_Absolute_Joint = Instance | Absolute | Joint,
+ Instance_Absolute_Independent = Instance | Absolute | Independent,
+ Instance_Relative_Joint = Instance | Relative | Joint,
+ Instance_Relative_Independent = Instance | Relative | Independent,
+ Local_Absolute_Joint = Local | Absolute | Joint,
+ Local_Absolute_Independent = Local | Absolute | Independent,
+ Local_Relative_Joint = Local | Relative | Joint,
+ Local_Relative_Independent = Local | Relative | Independent,
+ };
+
+ TransformationType() : m_value(World) {}
+ TransformationType(Enum value) : m_value(value) {}
+ TransformationType& operator=(Enum value) { m_value = value; return *this; }
+
+ Enum operator()() const { return m_value; }
+ bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; }
+
+ void set_world() { this->remove(Instance); this->remove(Local); }
+ void set_instance() { this->remove(Local); this->add(Instance); }
+ void set_local() { this->remove(Instance); this->add(Local); }
+ void set_absolute() { this->remove(Relative); }
+ void set_relative() { this->add(Relative); }
+ void set_joint() { this->remove(Independent); }
+ void set_independent() { this->add(Independent); }
+
+ bool world() const { return !this->has(Instance) && !this->has(Local); }
+ bool instance() const { return this->has(Instance); }
+ bool local() const { return this->has(Local); }
+ bool absolute() const { return !this->has(Relative); }
+ bool relative() const { return this->has(Relative); }
+ bool joint() const { return !this->has(Independent); }
+ bool independent() const { return this->has(Independent); }
+
+private:
+ void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); }
+ void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); }
+
+ Enum m_value;
+};
+
+#endif // ENABLE_WORLD_COORDINATE
+
+} // namespace Slic3r
+} // namespace GUI
+
+#endif // slic3r_GUI_Geometry_hpp_
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 4370a2f64..bfedd8e1e 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -1530,9 +1530,13 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo
const BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx);
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
- const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
+ const GLVolume* v = selection.get_first_volume();
const Geometry::Transformation inst_transform = v->get_instance_transformation();
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d inv_inst_transform = inst_transform.get_matrix_no_offset().inverse();
+#else
const Transform3d inv_inst_transform = inst_transform.get_matrix(true).inverse();
+#endif // ENABLE_WORLD_COORDINATE
const Vec3d instance_offset = v->get_instance_offset();
for (size_t i = 0; i < input_files.size(); ++i) {
@@ -1580,9 +1584,15 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo
new_volume->source.mesh_offset = model.objects.front()->volumes.front()->source.mesh_offset;
if (from_galery) {
+#if ENABLE_WORLD_COORDINATE
+ // Transform the new modifier to be aligned with the print bed.
+ new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse());
+ const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
+#else
// Transform the new modifier to be aligned with the print bed.
const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(inst_transform, mesh_bb));
+#endif // ENABLE_WORLD_COORDINATE
// Set the modifier position.
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
const Vec3d offset = Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - instance_offset;
@@ -1650,17 +1660,27 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
ModelVolume *new_volume = model_object.add_volume(std::move(mesh), type);
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
- const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
+ const GLVolume* v = selection.get_first_volume();
+#if ENABLE_WORLD_COORDINATE
// Transform the new modifier to be aligned with the print bed.
- const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
+ new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse());
+ const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
+#else
+ // Transform the new modifier to be aligned with the print bed.
+ const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb));
+#endif // ENABLE_WORLD_COORDINATE
// Set the modifier position.
auto offset = (type_name == "Slab") ?
// Slab: Lift to print bed
Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) :
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - v->get_instance_offset();
+#if ENABLE_WORLD_COORDINATE
+ new_volume->set_offset(v->get_instance_transformation().get_matrix_no_offset().inverse() * offset);
+#else
new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset);
+#endif // ENABLE_WORLD_COORDINATE
const wxString name = _L("Generic") + "-" + _(type_name);
new_volume->name = into_u8(name);
@@ -2545,7 +2565,13 @@ void ObjectList::part_selection_changed()
Sidebar& panel = wxGetApp().sidebar();
panel.Freeze();
+#if ENABLE_WORLD_COORDINATE
+ const ManipulationEditor* const editor = wxGetApp().obj_manipul()->get_focused_editor();
+ const std::string opt_key = (editor != nullptr) ? editor->get_full_opt_name() : "";
+ wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, !opt_key.empty());
+#else
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);
+#endif // ENABLE_WORLD_COORDINATE
wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations);
wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings);
wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers);
@@ -3263,8 +3289,12 @@ void ObjectList::update_selections()
return;
sels.Add(m_objects_model->GetItemById(selection.get_object_idx()));
}
+#if ENABLE_WORLD_COORDINATE
+ else if (selection.is_single_volume_or_modifier()) {
+#else
else if (selection.is_single_volume() || selection.is_any_modifier()) {
- const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin());
+#endif // ENABLE_WORLD_COORDINATE
+ const auto gl_vol = selection.get_first_volume();
if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx())
return;
}
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
index 6ab87150b..24ae01389 100644
--- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
@@ -52,10 +52,17 @@ static choice_ctrl* create_word_local_combo(wxWindow *parent)
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
+#if ENABLE_WORLD_COORDINATE
+ temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World));
+ temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance));
+ temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local));
+ temp->Select((int)ECoordinatesType::World);
+#else
temp->Append(_L("World coordinates"));
temp->Append(_L("Local coordinates"));
temp->SetSelection(0);
temp->SetValue(temp->GetString(0));
+#endif // ENABLE_WORLD_COORDINATE
temp->SetToolTip(_L("Select coordinate space, in which the transformation will be performed."));
return temp;
@@ -81,8 +88,14 @@ void msw_rescale_word_local_combo(choice_ctrl* combo)
// Set rescaled size
combo->SetSize(size);
+#if ENABLE_WORLD_COORDINATE
+ combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World));
+ combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance));
+ combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local));
+#else
combo->Append(_L("World coordinates"));
combo->Append(_L("Local coordinates"));
+#endif // ENABLE_WORLD_COORDINATE
combo->SetValue(selection);
#else
@@ -101,6 +114,7 @@ static void set_font_and_background_style(wxWindow* win, const wxFont& font)
static const wxString axes_color_text[] = { "#990000", "#009900", "#000099" };
static const wxString axes_color_back[] = { "#f5dcdc", "#dcf5dc", "#dcdcf5" };
+
ObjectManipulation::ObjectManipulation(wxWindow* parent) :
OG_Settings(parent, true)
{
@@ -157,8 +171,12 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
// Add world local combobox
m_word_local_combo = create_word_local_combo(parent);
m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) {
+#if ENABLE_WORLD_COORDINATE
+ this->set_coordinates_type(evt.GetString());
+#else
this->set_world_coordinates(evt.GetSelection() != 1);
- }), m_word_local_combo->GetId());
+#endif // ENABLE_WORLD_COORDINATE
+ }), m_word_local_combo->GetId());
// Small trick to correct layouting in different view_mode :
// Show empty string of a same height as a m_word_local_combo, when m_word_local_combo is hidden
@@ -264,8 +282,12 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
+#if ENABLE_WORLD_COORDINATE
+ if (selection.is_single_volume_or_modifier()) {
+#else
if (selection.is_single_volume() || selection.is_single_modifier()) {
- GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
+#endif // ENABLE_WORLD_COORDINATE
+ GLVolume* volume = const_cast<GLVolume*>(selection.get_first_volume());
volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis));
}
else if (selection.is_single_full_instance()) {
@@ -278,7 +300,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
return;
// Update mirroring at the GLVolumes.
- selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
+ selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL);
selection.synchronize_unselected_volumes();
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_mirror(L("Set Mirror"));
@@ -327,28 +349,60 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
+#if ENABLE_WORLD_COORDINATE
+ if (selection.is_single_volume_or_modifier()) {
+ const GLVolume* volume = selection.get_first_volume();
+ const double min_z = get_volume_min_z(*volume);
+ if (!is_world_coordinates()) {
+ const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ());
+
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
+ change_position_value(0, diff.x());
+ change_position_value(1, diff.y());
+ change_position_value(2, diff.z());
+ }
+ else {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
+ change_position_value(2, m_cache.position.z() - min_z);
+ }
+#else
if (selection.is_single_volume() || selection.is_single_modifier()) {
- const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
-
- const Geometry::Transformation& instance_trafo = volume->get_instance_transformation();
- const Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(*volume));
+ const GLVolume* volume = selection.get_first_volume();
+ const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (get_volume_min_z(*volume) * Vec3d::UnitZ());
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(0, diff.x());
change_position_value(1, diff.y());
change_position_value(2, diff.z());
+#endif // ENABLE_WORLD_COORDINATE
}
else if (selection.is_single_full_instance()) {
+#if ENABLE_WORLD_COORDINATE
+ const double min_z = selection.get_scaled_instance_bounding_box().min.z();
+ if (!is_world_coordinates()) {
+ const GLVolume* volume = selection.get_first_volume();
+ const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ());
+
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
+ change_position_value(0, diff.x());
+ change_position_value(1, diff.y());
+ change_position_value(2, diff.z());
+ }
+ else {
+#else
const ModelObjectPtrs& objects = wxGetApp().model().objects;
const int idx = selection.get_object_idx();
if (0 <= idx && idx < static_cast<int>(objects.size())) {
const ModelObject* mo = wxGetApp().model().objects[idx];
const double min_z = mo->bounding_box().min.z();
if (std::abs(min_z) > SINKING_Z_THRESHOLD) {
+#endif // ENABLE_WORLD_COORDINATE
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(2, m_cache.position.z() - min_z);
}
+#if !ENABLE_WORLD_COORDINATE
}
+#endif // !ENABLE_WORLD_COORDINATE
}
});
editors_grid_sizer->Add(m_drop_to_bed_button);
@@ -365,21 +419,22 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
- if (selection.is_single_volume() || selection.is_single_modifier()) {
- GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
- volume->set_volume_rotation(Vec3d::Zero());
- }
+#if ENABLE_WORLD_COORDINATE
+ if (selection.is_single_volume_or_modifier())
+#else
+ if (selection.is_single_volume() || selection.is_single_modifier())
+#endif // ENABLE_WORLD_COORDINATE
+ const_cast<GLVolume*>(selection.get_first_volume())->set_volume_rotation(Vec3d::Zero());
else if (selection.is_single_full_instance()) {
for (unsigned int idx : selection.get_volume_idxs()) {
- GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
- volume->set_instance_rotation(Vec3d::Zero());
+ const_cast<GLVolume*>(selection.get_volume(idx))->set_instance_rotation(Vec3d::Zero());
}
}
else
return;
// Update rotation at the GLVolumes.
- selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
+ selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL);
selection.synchronize_unselected_volumes();
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_rotate(L("Reset Rotation"));
@@ -397,11 +452,29 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
m_reset_scale_button->SetToolTip(_L("Reset scale"));
m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
+#if ENABLE_WORLD_COORDINATE
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ Selection& selection = canvas->get_selection();
+ if (selection.is_single_volume_or_modifier())
+ const_cast<GLVolume*>(selection.get_first_volume())->set_volume_scaling_factor(Vec3d::Ones());
+ else if (selection.is_single_full_instance()) {
+ for (unsigned int idx : selection.get_volume_idxs()) {
+ const_cast<GLVolume*>(selection.get_volume(idx))->set_instance_scaling_factor(Vec3d::Ones());
+ }
+ }
+ else
+ return;
+
+ canvas->do_scale(L("Reset scale"));
+
+ UpdateAndShow(true);
+#else
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset scale"));
change_scale_value(0, 100.);
change_scale_value(1, 100.);
change_scale_value(2, 100.);
- });
+#endif // ENABLE_WORLD_COORDINATE
+ });
editors_grid_sizer->Add(m_reset_scale_button);
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
@@ -411,6 +484,25 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND);
+#if ENABLE_WORLD_COORDINATE
+ m_skew_label = new wxStaticText(parent, wxID_ANY, _L("Skew"));
+ m_main_grid_sizer->Add(m_skew_label, 1, wxEXPAND);
+
+ m_reset_skew_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
+ m_reset_skew_button->SetToolTip(_L("Reset skew"));
+ m_reset_skew_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ Selection& selection = canvas->get_selection();
+ if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) {
+ selection.setup_cache();
+ selection.reset_skew();
+ canvas->do_reset_skew(L("Reset skew"));
+ UpdateAndShow(true);
+ }
+ });
+ m_main_grid_sizer->Add(m_reset_skew_button);
+#endif // ENABLE_WORLD_COORDINATE
+
m_check_inch = new wxCheckBox(parent, wxID_ANY, _L("Inches"));
m_check_inch->SetFont(wxGetApp().normal_font());
@@ -444,8 +536,27 @@ void ObjectManipulation::Show(const bool show)
if (show) {
// Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only.
- bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple;
- m_word_local_combo->Show(show_world_local_combo);
+#if ENABLE_WORLD_COORDINATE
+ const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier());
+ if (selection.is_single_volume_or_modifier() && m_word_local_combo->GetCount() < 3) {
+#ifdef __linux__
+ m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), 1);
+#else
+ m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), wxNullBitmap, 1);
+#endif // __linux__
+ m_word_local_combo->Select((int)ECoordinatesType::World);
+ this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection()));
+ }
+ else if (selection.is_single_full_instance() && m_word_local_combo->GetCount() > 2) {
+ m_word_local_combo->Delete(1);
+ m_word_local_combo->Select((int)ECoordinatesType::World);
+ this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection()));
+ }
+#else
+ bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple;
+#endif // ENABLE_WORLD_COORDINATE
+ m_word_local_combo->Show(show_world_local_combo);
m_empty_str->Show(!show_world_local_combo);
}
}
@@ -489,8 +600,7 @@ void ObjectManipulation::update_ui_from_settings()
}
m_check_inch->SetValue(m_imperial_units);
- if (m_use_colors != (wxGetApp().app_config->get("color_mapinulation_panel") == "1"))
- {
+ if (m_use_colors != (wxGetApp().app_config->get("color_mapinulation_panel") == "1")) {
m_use_colors = wxGetApp().app_config->get("color_mapinulation_panel") == "1";
// update colors for edit-boxes
int axis_id = 0;
@@ -522,33 +632,49 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
m_new_rotate_label_string = L("Rotation");
m_new_scale_label_string = L("Scale factors");
+#if !ENABLE_WORLD_COORDINATE
if (wxGetApp().get_mode() == comSimple)
m_world_coordinates = true;
+#endif // !ENABLE_WORLD_COORDINATE
ObjectList* obj_list = wxGetApp().obj_list();
if (selection.is_single_full_instance()) {
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
- const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
+ const GLVolume* volume = selection.get_first_volume();
+#if !ENABLE_WORLD_COORDINATE
m_new_position = volume->get_instance_offset();
// Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible.
- if (m_world_coordinates && ! m_uniform_scale &&
+ if (m_world_coordinates && ! m_uniform_scale &&
! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) {
// Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling.
m_uniform_scale = true;
m_lock_bnt->SetLock(true);
}
+#endif // !ENABLE_WORLD_COORDINATE
+#if ENABLE_WORLD_COORDINATE
+ if (is_world_coordinates()) {
+ m_new_position = volume->get_instance_offset();
+#else
if (m_world_coordinates) {
- m_new_rotate_label_string = L("Rotate");
- m_new_rotation = Vec3d::Zero();
- m_new_size = selection.get_scaled_instance_bounding_box().size();
- m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.;
- }
+#endif // ENABLE_WORLD_COORDINATE
+ m_new_rotate_label_string = L("Rotate");
+ m_new_rotation = Vec3d::Zero();
+ m_new_size = selection.get_scaled_instance_bounding_box().size();
+ m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0;
+ }
else {
- m_new_rotation = volume->get_instance_rotation() * (180. / M_PI);
- m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size());
- m_new_scale = volume->get_instance_scaling_factor() * 100.;
+#if ENABLE_WORLD_COORDINATE
+ m_new_move_label_string = L("Translate");
+ m_new_rotate_label_string = L("Rotate");
+ m_new_position = Vec3d::Zero();
+ m_new_rotation = Vec3d::Zero();
+#else
+ m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI);
+#endif // ENABLE_WORLD_COORDINATE
+ m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size());
+ m_new_scale = volume->get_instance_scaling_factor() * 100.0;
}
m_new_enabled = true;
@@ -557,19 +683,52 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
const BoundingBoxf3& box = selection.get_bounding_box();
m_new_position = box.center();
m_new_rotation = Vec3d::Zero();
- m_new_scale = Vec3d(100., 100., 100.);
+ m_new_scale = Vec3d(100.0, 100.0, 100.0);
m_new_size = box.size();
m_new_rotate_label_string = L("Rotate");
m_new_scale_label_string = L("Scale");
m_new_enabled = true;
}
+#if ENABLE_WORLD_COORDINATE
+ else if (selection.is_single_volume_or_modifier()) {
+#else
else if (selection.is_single_modifier() || selection.is_single_volume()) {
+#endif // ENABLE_WORLD_COORDINATE
// the selection contains a single volume
- const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
+ const GLVolume* volume = selection.get_first_volume();
+#if ENABLE_WORLD_COORDINATE
+ if (is_world_coordinates()) {
+ const Geometry::Transformation trafo(volume->world_matrix());
+
+ const Vec3d& offset = trafo.get_offset();
+
+ m_new_position = offset;
+ m_new_rotate_label_string = L("Rotate");
+ m_new_rotation = Vec3d::Zero();
+ m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size();
+ m_new_scale = m_new_size.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 100.0;
+ }
+ else if (is_local_coordinates()) {
+ m_new_move_label_string = L("Translate");
+ m_new_rotate_label_string = L("Rotate");
+ m_new_position = Vec3d::Zero();
+ m_new_rotation = Vec3d::Zero();
+ m_new_scale = volume->get_volume_scaling_factor() * 100.0;
+ m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size());
+ }
+ else {
+#endif // ENABLE_WORLD_COORDINATE
m_new_position = volume->get_volume_offset();
- m_new_rotation = volume->get_volume_rotation() * (180. / M_PI);
- m_new_scale = volume->get_volume_scaling_factor() * 100.;
- m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()));
+ m_new_rotate_label_string = L("Rotate");
+ m_new_rotation = Vec3d::Zero();
+#if ENABLE_WORLD_COORDINATE
+ m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size();
+ m_new_scale = m_new_size.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 100.0;
+ }
+#else
+ m_new_scale = volume->get_volume_scaling_factor() * 100.0;
+ m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()));
+#endif // ENABLE_WORLD_COORDINATE
m_new_enabled = true;
}
else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) {
@@ -635,22 +794,26 @@ void ObjectManipulation::update_if_dirty()
update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation);
}
+#if !ENABLE_WORLD_COORDINATE
if (selection.requires_uniform_scale()) {
m_lock_bnt->SetLock(true);
m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection"));
m_lock_bnt->disable();
}
else {
+#endif // !ENABLE_WORLD_COORDINATE
m_lock_bnt->SetLock(m_uniform_scale);
m_lock_bnt->SetToolTip(wxEmptyString);
m_lock_bnt->enable();
+#if !ENABLE_WORLD_COORDINATE
}
- {
+ {
int new_selection = m_world_coordinates ? 0 : 1;
if (m_word_local_combo->GetSelection() != new_selection)
m_word_local_combo->SetSelection(new_selection);
}
+#endif // !ENABLE_WORLD_COORDINATE
if (m_new_enabled)
m_og->enable();
@@ -677,29 +840,75 @@ void ObjectManipulation::update_reset_buttons_visibility()
bool show_rotation = false;
bool show_scale = false;
bool show_drop_to_bed = false;
-
+#if ENABLE_WORLD_COORDINATE
+ bool show_skew = false;
+
+ if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) {
+ const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() :
+ get_volume_min_z(*selection.get_first_volume());
+
+ show_drop_to_bed = std::abs(min_z) > EPSILON;
+ const GLVolume* volume = selection.get_first_volume();
+ Transform3d rotation = Transform3d::Identity();
+ Transform3d scale = Transform3d::Identity();
+ Geometry::Transformation skew;
+#else
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
- const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
+ const GLVolume* volume = selection.get_first_volume();
Vec3d rotation;
Vec3d scale;
- double min_z = 0.;
+ double min_z = 0.0;
+#endif // ENABLE_WORLD_COORDINATE
if (selection.is_single_full_instance()) {
+#if ENABLE_WORLD_COORDINATE
+ const Geometry::Transformation& trafo = volume->get_instance_transformation();
+ rotation = trafo.get_rotation_matrix();
+ scale = trafo.get_scaling_factor_matrix();
+ const Selection::IndicesList& idxs = selection.get_volume_idxs();
+ for (unsigned int id : idxs) {
+ const Geometry::Transformation world_trafo(selection.get_volume(id)->world_matrix());
+ if (world_trafo.has_skew()) {
+ skew = world_trafo;
+ break;
+ }
+ }
+#else
rotation = volume->get_instance_rotation();
scale = volume->get_instance_scaling_factor();
- min_z = wxGetApp().model().objects[volume->composite_id.object_id]->bounding_box().min.z();
+ min_z = selection.get_scaled_instance_bounding_box().min.z();
+#endif // ENABLE_WORLD_COORDINATE
}
else {
+#if ENABLE_WORLD_COORDINATE
+ const Geometry::Transformation& trafo = volume->get_volume_transformation();
+ rotation = trafo.get_rotation_matrix();
+ scale = trafo.get_scaling_factor_matrix();
+ const Geometry::Transformation world_trafo(volume->world_matrix());
+ if (world_trafo.has_skew())
+ skew = world_trafo;
+#else
rotation = volume->get_volume_rotation();
scale = volume->get_volume_scaling_factor();
min_z = get_volume_min_z(*volume);
+#endif // ENABLE_WORLD_COORDINATE
}
+#if ENABLE_WORLD_COORDINATE
+ show_rotation = !rotation.isApprox(Transform3d::Identity());
+ show_scale = !scale.isApprox(Transform3d::Identity());
+ show_skew = skew.has_skew();
+#else
show_rotation = !rotation.isApprox(Vec3d::Zero());
show_scale = !scale.isApprox(Vec3d::Ones());
show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD;
+#endif // ENABLE_WORLD_COORDINATE
}
+#if ENABLE_WORLD_COORDINATE
+ wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed, show_skew] {
+#else
wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] {
+#endif // ENABLE_WORLD_COORDINATE
// There is a case (under OSX), when this function is called after the Manipulation panel is hidden
// So, let check if Manipulation panel is still shown for this moment
if (!this->IsShown())
@@ -707,6 +916,10 @@ void ObjectManipulation::update_reset_buttons_visibility()
m_reset_rotation_button->Show(show_rotation);
m_reset_scale_button->Show(show_scale);
m_drop_to_bed_button->Show(show_drop_to_bed);
+#if ENABLE_WORLD_COORDINATE
+ m_reset_skew_button->Show(show_skew);
+ m_skew_label->Show(show_skew);
+#endif // ENABLE_WORLD_COORDINATE
// Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time
Sidebar& panel = wxGetApp().sidebar();
@@ -726,9 +939,17 @@ void ObjectManipulation::update_mirror_buttons_visibility()
Selection& selection = canvas->get_selection();
std::array<MirrorButtonState, 3> new_states = {mbHidden, mbHidden, mbHidden};
+#if ENABLE_WORLD_COORDINATE
+ if (is_local_coordinates()) {
+#else
if (!m_world_coordinates) {
+#endif // ENABLE_WORLD_COORDINATE
+#if ENABLE_WORLD_COORDINATE
+ if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) {
+#else
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
- const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
+#endif // ENABLE_WORLD_COORDINATE
+ const GLVolume* volume = selection.get_first_volume();
Vec3d mirror;
if (selection.is_single_full_instance())
@@ -792,6 +1013,19 @@ void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning
m_fix_throught_netfab_bitmap->SetToolTip(tooltip);
}
+#if ENABLE_WORLD_COORDINATE
+wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type)
+{
+ switch (type)
+ {
+ case ECoordinatesType::World: { return _L("World coordinates"); }
+ case ECoordinatesType::Instance: { return _L("Instance coordinates"); }
+ case ECoordinatesType::Local: { return _L("Local coordinates"); }
+ default: { assert(false); return _L("Unknown"); }
+ }
+}
+#endif // ENABLE_WORLD_COORDINATE
+
void ObjectManipulation::reset_settings_value()
{
m_new_position = Vec3d::Zero();
@@ -815,7 +1049,19 @@ void ObjectManipulation::change_position_value(int axis, double value)
auto canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
selection.setup_cache();
+#if ENABLE_WORLD_COORDINATE
+ TransformationType trafo_type;
+ trafo_type.set_relative();
+ switch (get_coordinates_type())
+ {
+ case ECoordinatesType::Instance: { trafo_type.set_instance(); break; }
+ case ECoordinatesType::Local: { trafo_type.set_local(); break; }
+ default: { break; }
+ }
+ selection.translate(position - m_cache.position, trafo_type);
+#else
selection.translate(position - m_cache.position, selection.requires_local_axes());
+#endif // ENABLE_WORLD_COORDINATE
canvas->do_move(L("Set Position"));
m_cache.position = position;
@@ -834,6 +1080,18 @@ void ObjectManipulation::change_rotation_value(int axis, double value)
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
+#if ENABLE_WORLD_COORDINATE
+ TransformationType transformation_type;
+ transformation_type.set_relative();
+ if (selection.is_single_full_instance())
+ transformation_type.set_independent();
+
+ if (is_local_coordinates())
+ transformation_type.set_local();
+
+ if (is_instance_coordinates())
+ transformation_type.set_instance();
+#else
TransformationType transformation_type(TransformationType::World_Relative_Joint);
if (selection.is_single_full_instance() || selection.requires_local_axes())
transformation_type.set_independent();
@@ -842,6 +1100,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value)
// transformation_type.set_absolute();
transformation_type.set_local();
}
+#endif // ENABLE_WORLD_COORDINATE
selection.setup_cache();
selection.rotate(
@@ -887,8 +1146,12 @@ void ObjectManipulation::change_size_value(int axis, double value)
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
Vec3d ref_size = m_cache.size;
+#if ENABLE_WORLD_COORDINATE
+ if (selection.is_single_volume_or_modifier()) {
+#else
if (selection.is_single_volume() || selection.is_single_modifier()) {
- const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
+#endif // ENABLE_WORLD_COORDINATE
+ const GLVolume* v = selection.get_first_volume();
const Vec3d local_size = size.cwiseQuotient(v->get_instance_scaling_factor());
const Vec3d local_ref_size = v->bounding_box().size().cwiseProduct(v->get_volume_scaling_factor());
const Vec3d local_change = local_size.cwiseQuotient(local_ref_size);
@@ -897,11 +1160,19 @@ void ObjectManipulation::change_size_value(int axis, double value)
ref_size = Vec3d::Ones();
}
else if (selection.is_single_full_instance())
- ref_size = m_world_coordinates ?
+#if ENABLE_WORLD_COORDINATE
+ ref_size = is_world_coordinates() ?
+#else
+ ref_size = m_world_coordinates ?
+#endif // ENABLE_WORLD_COORDINATE
selection.get_unscaled_instance_bounding_box().size() :
- wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size();
+ wxGetApp().model().objects[selection.get_first_volume()->object_idx()]->raw_mesh_bounding_box().size();
+#if ENABLE_WORLD_COORDINATE
+ this->do_size(axis, size.cwiseQuotient(ref_size));
+#else
this->do_scale(axis, size.cwiseQuotient(ref_size));
+#endif // ENABLE_WORLD_COORDINATE
m_cache.size = size;
m_cache.size_rounded(axis) = DBL_MAX;
@@ -911,8 +1182,22 @@ void ObjectManipulation::change_size_value(int axis, double value)
void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
{
Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+#if !ENABLE_WORLD_COORDINATE
Vec3d scaling_factor = scale;
+#endif // !ENABLE_WORLD_COORDINATE
+#if ENABLE_WORLD_COORDINATE
+ TransformationType transformation_type;
+ if (is_local_coordinates())
+ transformation_type.set_local();
+ else if (is_instance_coordinates())
+ transformation_type.set_instance();
+
+ if (!selection.is_single_full_instance() && !selection.is_single_volume_or_modifier())
+ transformation_type.set_relative();
+
+ const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale;
+#else
TransformationType transformation_type(TransformationType::World_Relative_Joint);
if (selection.is_single_full_instance()) {
transformation_type.set_absolute();
@@ -922,12 +1207,31 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
if (m_uniform_scale || selection.requires_uniform_scale())
scaling_factor = scale(axis) * Vec3d::Ones();
+#endif // ENABLE_WORLD_COORDINATE
selection.setup_cache();
selection.scale(scaling_factor, transformation_type);
wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale"));
}
+#if ENABLE_WORLD_COORDINATE
+void ObjectManipulation::do_size(int axis, const Vec3d& scale) const
+{
+ Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+
+ TransformationType transformation_type;
+ if (is_local_coordinates())
+ transformation_type.set_local();
+ else if (is_instance_coordinates())
+ transformation_type.set_instance();
+
+ const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale;
+ selection.setup_cache();
+ selection.scale(scaling_factor, transformation_type);
+ wxGetApp().plater()->canvas3D()->do_scale(L("Set Size"));
+}
+#endif // ENABLE_WORLD_COORDINATE
+
void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value)
{
if (!m_cache.is_valid())
@@ -962,17 +1266,26 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double
}
}
-void ObjectManipulation::set_uniform_scaling(const bool new_value)
+void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale)
{
- const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection();
- if (selection.is_single_full_instance() && m_world_coordinates && !new_value) {
+#if ENABLE_WORLD_COORDINATE
+ if (!use_uniform_scale)
+ // Recalculate cached values at this panel, refresh the screen.
+ this->UpdateAndShow(true);
+
+ m_uniform_scale = use_uniform_scale;
+
+ set_dirty();
+#else
+ const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ if (selection.is_single_full_instance() && m_world_coordinates && !use_uniform_scale) {
// Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible.
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
- const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
+ const GLVolume* volume = selection.get_first_volume();
// Is the angle close to a multiple of 90 degrees?
- if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) {
+ if (!Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) {
// Cannot apply scaling in the world coordinate system.
- //wxMessageDialog dlg(GUI::wxGetApp().mainframe,
+ //wxMessageDialog dlg(GUI::wxGetApp().mainframe,
MessageDialog dlg(GUI::wxGetApp().mainframe,
_L("The currently manipulated object is tilted (rotation angles are not multiples of 90°).\n"
"Non-uniform scaling of tilted objects is only possible in the World coordinate system,\n"
@@ -980,7 +1293,7 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value)
_L("This operation is irreversible.\n"
"Do you want to proceed?"),
SLIC3R_APP_NAME,
- wxYES_NO | wxCANCEL | wxCANCEL_DEFAULT | wxICON_QUESTION);
+ wxYES_NO | wxCANCEL | wxCANCEL_DEFAULT | wxICON_QUESTION);
if (dlg.ShowModal() != wxID_YES) {
// Enforce uniform scaling.
m_lock_bnt->SetLock(true);
@@ -994,9 +1307,29 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value)
this->UpdateAndShow(true);
}
}
- m_uniform_scale = new_value;
+
+ m_uniform_scale = use_uniform_scale;
+#endif // ENABLE_WORLD_COORDINATE
}
+#if ENABLE_WORLD_COORDINATE
+void ObjectManipulation::set_coordinates_type(ECoordinatesType type)
+{
+ if (wxGetApp().get_mode() == comSimple)
+ type = ECoordinatesType::World;
+
+ if (m_coordinates_type == type)
+ return;
+
+ m_coordinates_type = type;
+ this->UpdateAndShow(true);
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ canvas->get_gizmos_manager().update_data();
+ canvas->set_as_dirty();
+ canvas->request_extra_frame();
+}
+#endif // ENABLE_WORLD_COORDINATE
+
void ObjectManipulation::msw_rescale()
{
const int em = wxGetApp().em_unit();
@@ -1014,6 +1347,9 @@ void ObjectManipulation::msw_rescale()
m_mirror_bitmap_hidden.msw_rescale();
m_reset_scale_button->msw_rescale();
m_reset_rotation_button->msw_rescale();
+#if ENABLE_WORLD_COORDINATE
+ m_reset_skew_button->msw_rescale();
+#endif /// ENABLE_WORLD_COORDINATE
m_drop_to_bed_button->msw_rescale();
m_lock_bnt->msw_rescale();
@@ -1053,6 +1389,9 @@ void ObjectManipulation::sys_color_changed()
m_mirror_bitmap_hidden.msw_rescale();
m_reset_scale_button->msw_rescale();
m_reset_rotation_button->msw_rescale();
+#if ENABLE_WORLD_COORDINATE
+ m_reset_skew_button->msw_rescale();
+#endif // ENABLE_WORLD_COORDINATE
m_drop_to_bed_button->msw_rescale();
m_lock_bnt->msw_rescale();
@@ -1060,6 +1399,19 @@ void ObjectManipulation::sys_color_changed()
m_mirror_buttons[id].first->msw_rescale();
}
+#if ENABLE_WORLD_COORDINATE
+void ObjectManipulation::set_coordinates_type(const wxString& type_string)
+{
+ ECoordinatesType type = ECoordinatesType::World;
+ if (type_string == coordinate_type_str(ECoordinatesType::Instance))
+ type = ECoordinatesType::Instance;
+ else if (type_string == coordinate_type_str(ECoordinatesType::Local))
+ type = ECoordinatesType::Local;
+
+ this->set_coordinates_type(type);
+}
+#endif // ENABLE_WORLD_COORDINATE
+
static const char axes[] = { 'x', 'y', 'z' };
ManipulationEditor::ManipulationEditor(ObjectManipulation* parent,
const std::string& opt_key,
@@ -1101,8 +1453,8 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent,
parent->set_focused_editor(nullptr);
#if ENABLE_OBJECT_MANIPULATOR_FOCUS
- // if the widgets exchanging focus are both manipulator fields, call kill_focus
- if (dynamic_cast<ManipulationEditor*>(e.GetEventObject()) != nullptr && dynamic_cast<ManipulationEditor*>(e.GetWindow()) != nullptr)
+ // if the widgets loosing focus is a manipulator field, call kill_focus
+ if (dynamic_cast<ManipulationEditor*>(e.GetEventObject()) != nullptr)
#else
if (!m_enter_pressed)
#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp
index a15c72fb8..cfa43b43a 100644
--- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp
@@ -5,6 +5,9 @@
#include "GUI_ObjectSettings.hpp"
#include "GUI_ObjectList.hpp"
+#if ENABLE_WORLD_COORDINATE
+#include "GUI_Geometry.hpp"
+#endif // ENABLE_WORLD_COORDINATE
#include "libslic3r/Point.hpp"
#include <float.h>
@@ -57,6 +60,10 @@ public:
void set_value(const wxString& new_value);
void kill_focus(ObjectManipulation *parent);
+#if ENABLE_WORLD_COORDINATE
+ const std::string& get_full_opt_name() const { return m_full_opt_name; }
+#endif // ENABLE_WORLD_COORDINATE
+
private:
double get_value();
};
@@ -113,9 +120,12 @@ private:
wxStaticText* m_empty_str = nullptr;
// Non-owning pointers to the reset buttons, so we can hide and show them.
- ScalableButton* m_reset_scale_button = nullptr;
- ScalableButton* m_reset_rotation_button = nullptr;
- ScalableButton* m_drop_to_bed_button = nullptr;
+ ScalableButton* m_reset_scale_button{ nullptr };
+ ScalableButton* m_reset_rotation_button{ nullptr };
+#if ENABLE_WORLD_COORDINATE
+ ScalableButton* m_reset_skew_button{ nullptr };
+#endif // ENABLE_WORLD_COORDINATE
+ ScalableButton* m_drop_to_bed_button{ nullptr };
wxCheckBox* m_check_inch {nullptr};
@@ -144,22 +154,35 @@ private:
Vec3d m_new_size;
bool m_new_enabled {true};
bool m_uniform_scale {true};
+#if ENABLE_WORLD_COORDINATE
+ ECoordinatesType m_coordinates_type{ ECoordinatesType::World };
+#else
// Does the object manipulation panel work in World or Local coordinates?
bool m_world_coordinates = true;
+#endif // ENABLE_WORLD_COORDINATE
LockButton* m_lock_bnt{ nullptr };
choice_ctrl* m_word_local_combo { nullptr };
ScalableBitmap m_manifold_warning_bmp;
wxStaticBitmap* m_fix_throught_netfab_bitmap;
+#if ENABLE_WORLD_COORDINATE
+ // Currently focused editor (nullptr if none)
+ ManipulationEditor* m_focused_editor{ nullptr };
+#else
#ifndef __APPLE__
// Currently focused editor (nullptr if none)
ManipulationEditor* m_focused_editor {nullptr};
#endif // __APPLE__
+#endif // ENABLE_WORLD_COORDINATE
wxFlexGridSizer* m_main_grid_sizer;
wxFlexGridSizer* m_labels_grid_sizer;
+#if ENABLE_WORLD_COORDINATE
+ wxStaticText* m_skew_label{ nullptr };
+#endif // ENABLE_WORLD_COORDINATE
+
// sizers, used for msw_rescale
wxBoxSizer* m_word_local_combo_sizer;
std::vector<wxBoxSizer*> m_rescalable_sizers;
@@ -180,11 +203,19 @@ public:
// Called from the App to update the UI if dirty.
void update_if_dirty();
- void set_uniform_scaling(const bool uniform_scale);
+ void set_uniform_scaling(const bool use_uniform_scale);
bool get_uniform_scaling() const { return m_uniform_scale; }
+#if ENABLE_WORLD_COORDINATE
+ void set_coordinates_type(ECoordinatesType type);
+ ECoordinatesType get_coordinates_type() const { return m_coordinates_type; }
+ bool is_world_coordinates() const { return m_coordinates_type == ECoordinatesType::World; }
+ bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; }
+ bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; }
+#else
// Does the object manipulation panel work in World or Local coordinates?
void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); }
bool get_world_coordinates() const { return m_world_coordinates; }
+#endif // ENABLE_WORLD_COORDINATE
void reset_cache() { m_cache.reset(); }
#ifndef __APPLE__
@@ -200,11 +231,23 @@ public:
void sys_color_changed();
void on_change(const std::string& opt_key, int axis, double new_value);
void set_focused_editor(ManipulationEditor* focused_editor) {
+#if ENABLE_WORLD_COORDINATE
+ m_focused_editor = focused_editor;
+#else
#ifndef __APPLE__
m_focused_editor = focused_editor;
#endif // __APPLE__
+#endif // ENABLE_WORLD_COORDINATE
}
+#if ENABLE_WORLD_COORDINATE
+ ManipulationEditor* get_focused_editor() { return m_focused_editor; }
+#endif // ENABLE_WORLD_COORDINATE
+
+#if ENABLE_WORLD_COORDINATE
+ static wxString coordinate_type_str(ECoordinatesType type);
+#endif // ENABLE_WORLD_COORDINATE
+
private:
void reset_settings_value();
void update_settings_value(const Selection& selection);
@@ -220,6 +263,11 @@ private:
void change_scale_value(int axis, double value);
void change_size_value(int axis, double value);
void do_scale(int axis, const Vec3d &scale) const;
+#if ENABLE_WORLD_COORDINATE
+ void do_size(int axis, const Vec3d& scale) const;
+
+ void set_coordinates_type(const wxString& type_string);
+#endif // ENABLE_WORLD_COORDINATE
};
}}
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
index 1e0ff6c9e..0810ddc97 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
@@ -333,7 +333,11 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
wxGetApp().obj_manipul()->set_dirty();
m_parent.set_as_dirty();
return true;
- } else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) {
+ }
+ else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) {
+#if ENABLE_WORLD_COORDINATE
+ do_stop_dragging(is_leaving);
+#else
for (auto &grabber : m_grabbers) grabber.dragging = false;
m_dragging = false;
@@ -356,12 +360,41 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
m_parent.refresh_camera_scene_box();
+#endif // ENABLE_WORLD_COORDINATE
return true;
}
}
return false;
}
+#if ENABLE_WORLD_COORDINATE
+void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup)
+{
+ for (auto& grabber : m_grabbers) grabber.dragging = false;
+ m_dragging = false;
+
+ // NOTE: This should be part of GLCanvas3D
+ // Reset hover_id when leave window
+ if (perform_mouse_cleanup) m_parent.mouse_up_cleanup();
+
+ on_stop_dragging();
+
+ // There is prediction that after draggign, data are changed
+ // Data are updated twice also by canvas3D::reload_scene.
+ // Should be fixed.
+ m_parent.get_gizmos_manager().update_data();
+
+ wxGetApp().obj_manipul()->set_dirty();
+
+ // Let the plater know that the dragging finished, so a delayed
+ // refresh of the scene with the background processing data should
+ // be performed.
+ m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
+ // updates camera target constraints
+ m_parent.refresh_camera_scene_box();
+}
+#endif // ENABLE_WORLD_COORDINATE
+
std::string GLGizmoBase::format(float value, unsigned int decimals) const
{
return Slic3r::string_printf("%.*f", decimals, value);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
index f61654183..95763f004 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
@@ -89,151 +89,156 @@ protected:
static GLModel s_cone;
#else
GLModel m_cube;
-#endif // ENABLE_GIZMO_GRABBER_REFACTOR
- };
-
-public:
- enum EState
- {
- Off,
- On,
- Num_States
- };
-
- struct UpdateData
- {
- const Linef3& mouse_ray;
- const Point& mouse_pos;
-
- UpdateData(const Linef3& mouse_ray, const Point& mouse_pos)
- : mouse_ray(mouse_ray), mouse_pos(mouse_pos)
- {}
- };
-
-protected:
- GLCanvas3D& m_parent;
- int m_group_id; // TODO: remove only for rotate
- EState m_state;
- int m_shortcut_key;
- std::string m_icon_filename;
- unsigned int m_sprite_id;
- int m_hover_id;
- bool m_dragging;
- mutable std::vector<Grabber> m_grabbers;
- ImGuiWrapper* m_imgui;
- bool m_first_input_window_render;
- CommonGizmosDataPool* m_c;
-public:
- GLGizmoBase(GLCanvas3D& parent,
- const std::string& icon_filename,
- unsigned int sprite_id);
- virtual ~GLGizmoBase() = default;
-
- bool init() { return on_init(); }
-
- void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); }
- void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); }
-
- std::string get_name(bool include_shortcut = true) const;
-
- EState get_state() const { return m_state; }
- void set_state(EState state) { m_state = state; on_set_state(); }
-
- int get_shortcut_key() const { return m_shortcut_key; }
-
- const std::string& get_icon_filename() const { return m_icon_filename; }
-
- bool is_activable() const { return on_is_activable(); }
- bool is_selectable() const { return on_is_selectable(); }
- CommonGizmosDataID get_requirements() const { return on_get_requirements(); }
- virtual bool wants_enter_leave_snapshots() const { return false; }
- virtual std::string get_gizmo_entering_text() const { assert(false); return ""; }
- virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; }
- virtual std::string get_action_snapshot_name() { return _u8L("Gizmo action"); }
- void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; }
-
- unsigned int get_sprite_id() const { return m_sprite_id; }
-
- int get_hover_id() const { return m_hover_id; }
- void set_hover_id(int id);
-
- bool is_dragging() const { return m_dragging; }
-
- // returns True when Gizmo changed its state
- bool update_items_state();
-
- void render() { on_render(); }
- void render_for_picking() { on_render_for_picking(); }
- void render_input_window(float x, float y, float bottom_limit);
-
- /// <summary>
- /// Mouse tooltip text
- /// </summary>
- /// <returns>Text to be visible in mouse tooltip</returns>
- virtual std::string get_tooltip() const { return ""; }
-
- /// <summary>
- /// Is called when data (Selection) is changed
- /// </summary>
- virtual void data_changed(){};
-
- /// <summary>
- /// Implement when want to process mouse events in gizmo
- /// Click, Right click, move, drag, ...
- /// </summary>
- /// <param name="mouse_event">Keep information about mouse click</param>
- /// <returns>Return True when use the information and don't want to propagate it otherwise False.</returns>
- virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; }
-protected:
- virtual bool on_init() = 0;
- virtual void on_load(cereal::BinaryInputArchive& ar) {}
- virtual void on_save(cereal::BinaryOutputArchive& ar) const {}
- virtual std::string on_get_name() const = 0;
- virtual void on_set_state() {}
- virtual void on_set_hover_id() {}
- virtual bool on_is_activable() const { return true; }
- virtual bool on_is_selectable() const { return true; }
- virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); }
- virtual void on_enable_grabber(unsigned int id) {}
- virtual void on_disable_grabber(unsigned int id) {}
-
- // called inside use_grabbers
- virtual void on_start_dragging() {}
- virtual void on_stop_dragging() {}
- virtual void on_dragging(const UpdateData& data) {}
-
- virtual void on_render() = 0;
- virtual void on_render_for_picking() = 0;
- virtual void on_render_input_window(float x, float y, float bottom_limit) {}
-
- // Returns the picking color for the given id, based on the BASE_ID constant
- // No check is made for clashing with other picking color (i.e. GLVolumes)
- ColorRGBA picking_color_component(unsigned int id) const;
-
- void render_grabbers(const BoundingBoxf3& box) const;
- void render_grabbers(float size) const;
- void render_grabbers_for_picking(const BoundingBoxf3& box) const;
-
- std::string format(float value, unsigned int decimals) const;
-
- // Mark gizmo as dirty to Re-Render when idle()
- void set_dirty();
-
- /// <summary>
- /// function which
- /// Set up m_dragging and call functions
- /// on_start_dragging / on_dragging / on_stop_dragging
- /// </summary>
- /// <param name="mouse_event">Keep information about mouse click</param>
- /// <returns>same as on_mouse</returns>
- bool use_grabbers(const wxMouseEvent &mouse_event);
-private:
- // Flag for dirty visible state of Gizmo
- // When True then need new rendering
- bool m_dirty;
-};
-
-} // namespace GUI
-} // namespace Slic3r
-
-#endif // slic3r_GLGizmoBase_hpp_
+#endif // ENABLE_GIZMO_GRABBER_REFACTOR
+ };
+
+public:
+ enum EState
+ {
+ Off,
+ On,
+ Num_States
+ };
+
+ struct UpdateData
+ {
+ const Linef3& mouse_ray;
+ const Point& mouse_pos;
+
+ UpdateData(const Linef3& mouse_ray, const Point& mouse_pos)
+ : mouse_ray(mouse_ray), mouse_pos(mouse_pos)
+ {}
+ };
+
+protected:
+ GLCanvas3D& m_parent;
+ int m_group_id; // TODO: remove only for rotate
+ EState m_state;
+ int m_shortcut_key;
+ std::string m_icon_filename;
+ unsigned int m_sprite_id;
+ int m_hover_id;
+ bool m_dragging;
+ mutable std::vector<Grabber> m_grabbers;
+ ImGuiWrapper* m_imgui;
+ bool m_first_input_window_render;
+ CommonGizmosDataPool* m_c;
+public:
+ GLGizmoBase(GLCanvas3D& parent,
+ const std::string& icon_filename,
+ unsigned int sprite_id);
+ virtual ~GLGizmoBase() = default;
+
+ bool init() { return on_init(); }
+
+ void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); }
+ void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); }
+
+ std::string get_name(bool include_shortcut = true) const;
+
+ EState get_state() const { return m_state; }
+ void set_state(EState state) { m_state = state; on_set_state(); }
+
+ int get_shortcut_key() const { return m_shortcut_key; }
+
+ const std::string& get_icon_filename() const { return m_icon_filename; }
+
+ bool is_activable() const { return on_is_activable(); }
+ bool is_selectable() const { return on_is_selectable(); }
+ CommonGizmosDataID get_requirements() const { return on_get_requirements(); }
+ virtual bool wants_enter_leave_snapshots() const { return false; }
+ virtual std::string get_gizmo_entering_text() const { assert(false); return ""; }
+ virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; }
+ virtual std::string get_action_snapshot_name() { return _u8L("Gizmo action"); }
+ void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; }
+
+ unsigned int get_sprite_id() const { return m_sprite_id; }
+
+ int get_hover_id() const { return m_hover_id; }
+ void set_hover_id(int id);
+
+ bool is_dragging() const { return m_dragging; }
+
+ // returns True when Gizmo changed its state
+ bool update_items_state();
+
+ void render() { on_render(); }
+ void render_for_picking() { on_render_for_picking(); }
+ void render_input_window(float x, float y, float bottom_limit);
+
+ /// <summary>
+ /// Mouse tooltip text
+ /// </summary>
+ /// <returns>Text to be visible in mouse tooltip</returns>
+ virtual std::string get_tooltip() const { return ""; }
+
+ /// <summary>
+ /// Is called when data (Selection) is changed
+ /// </summary>
+ virtual void data_changed(){};
+
+ /// <summary>
+ /// Implement when want to process mouse events in gizmo
+ /// Click, Right click, move, drag, ...
+ /// </summary>
+ /// <param name="mouse_event">Keep information about mouse click</param>
+ /// <returns>Return True when use the information and don't want to propagate it otherwise False.</returns>
+ virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; }
+protected:
+ virtual bool on_init() = 0;
+ virtual void on_load(cereal::BinaryInputArchive& ar) {}
+ virtual void on_save(cereal::BinaryOutputArchive& ar) const {}
+ virtual std::string on_get_name() const = 0;
+ virtual void on_set_state() {}
+ virtual void on_set_hover_id() {}
+ virtual bool on_is_activable() const { return true; }
+ virtual bool on_is_selectable() const { return true; }
+ virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); }
+ virtual void on_enable_grabber(unsigned int id) {}
+ virtual void on_disable_grabber(unsigned int id) {}
+
+ // called inside use_grabbers
+ virtual void on_start_dragging() {}
+ virtual void on_stop_dragging() {}
+ virtual void on_dragging(const UpdateData& data) {}
+
+ virtual void on_render() = 0;
+ virtual void on_render_for_picking() = 0;
+ virtual void on_render_input_window(float x, float y, float bottom_limit) {}
+
+ // Returns the picking color for the given id, based on the BASE_ID constant
+ // No check is made for clashing with other picking color (i.e. GLVolumes)
+ ColorRGBA picking_color_component(unsigned int id) const;
+
+ void render_grabbers(const BoundingBoxf3& box) const;
+ void render_grabbers(float size) const;
+ void render_grabbers_for_picking(const BoundingBoxf3& box) const;
+
+ std::string format(float value, unsigned int decimals) const;
+
+ // Mark gizmo as dirty to Re-Render when idle()
+ void set_dirty();
+
+ /// <summary>
+ /// function which
+ /// Set up m_dragging and call functions
+ /// on_start_dragging / on_dragging / on_stop_dragging
+ /// </summary>
+ /// <param name="mouse_event">Keep information about mouse click</param>
+ /// <returns>same as on_mouse</returns>
+ bool use_grabbers(const wxMouseEvent &mouse_event);
+
+#if ENABLE_WORLD_COORDINATE
+ void do_stop_dragging(bool perform_mouse_cleanup);
+#endif // ENABLE_WORLD_COORDINATE
+
+private:
+ // Flag for dirty visible state of Gizmo
+ // When True then need new rendering
+ bool m_dirty;
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLGizmoBase_hpp_
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
index 9a87d5a45..dfad3817c 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
@@ -1,416 +1,416 @@
-// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
-#include "GLGizmoCut.hpp"
-#include "slic3r/GUI/GLCanvas3D.hpp"
-
-#include <GL/glew.h>
-
-#include <wx/button.h>
-#include <wx/checkbox.h>
-#include <wx/stattext.h>
-#include <wx/sizer.h>
-
-#include <algorithm>
-
-#include "slic3r/GUI/GUI_App.hpp"
-#include "slic3r/GUI/Plater.hpp"
-#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
-#include "libslic3r/AppConfig.hpp"
-#include "libslic3r/Model.hpp"
-#include "libslic3r/TriangleMeshSlicer.hpp"
-
-namespace Slic3r {
-namespace GUI {
-
-const double GLGizmoCut::Offset = 10.0;
-const double GLGizmoCut::Margin = 20.0;
-static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE();
-
-GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
- : GLGizmoBase(parent, icon_filename, sprite_id)
-{}
-
-std::string GLGizmoCut::get_tooltip() const
-{
- double cut_z = m_cut_z;
- if (wxGetApp().app_config->get("use_inches") == "1")
- cut_z *= ObjectManipulation::mm_to_in;
-
- return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : "";
-}
-
-bool GLGizmoCut::on_mouse(const wxMouseEvent &mouse_event)
-{
- return use_grabbers(mouse_event);
-}
-
-bool GLGizmoCut::on_init()
-{
- m_grabbers.emplace_back();
- m_shortcut_key = WXK_CONTROL_C;
- return true;
-}
-
-std::string GLGizmoCut::on_get_name() const
-{
- return _u8L("Cut");
-}
-
-void GLGizmoCut::on_set_state()
-{
- // Reset m_cut_z on gizmo activation
- if (m_state == On)
- m_cut_z = bounding_box().center().z();
-}
-
-bool GLGizmoCut::on_is_activable() const
-{
- const Selection& selection = m_parent.get_selection();
- return selection.is_single_full_instance() && !selection.is_wipe_tower();
-}
-
-void GLGizmoCut::on_start_dragging()
-{
- if (m_hover_id == -1)
- return;
-
- const BoundingBoxf3 box = bounding_box();
- m_max_z = box.max.z();
- m_start_z = m_cut_z;
- m_drag_pos = m_grabbers[m_hover_id].center;
- m_drag_center = box.center();
- m_drag_center.z() = m_cut_z;
-}
-
-void GLGizmoCut::on_dragging(const UpdateData &data)
-{
- assert(m_hover_id != -1);
- set_cut_z(m_start_z + calc_projection(data.mouse_ray));
-}
-
-void GLGizmoCut::on_render()
-{
- const BoundingBoxf3 box = bounding_box();
- Vec3d plane_center = box.center();
- plane_center.z() = m_cut_z;
- m_max_z = box.max.z();
- set_cut_z(m_cut_z);
-
- update_contours();
-
- const float min_x = box.min.x() - Margin;
- const float max_x = box.max.x() + Margin;
- const float min_y = box.min.y() - Margin;
- const float max_y = box.max.y() + Margin;
- glsafe(::glEnable(GL_DEPTH_TEST));
- glsafe(::glDisable(GL_CULL_FACE));
- glsafe(::glEnable(GL_BLEND));
- glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLShaderProgram* shader = wxGetApp().get_shader("flat");
- if (shader != nullptr) {
- shader->start_using();
- const Vec3d diff = plane_center - m_old_center;
- // Z changed when move with cut plane
- // X and Y changed when move with cutted object
- bool is_changed = std::abs(diff.x()) > EPSILON ||
- std::abs(diff.y()) > EPSILON ||
- std::abs(diff.z()) > EPSILON;
- m_old_center = plane_center;
-
- if (!m_plane.is_initialized() || is_changed) {
- m_plane.reset();
-
- GLModel::Geometry init_data;
- init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 };
- init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f };
- init_data.reserve_vertices(4);
- init_data.reserve_indices(6);
-
- // vertices
- init_data.add_vertex(Vec3f(min_x, min_y, plane_center.z()));
- init_data.add_vertex(Vec3f(max_x, min_y, plane_center.z()));
- init_data.add_vertex(Vec3f(max_x, max_y, plane_center.z()));
- init_data.add_vertex(Vec3f(min_x, max_y, plane_center.z()));
-
- // indices
- init_data.add_triangle(0, 1, 2);
- init_data.add_triangle(2, 3, 0);
-
- m_plane.init_from(std::move(init_data));
- }
-
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Camera& camera = wxGetApp().plater()->get_camera();
- shader->set_uniform("view_model_matrix", camera.get_view_matrix());
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
- m_plane.render();
-#else
- // Draw the cutting plane
- ::glBegin(GL_QUADS);
- ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f);
- ::glVertex3f(min_x, min_y, plane_center.z());
- ::glVertex3f(max_x, min_y, plane_center.z());
- ::glVertex3f(max_x, max_y, plane_center.z());
- ::glVertex3f(min_x, max_y, plane_center.z());
- glsafe(::glEnd());
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- glsafe(::glEnable(GL_CULL_FACE));
- glsafe(::glDisable(GL_BLEND));
-
- // Draw the grabber and the connecting line
- m_grabbers[0].center = plane_center;
- m_grabbers[0].center.z() = plane_center.z() + Offset;
-
- glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
-
- glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f));
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- if (!m_grabber_connection.is_initialized() || is_changed) {
- m_grabber_connection.reset();
-
- GLModel::Geometry init_data;
- init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
- init_data.color = ColorRGBA::YELLOW();
- init_data.reserve_vertices(2);
- init_data.reserve_indices(2);
-
- // vertices
- init_data.add_vertex((Vec3f)plane_center.cast<float>());
- init_data.add_vertex((Vec3f)m_grabbers[0].center.cast<float>());
-
- // indices
- init_data.add_line(0, 1);
-
- m_grabber_connection.init_from(std::move(init_data));
- }
-
- m_grabber_connection.render();
-
- shader->stop_using();
- }
-
- shader = wxGetApp().get_shader("gouraud_light");
-#else
- glsafe(::glColor3f(1.0, 1.0, 0.0));
- ::glBegin(GL_LINES);
- ::glVertex3dv(plane_center.data());
- ::glVertex3dv(m_grabbers[0].center.data());
- glsafe(::glEnd());
-
- GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- if (shader != nullptr) {
- shader->start_using();
- shader->set_uniform("emission_factor", 0.1f);
-
- m_grabbers[0].color = GRABBER_COLOR;
- m_grabbers[0].render(m_hover_id == 0, float((box.size().x() + box.size().y() + box.size().z()) / 3.0));
-
- shader->stop_using();
- }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- shader = wxGetApp().get_shader("flat");
- if (shader != nullptr) {
- shader->start_using();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Camera& camera = wxGetApp().plater()->get_camera();
- shader->set_uniform("view_model_matrix", camera.get_view_matrix()* Geometry::assemble_transform(m_cut_contours.shift));
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
-#else
- glsafe(::glPushMatrix());
- glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z()));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glLineWidth(2.0f));
- m_cut_contours.contours.render();
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- shader->stop_using();
- }
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- }
-
-void GLGizmoCut::on_render_for_picking()
-{
- glsafe(::glDisable(GL_DEPTH_TEST));
- render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
-}
-
-void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
-{
- static float last_y = 0.0f;
- static float last_h = 0.0f;
-
- m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
-
- const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
-
- // adjust window position to avoid overlap the view toolbar
- const float win_h = ImGui::GetWindowHeight();
- y = std::min(y, bottom_limit - win_h);
- ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
- if (last_h != win_h || last_y != y) {
- // ask canvas for another frame to render the window in the correct position
- m_imgui->set_requires_extra_frame();
- if (last_h != win_h)
- last_h = win_h;
- if (last_y != y)
- last_y = y;
- }
-
- ImGui::AlignTextToFramePadding();
- m_imgui->text("Z");
- ImGui::SameLine();
- ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f);
-
- double cut_z = m_cut_z;
- if (imperial_units)
- cut_z *= ObjectManipulation::mm_to_in;
- ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal);
-
- ImGui::SameLine();
- m_imgui->text(imperial_units ? _L("in") : _L("mm"));
-
- m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0);
-
- ImGui::Separator();
-
- m_imgui->checkbox(_L("Keep upper part"), m_keep_upper);
- m_imgui->checkbox(_L("Keep lower part"), m_keep_lower);
- m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower);
-
- ImGui::Separator();
-
- m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z);
- const bool cut_clicked = m_imgui->button(_L("Perform cut"));
- m_imgui->disabled_end();
-
- m_imgui->end();
-
- if (cut_clicked && (m_keep_upper || m_keep_lower))
- perform_cut(m_parent.get_selection());
-}
-
-void GLGizmoCut::set_cut_z(double cut_z)
-{
- // Clamp the plane to the object's bounding box
- m_cut_z = std::clamp(cut_z, 0.0, m_max_z);
-}
-
-void GLGizmoCut::perform_cut(const Selection& selection)
-{
- const int instance_idx = selection.get_instance_idx();
- const int object_idx = selection.get_object_idx();
-
- wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection");
-
- // m_cut_z is the distance from the bed. Subtract possible SLA elevation.
- const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin());
- const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
-
- if (0.0 < object_cut_z && object_cut_z < m_max_z)
- wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z,
- only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
- only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) |
- only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower));
- else {
- // the object is SLA-elevated and the plane is under it.
- }
-}
-
-double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const
-{
- double projection = 0.0;
-
- const Vec3d starting_vec = m_drag_pos - m_drag_center;
- const double len_starting_vec = starting_vec.norm();
- if (len_starting_vec != 0.0) {
- const Vec3d mouse_dir = mouse_ray.unit_vector();
- // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
- // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
- // in our case plane normal and ray direction are the same (orthogonal view)
- // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
- const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
- // vector from the starting position to the found intersection
- const Vec3d inters_vec = inters - m_drag_pos;
-
- // finds projection of the vector along the staring direction
- projection = inters_vec.dot(starting_vec.normalized());
- }
- return projection;
-}
-
-BoundingBoxf3 GLGizmoCut::bounding_box() const
-{
- BoundingBoxf3 ret;
- const Selection& selection = m_parent.get_selection();
- const Selection::IndicesList& idxs = selection.get_volume_idxs();
- return selection.get_bounding_box();
-
- for (unsigned int i : idxs) {
- const GLVolume* volume = selection.get_volume(i);
- if (!volume->is_modifier)
- ret.merge(volume->transformed_convex_hull_bounding_box());
- }
- return ret;
-}
-
-void GLGizmoCut::update_contours()
-{
- const Selection& selection = m_parent.get_selection();
- const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin());
- const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box();
-
- const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()];
- const int instance_idx = selection.get_instance_idx();
- std::vector<ObjectID> volumes_idxs = std::vector<ObjectID>(model_object->volumes.size());
- for (size_t i = 0; i < model_object->volumes.size(); ++i) {
- volumes_idxs[i] = model_object->volumes[i]->id();
- }
-
- if (0.0 < m_cut_z && m_cut_z < m_max_z) {
- if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() ||
- m_cut_contours.instance_idx != instance_idx || m_cut_contours.volumes_idxs != volumes_idxs) {
- m_cut_contours.cut_z = m_cut_z;
-
- if (m_cut_contours.object_id != model_object->id() || m_cut_contours.volumes_idxs != volumes_idxs)
- m_cut_contours.mesh = model_object->raw_mesh();
-
- m_cut_contours.position = box.center();
- m_cut_contours.shift = Vec3d::Zero();
- m_cut_contours.object_id = model_object->id();
- m_cut_contours.instance_idx = instance_idx;
- m_cut_contours.volumes_idxs = volumes_idxs;
- m_cut_contours.contours.reset();
-
- MeshSlicingParams slicing_params;
- slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix();
- slicing_params.trafo.pretranslate(Vec3d(0., 0., first_glvolume->get_sla_shift_z()));
-
- const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params);
- if (!polys.empty()) {
- m_cut_contours.contours.init_from(polys, static_cast<float>(m_cut_z));
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_cut_contours.contours.set_color(ColorRGBA::WHITE());
-#else
- m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f });
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- }
- }
- else if (box.center() != m_cut_contours.position) {
- m_cut_contours.shift = box.center() - m_cut_contours.position;
- }
- }
- else
- m_cut_contours.contours.reset();
-}
-
-} // namespace GUI
-} // namespace Slic3r
+// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
+#include "GLGizmoCut.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+
+#include <GL/glew.h>
+
+#include <wx/button.h>
+#include <wx/checkbox.h>
+#include <wx/stattext.h>
+#include <wx/sizer.h>
+
+#include <algorithm>
+
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
+#include "libslic3r/AppConfig.hpp"
+#include "libslic3r/Model.hpp"
+#include "libslic3r/TriangleMeshSlicer.hpp"
+
+namespace Slic3r {
+namespace GUI {
+
+const double GLGizmoCut::Offset = 10.0;
+const double GLGizmoCut::Margin = 20.0;
+static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE();
+
+GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
+ : GLGizmoBase(parent, icon_filename, sprite_id)
+{}
+
+std::string GLGizmoCut::get_tooltip() const
+{
+ double cut_z = m_cut_z;
+ if (wxGetApp().app_config->get("use_inches") == "1")
+ cut_z *= ObjectManipulation::mm_to_in;
+
+ return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : "";
+}
+
+bool GLGizmoCut::on_mouse(const wxMouseEvent &mouse_event)
+{
+ return use_grabbers(mouse_event);
+}
+
+bool GLGizmoCut::on_init()
+{
+ m_grabbers.emplace_back();
+ m_shortcut_key = WXK_CONTROL_C;
+ return true;
+}
+
+std::string GLGizmoCut::on_get_name() const
+{
+ return _u8L("Cut");
+}
+
+void GLGizmoCut::on_set_state()
+{
+ // Reset m_cut_z on gizmo activation
+ if (m_state == On)
+ m_cut_z = bounding_box().center().z();
+}
+
+bool GLGizmoCut::on_is_activable() const
+{
+ const Selection& selection = m_parent.get_selection();
+ return selection.is_single_full_instance() && !selection.is_wipe_tower();
+}
+
+void GLGizmoCut::on_start_dragging()
+{
+ if (m_hover_id == -1)
+ return;
+
+ const BoundingBoxf3 box = bounding_box();
+ m_max_z = box.max.z();
+ m_start_z = m_cut_z;
+ m_drag_pos = m_grabbers[m_hover_id].center;
+ m_drag_center = box.center();
+ m_drag_center.z() = m_cut_z;
+}
+
+void GLGizmoCut::on_dragging(const UpdateData &data)
+{
+ assert(m_hover_id != -1);
+ set_cut_z(m_start_z + calc_projection(data.mouse_ray));
+}
+
+void GLGizmoCut::on_render()
+{
+ const BoundingBoxf3 box = bounding_box();
+ Vec3d plane_center = box.center();
+ plane_center.z() = m_cut_z;
+ m_max_z = box.max.z();
+ set_cut_z(m_cut_z);
+
+ update_contours();
+
+ const float min_x = box.min.x() - Margin;
+ const float max_x = box.max.x() + Margin;
+ const float min_y = box.min.y() - Margin;
+ const float max_y = box.max.y() + Margin;
+ glsafe(::glEnable(GL_DEPTH_TEST));
+ glsafe(::glDisable(GL_CULL_FACE));
+ glsafe(::glEnable(GL_BLEND));
+ glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLShaderProgram* shader = wxGetApp().get_shader("flat");
+ if (shader != nullptr) {
+ shader->start_using();
+ const Vec3d diff = plane_center - m_old_center;
+ // Z changed when move with cut plane
+ // X and Y changed when move with cutted object
+ bool is_changed = std::abs(diff.x()) > EPSILON ||
+ std::abs(diff.y()) > EPSILON ||
+ std::abs(diff.z()) > EPSILON;
+ m_old_center = plane_center;
+
+ if (!m_plane.is_initialized() || is_changed) {
+ m_plane.reset();
+
+ GLModel::Geometry init_data;
+ init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 };
+ init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f };
+ init_data.reserve_vertices(4);
+ init_data.reserve_indices(6);
+
+ // vertices
+ init_data.add_vertex(Vec3f(min_x, min_y, plane_center.z()));
+ init_data.add_vertex(Vec3f(max_x, min_y, plane_center.z()));
+ init_data.add_vertex(Vec3f(max_x, max_y, plane_center.z()));
+ init_data.add_vertex(Vec3f(min_x, max_y, plane_center.z()));
+
+ // indices
+ init_data.add_triangle(0, 1, 2);
+ init_data.add_triangle(2, 3, 0);
+
+ m_plane.init_from(std::move(init_data));
+ }
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+ shader->set_uniform("projection_matrix", camera.get_projection_matrix());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ m_plane.render();
+#else
+ // Draw the cutting plane
+ ::glBegin(GL_QUADS);
+ ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f);
+ ::glVertex3f(min_x, min_y, plane_center.z());
+ ::glVertex3f(max_x, min_y, plane_center.z());
+ ::glVertex3f(max_x, max_y, plane_center.z());
+ ::glVertex3f(min_x, max_y, plane_center.z());
+ glsafe(::glEnd());
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ glsafe(::glEnable(GL_CULL_FACE));
+ glsafe(::glDisable(GL_BLEND));
+
+ // Draw the grabber and the connecting line
+ m_grabbers[0].center = plane_center;
+ m_grabbers[0].center.z() = plane_center.z() + Offset;
+
+ glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
+
+ glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f));
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ if (!m_grabber_connection.is_initialized() || is_changed) {
+ m_grabber_connection.reset();
+
+ GLModel::Geometry init_data;
+ init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
+ init_data.color = ColorRGBA::YELLOW();
+ init_data.reserve_vertices(2);
+ init_data.reserve_indices(2);
+
+ // vertices
+ init_data.add_vertex((Vec3f)plane_center.cast<float>());
+ init_data.add_vertex((Vec3f)m_grabbers[0].center.cast<float>());
+
+ // indices
+ init_data.add_line(0, 1);
+
+ m_grabber_connection.init_from(std::move(init_data));
+ }
+
+ m_grabber_connection.render();
+
+ shader->stop_using();
+ }
+
+ shader = wxGetApp().get_shader("gouraud_light");
+#else
+ glsafe(::glColor3f(1.0, 1.0, 0.0));
+ ::glBegin(GL_LINES);
+ ::glVertex3dv(plane_center.data());
+ ::glVertex3dv(m_grabbers[0].center.data());
+ glsafe(::glEnd());
+
+ GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ if (shader != nullptr) {
+ shader->start_using();
+ shader->set_uniform("emission_factor", 0.1f);
+
+ m_grabbers[0].color = GRABBER_COLOR;
+ m_grabbers[0].render(m_hover_id == 0, float((box.size().x() + box.size().y() + box.size().z()) / 3.0));
+
+ shader->stop_using();
+ }
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ shader = wxGetApp().get_shader("flat");
+ if (shader != nullptr) {
+ shader->start_using();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix()* Geometry::assemble_transform(m_cut_contours.shift));
+ shader->set_uniform("projection_matrix", camera.get_projection_matrix());
+#else
+ glsafe(::glPushMatrix());
+ glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z()));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glLineWidth(2.0f));
+ m_cut_contours.contours.render();
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ shader->stop_using();
+ }
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ }
+
+void GLGizmoCut::on_render_for_picking()
+{
+ glsafe(::glDisable(GL_DEPTH_TEST));
+ render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
+}
+
+void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
+{
+ static float last_y = 0.0f;
+ static float last_h = 0.0f;
+
+ m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
+
+ const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
+
+ // adjust window position to avoid overlap the view toolbar
+ const float win_h = ImGui::GetWindowHeight();
+ y = std::min(y, bottom_limit - win_h);
+ ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
+ if (last_h != win_h || last_y != y) {
+ // ask canvas for another frame to render the window in the correct position
+ m_imgui->set_requires_extra_frame();
+ if (last_h != win_h)
+ last_h = win_h;
+ if (last_y != y)
+ last_y = y;
+ }
+
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text("Z");
+ ImGui::SameLine();
+ ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f);
+
+ double cut_z = m_cut_z;
+ if (imperial_units)
+ cut_z *= ObjectManipulation::mm_to_in;
+ ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal);
+
+ ImGui::SameLine();
+ m_imgui->text(imperial_units ? _L("in") : _L("mm"));
+
+ m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0);
+
+ ImGui::Separator();
+
+ m_imgui->checkbox(_L("Keep upper part"), m_keep_upper);
+ m_imgui->checkbox(_L("Keep lower part"), m_keep_lower);
+ m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower);
+
+ ImGui::Separator();
+
+ m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z);
+ const bool cut_clicked = m_imgui->button(_L("Perform cut"));
+ m_imgui->disabled_end();
+
+ m_imgui->end();
+
+ if (cut_clicked && (m_keep_upper || m_keep_lower))
+ perform_cut(m_parent.get_selection());
+}
+
+void GLGizmoCut::set_cut_z(double cut_z)
+{
+ // Clamp the plane to the object's bounding box
+ m_cut_z = std::clamp(cut_z, 0.0, m_max_z);
+}
+
+void GLGizmoCut::perform_cut(const Selection& selection)
+{
+ const int instance_idx = selection.get_instance_idx();
+ const int object_idx = selection.get_object_idx();
+
+ wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection");
+
+ // m_cut_z is the distance from the bed. Subtract possible SLA elevation.
+ const GLVolume* first_glvolume = selection.get_first_volume();
+ const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
+
+ if (0.0 < object_cut_z && object_cut_z < m_max_z)
+ wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z,
+ only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
+ only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) |
+ only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower));
+ else {
+ // the object is SLA-elevated and the plane is under it.
+ }
+}
+
+double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const
+{
+ double projection = 0.0;
+
+ const Vec3d starting_vec = m_drag_pos - m_drag_center;
+ const double len_starting_vec = starting_vec.norm();
+ if (len_starting_vec != 0.0) {
+ const Vec3d mouse_dir = mouse_ray.unit_vector();
+ // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
+ // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
+ // in our case plane normal and ray direction are the same (orthogonal view)
+ // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
+ const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
+ // vector from the starting position to the found intersection
+ const Vec3d inters_vec = inters - m_drag_pos;
+
+ // finds projection of the vector along the staring direction
+ projection = inters_vec.dot(starting_vec.normalized());
+ }
+ return projection;
+}
+
+BoundingBoxf3 GLGizmoCut::bounding_box() const
+{
+ BoundingBoxf3 ret;
+ const Selection& selection = m_parent.get_selection();
+ const Selection::IndicesList& idxs = selection.get_volume_idxs();
+ return selection.get_bounding_box();
+
+ for (unsigned int i : idxs) {
+ const GLVolume* volume = selection.get_volume(i);
+ if (!volume->is_modifier)
+ ret.merge(volume->transformed_convex_hull_bounding_box());
+ }
+ return ret;
+}
+
+void GLGizmoCut::update_contours()
+{
+ const Selection& selection = m_parent.get_selection();
+ const GLVolume* first_glvolume = selection.get_first_volume();
+ const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box();
+
+ const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()];
+ const int instance_idx = selection.get_instance_idx();
+ std::vector<ObjectID> volumes_idxs = std::vector<ObjectID>(model_object->volumes.size());
+ for (size_t i = 0; i < model_object->volumes.size(); ++i) {
+ volumes_idxs[i] = model_object->volumes[i]->id();
+ }
+
+ if (0.0 < m_cut_z && m_cut_z < m_max_z) {
+ if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() ||
+ m_cut_contours.instance_idx != instance_idx || m_cut_contours.volumes_idxs != volumes_idxs) {
+ m_cut_contours.cut_z = m_cut_z;
+
+ if (m_cut_contours.object_id != model_object->id() || m_cut_contours.volumes_idxs != volumes_idxs)
+ m_cut_contours.mesh = model_object->raw_mesh();
+
+ m_cut_contours.position = box.center();
+ m_cut_contours.shift = Vec3d::Zero();
+ m_cut_contours.object_id = model_object->id();
+ m_cut_contours.instance_idx = instance_idx;
+ m_cut_contours.volumes_idxs = volumes_idxs;
+ m_cut_contours.contours.reset();
+
+ MeshSlicingParams slicing_params;
+ slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix();
+ slicing_params.trafo.pretranslate(Vec3d(0., 0., first_glvolume->get_sla_shift_z()));
+
+ const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params);
+ if (!polys.empty()) {
+ m_cut_contours.contours.init_from(polys, static_cast<float>(m_cut_z));
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_cut_contours.contours.set_color(ColorRGBA::WHITE());
+#else
+ m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f });
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ }
+ }
+ else if (box.center() != m_cut_contours.position) {
+ m_cut_contours.shift = box.center() - m_cut_contours.position;
+ }
+ }
+ else
+ m_cut_contours.contours.reset();
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
index 66b6dcf60..49e97ee1f 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
@@ -1,437 +1,441 @@
-#include "GLGizmoFdmSupports.hpp"
-
-#include "libslic3r/Model.hpp"
-
-//#include "slic3r/GUI/3DScene.hpp"
-#include "slic3r/GUI/GLCanvas3D.hpp"
-#include "slic3r/GUI/GUI_App.hpp"
-#include "slic3r/GUI/ImGuiWrapper.hpp"
-#include "slic3r/GUI/Plater.hpp"
-#include "slic3r/GUI/GUI_ObjectList.hpp"
-#include "slic3r/GUI/format.hpp"
-#include "slic3r/Utils/UndoRedo.hpp"
-
-
-#include <GL/glew.h>
-
-
-namespace Slic3r::GUI {
-
-
-
-void GLGizmoFdmSupports::on_shutdown()
-{
- m_highlight_by_angle_threshold_deg = 0.f;
- m_parent.use_slope(false);
- m_parent.toggle_model_objects_visibility(true);
-}
-
-
-
-std::string GLGizmoFdmSupports::on_get_name() const
-{
- return _u8L("Paint-on supports");
-}
-
-
-
-bool GLGizmoFdmSupports::on_init()
-{
- m_shortcut_key = WXK_CONTROL_L;
-
- m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
- m_desc["reset_direction"] = _L("Reset direction");
- m_desc["cursor_size"] = _L("Brush size") + ": ";
- m_desc["cursor_type"] = _L("Brush shape") + ": ";
- m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
- m_desc["enforce"] = _L("Enforce supports");
- m_desc["block_caption"] = _L("Right mouse button") + ": ";
- m_desc["block"] = _L("Block supports");
- m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
- m_desc["remove"] = _L("Remove selection");
- m_desc["remove_all"] = _L("Remove all selection");
- m_desc["circle"] = _L("Circle");
- m_desc["sphere"] = _L("Sphere");
- m_desc["pointer"] = _L("Triangles");
- m_desc["highlight_by_angle"] = _L("Highlight overhang by angle");
- m_desc["enforce_button"] = _L("Enforce");
- m_desc["cancel"] = _L("Cancel");
-
- m_desc["tool_type"] = _L("Tool type") + ": ";
- m_desc["tool_brush"] = _L("Brush");
- m_desc["tool_smart_fill"] = _L("Smart fill");
-
- m_desc["smart_fill_angle"] = _L("Smart fill angle");
-
- m_desc["split_triangles"] = _L("Split triangles");
- m_desc["on_overhangs_only"] = _L("On overhangs only");
-
- return true;
-}
-
-void GLGizmoFdmSupports::render_painter_gizmo()
-{
- const Selection& selection = m_parent.get_selection();
-
- glsafe(::glEnable(GL_BLEND));
- glsafe(::glEnable(GL_DEPTH_TEST));
-
- render_triangles(selection);
- m_c->object_clipper()->render_cut();
- m_c->instances_hider()->render_cut();
- render_cursor();
-
- glsafe(::glDisable(GL_BLEND));
-}
-
-
-
-void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit)
-{
- if (! m_c->selection_info()->model_object())
- return;
-
- const float approx_height = m_imgui->scaled(23.f);
- y = std::min(y, bottom_limit - approx_height);
- m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
-
- m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
-
- // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
- const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
- m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
- const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
- const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f);
- const float autoset_slider_label_max_width = m_imgui->scaled(7.5f);
- const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f);
-
- const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
- const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
- const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
-
- const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
- const float button_enforce_width = m_imgui->calc_text_size(m_desc.at("enforce_button")).x;
- const float button_cancel_width = m_imgui->calc_text_size(m_desc.at("cancel")).x;
- const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f);
- const float minimal_slider_width = m_imgui->scaled(4.f);
-
- const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f);
- const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
- const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
-
- const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f);
- const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f);
-
- float caption_max = 0.f;
- float total_text_max = 0.f;
- for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"}) {
- caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
- total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
- }
- total_text_max += caption_max + m_imgui->scaled(1.f);
- caption_max += m_imgui->scaled(1.f);
-
- const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left));
- const float slider_icon_width = m_imgui->get_slider_icon_size().x;
- float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
- window_width = std::max(window_width, total_text_max);
- window_width = std::max(window_width, button_width);
- window_width = std::max(window_width, split_triangles_checkbox_width);
- window_width = std::max(window_width, on_overhangs_only_checkbox_width);
- window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
- window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill);
- window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
-
- auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
- m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption);
- ImGui::SameLine(caption_max);
- m_imgui->text(text);
- };
-
- for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"})
- draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
-
- ImGui::Separator();
-
- float position_before_text_y = ImGui::GetCursorPos().y;
- ImGui::AlignTextToFramePadding();
- m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width);
- ImGui::AlignTextToFramePadding();
- float position_after_text_y = ImGui::GetCursorPos().y;
- std::string format_str = std::string("%.f") + I18N::translate_utf8("°",
- "Degree sign to use in the respective slider in FDM supports gizmo,"
- "placed after the number with no whitespace in between.");
- ImGui::SameLine(sliders_left_width);
-
- float slider_height = m_imgui->get_slider_float_height();
- // Makes slider to be aligned to bottom of the multi-line text.
- float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height);
- ImGui::SetCursorPosY(slider_start_position_y);
-
- ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
- wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when "
- "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]);
- if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) {
- m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg);
- if (! m_parent.is_using_slope()) {
- m_parent.use_slope(true);
- m_parent.set_as_dirty();
- }
- }
-
- // Restores the cursor position to be below the multi-line text.
- ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y));
-
- const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
-
- m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f);
- ImGui::NewLine();
- ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f));
- if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) {
- select_facets_by_angle(m_highlight_by_angle_threshold_deg, false);
- m_highlight_by_angle_threshold_deg = 0.f;
- m_parent.use_slope(false);
- }
- ImGui::SameLine(window_width - buttons_width);
- if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) {
- m_highlight_by_angle_threshold_deg = 0.f;
- m_parent.use_slope(false);
- }
- m_imgui->disabled_end();
-
-
- ImGui::Separator();
-
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc["tool_type"]);
-
- float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f;
- ImGui::SameLine(tool_type_offset);
- ImGui::PushItemWidth(tool_type_radio_brush);
- if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH))
- m_tool_type = ToolType::BRUSH;
-
- if (ImGui::IsItemHovered())
- m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width);
-
- ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
- ImGui::PushItemWidth(tool_type_radio_smart_fill);
- if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL))
- m_tool_type = ToolType::SMART_FILL;
-
- if (ImGui::IsItemHovered())
- m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
-
- m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only);
- if (ImGui::IsItemHovered())
- m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width);
-
- ImGui::Separator();
-
- if (m_tool_type == ToolType::BRUSH) {
- m_imgui->text(m_desc.at("cursor_type"));
- ImGui::NewLine();
-
- float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f;
- ImGui::SameLine(cursor_type_offset);
- ImGui::PushItemWidth(cursor_type_radio_sphere);
- if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
- m_cursor_type = TriangleSelector::CursorType::SPHERE;
-
- if (ImGui::IsItemHovered())
- m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
-
- ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
- ImGui::PushItemWidth(cursor_type_radio_circle);
-
- if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
- m_cursor_type = TriangleSelector::CursorType::CIRCLE;
-
- if (ImGui::IsItemHovered())
- m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
-
- ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle);
- ImGui::PushItemWidth(cursor_type_radio_pointer);
-
- if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
- m_cursor_type = TriangleSelector::CursorType::POINTER;
-
- if (ImGui::IsItemHovered())
- m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width);
-
- m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
-
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("cursor_size"));
- ImGui::SameLine(sliders_left_width);
- ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
- m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
-
- m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
-
- if (ImGui::IsItemHovered())
- m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width);
-
- m_imgui->disabled_end();
- } else {
- assert(m_tool_type == ToolType::SMART_FILL);
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc["smart_fill_angle"] + ":");
-
- ImGui::SameLine(sliders_left_width);
- ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
- if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
- for (auto &triangle_selector : m_triangle_selectors) {
- triangle_selector->seed_fill_unselect_all_triangles();
- triangle_selector->request_update_render_data();
- }
- }
-
- ImGui::Separator();
- if (m_c->object_clipper()->get_position() == 0.f) {
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("clipping_of_view"));
- }
- else {
- if (m_imgui->button(m_desc.at("reset_direction"))) {
- wxGetApp().CallAfter([this](){
- m_c->object_clipper()->set_position(-1., false);
- });
- }
- }
-
- auto clp_dist = float(m_c->object_clipper()->get_position());
- ImGui::SameLine(sliders_left_width);
- ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
- if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
- m_c->object_clipper()->set_position(clp_dist, true);
-
- ImGui::Separator();
- if (m_imgui->button(m_desc.at("remove_all"))) {
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction);
- ModelObject *mo = m_c->selection_info()->model_object();
- int idx = -1;
- for (ModelVolume *mv : mo->volumes)
- if (mv->is_model_part()) {
- ++idx;
- m_triangle_selectors[idx]->reset();
- m_triangle_selectors[idx]->request_update_render_data();
- }
-
- update_model_object();
- m_parent.set_as_dirty();
- }
-
- m_imgui->end();
-}
-
-
-
-void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
-{
- float threshold = (float(M_PI)/180.f)*threshold_deg;
- const Selection& selection = m_parent.get_selection();
- const ModelObject* mo = m_c->selection_info()->model_object();
- const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
-
- int mesh_id = -1;
- for (const ModelVolume* mv : mo->volumes) {
- if (! mv->is_model_part())
- continue;
-
- ++mesh_id;
-
- const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true);
- Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized();
- Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized();
-
- float dot_limit = limit.dot(down);
-
- // Now calculate dot product of vert_direction and facets' normals.
- int idx = 0;
- const indexed_triangle_set &its = mv->mesh().its;
- for (const stl_triangle_vertex_indices &face : its.indices) {
- if (its_face_normal(its, face).dot(down) > dot_limit) {
- m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER);
- m_triangle_selectors.back()->request_update_render_data();
- }
- ++ idx;
- }
- }
-
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
- : _L("Add supports by angle"));
- update_model_object();
- m_parent.set_as_dirty();
-}
-
-
-
-void GLGizmoFdmSupports::update_model_object() const
-{
- bool updated = false;
- ModelObject* mo = m_c->selection_info()->model_object();
- int idx = -1;
- for (ModelVolume* mv : mo->volumes) {
- if (! mv->is_model_part())
- continue;
- ++idx;
- updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get());
- }
-
- if (updated) {
- const ModelObjectPtrs& mos = wxGetApp().model().objects;
- wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
-
- m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
- }
-}
-
-
-
-void GLGizmoFdmSupports::update_from_model_object()
-{
- wxBusyCursor wait;
-
- const ModelObject* mo = m_c->selection_info()->model_object();
- m_triangle_selectors.clear();
-
- int volume_id = -1;
- for (const ModelVolume* mv : mo->volumes) {
- if (! mv->is_model_part())
- continue;
-
- ++volume_id;
-
- // This mesh does not account for the possible Z up SLA offset.
- const TriangleMesh* mesh = &mv->mesh();
-
- m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
- // Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize().
- m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data(), false);
- m_triangle_selectors.back()->request_update_render_data();
- }
-}
-
-
-
-PainterGizmoType GLGizmoFdmSupports::get_painter_type() const
-{
- return PainterGizmoType::FDM_SUPPORTS;
-}
-
-wxString GLGizmoFdmSupports::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
-{
- wxString action_name;
- if (shift_down)
- action_name = _L("Remove selection");
- else {
- if (button_down == Button::Left)
- action_name = _L("Add supports");
- else
- action_name = _L("Block supports");
- }
- return action_name;
-}
-
-} // namespace Slic3r::GUI
+#include "GLGizmoFdmSupports.hpp"
+
+#include "libslic3r/Model.hpp"
+
+//#include "slic3r/GUI/3DScene.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/ImGuiWrapper.hpp"
+#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/GUI_ObjectList.hpp"
+#include "slic3r/GUI/format.hpp"
+#include "slic3r/Utils/UndoRedo.hpp"
+
+
+#include <GL/glew.h>
+
+
+namespace Slic3r::GUI {
+
+
+
+void GLGizmoFdmSupports::on_shutdown()
+{
+ m_highlight_by_angle_threshold_deg = 0.f;
+ m_parent.use_slope(false);
+ m_parent.toggle_model_objects_visibility(true);
+}
+
+
+
+std::string GLGizmoFdmSupports::on_get_name() const
+{
+ return _u8L("Paint-on supports");
+}
+
+
+
+bool GLGizmoFdmSupports::on_init()
+{
+ m_shortcut_key = WXK_CONTROL_L;
+
+ m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
+ m_desc["reset_direction"] = _L("Reset direction");
+ m_desc["cursor_size"] = _L("Brush size") + ": ";
+ m_desc["cursor_type"] = _L("Brush shape") + ": ";
+ m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
+ m_desc["enforce"] = _L("Enforce supports");
+ m_desc["block_caption"] = _L("Right mouse button") + ": ";
+ m_desc["block"] = _L("Block supports");
+ m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
+ m_desc["remove"] = _L("Remove selection");
+ m_desc["remove_all"] = _L("Remove all selection");
+ m_desc["circle"] = _L("Circle");
+ m_desc["sphere"] = _L("Sphere");
+ m_desc["pointer"] = _L("Triangles");
+ m_desc["highlight_by_angle"] = _L("Highlight overhang by angle");
+ m_desc["enforce_button"] = _L("Enforce");
+ m_desc["cancel"] = _L("Cancel");
+
+ m_desc["tool_type"] = _L("Tool type") + ": ";
+ m_desc["tool_brush"] = _L("Brush");
+ m_desc["tool_smart_fill"] = _L("Smart fill");
+
+ m_desc["smart_fill_angle"] = _L("Smart fill angle");
+
+ m_desc["split_triangles"] = _L("Split triangles");
+ m_desc["on_overhangs_only"] = _L("On overhangs only");
+
+ return true;
+}
+
+void GLGizmoFdmSupports::render_painter_gizmo()
+{
+ const Selection& selection = m_parent.get_selection();
+
+ glsafe(::glEnable(GL_BLEND));
+ glsafe(::glEnable(GL_DEPTH_TEST));
+
+ render_triangles(selection);
+ m_c->object_clipper()->render_cut();
+ m_c->instances_hider()->render_cut();
+ render_cursor();
+
+ glsafe(::glDisable(GL_BLEND));
+}
+
+
+
+void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit)
+{
+ if (! m_c->selection_info()->model_object())
+ return;
+
+ const float approx_height = m_imgui->scaled(23.f);
+ y = std::min(y, bottom_limit - approx_height);
+ m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
+
+ m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
+
+ // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
+ const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
+ m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
+ const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
+ const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f);
+ const float autoset_slider_label_max_width = m_imgui->scaled(7.5f);
+ const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f);
+
+ const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
+ const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
+ const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
+
+ const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
+ const float button_enforce_width = m_imgui->calc_text_size(m_desc.at("enforce_button")).x;
+ const float button_cancel_width = m_imgui->calc_text_size(m_desc.at("cancel")).x;
+ const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f);
+ const float minimal_slider_width = m_imgui->scaled(4.f);
+
+ const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f);
+ const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
+ const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
+
+ const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f);
+ const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f);
+
+ float caption_max = 0.f;
+ float total_text_max = 0.f;
+ for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"}) {
+ caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
+ total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
+ }
+ total_text_max += caption_max + m_imgui->scaled(1.f);
+ caption_max += m_imgui->scaled(1.f);
+
+ const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left));
+ const float slider_icon_width = m_imgui->get_slider_icon_size().x;
+ float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
+ window_width = std::max(window_width, total_text_max);
+ window_width = std::max(window_width, button_width);
+ window_width = std::max(window_width, split_triangles_checkbox_width);
+ window_width = std::max(window_width, on_overhangs_only_checkbox_width);
+ window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
+ window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill);
+ window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
+
+ auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
+ m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption);
+ ImGui::SameLine(caption_max);
+ m_imgui->text(text);
+ };
+
+ for (const auto &t : std::array<std::string, 3>{"enforce", "block", "remove"})
+ draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
+
+ ImGui::Separator();
+
+ float position_before_text_y = ImGui::GetCursorPos().y;
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width);
+ ImGui::AlignTextToFramePadding();
+ float position_after_text_y = ImGui::GetCursorPos().y;
+ std::string format_str = std::string("%.f") + I18N::translate_utf8("°",
+ "Degree sign to use in the respective slider in FDM supports gizmo,"
+ "placed after the number with no whitespace in between.");
+ ImGui::SameLine(sliders_left_width);
+
+ float slider_height = m_imgui->get_slider_float_height();
+ // Makes slider to be aligned to bottom of the multi-line text.
+ float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height);
+ ImGui::SetCursorPosY(slider_start_position_y);
+
+ ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
+ wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when "
+ "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]);
+ if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) {
+ m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg);
+ if (! m_parent.is_using_slope()) {
+ m_parent.use_slope(true);
+ m_parent.set_as_dirty();
+ }
+ }
+
+ // Restores the cursor position to be below the multi-line text.
+ ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y));
+
+ const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
+
+ m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f);
+ ImGui::NewLine();
+ ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f));
+ if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) {
+ select_facets_by_angle(m_highlight_by_angle_threshold_deg, false);
+ m_highlight_by_angle_threshold_deg = 0.f;
+ m_parent.use_slope(false);
+ }
+ ImGui::SameLine(window_width - buttons_width);
+ if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) {
+ m_highlight_by_angle_threshold_deg = 0.f;
+ m_parent.use_slope(false);
+ }
+ m_imgui->disabled_end();
+
+
+ ImGui::Separator();
+
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc["tool_type"]);
+
+ float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f;
+ ImGui::SameLine(tool_type_offset);
+ ImGui::PushItemWidth(tool_type_radio_brush);
+ if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH))
+ m_tool_type = ToolType::BRUSH;
+
+ if (ImGui::IsItemHovered())
+ m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width);
+
+ ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
+ ImGui::PushItemWidth(tool_type_radio_smart_fill);
+ if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL))
+ m_tool_type = ToolType::SMART_FILL;
+
+ if (ImGui::IsItemHovered())
+ m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
+
+ m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only);
+ if (ImGui::IsItemHovered())
+ m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width);
+
+ ImGui::Separator();
+
+ if (m_tool_type == ToolType::BRUSH) {
+ m_imgui->text(m_desc.at("cursor_type"));
+ ImGui::NewLine();
+
+ float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f;
+ ImGui::SameLine(cursor_type_offset);
+ ImGui::PushItemWidth(cursor_type_radio_sphere);
+ if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
+ m_cursor_type = TriangleSelector::CursorType::SPHERE;
+
+ if (ImGui::IsItemHovered())
+ m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
+
+ ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
+ ImGui::PushItemWidth(cursor_type_radio_circle);
+
+ if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
+ m_cursor_type = TriangleSelector::CursorType::CIRCLE;
+
+ if (ImGui::IsItemHovered())
+ m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width);
+
+ ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle);
+ ImGui::PushItemWidth(cursor_type_radio_pointer);
+
+ if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
+ m_cursor_type = TriangleSelector::CursorType::POINTER;
+
+ if (ImGui::IsItemHovered())
+ m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width);
+
+ m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
+
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("cursor_size"));
+ ImGui::SameLine(sliders_left_width);
+ ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
+ m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
+
+ m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
+
+ if (ImGui::IsItemHovered())
+ m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width);
+
+ m_imgui->disabled_end();
+ } else {
+ assert(m_tool_type == ToolType::SMART_FILL);
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc["smart_fill_angle"] + ":");
+
+ ImGui::SameLine(sliders_left_width);
+ ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
+ if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
+ for (auto &triangle_selector : m_triangle_selectors) {
+ triangle_selector->seed_fill_unselect_all_triangles();
+ triangle_selector->request_update_render_data();
+ }
+ }
+
+ ImGui::Separator();
+ if (m_c->object_clipper()->get_position() == 0.f) {
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("clipping_of_view"));
+ }
+ else {
+ if (m_imgui->button(m_desc.at("reset_direction"))) {
+ wxGetApp().CallAfter([this](){
+ m_c->object_clipper()->set_position(-1., false);
+ });
+ }
+ }
+
+ auto clp_dist = float(m_c->object_clipper()->get_position());
+ ImGui::SameLine(sliders_left_width);
+ ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
+ if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
+ m_c->object_clipper()->set_position(clp_dist, true);
+
+ ImGui::Separator();
+ if (m_imgui->button(m_desc.at("remove_all"))) {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction);
+ ModelObject *mo = m_c->selection_info()->model_object();
+ int idx = -1;
+ for (ModelVolume *mv : mo->volumes)
+ if (mv->is_model_part()) {
+ ++idx;
+ m_triangle_selectors[idx]->reset();
+ m_triangle_selectors[idx]->request_update_render_data();
+ }
+
+ update_model_object();
+ m_parent.set_as_dirty();
+ }
+
+ m_imgui->end();
+}
+
+
+
+void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
+{
+ float threshold = (float(M_PI)/180.f)*threshold_deg;
+ const Selection& selection = m_parent.get_selection();
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
+
+ int mesh_id = -1;
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+
+ ++mesh_id;
+
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d trafo_matrix = mi->get_matrix_no_offset() * mv->get_matrix_no_offset();
+#else
+ const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true);
+#endif // ENABLE_WORLD_COORDINATE
+ Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized();
+ Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized();
+
+ float dot_limit = limit.dot(down);
+
+ // Now calculate dot product of vert_direction and facets' normals.
+ int idx = 0;
+ const indexed_triangle_set &its = mv->mesh().its;
+ for (const stl_triangle_vertex_indices &face : its.indices) {
+ if (its_face_normal(its, face).dot(down) > dot_limit) {
+ m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER);
+ m_triangle_selectors.back()->request_update_render_data();
+ }
+ ++ idx;
+ }
+ }
+
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
+ : _L("Add supports by angle"));
+ update_model_object();
+ m_parent.set_as_dirty();
+}
+
+
+
+void GLGizmoFdmSupports::update_model_object() const
+{
+ bool updated = false;
+ ModelObject* mo = m_c->selection_info()->model_object();
+ int idx = -1;
+ for (ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+ ++idx;
+ updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get());
+ }
+
+ if (updated) {
+ const ModelObjectPtrs& mos = wxGetApp().model().objects;
+ wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
+
+ m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
+ }
+}
+
+
+
+void GLGizmoFdmSupports::update_from_model_object()
+{
+ wxBusyCursor wait;
+
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ m_triangle_selectors.clear();
+
+ int volume_id = -1;
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+
+ ++volume_id;
+
+ // This mesh does not account for the possible Z up SLA offset.
+ const TriangleMesh* mesh = &mv->mesh();
+
+ m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
+ // Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize().
+ m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data(), false);
+ m_triangle_selectors.back()->request_update_render_data();
+ }
+}
+
+
+
+PainterGizmoType GLGizmoFdmSupports::get_painter_type() const
+{
+ return PainterGizmoType::FDM_SUPPORTS;
+}
+
+wxString GLGizmoFdmSupports::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
+{
+ wxString action_name;
+ if (shift_down)
+ action_name = _L("Remove selection");
+ else {
+ if (button_down == Button::Left)
+ action_name = _L("Add supports");
+ else
+ action_name = _L("Block supports");
+ }
+ return action_name;
+}
+
+} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
index aa291f623..f854edb81 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
@@ -1,475 +1,479 @@
-// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
-#include "GLGizmoFlatten.hpp"
-#include "slic3r/GUI/GLCanvas3D.hpp"
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-#include "slic3r/GUI/GUI_App.hpp"
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-#if ENABLE_GL_SHADERS_ATTRIBUTES
-#include "slic3r/GUI/Plater.hpp"
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
-#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
-
-#include "libslic3r/Geometry/ConvexHull.hpp"
-#include "libslic3r/Model.hpp"
-
-#include <numeric>
-
-#include <GL/glew.h>
-
-namespace Slic3r {
-namespace GUI {
-
-static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f };
-static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f };
-
-GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
- : GLGizmoBase(parent, icon_filename, sprite_id)
-{}
-
-bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event)
-{
- if (mouse_event.Moving()) {
- // only for sure
- m_mouse_left_down = false;
- return false;
- }
- if (mouse_event.LeftDown()) {
- if (m_hover_id != -1) {
- m_mouse_left_down = true;
- Selection &selection = m_parent.get_selection();
- if (selection.is_single_full_instance()) {
- // Rotate the object so the normal points downward:
- selection.flattening_rotate(m_planes[m_hover_id].normal);
- m_parent.do_rotate(L("Gizmo-Place on Face"));
- }
- return true;
- }
-
- // fix: prevent restart gizmo when reselect object
- // take responsibility for left up
- if (m_parent.get_first_hover_volume_idx() >= 0) m_mouse_left_down = true;
-
- } else if (mouse_event.LeftUp()) {
- if (m_mouse_left_down) {
- // responsible for mouse left up after selecting plane
- m_mouse_left_down = false;
- return true;
- }
- } else if (mouse_event.Leaving()) {
- m_mouse_left_down = false;
- }
- return false;
-}
-
-void GLGizmoFlatten::data_changed()
-{
- const Selection & selection = m_parent.get_selection();
- const ModelObject *model_object = nullptr;
- if (selection.is_single_full_instance() ||
- selection.is_from_single_object() ) {
- model_object = selection.get_model()->objects[selection.get_object_idx()];
- }
- set_flattening_data(model_object);
-}
-
-bool GLGizmoFlatten::on_init()
-{
- m_shortcut_key = WXK_CONTROL_F;
- return true;
-}
-
-void GLGizmoFlatten::on_set_state()
-{
-}
-
-CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const
-{
- return CommonGizmosDataID::SelectionInfo;
-}
-
-std::string GLGizmoFlatten::on_get_name() const
-{
- return _u8L("Place on face");
-}
-
-bool GLGizmoFlatten::on_is_activable() const
-{
- // This is assumed in GLCanvas3D::do_rotate, do not change this
- // without updating that function too.
- return m_parent.get_selection().is_single_full_instance();
-}
-
-void GLGizmoFlatten::on_render()
-{
- const Selection& selection = m_parent.get_selection();
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLShaderProgram* shader = wxGetApp().get_shader("flat");
- if (shader == nullptr)
- return;
-
- shader->start_using();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
-
- glsafe(::glEnable(GL_DEPTH_TEST));
- glsafe(::glEnable(GL_BLEND));
-
- if (selection.is_single_full_instance()) {
- const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix();
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Camera& camera = wxGetApp().plater()->get_camera();
- const Transform3d view_model_matrix = camera.get_view_matrix() *
- Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m;
-
- shader->set_uniform("view_model_matrix", view_model_matrix);
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
-#else
- glsafe(::glPushMatrix());
- glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()));
- glsafe(::glMultMatrixd(m.data()));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- if (this->is_plane_update_necessary())
- update_planes();
- for (int i = 0; i < (int)m_planes.size(); ++i) {
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR);
- m_planes[i].vbo.render();
-#else
- glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data()));
- if (m_planes[i].vbo.has_VBOs())
- m_planes[i].vbo.render();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- }
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
- }
-
- glsafe(::glEnable(GL_CULL_FACE));
- glsafe(::glDisable(GL_BLEND));
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- shader->stop_using();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-}
-
-void GLGizmoFlatten::on_render_for_picking()
-{
- const Selection& selection = m_parent.get_selection();
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLShaderProgram* shader = wxGetApp().get_shader("flat");
- if (shader == nullptr)
- return;
-
- shader->start_using();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- glsafe(::glDisable(GL_DEPTH_TEST));
- glsafe(::glDisable(GL_BLEND));
-
- if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) {
- const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix();
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Camera& camera = wxGetApp().plater()->get_camera();
- const Transform3d view_model_matrix = camera.get_view_matrix() *
- Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m;
-
- shader->set_uniform("view_model_matrix", view_model_matrix);
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
-#else
- glsafe(::glPushMatrix());
- glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()));
- glsafe(::glMultMatrixd(m.data()));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- if (this->is_plane_update_necessary())
- update_planes();
- for (int i = 0; i < (int)m_planes.size(); ++i) {
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_planes[i].vbo.set_color(picking_color_component(i));
-#else
- glsafe(::glColor4fv(picking_color_component(i).data()));
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- m_planes[i].vbo.render();
- }
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
- }
-
- glsafe(::glEnable(GL_CULL_FACE));
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- shader->stop_using();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-}
-
-void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object)
-{
- if (model_object != m_old_model_object) {
- m_planes.clear();
- m_planes_valid = false;
- }
-}
-
-void GLGizmoFlatten::update_planes()
-{
- const ModelObject* mo = m_c->selection_info()->model_object();
- TriangleMesh ch;
- for (const ModelVolume* vol : mo->volumes) {
- if (vol->type() != ModelVolumeType::MODEL_PART)
- continue;
- TriangleMesh vol_ch = vol->get_convex_hull();
- vol_ch.transform(vol->get_matrix());
- ch.merge(vol_ch);
- }
- ch = ch.convex_hull_3d();
- m_planes.clear();
- const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true);
-
- // Following constants are used for discarding too small polygons.
- const float minimal_area = 5.f; // in square mm (world coordinates)
- const float minimal_side = 1.f; // mm
-
- // Now we'll go through all the facets and append Points of facets sharing the same normal.
- // This part is still performed in mesh coordinate system.
- const int num_of_facets = ch.facets_count();
- const std::vector<Vec3f> face_normals = its_face_normals(ch.its);
- const std::vector<Vec3i> face_neighbors = its_face_neighbors(ch.its);
- std::vector<int> facet_queue(num_of_facets, 0);
- std::vector<bool> facet_visited(num_of_facets, false);
- int facet_queue_cnt = 0;
- const stl_normal* normal_ptr = nullptr;
- int facet_idx = 0;
- while (1) {
- // Find next unvisited triangle:
- for (; facet_idx < num_of_facets; ++ facet_idx)
- if (!facet_visited[facet_idx]) {
- facet_queue[facet_queue_cnt ++] = facet_idx;
- facet_visited[facet_idx] = true;
- normal_ptr = &face_normals[facet_idx];
- m_planes.emplace_back();
- break;
- }
- if (facet_idx == num_of_facets)
- break; // Everything was visited already
-
- while (facet_queue_cnt > 0) {
- int facet_idx = facet_queue[-- facet_queue_cnt];
- const stl_normal& this_normal = face_normals[facet_idx];
- if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) {
- const Vec3i face = ch.its.indices[facet_idx];
- for (int j=0; j<3; ++j)
- m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast<double>());
-
- facet_visited[facet_idx] = true;
- for (int j = 0; j < 3; ++ j)
- if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx])
- facet_queue[facet_queue_cnt ++] = neighbor_idx;
- }
- }
- m_planes.back().normal = normal_ptr->cast<double>();
-
- Pointf3s& verts = m_planes.back().vertices;
- // Now we'll transform all the points into world coordinates, so that the areas, angles and distances
- // make real sense.
- verts = transform(verts, inst_matrix);
-
- // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway):
- if (verts.size() == 3 &&
- ((verts[0] - verts[1]).norm() < minimal_side
- || (verts[0] - verts[2]).norm() < minimal_side
- || (verts[1] - verts[2]).norm() < minimal_side))
- m_planes.pop_back();
- }
-
- // Let's prepare transformation of the normal vector from mesh to instance coordinates.
- Geometry::Transformation t(inst_matrix);
- Vec3d scaling = t.get_scaling_factor();
- t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2)));
-
- // Now we'll go through all the polygons, transform the points into xy plane to process them:
- for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) {
- Pointf3s& polygon = m_planes[polygon_id].vertices;
- const Vec3d& normal = m_planes[polygon_id].normal;
-
- // transform the normal according to the instance matrix:
- Vec3d normal_transformed = t.get_matrix() * normal;
-
- // We are going to rotate about z and y to flatten the plane
- Eigen::Quaterniond q;
- Transform3d m = Transform3d::Identity();
- m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix();
- polygon = transform(polygon, m);
-
- // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since
- // it works in fixed point representation, we will rescale the polygon to avoid overflows.
- // And yes, it is a nasty thing to do. Whoever has time is free to refactor.
- Vec3d bb_size = BoundingBoxf3(polygon).size();
- float sf = std::min(1./bb_size(0), 1./bb_size(1));
- Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f));
- polygon = transform(polygon, tr);
- polygon = Slic3r::Geometry::convex_hull(polygon);
- polygon = transform(polygon, tr.inverse());
-
- // Calculate area of the polygons and discard ones that are too small
- float& area = m_planes[polygon_id].area;
- area = 0.f;
- for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula
- area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1);
- area = 0.5f * std::abs(area);
-
- bool discard = false;
- if (area < minimal_area)
- discard = true;
- else {
- // We also check the inner angles and discard polygons with angles smaller than the following threshold
- const double angle_threshold = ::cos(10.0 * (double)PI / 180.0);
-
- for (unsigned int i = 0; i < polygon.size(); ++i) {
- const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1];
- const Vec3d& curr = polygon[i];
- const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1];
-
- if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) {
- discard = true;
- break;
- }
- }
- }
-
- if (discard) {
- m_planes[polygon_id--] = std::move(m_planes.back());
- m_planes.pop_back();
- continue;
- }
-
- // We will shrink the polygon a little bit so it does not touch the object edges:
- Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0));
- centroid /= (double)polygon.size();
- for (auto& vertex : polygon)
- vertex = 0.9f*vertex + 0.1f*centroid;
-
- // Polygon is now simple and convex, we'll round the corners to make them look nicer.
- // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex
- // towards their average (controlled by 'aggressivity'). This is repeated k times.
- // In next iterations, the neighbours are not always taken at the middle (to increase the
- // rounding effect at the corners, where we need it most).
- const unsigned int k = 10; // number of iterations
- const float aggressivity = 0.2f; // agressivity
- const unsigned int N = polygon.size();
- std::vector<std::pair<unsigned int, unsigned int>> neighbours;
- if (k != 0) {
- Pointf3s points_out(2*k*N); // vector long enough to store the future vertices
- for (unsigned int j=0; j<N; ++j) {
- points_out[j*2*k] = polygon[j];
- neighbours.push_back(std::make_pair((int)(j*2*k-k) < 0 ? (N-1)*2*k+k : j*2*k-k, j*2*k+k));
- }
-
- for (unsigned int i=0; i<k; ++i) {
- // Calculate middle of each edge so that neighbours points to something useful:
- for (unsigned int j=0; j<N; ++j)
- if (i==0)
- points_out[j*2*k+k] = 0.5f * (points_out[j*2*k] + points_out[j==N-1 ? 0 : (j+1)*2*k]);
- else {
- float r = 0.2+0.3/(k-1)*i; // the neighbours are not always taken in the middle
- points_out[neighbours[j].first] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].first-1];
- points_out[neighbours[j].second] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].second+1];
- }
- // Now we have a triangle and valid neighbours, we can do an iteration:
- for (unsigned int j=0; j<N; ++j)
- points_out[2*k*j] = (1-aggressivity) * points_out[2*k*j] +
- aggressivity*0.5f*(points_out[neighbours[j].first] + points_out[neighbours[j].second]);
-
- for (auto& n : neighbours) {
- ++n.first;
- --n.second;
- }
- }
- polygon = points_out; // replace the coarse polygon with the smooth one that we just created
- }
-
-
- // Raise a bit above the object surface to avoid flickering:
- for (auto& b : polygon)
- b(2) += 0.1f;
-
- // Transform back to 3D (and also back to mesh coordinates)
- polygon = transform(polygon, inst_matrix.inverse() * m.inverse());
- }
-
- // We'll sort the planes by area and only keep the 254 largest ones (because of the picking pass limitations):
- std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData& a, const PlaneData& b) { return a.area < b.area; });
- m_planes.resize(std::min((int)m_planes.size(), 254));
-
- // Planes are finished - let's save what we calculated it from:
- m_volumes_matrices.clear();
- m_volumes_types.clear();
- for (const ModelVolume* vol : mo->volumes) {
- m_volumes_matrices.push_back(vol->get_matrix());
- m_volumes_types.push_back(vol->type());
- }
- m_first_instance_scale = mo->instances.front()->get_scaling_factor();
- m_first_instance_mirror = mo->instances.front()->get_mirror();
- m_old_model_object = mo;
-
- // And finally create respective VBOs. The polygon is convex with
- // the vertices in order, so triangulation is trivial.
- for (auto& plane : m_planes) {
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLModel::Geometry init_data;
- init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3 };
- init_data.reserve_vertices(plane.vertices.size());
- init_data.reserve_indices(plane.vertices.size());
- // vertices + indices
- for (size_t i = 0; i < plane.vertices.size(); ++i) {
- init_data.add_vertex((Vec3f)plane.vertices[i].cast<float>(), (Vec3f)plane.normal.cast<float>());
- init_data.add_index((unsigned int)i);
- }
- plane.vbo.init_from(std::move(init_data));
-#else
- plane.vbo.reserve(plane.vertices.size());
- for (const auto& vert : plane.vertices)
- plane.vbo.push_geometry(vert, plane.normal);
- for (size_t i=1; i<plane.vertices.size()-1; ++i)
- plane.vbo.push_triangle(0, i, i+1); // triangle fan
- plane.vbo.finalize_geometry(true);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- // FIXME: vertices should really be local, they need not
- // persist now when we use VBOs
- plane.vertices.clear();
- plane.vertices.shrink_to_fit();
- }
-
- m_planes_valid = true;
-}
-
-
-bool GLGizmoFlatten::is_plane_update_necessary() const
-{
- const ModelObject* mo = m_c->selection_info()->model_object();
- if (m_state != On || ! mo || mo->instances.empty())
- return false;
-
- if (! m_planes_valid || mo != m_old_model_object
- || mo->volumes.size() != m_volumes_matrices.size())
- return true;
-
- // We want to recalculate when the scale changes - some planes could (dis)appear.
- if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale)
- || ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror))
- return true;
-
- for (unsigned int i=0; i < mo->volumes.size(); ++i)
- if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i])
- || mo->volumes[i]->type() != m_volumes_types[i])
- return true;
-
- return false;
-}
-
-} // namespace GUI
-} // namespace Slic3r
+// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
+#include "GLGizmoFlatten.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+#include "slic3r/GUI/GUI_App.hpp"
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+#include "slic3r/GUI/Plater.hpp"
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
+
+#include "libslic3r/Geometry/ConvexHull.hpp"
+#include "libslic3r/Model.hpp"
+
+#include <numeric>
+
+#include <GL/glew.h>
+
+namespace Slic3r {
+namespace GUI {
+
+static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f };
+static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f };
+
+GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
+ : GLGizmoBase(parent, icon_filename, sprite_id)
+{}
+
+bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event)
+{
+ if (mouse_event.Moving()) {
+ // only for sure
+ m_mouse_left_down = false;
+ return false;
+ }
+ if (mouse_event.LeftDown()) {
+ if (m_hover_id != -1) {
+ m_mouse_left_down = true;
+ Selection &selection = m_parent.get_selection();
+ if (selection.is_single_full_instance()) {
+ // Rotate the object so the normal points downward:
+ selection.flattening_rotate(m_planes[m_hover_id].normal);
+ m_parent.do_rotate(L("Gizmo-Place on Face"));
+ }
+ return true;
+ }
+
+ // fix: prevent restart gizmo when reselect object
+ // take responsibility for left up
+ if (m_parent.get_first_hover_volume_idx() >= 0) m_mouse_left_down = true;
+
+ } else if (mouse_event.LeftUp()) {
+ if (m_mouse_left_down) {
+ // responsible for mouse left up after selecting plane
+ m_mouse_left_down = false;
+ return true;
+ }
+ } else if (mouse_event.Leaving()) {
+ m_mouse_left_down = false;
+ }
+ return false;
+}
+
+void GLGizmoFlatten::data_changed()
+{
+ const Selection & selection = m_parent.get_selection();
+ const ModelObject *model_object = nullptr;
+ if (selection.is_single_full_instance() ||
+ selection.is_from_single_object() ) {
+ model_object = selection.get_model()->objects[selection.get_object_idx()];
+ }
+ set_flattening_data(model_object);
+}
+
+bool GLGizmoFlatten::on_init()
+{
+ m_shortcut_key = WXK_CONTROL_F;
+ return true;
+}
+
+void GLGizmoFlatten::on_set_state()
+{
+}
+
+CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const
+{
+ return CommonGizmosDataID::SelectionInfo;
+}
+
+std::string GLGizmoFlatten::on_get_name() const
+{
+ return _u8L("Place on face");
+}
+
+bool GLGizmoFlatten::on_is_activable() const
+{
+ // This is assumed in GLCanvas3D::do_rotate, do not change this
+ // without updating that function too.
+ return m_parent.get_selection().is_single_full_instance();
+}
+
+void GLGizmoFlatten::on_render()
+{
+ const Selection& selection = m_parent.get_selection();
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLShaderProgram* shader = wxGetApp().get_shader("flat");
+ if (shader == nullptr)
+ return;
+
+ shader->start_using();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
+
+ glsafe(::glEnable(GL_DEPTH_TEST));
+ glsafe(::glEnable(GL_BLEND));
+
+ if (selection.is_single_full_instance()) {
+ const Transform3d& m = selection.get_first_volume()->get_instance_transformation().get_matrix();
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ const Transform3d view_model_matrix = camera.get_view_matrix() *
+ Geometry::assemble_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * m;
+
+ shader->set_uniform("view_model_matrix", view_model_matrix);
+ shader->set_uniform("projection_matrix", camera.get_projection_matrix());
+#else
+ glsafe(::glPushMatrix());
+ glsafe(::glTranslatef(0.f, 0.f, selection.get_first_volume()->get_sla_shift_z()));
+ glsafe(::glMultMatrixd(m.data()));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ if (this->is_plane_update_necessary())
+ update_planes();
+ for (int i = 0; i < (int)m_planes.size(); ++i) {
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR);
+ m_planes[i].vbo.render();
+#else
+ glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data()));
+ if (m_planes[i].vbo.has_VBOs())
+ m_planes[i].vbo.render();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ }
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+ }
+
+ glsafe(::glEnable(GL_CULL_FACE));
+ glsafe(::glDisable(GL_BLEND));
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ shader->stop_using();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+}
+
+void GLGizmoFlatten::on_render_for_picking()
+{
+ const Selection& selection = m_parent.get_selection();
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLShaderProgram* shader = wxGetApp().get_shader("flat");
+ if (shader == nullptr)
+ return;
+
+ shader->start_using();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ glsafe(::glDisable(GL_DEPTH_TEST));
+ glsafe(::glDisable(GL_BLEND));
+
+ if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) {
+ const Transform3d& m = selection.get_first_volume()->get_instance_transformation().get_matrix();
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ const Transform3d view_model_matrix = camera.get_view_matrix() *
+ Geometry::assemble_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * m;
+
+ shader->set_uniform("view_model_matrix", view_model_matrix);
+ shader->set_uniform("projection_matrix", camera.get_projection_matrix());
+#else
+ glsafe(::glPushMatrix());
+ glsafe(::glTranslatef(0.f, 0.f, selection.get_first_volume()->get_sla_shift_z()));
+ glsafe(::glMultMatrixd(m.data()));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ if (this->is_plane_update_necessary())
+ update_planes();
+ for (int i = 0; i < (int)m_planes.size(); ++i) {
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_planes[i].vbo.set_color(picking_color_component(i));
+#else
+ glsafe(::glColor4fv(picking_color_component(i).data()));
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ m_planes[i].vbo.render();
+ }
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+ }
+
+ glsafe(::glEnable(GL_CULL_FACE));
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ shader->stop_using();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+}
+
+void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object)
+{
+ if (model_object != m_old_model_object) {
+ m_planes.clear();
+ m_planes_valid = false;
+ }
+}
+
+void GLGizmoFlatten::update_planes()
+{
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ TriangleMesh ch;
+ for (const ModelVolume* vol : mo->volumes) {
+ if (vol->type() != ModelVolumeType::MODEL_PART)
+ continue;
+ TriangleMesh vol_ch = vol->get_convex_hull();
+ vol_ch.transform(vol->get_matrix());
+ ch.merge(vol_ch);
+ }
+ ch = ch.convex_hull_3d();
+ m_planes.clear();
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d inst_matrix = mo->instances.front()->get_matrix_no_offset();
+#else
+ const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true);
+#endif // ENABLE_WORLD_COORDINATE
+
+ // Following constants are used for discarding too small polygons.
+ const float minimal_area = 5.f; // in square mm (world coordinates)
+ const float minimal_side = 1.f; // mm
+
+ // Now we'll go through all the facets and append Points of facets sharing the same normal.
+ // This part is still performed in mesh coordinate system.
+ const int num_of_facets = ch.facets_count();
+ const std::vector<Vec3f> face_normals = its_face_normals(ch.its);
+ const std::vector<Vec3i> face_neighbors = its_face_neighbors(ch.its);
+ std::vector<int> facet_queue(num_of_facets, 0);
+ std::vector<bool> facet_visited(num_of_facets, false);
+ int facet_queue_cnt = 0;
+ const stl_normal* normal_ptr = nullptr;
+ int facet_idx = 0;
+ while (1) {
+ // Find next unvisited triangle:
+ for (; facet_idx < num_of_facets; ++ facet_idx)
+ if (!facet_visited[facet_idx]) {
+ facet_queue[facet_queue_cnt ++] = facet_idx;
+ facet_visited[facet_idx] = true;
+ normal_ptr = &face_normals[facet_idx];
+ m_planes.emplace_back();
+ break;
+ }
+ if (facet_idx == num_of_facets)
+ break; // Everything was visited already
+
+ while (facet_queue_cnt > 0) {
+ int facet_idx = facet_queue[-- facet_queue_cnt];
+ const stl_normal& this_normal = face_normals[facet_idx];
+ if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) {
+ const Vec3i face = ch.its.indices[facet_idx];
+ for (int j=0; j<3; ++j)
+ m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast<double>());
+
+ facet_visited[facet_idx] = true;
+ for (int j = 0; j < 3; ++ j)
+ if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx])
+ facet_queue[facet_queue_cnt ++] = neighbor_idx;
+ }
+ }
+ m_planes.back().normal = normal_ptr->cast<double>();
+
+ Pointf3s& verts = m_planes.back().vertices;
+ // Now we'll transform all the points into world coordinates, so that the areas, angles and distances
+ // make real sense.
+ verts = transform(verts, inst_matrix);
+
+ // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway):
+ if (verts.size() == 3 &&
+ ((verts[0] - verts[1]).norm() < minimal_side
+ || (verts[0] - verts[2]).norm() < minimal_side
+ || (verts[1] - verts[2]).norm() < minimal_side))
+ m_planes.pop_back();
+ }
+
+ // Let's prepare transformation of the normal vector from mesh to instance coordinates.
+ Geometry::Transformation t(inst_matrix);
+ Vec3d scaling = t.get_scaling_factor();
+ t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2)));
+
+ // Now we'll go through all the polygons, transform the points into xy plane to process them:
+ for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) {
+ Pointf3s& polygon = m_planes[polygon_id].vertices;
+ const Vec3d& normal = m_planes[polygon_id].normal;
+
+ // transform the normal according to the instance matrix:
+ Vec3d normal_transformed = t.get_matrix() * normal;
+
+ // We are going to rotate about z and y to flatten the plane
+ Eigen::Quaterniond q;
+ Transform3d m = Transform3d::Identity();
+ m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix();
+ polygon = transform(polygon, m);
+
+ // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since
+ // it works in fixed point representation, we will rescale the polygon to avoid overflows.
+ // And yes, it is a nasty thing to do. Whoever has time is free to refactor.
+ Vec3d bb_size = BoundingBoxf3(polygon).size();
+ float sf = std::min(1./bb_size(0), 1./bb_size(1));
+ Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f));
+ polygon = transform(polygon, tr);
+ polygon = Slic3r::Geometry::convex_hull(polygon);
+ polygon = transform(polygon, tr.inverse());
+
+ // Calculate area of the polygons and discard ones that are too small
+ float& area = m_planes[polygon_id].area;
+ area = 0.f;
+ for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula
+ area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1);
+ area = 0.5f * std::abs(area);
+
+ bool discard = false;
+ if (area < minimal_area)
+ discard = true;
+ else {
+ // We also check the inner angles and discard polygons with angles smaller than the following threshold
+ const double angle_threshold = ::cos(10.0 * (double)PI / 180.0);
+
+ for (unsigned int i = 0; i < polygon.size(); ++i) {
+ const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1];
+ const Vec3d& curr = polygon[i];
+ const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1];
+
+ if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) {
+ discard = true;
+ break;
+ }
+ }
+ }
+
+ if (discard) {
+ m_planes[polygon_id--] = std::move(m_planes.back());
+ m_planes.pop_back();
+ continue;
+ }
+
+ // We will shrink the polygon a little bit so it does not touch the object edges:
+ Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0));
+ centroid /= (double)polygon.size();
+ for (auto& vertex : polygon)
+ vertex = 0.9f*vertex + 0.1f*centroid;
+
+ // Polygon is now simple and convex, we'll round the corners to make them look nicer.
+ // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex
+ // towards their average (controlled by 'aggressivity'). This is repeated k times.
+ // In next iterations, the neighbours are not always taken at the middle (to increase the
+ // rounding effect at the corners, where we need it most).
+ const unsigned int k = 10; // number of iterations
+ const float aggressivity = 0.2f; // agressivity
+ const unsigned int N = polygon.size();
+ std::vector<std::pair<unsigned int, unsigned int>> neighbours;
+ if (k != 0) {
+ Pointf3s points_out(2*k*N); // vector long enough to store the future vertices
+ for (unsigned int j=0; j<N; ++j) {
+ points_out[j*2*k] = polygon[j];
+ neighbours.push_back(std::make_pair((int)(j*2*k-k) < 0 ? (N-1)*2*k+k : j*2*k-k, j*2*k+k));
+ }
+
+ for (unsigned int i=0; i<k; ++i) {
+ // Calculate middle of each edge so that neighbours points to something useful:
+ for (unsigned int j=0; j<N; ++j)
+ if (i==0)
+ points_out[j*2*k+k] = 0.5f * (points_out[j*2*k] + points_out[j==N-1 ? 0 : (j+1)*2*k]);
+ else {
+ float r = 0.2+0.3/(k-1)*i; // the neighbours are not always taken in the middle
+ points_out[neighbours[j].first] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].first-1];
+ points_out[neighbours[j].second] = r*points_out[j*2*k] + (1-r) * points_out[neighbours[j].second+1];
+ }
+ // Now we have a triangle and valid neighbours, we can do an iteration:
+ for (unsigned int j=0; j<N; ++j)
+ points_out[2*k*j] = (1-aggressivity) * points_out[2*k*j] +
+ aggressivity*0.5f*(points_out[neighbours[j].first] + points_out[neighbours[j].second]);
+
+ for (auto& n : neighbours) {
+ ++n.first;
+ --n.second;
+ }
+ }
+ polygon = points_out; // replace the coarse polygon with the smooth one that we just created
+ }
+
+
+ // Raise a bit above the object surface to avoid flickering:
+ for (auto& b : polygon)
+ b(2) += 0.1f;
+
+ // Transform back to 3D (and also back to mesh coordinates)
+ polygon = transform(polygon, inst_matrix.inverse() * m.inverse());
+ }
+
+ // We'll sort the planes by area and only keep the 254 largest ones (because of the picking pass limitations):
+ std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData& a, const PlaneData& b) { return a.area < b.area; });
+ m_planes.resize(std::min((int)m_planes.size(), 254));
+
+ // Planes are finished - let's save what we calculated it from:
+ m_volumes_matrices.clear();
+ m_volumes_types.clear();
+ for (const ModelVolume* vol : mo->volumes) {
+ m_volumes_matrices.push_back(vol->get_matrix());
+ m_volumes_types.push_back(vol->type());
+ }
+ m_first_instance_scale = mo->instances.front()->get_scaling_factor();
+ m_first_instance_mirror = mo->instances.front()->get_mirror();
+ m_old_model_object = mo;
+
+ // And finally create respective VBOs. The polygon is convex with
+ // the vertices in order, so triangulation is trivial.
+ for (auto& plane : m_planes) {
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLModel::Geometry init_data;
+ init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3 };
+ init_data.reserve_vertices(plane.vertices.size());
+ init_data.reserve_indices(plane.vertices.size());
+ // vertices + indices
+ for (size_t i = 0; i < plane.vertices.size(); ++i) {
+ init_data.add_vertex((Vec3f)plane.vertices[i].cast<float>(), (Vec3f)plane.normal.cast<float>());
+ init_data.add_index((unsigned int)i);
+ }
+ plane.vbo.init_from(std::move(init_data));
+#else
+ plane.vbo.reserve(plane.vertices.size());
+ for (const auto& vert : plane.vertices)
+ plane.vbo.push_geometry(vert, plane.normal);
+ for (size_t i=1; i<plane.vertices.size()-1; ++i)
+ plane.vbo.push_triangle(0, i, i+1); // triangle fan
+ plane.vbo.finalize_geometry(true);
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ // FIXME: vertices should really be local, they need not
+ // persist now when we use VBOs
+ plane.vertices.clear();
+ plane.vertices.shrink_to_fit();
+ }
+
+ m_planes_valid = true;
+}
+
+
+bool GLGizmoFlatten::is_plane_update_necessary() const
+{
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ if (m_state != On || ! mo || mo->instances.empty())
+ return false;
+
+ if (! m_planes_valid || mo != m_old_model_object
+ || mo->volumes.size() != m_volumes_matrices.size())
+ return true;
+
+ // We want to recalculate when the scale changes - some planes could (dis)appear.
+ if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale)
+ || ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror))
+ return true;
+
+ for (unsigned int i=0; i < mo->volumes.size(); ++i)
+ if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i])
+ || mo->volumes[i]->type() != m_volumes_types[i])
+ return true;
+
+ return false;
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
index 88b319f25..7272a5ef7 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
@@ -1,988 +1,992 @@
-#include "GLGizmoHollow.hpp"
-#include "slic3r/GUI/GLCanvas3D.hpp"
-#include "slic3r/GUI/Camera.hpp"
-#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
-
-#include <GL/glew.h>
-
-#include "slic3r/GUI/GUI_App.hpp"
-#include "slic3r/GUI/GUI_ObjectSettings.hpp"
-#include "slic3r/GUI/GUI_ObjectList.hpp"
-#include "slic3r/GUI/Plater.hpp"
-#include "libslic3r/PresetBundle.hpp"
-
-#include "libslic3r/Model.hpp"
-
-
-namespace Slic3r {
-namespace GUI {
-
-GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
- : GLGizmoBase(parent, icon_filename, sprite_id)
-{
-}
-
-
-bool GLGizmoHollow::on_init()
-{
- m_shortcut_key = WXK_CONTROL_H;
- m_desc["enable"] = _(L("Hollow this object"));
- m_desc["preview"] = _(L("Preview hollowed and drilled model"));
- m_desc["offset"] = _(L("Offset")) + ": ";
- m_desc["quality"] = _(L("Quality")) + ": ";
- m_desc["closing_distance"] = _(L("Closing distance")) + ": ";
- m_desc["hole_diameter"] = _(L("Hole diameter")) + ": ";
- m_desc["hole_depth"] = _(L("Hole depth")) + ": ";
- m_desc["remove_selected"] = _(L("Remove selected holes"));
- m_desc["remove_all"] = _(L("Remove all holes"));
- m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": ";
- m_desc["reset_direction"] = _(L("Reset direction"));
- m_desc["show_supports"] = _(L("Show supports"));
-
- return true;
-}
-
-void GLGizmoHollow::data_changed()
-{
- if (! m_c->selection_info())
- return;
-
- const ModelObject* mo = m_c->selection_info()->model_object();
- if (m_state == On && mo) {
- if (m_old_mo_id != mo->id()) {
- reload_cache();
- m_old_mo_id = mo->id();
- }
- if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh())
- m_holes_in_drilled_mesh = mo->sla_drain_holes;
- }
-}
-
-
-
-void GLGizmoHollow::on_render()
-{
- if (!m_cylinder.is_initialized())
- m_cylinder.init_from(its_make_cylinder(1.0, 1.0));
-
- const Selection& selection = m_parent.get_selection();
- const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info();
-
- // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off
- if (m_state == On
- && (sel_info->model_object() != selection.get_model()->objects[selection.get_object_idx()]
- || sel_info->get_active_instance() != selection.get_instance_idx())) {
- m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
- return;
- }
-
- glsafe(::glEnable(GL_BLEND));
- glsafe(::glEnable(GL_DEPTH_TEST));
-
- if (selection.is_from_single_instance())
- render_points(selection, false);
-
- m_selection_rectangle.render(m_parent);
- m_c->object_clipper()->render_cut();
- m_c->supports_clipper()->render_cut();
-
- glsafe(::glDisable(GL_BLEND));
-}
-
-
-void GLGizmoHollow::on_render_for_picking()
-{
- const Selection& selection = m_parent.get_selection();
-//#if ENABLE_RENDER_PICKING_PASS
-// m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
-//#endif
-
- glsafe(::glEnable(GL_DEPTH_TEST));
- render_points(selection, true);
-}
-
-void GLGizmoHollow::render_points(const Selection& selection, bool picking)
-{
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light");
- if (shader == nullptr)
- return;
-
- shader->start_using();
- ScopeGuard guard([shader]() { shader->stop_using(); });
-#else
- GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light");
- if (shader)
- shader->start_using();
- ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
- Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation();
-
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse();
- const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix();
-
- const Camera& camera = wxGetApp().plater()->get_camera();
- const Transform3d& view_matrix = camera.get_view_matrix();
- const Transform3d& projection_matrix = camera.get_projection_matrix();
-
- shader->set_uniform("projection_matrix", projection_matrix);
-#else
- const Transform3d& instance_scaling_matrix_inverse = trafo.get_matrix(true, true, false, true).inverse();
- const Transform3d& instance_matrix = trafo.get_matrix();
-
- glsafe(::glPushMatrix());
- glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift()));
- glsafe(::glMultMatrixd(instance_matrix.data()));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
- ColorRGBA render_color;
- const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
- const size_t cache_size = drain_holes.size();
-
- for (size_t i = 0; i < cache_size; ++i) {
- const sla::DrainHole& drain_hole = drain_holes[i];
- const bool point_selected = m_selected[i];
-
- if (is_mesh_point_clipped(drain_hole.pos.cast<double>()))
- continue;
-
- // First decide about the color of the point.
- if (picking)
- render_color = picking_color_component(i);
- else {
- if (size_t(m_hover_id) == i)
- render_color = ColorRGBA::CYAN();
- else if (m_c->hollowed_mesh() &&
- i < m_c->hollowed_mesh()->get_drainholes().size() &&
- m_c->hollowed_mesh()->get_drainholes()[i].failed) {
- render_color = { 1.0f, 0.0f, 0.0f, 0.5f };
- }
- else // neither hover nor picking
- render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f);
- }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_cylinder.set_color(render_color);
-#else
- const_cast<GLModel*>(&m_cylinder)->set_color(-1, render_color);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast<double>()) * instance_scaling_matrix_inverse;
-#else
- glsafe(::glPushMatrix());
- glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z()));
- glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data()));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
- if (vol->is_left_handed())
- glFrontFace(GL_CW);
-
- // Matrices set, we can render the point mark now.
- Eigen::Quaterniond q;
- q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast<double>());
- const Eigen::AngleAxisd aa(q);
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) *
- Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength));
-
- shader->set_uniform("view_model_matrix", view_model_matrix);
- shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
-#else
- glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z()));
- glsafe(::glTranslated(0., 0., -drain_hole.height));
- glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- m_cylinder.render();
-
- if (vol->is_left_handed())
- glFrontFace(GL_CCW);
-
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
- }
-
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-}
-
-bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const
-{
- if (m_c->object_clipper()->get_position() == 0.)
- return false;
-
- auto sel_info = m_c->selection_info();
- int active_inst = m_c->selection_info()->get_active_instance();
- const ModelInstance* mi = sel_info->model_object()->instances[active_inst];
- const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix();
-
- Vec3d transformed_point = trafo * point;
- transformed_point(2) += sel_info->get_sla_shift();
- return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
-}
-
-
-
-// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
-// Return false if no intersection was found, true otherwise.
-bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
-{
- if (! m_c->raycaster()->raycaster())
- return false;
-
- const Camera& camera = wxGetApp().plater()->get_camera();
- const Selection& selection = m_parent.get_selection();
- const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
- Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation();
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
-
- double clp_dist = m_c->object_clipper()->get_position();
- const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
-
- // The raycaster query
- Vec3f hit;
- Vec3f normal;
- if (m_c->raycaster()->raycaster()->unproject_on_mesh(
- mouse_pos,
- trafo.get_matrix(),
- camera,
- hit,
- normal,
- clp_dist != 0. ? clp : nullptr))
- {
- if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) {
- // in this case the raycaster sees the hollowed and drilled mesh.
- // if the point lies on the surface created by the hole, we want
- // to ignore it.
- for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) {
- sla::DrainHole outer(hole);
- outer.radius *= 1.001f;
- outer.height *= 1.001f;
- if (outer.is_inside(hit))
- return false;
- }
- }
-
- // Return both the point and the facet normal.
- pos_and_normal = std::make_pair(hit, normal);
- return true;
- }
- else
- return false;
-}
-
-// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
-// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
-// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
-// concludes that the event was not intended for it, it should return false.
-bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
-{
- ModelObject* mo = m_c->selection_info()->model_object();
- int active_inst = m_c->selection_info()->get_active_instance();
-
-
- // left down with shift - show the selection rectangle:
- if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) {
- if (m_hover_id == -1) {
- if (shift_down || alt_down) {
- m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect);
- }
- }
- else {
- if (m_selected[m_hover_id])
- unselect_point(m_hover_id);
- else {
- if (!alt_down)
- select_point(m_hover_id);
- }
- }
-
- return true;
- }
-
- // left down without selection rectangle - place point on the mesh:
- if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) {
- // If any point is in hover state, this should initiate its move - return control back to GLCanvas:
- if (m_hover_id != -1)
- return false;
-
- // If there is some selection, don't add new point and deselect everything instead.
- if (m_selection_empty) {
- std::pair<Vec3f, Vec3f> pos_and_normal;
- if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole")));
-
- mo->sla_drain_holes.emplace_back(pos_and_normal.first,
- -pos_and_normal.second, m_new_hole_radius, m_new_hole_height);
- m_selected.push_back(false);
- assert(m_selected.size() == mo->sla_drain_holes.size());
- m_parent.set_as_dirty();
- m_wait_for_up_event = true;
- }
- else
- return false;
- }
- else
- select_point(NoPoints);
-
- return true;
- }
-
- // left up with selection rectangle - select points inside the rectangle:
- if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) {
- // Is this a selection or deselection rectangle?
- GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
-
- // First collect positions of all the points in world coordinates.
- Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation();
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
- std::vector<Vec3d> points;
- for (unsigned int i=0; i<mo->sla_drain_holes.size(); ++i)
- points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast<double>());
-
- // Now ask the rectangle which of the points are inside.
- std::vector<Vec3f> points_inside;
- std::vector<unsigned int> points_idxs = m_selection_rectangle.stop_dragging(m_parent, points);
- for (size_t idx : points_idxs)
- points_inside.push_back(points[idx].cast<float>());
-
- // Only select/deselect points that are actually visible
- for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs(
- trafo, wxGetApp().plater()->get_camera(), points_inside,
- m_c->object_clipper()->get_clipping_plane()))
- {
- if (rectangle_status == GLSelectionRectangle::EState::Deselect)
- unselect_point(points_idxs[idx]);
- else
- select_point(points_idxs[idx]);
- }
- return true;
- }
-
- // left up with no selection rectangle
- if (action == SLAGizmoEventType::LeftUp) {
- if (m_wait_for_up_event) {
- m_wait_for_up_event = false;
- return true;
- }
- }
-
- // dragging the selection rectangle:
- if (action == SLAGizmoEventType::Dragging) {
- if (m_wait_for_up_event)
- return true; // point has been placed and the button not released yet
- // this prevents GLCanvas from starting scene rotation
-
- if (m_selection_rectangle.is_dragging()) {
- m_selection_rectangle.dragging(mouse_position);
- return true;
- }
-
- return false;
- }
-
- if (action == SLAGizmoEventType::Delete) {
- // delete key pressed
- delete_selected_points();
- return true;
- }
-
- if (action == SLAGizmoEventType::RightDown) {
- if (m_hover_id != -1) {
- select_point(NoPoints);
- select_point(m_hover_id);
- delete_selected_points();
- return true;
- }
- return false;
- }
-
- if (action == SLAGizmoEventType::SelectAll) {
- select_point(AllPoints);
- return true;
- }
-
- if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
- double pos = m_c->object_clipper()->get_position();
- pos = std::min(1., pos + 0.01);
- m_c->object_clipper()->set_position(pos, true);
- return true;
- }
-
- if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
- double pos = m_c->object_clipper()->get_position();
- pos = std::max(0., pos - 0.01);
- m_c->object_clipper()->set_position(pos, true);
- return true;
- }
-
- if (action == SLAGizmoEventType::ResetClippingPlane) {
- m_c->object_clipper()->set_position(-1., false);
- return true;
- }
-
- return false;
-}
-
-void GLGizmoHollow::delete_selected_points()
-{
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete drainage hole")));
- sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
-
- for (unsigned int idx=0; idx<drain_holes.size(); ++idx) {
- if (m_selected[idx]) {
- m_selected.erase(m_selected.begin()+idx);
- drain_holes.erase(drain_holes.begin() + (idx--));
- }
- }
-
- select_point(NoPoints);
-}
-
-bool GLGizmoHollow::on_mouse(const wxMouseEvent &mouse_event)
-{
- if (mouse_event.Moving()) return false;
- if (use_grabbers(mouse_event)) return true;
-
- // wxCoord == int --> wx/types.h
- Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
- Vec2d mouse_pos = mouse_coord.cast<double>();
-
- static bool pending_right_up = false;
- if (mouse_event.LeftDown()) {
- bool control_down = mouse_event.CmdDown();
- bool grabber_contains_mouse = (get_hover_id() != -1);
- if ((!control_down || grabber_contains_mouse) &&
- gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false))
- // the gizmo got the event and took some action, there is no need
- // to do anything more
- return true;
- } else if (mouse_event.Dragging()) {
- if (m_parent.get_move_volume_id() != -1)
- // don't allow dragging objects with the Sla gizmo on
- return true;
-
- bool control_down = mouse_event.CmdDown();
- if (control_down) {
- // CTRL has been pressed while already dragging -> stop current action
- if (mouse_event.LeftIsDown())
- gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
- else if (mouse_event.RightIsDown()) {
- pending_right_up = false;
- }
- } else if(gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) {
- // the gizmo got the event and took some action, no need to do
- // anything more here
- m_parent.set_as_dirty();
- return true;
- }
- } else if (mouse_event.LeftUp()) {
- if (!m_parent.is_mouse_dragging()) {
- bool control_down = mouse_event.CmdDown();
- // in case gizmo is selected, we just pass the LeftUp event
- // and stop processing - neither object moving or selecting is
- // suppressed in that case
- gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down);
- return true;
- }
- } else if (mouse_event.RightDown()) {
- if (m_parent.get_selection().get_object_idx() != -1 &&
- gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) {
- // we need to set the following right up as processed to avoid showing
- // the context menu if the user release the mouse over the object
- pending_right_up = true;
- // event was taken care of by the SlaSupports gizmo
- return true;
- }
- } else if (mouse_event.RightUp()) {
- if (pending_right_up) {
- pending_right_up = false;
- return true;
- }
- }
- return false;
-}
-
-void GLGizmoHollow::hollow_mesh(bool postpone_error_messages)
-{
- wxGetApp().CallAfter([this, postpone_error_messages]() {
- wxGetApp().plater()->reslice_SLA_hollowing(
- *m_c->selection_info()->model_object(), postpone_error_messages);
- });
-}
-
-
-std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>>
-GLGizmoHollow::get_config_options(const std::vector<std::string>& keys) const
-{
- std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> out;
- const ModelObject* mo = m_c->selection_info()->model_object();
-
- if (! mo)
- return out;
-
- const DynamicPrintConfig& object_cfg = mo->config.get();
- const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
- std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
-
- for (const std::string& key : keys) {
- if (object_cfg.has(key))
- out.emplace_back(object_cfg.option(key), &object_cfg.def()->options.at(key)); // at() needed for const map
- else
- if (print_cfg.has(key))
- out.emplace_back(print_cfg.option(key), &print_cfg.def()->options.at(key));
- else { // we must get it from defaults
- if (default_cfg == nullptr)
- default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys));
- out.emplace_back(default_cfg->option(key), &default_cfg->def()->options.at(key));
- }
- }
-
- return out;
-}
-
-
-void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit)
-{
- ModelObject* mo = m_c->selection_info()->model_object();
- if (! mo)
- return;
-
- bool first_run = true; // This is a hack to redraw the button when all points are removed,
- // so it is not delayed until the background process finishes.
-
- ConfigOptionMode current_mode = wxGetApp().get_mode();
-
- std::vector<std::string> opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"};
- auto opts = get_config_options(opts_keys);
- auto* offset_cfg = static_cast<const ConfigOptionFloat*>(opts[0].first);
- float offset = offset_cfg->value;
- double offset_min = opts[0].second->min;
- double offset_max = opts[0].second->max;
-
- auto* quality_cfg = static_cast<const ConfigOptionFloat*>(opts[1].first);
- float quality = quality_cfg->value;
- double quality_min = opts[1].second->min;
- double quality_max = opts[1].second->max;
- ConfigOptionMode quality_mode = opts[1].second->mode;
-
- auto* closing_d_cfg = static_cast<const ConfigOptionFloat*>(opts[2].first);
- float closing_d = closing_d_cfg->value;
- double closing_d_min = opts[2].second->min;
- double closing_d_max = opts[2].second->max;
- ConfigOptionMode closing_d_mode = opts[2].second->mode;
-
- m_desc["offset"] = _(opts[0].second->label) + ":";
- m_desc["quality"] = _(opts[1].second->label) + ":";
- m_desc["closing_distance"] = _(opts[2].second->label) + ":";
-
-
-RENDER_AGAIN:
- const float approx_height = m_imgui->scaled(20.0f);
- y = std::min(y, bottom_limit - approx_height);
- m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
-
- m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
-
- // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
- const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
- m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(0.5f);
-
- const float settings_sliders_left =
- std::max(std::max({m_imgui->calc_text_size(m_desc.at("offset")).x,
- m_imgui->calc_text_size(m_desc.at("quality")).x,
- m_imgui->calc_text_size(m_desc.at("closing_distance")).x,
- m_imgui->calc_text_size(m_desc.at("hole_diameter")).x,
- m_imgui->calc_text_size(m_desc.at("hole_depth")).x}) + m_imgui->scaled(0.5f), clipping_slider_left);
-
- const float diameter_slider_left = settings_sliders_left; //m_imgui->calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f);
- const float minimal_slider_width = m_imgui->scaled(4.f);
-
- const float button_preview_width = m_imgui->calc_button_size(m_desc.at("preview")).x;
-
- float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left});
- window_width = std::max(window_width, button_preview_width);
-
- if (m_imgui->button(m_desc["preview"]))
- hollow_mesh();
-
- bool config_changed = false;
-
- ImGui::Separator();
-
- {
- auto opts = get_config_options({"hollowing_enable"});
- m_enable_hollowing = static_cast<const ConfigOptionBool*>(opts[0].first)->value;
- if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) {
- mo->config.set("hollowing_enable", m_enable_hollowing);
- wxGetApp().obj_list()->update_and_show_object_settings_item();
- config_changed = true;
- }
- }
-
- m_imgui->disabled_begin(! m_enable_hollowing);
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("offset"));
- ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
- ImGui::PushItemWidth(window_width - settings_sliders_left);
- m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip));
-
- bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider
- bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider
- bool slider_released =m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider
-
- if (current_mode >= quality_mode) {
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("quality"));
- ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
- m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip));
-
- slider_clicked |= m_imgui->get_last_slider_status().clicked;
- slider_edited |= m_imgui->get_last_slider_status().edited;
- slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
- }
-
- if (current_mode >= closing_d_mode) {
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("closing_distance"));
- ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
- m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm", 1.0f, true, _L(opts[2].second->tooltip));
-
- slider_clicked |= m_imgui->get_last_slider_status().clicked;
- slider_edited |= m_imgui->get_last_slider_status().edited;
- slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
- }
-
- if (slider_clicked) {
- m_offset_stash = offset;
- m_quality_stash = quality;
- m_closing_d_stash = closing_d;
- }
- if (slider_edited || slider_released) {
- if (slider_released) {
- mo->config.set("hollowing_min_thickness", m_offset_stash);
- mo->config.set("hollowing_quality", m_quality_stash);
- mo->config.set("hollowing_closing_distance", m_closing_d_stash);
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change")));
- }
- mo->config.set("hollowing_min_thickness", offset);
- mo->config.set("hollowing_quality", quality);
- mo->config.set("hollowing_closing_distance", closing_d);
- if (slider_released) {
- wxGetApp().obj_list()->update_and_show_object_settings_item();
- config_changed = true;
- }
- }
-
- m_imgui->disabled_end();
-
- bool force_refresh = false;
- bool remove_selected = false;
- bool remove_all = false;
-
- ImGui::Separator();
-
- float diameter_upper_cap = 60.;
- if (m_new_hole_radius * 2.f > diameter_upper_cap)
- m_new_hole_radius = diameter_upper_cap / 2.f;
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("hole_diameter"));
- ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x);
- ImGui::PushItemWidth(window_width - diameter_slider_left);
-
- float diam = 2.f * m_new_hole_radius;
- m_imgui->slider_float("##hole_diameter", &diam, 1.f, 25.f, "%.1f mm", 1.f, false);
- // Let's clamp the value (which could have been entered by keyboard) to a larger range
- // than the slider. This allows entering off-scale values and still protects against
- //complete non-sense.
- diam = std::clamp(diam, 0.1f, diameter_upper_cap);
- m_new_hole_radius = diam / 2.f;
- bool clicked = m_imgui->get_last_slider_status().clicked;
- bool edited = m_imgui->get_last_slider_status().edited;
- bool deactivated = m_imgui->get_last_slider_status().deactivated_after_edit;
-
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc["hole_depth"]);
- ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x);
- m_imgui->slider_float("##hole_depth", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false);
- // Same as above:
- m_new_hole_height = std::clamp(m_new_hole_height, 0.f, 100.f);
-
- clicked |= m_imgui->get_last_slider_status().clicked;
- edited |= m_imgui->get_last_slider_status().edited;
- deactivated |= m_imgui->get_last_slider_status().deactivated_after_edit;;
-
- // Following is a nasty way to:
- // - save the initial value of the slider before one starts messing with it
- // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
- // - take correct undo/redo snapshot after the user is done with moving the slider
- if (! m_selection_empty) {
- if (clicked) {
- m_holes_stash = mo->sla_drain_holes;
- }
- if (edited) {
- for (size_t idx=0; idx<m_selected.size(); ++idx)
- if (m_selected[idx]) {
- mo->sla_drain_holes[idx].radius = m_new_hole_radius;
- mo->sla_drain_holes[idx].height = m_new_hole_height;
- }
- }
- if (deactivated) {
- // momentarily restore the old value to take snapshot
- sla::DrainHoles new_holes = mo->sla_drain_holes;
- mo->sla_drain_holes = m_holes_stash;
- float backup_rad = m_new_hole_radius;
- float backup_hei = m_new_hole_height;
- for (size_t i=0; i<m_holes_stash.size(); ++i) {
- if (m_selected[i]) {
- m_new_hole_radius = m_holes_stash[i].radius;
- m_new_hole_height = m_holes_stash[i].height;
- break;
- }
- }
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Change drainage hole diameter")));
- m_new_hole_radius = backup_rad;
- m_new_hole_height = backup_hei;
- mo->sla_drain_holes = new_holes;
- }
- }
-
- m_imgui->disabled_begin(m_selection_empty);
- remove_selected = m_imgui->button(m_desc.at("remove_selected"));
- m_imgui->disabled_end();
-
- m_imgui->disabled_begin(mo->sla_drain_holes.empty());
- remove_all = m_imgui->button(m_desc.at("remove_all"));
- m_imgui->disabled_end();
-
- // Following is rendered in both editing and non-editing mode:
- // m_imgui->text("");
- ImGui::Separator();
- if (m_c->object_clipper()->get_position() == 0.f) {
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("clipping_of_view"));
- }
- else {
- if (m_imgui->button(m_desc.at("reset_direction"))) {
- wxGetApp().CallAfter([this](){
- m_c->object_clipper()->set_position(-1., false);
- });
- }
- }
-
- ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
- ImGui::PushItemWidth(window_width - settings_sliders_left);
- float clp_dist = m_c->object_clipper()->get_position();
- if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
- m_c->object_clipper()->set_position(clp_dist, true);
-
- // make sure supports are shown/hidden as appropriate
- bool show_sups = m_c->instances_hider()->are_supports_shown();
- if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) {
- m_c->instances_hider()->show_supports(show_sups);
- force_refresh = true;
- }
-
- m_imgui->end();
-
-
- if (remove_selected || remove_all) {
- force_refresh = false;
- m_parent.set_as_dirty();
-
- if (remove_all) {
- select_point(AllPoints);
- delete_selected_points();
- }
- if (remove_selected)
- delete_selected_points();
-
- if (first_run) {
- first_run = false;
- goto RENDER_AGAIN;
- }
- }
-
- if (force_refresh)
- m_parent.set_as_dirty();
-
- if (config_changed)
- m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
-}
-
-bool GLGizmoHollow::on_is_activable() const
-{
- const Selection& selection = m_parent.get_selection();
-
- if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA
- || !selection.is_from_single_instance())
- return false;
-
- // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
- const Selection::IndicesList& list = selection.get_volume_idxs();
- for (const auto& idx : list)
- if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0)
- return false;
-
- return true;
-}
-
-bool GLGizmoHollow::on_is_selectable() const
-{
- return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA);
-}
-
-std::string GLGizmoHollow::on_get_name() const
-{
- return _u8L("Hollow and drill");
-}
-
-
-CommonGizmosDataID GLGizmoHollow::on_get_requirements() const
-{
- return CommonGizmosDataID(
- int(CommonGizmosDataID::SelectionInfo)
- | int(CommonGizmosDataID::InstancesHider)
- | int(CommonGizmosDataID::Raycaster)
- | int(CommonGizmosDataID::HollowedMesh)
- | int(CommonGizmosDataID::ObjectClipper)
- | int(CommonGizmosDataID::SupportsClipper));
-}
-
-
-void GLGizmoHollow::on_set_state()
-{
- if (m_state == m_old_state)
- return;
-
- if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off
- m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
- m_old_state = m_state;
-}
-
-
-
-void GLGizmoHollow::on_start_dragging()
-{
- if (m_hover_id != -1) {
- select_point(NoPoints);
- select_point(m_hover_id);
- m_hole_before_drag = m_c->selection_info()->model_object()->sla_drain_holes[m_hover_id].pos;
- }
- else
- m_hole_before_drag = Vec3f::Zero();
-}
-
-
-void GLGizmoHollow::on_stop_dragging()
-{
- sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
- if (m_hover_id != -1) {
- Vec3f backup = drain_holes[m_hover_id].pos;
-
- if (m_hole_before_drag != Vec3f::Zero() // some point was touched
- && backup != m_hole_before_drag) // and it was moved, not just selected
- {
- drain_holes[m_hover_id].pos = m_hole_before_drag;
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move drainage hole")));
- drain_holes[m_hover_id].pos = backup;
- }
- }
- m_hole_before_drag = Vec3f::Zero();
-}
-
-
-void GLGizmoHollow::on_dragging(const UpdateData &data)
-{
- assert(m_hover_id != -1);
- std::pair<Vec3f, Vec3f> pos_and_normal;
- if (!unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
- return;
- sla::DrainHoles &drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
- drain_holes[m_hover_id].pos = pos_and_normal.first;
- drain_holes[m_hover_id].normal = -pos_and_normal.second;
-}
-
-
-void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar)
-{
- ar(m_new_hole_radius,
- m_new_hole_height,
- m_selected,
- m_selection_empty
- );
-}
-
-
-
-void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const
-{
- ar(m_new_hole_radius,
- m_new_hole_height,
- m_selected,
- m_selection_empty
- );
-}
-
-
-
-void GLGizmoHollow::select_point(int i)
-{
- const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
-
- if (i == AllPoints || i == NoPoints) {
- m_selected.assign(m_selected.size(), i == AllPoints);
- m_selection_empty = (i == NoPoints);
-
- if (i == AllPoints) {
- m_new_hole_radius = drain_holes[0].radius;
- m_new_hole_height = drain_holes[0].height;
- }
- }
- else {
- while (size_t(i) >= m_selected.size())
- m_selected.push_back(false);
- m_selected[i] = true;
- m_selection_empty = false;
- m_new_hole_radius = drain_holes[i].radius;
- m_new_hole_height = drain_holes[i].height;
- }
-}
-
-
-void GLGizmoHollow::unselect_point(int i)
-{
- m_selected[i] = false;
- m_selection_empty = true;
- for (const bool sel : m_selected) {
- if (sel) {
- m_selection_empty = false;
- break;
- }
- }
-}
-
-void GLGizmoHollow::reload_cache()
-{
- m_selected.clear();
- m_selected.assign(m_c->selection_info()->model_object()->sla_drain_holes.size(), false);
-}
-
-
-void GLGizmoHollow::on_set_hover_id()
-{
- if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id)
- m_hover_id = -1;
-}
-
-
-
-
-} // namespace GUI
-} // namespace Slic3r
+#include "GLGizmoHollow.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/Camera.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
+
+#include <GL/glew.h>
+
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/GUI_ObjectSettings.hpp"
+#include "slic3r/GUI/GUI_ObjectList.hpp"
+#include "slic3r/GUI/Plater.hpp"
+#include "libslic3r/PresetBundle.hpp"
+
+#include "libslic3r/Model.hpp"
+
+
+namespace Slic3r {
+namespace GUI {
+
+GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
+ : GLGizmoBase(parent, icon_filename, sprite_id)
+{
+}
+
+
+bool GLGizmoHollow::on_init()
+{
+ m_shortcut_key = WXK_CONTROL_H;
+ m_desc["enable"] = _(L("Hollow this object"));
+ m_desc["preview"] = _(L("Preview hollowed and drilled model"));
+ m_desc["offset"] = _(L("Offset")) + ": ";
+ m_desc["quality"] = _(L("Quality")) + ": ";
+ m_desc["closing_distance"] = _(L("Closing distance")) + ": ";
+ m_desc["hole_diameter"] = _(L("Hole diameter")) + ": ";
+ m_desc["hole_depth"] = _(L("Hole depth")) + ": ";
+ m_desc["remove_selected"] = _(L("Remove selected holes"));
+ m_desc["remove_all"] = _(L("Remove all holes"));
+ m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": ";
+ m_desc["reset_direction"] = _(L("Reset direction"));
+ m_desc["show_supports"] = _(L("Show supports"));
+
+ return true;
+}
+
+void GLGizmoHollow::data_changed()
+{
+ if (! m_c->selection_info())
+ return;
+
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ if (m_state == On && mo) {
+ if (m_old_mo_id != mo->id()) {
+ reload_cache();
+ m_old_mo_id = mo->id();
+ }
+ if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh())
+ m_holes_in_drilled_mesh = mo->sla_drain_holes;
+ }
+}
+
+
+
+void GLGizmoHollow::on_render()
+{
+ if (!m_cylinder.is_initialized())
+ m_cylinder.init_from(its_make_cylinder(1.0, 1.0));
+
+ const Selection& selection = m_parent.get_selection();
+ const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info();
+
+ // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off
+ if (m_state == On
+ && (sel_info->model_object() != selection.get_model()->objects[selection.get_object_idx()]
+ || sel_info->get_active_instance() != selection.get_instance_idx())) {
+ m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
+ return;
+ }
+
+ glsafe(::glEnable(GL_BLEND));
+ glsafe(::glEnable(GL_DEPTH_TEST));
+
+ if (selection.is_from_single_instance())
+ render_points(selection, false);
+
+ m_selection_rectangle.render(m_parent);
+ m_c->object_clipper()->render_cut();
+ m_c->supports_clipper()->render_cut();
+
+ glsafe(::glDisable(GL_BLEND));
+}
+
+
+void GLGizmoHollow::on_render_for_picking()
+{
+ const Selection& selection = m_parent.get_selection();
+//#if ENABLE_RENDER_PICKING_PASS
+// m_z_shift = selection.get_first_volume()->get_sla_shift_z();
+//#endif
+
+ glsafe(::glEnable(GL_DEPTH_TEST));
+ render_points(selection, true);
+}
+
+void GLGizmoHollow::render_points(const Selection& selection, bool picking)
+{
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light");
+ if (shader == nullptr)
+ return;
+
+ shader->start_using();
+ ScopeGuard guard([shader]() { shader->stop_using(); });
+#else
+ GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light");
+ if (shader)
+ shader->start_using();
+ ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ const GLVolume* vol = selection.get_first_volume();
+ Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation();
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_scaling_factor_matrix().inverse();
+#else
+ const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse();
+#endif // ENABLE_WORLD_COORDINATE
+ const Transform3d instance_matrix = Geometry::translation_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix();
+
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ const Transform3d& view_matrix = camera.get_view_matrix();
+ const Transform3d& projection_matrix = camera.get_projection_matrix();
+
+ shader->set_uniform("projection_matrix", projection_matrix);
+#else
+ const Transform3d& instance_scaling_matrix_inverse = trafo.get_matrix(true, true, false, true).inverse();
+ const Transform3d& instance_matrix = trafo.get_matrix();
+
+ glsafe(::glPushMatrix());
+ glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift()));
+ glsafe(::glMultMatrixd(instance_matrix.data()));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ ColorRGBA render_color;
+ const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
+ const size_t cache_size = drain_holes.size();
+
+ for (size_t i = 0; i < cache_size; ++i) {
+ const sla::DrainHole& drain_hole = drain_holes[i];
+ const bool point_selected = m_selected[i];
+
+ if (is_mesh_point_clipped(drain_hole.pos.cast<double>()))
+ continue;
+
+ // First decide about the color of the point.
+ if (picking)
+ render_color = picking_color_component(i);
+ else {
+ if (size_t(m_hover_id) == i)
+ render_color = ColorRGBA::CYAN();
+ else if (m_c->hollowed_mesh() &&
+ i < m_c->hollowed_mesh()->get_drainholes().size() &&
+ m_c->hollowed_mesh()->get_drainholes()[i].failed) {
+ render_color = { 1.0f, 0.0f, 0.0f, 0.5f };
+ }
+ else // neither hover nor picking
+ render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f);
+ }
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_cylinder.set_color(render_color);
+#else
+ const_cast<GLModel*>(&m_cylinder)->set_color(-1, render_color);
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast<double>()) * instance_scaling_matrix_inverse;
+#else
+ glsafe(::glPushMatrix());
+ glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z()));
+ glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data()));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ if (vol->is_left_handed())
+ glFrontFace(GL_CW);
+
+ // Matrices set, we can render the point mark now.
+ Eigen::Quaterniond q;
+ q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast<double>());
+ const Eigen::AngleAxisd aa(q);
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) *
+ Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength));
+
+ shader->set_uniform("view_model_matrix", view_model_matrix);
+ shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
+#else
+ glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z()));
+ glsafe(::glTranslated(0., 0., -drain_hole.height));
+ glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ m_cylinder.render();
+
+ if (vol->is_left_handed())
+ glFrontFace(GL_CCW);
+
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+ }
+
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+}
+
+bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const
+{
+ if (m_c->object_clipper()->get_position() == 0.)
+ return false;
+
+ auto sel_info = m_c->selection_info();
+ int active_inst = m_c->selection_info()->get_active_instance();
+ const ModelInstance* mi = sel_info->model_object()->instances[active_inst];
+ const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix();
+
+ Vec3d transformed_point = trafo * point;
+ transformed_point(2) += sel_info->get_sla_shift();
+ return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
+}
+
+
+
+// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
+// Return false if no intersection was found, true otherwise.
+bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
+{
+ if (! m_c->raycaster()->raycaster())
+ return false;
+
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ const Selection& selection = m_parent.get_selection();
+ const GLVolume* volume = selection.get_first_volume();
+ Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation();
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
+
+ double clp_dist = m_c->object_clipper()->get_position();
+ const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
+
+ // The raycaster query
+ Vec3f hit;
+ Vec3f normal;
+ if (m_c->raycaster()->raycaster()->unproject_on_mesh(
+ mouse_pos,
+ trafo.get_matrix(),
+ camera,
+ hit,
+ normal,
+ clp_dist != 0. ? clp : nullptr))
+ {
+ if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) {
+ // in this case the raycaster sees the hollowed and drilled mesh.
+ // if the point lies on the surface created by the hole, we want
+ // to ignore it.
+ for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) {
+ sla::DrainHole outer(hole);
+ outer.radius *= 1.001f;
+ outer.height *= 1.001f;
+ if (outer.is_inside(hit))
+ return false;
+ }
+ }
+
+ // Return both the point and the facet normal.
+ pos_and_normal = std::make_pair(hit, normal);
+ return true;
+ }
+ else
+ return false;
+}
+
+// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
+// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
+// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
+// concludes that the event was not intended for it, it should return false.
+bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
+{
+ ModelObject* mo = m_c->selection_info()->model_object();
+ int active_inst = m_c->selection_info()->get_active_instance();
+
+
+ // left down with shift - show the selection rectangle:
+ if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) {
+ if (m_hover_id == -1) {
+ if (shift_down || alt_down) {
+ m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect);
+ }
+ }
+ else {
+ if (m_selected[m_hover_id])
+ unselect_point(m_hover_id);
+ else {
+ if (!alt_down)
+ select_point(m_hover_id);
+ }
+ }
+
+ return true;
+ }
+
+ // left down without selection rectangle - place point on the mesh:
+ if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) {
+ // If any point is in hover state, this should initiate its move - return control back to GLCanvas:
+ if (m_hover_id != -1)
+ return false;
+
+ // If there is some selection, don't add new point and deselect everything instead.
+ if (m_selection_empty) {
+ std::pair<Vec3f, Vec3f> pos_and_normal;
+ if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole")));
+
+ mo->sla_drain_holes.emplace_back(pos_and_normal.first,
+ -pos_and_normal.second, m_new_hole_radius, m_new_hole_height);
+ m_selected.push_back(false);
+ assert(m_selected.size() == mo->sla_drain_holes.size());
+ m_parent.set_as_dirty();
+ m_wait_for_up_event = true;
+ }
+ else
+ return false;
+ }
+ else
+ select_point(NoPoints);
+
+ return true;
+ }
+
+ // left up with selection rectangle - select points inside the rectangle:
+ if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) {
+ // Is this a selection or deselection rectangle?
+ GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
+
+ // First collect positions of all the points in world coordinates.
+ Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation();
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
+ std::vector<Vec3d> points;
+ for (unsigned int i=0; i<mo->sla_drain_holes.size(); ++i)
+ points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast<double>());
+
+ // Now ask the rectangle which of the points are inside.
+ std::vector<Vec3f> points_inside;
+ std::vector<unsigned int> points_idxs = m_selection_rectangle.stop_dragging(m_parent, points);
+ for (size_t idx : points_idxs)
+ points_inside.push_back(points[idx].cast<float>());
+
+ // Only select/deselect points that are actually visible
+ for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs(
+ trafo, wxGetApp().plater()->get_camera(), points_inside,
+ m_c->object_clipper()->get_clipping_plane()))
+ {
+ if (rectangle_status == GLSelectionRectangle::EState::Deselect)
+ unselect_point(points_idxs[idx]);
+ else
+ select_point(points_idxs[idx]);
+ }
+ return true;
+ }
+
+ // left up with no selection rectangle
+ if (action == SLAGizmoEventType::LeftUp) {
+ if (m_wait_for_up_event) {
+ m_wait_for_up_event = false;
+ return true;
+ }
+ }
+
+ // dragging the selection rectangle:
+ if (action == SLAGizmoEventType::Dragging) {
+ if (m_wait_for_up_event)
+ return true; // point has been placed and the button not released yet
+ // this prevents GLCanvas from starting scene rotation
+
+ if (m_selection_rectangle.is_dragging()) {
+ m_selection_rectangle.dragging(mouse_position);
+ return true;
+ }
+
+ return false;
+ }
+
+ if (action == SLAGizmoEventType::Delete) {
+ // delete key pressed
+ delete_selected_points();
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::RightDown) {
+ if (m_hover_id != -1) {
+ select_point(NoPoints);
+ select_point(m_hover_id);
+ delete_selected_points();
+ return true;
+ }
+ return false;
+ }
+
+ if (action == SLAGizmoEventType::SelectAll) {
+ select_point(AllPoints);
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
+ double pos = m_c->object_clipper()->get_position();
+ pos = std::min(1., pos + 0.01);
+ m_c->object_clipper()->set_position(pos, true);
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
+ double pos = m_c->object_clipper()->get_position();
+ pos = std::max(0., pos - 0.01);
+ m_c->object_clipper()->set_position(pos, true);
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::ResetClippingPlane) {
+ m_c->object_clipper()->set_position(-1., false);
+ return true;
+ }
+
+ return false;
+}
+
+void GLGizmoHollow::delete_selected_points()
+{
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete drainage hole")));
+ sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
+
+ for (unsigned int idx=0; idx<drain_holes.size(); ++idx) {
+ if (m_selected[idx]) {
+ m_selected.erase(m_selected.begin()+idx);
+ drain_holes.erase(drain_holes.begin() + (idx--));
+ }
+ }
+
+ select_point(NoPoints);
+}
+
+bool GLGizmoHollow::on_mouse(const wxMouseEvent &mouse_event)
+{
+ if (mouse_event.Moving()) return false;
+ if (use_grabbers(mouse_event)) return true;
+
+ // wxCoord == int --> wx/types.h
+ Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
+ Vec2d mouse_pos = mouse_coord.cast<double>();
+
+ static bool pending_right_up = false;
+ if (mouse_event.LeftDown()) {
+ bool control_down = mouse_event.CmdDown();
+ bool grabber_contains_mouse = (get_hover_id() != -1);
+ if ((!control_down || grabber_contains_mouse) &&
+ gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false))
+ // the gizmo got the event and took some action, there is no need
+ // to do anything more
+ return true;
+ } else if (mouse_event.Dragging()) {
+ if (m_parent.get_move_volume_id() != -1)
+ // don't allow dragging objects with the Sla gizmo on
+ return true;
+
+ bool control_down = mouse_event.CmdDown();
+ if (control_down) {
+ // CTRL has been pressed while already dragging -> stop current action
+ if (mouse_event.LeftIsDown())
+ gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
+ else if (mouse_event.RightIsDown()) {
+ pending_right_up = false;
+ }
+ } else if(gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) {
+ // the gizmo got the event and took some action, no need to do
+ // anything more here
+ m_parent.set_as_dirty();
+ return true;
+ }
+ } else if (mouse_event.LeftUp()) {
+ if (!m_parent.is_mouse_dragging()) {
+ bool control_down = mouse_event.CmdDown();
+ // in case gizmo is selected, we just pass the LeftUp event
+ // and stop processing - neither object moving or selecting is
+ // suppressed in that case
+ gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down);
+ return true;
+ }
+ } else if (mouse_event.RightDown()) {
+ if (m_parent.get_selection().get_object_idx() != -1 &&
+ gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) {
+ // we need to set the following right up as processed to avoid showing
+ // the context menu if the user release the mouse over the object
+ pending_right_up = true;
+ // event was taken care of by the SlaSupports gizmo
+ return true;
+ }
+ } else if (mouse_event.RightUp()) {
+ if (pending_right_up) {
+ pending_right_up = false;
+ return true;
+ }
+ }
+ return false;
+}
+
+void GLGizmoHollow::hollow_mesh(bool postpone_error_messages)
+{
+ wxGetApp().CallAfter([this, postpone_error_messages]() {
+ wxGetApp().plater()->reslice_SLA_hollowing(
+ *m_c->selection_info()->model_object(), postpone_error_messages);
+ });
+}
+
+
+std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>>
+GLGizmoHollow::get_config_options(const std::vector<std::string>& keys) const
+{
+ std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> out;
+ const ModelObject* mo = m_c->selection_info()->model_object();
+
+ if (! mo)
+ return out;
+
+ const DynamicPrintConfig& object_cfg = mo->config.get();
+ const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
+ std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
+
+ for (const std::string& key : keys) {
+ if (object_cfg.has(key))
+ out.emplace_back(object_cfg.option(key), &object_cfg.def()->options.at(key)); // at() needed for const map
+ else
+ if (print_cfg.has(key))
+ out.emplace_back(print_cfg.option(key), &print_cfg.def()->options.at(key));
+ else { // we must get it from defaults
+ if (default_cfg == nullptr)
+ default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys));
+ out.emplace_back(default_cfg->option(key), &default_cfg->def()->options.at(key));
+ }
+ }
+
+ return out;
+}
+
+
+void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit)
+{
+ ModelObject* mo = m_c->selection_info()->model_object();
+ if (! mo)
+ return;
+
+ bool first_run = true; // This is a hack to redraw the button when all points are removed,
+ // so it is not delayed until the background process finishes.
+
+ ConfigOptionMode current_mode = wxGetApp().get_mode();
+
+ std::vector<std::string> opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"};
+ auto opts = get_config_options(opts_keys);
+ auto* offset_cfg = static_cast<const ConfigOptionFloat*>(opts[0].first);
+ float offset = offset_cfg->value;
+ double offset_min = opts[0].second->min;
+ double offset_max = opts[0].second->max;
+
+ auto* quality_cfg = static_cast<const ConfigOptionFloat*>(opts[1].first);
+ float quality = quality_cfg->value;
+ double quality_min = opts[1].second->min;
+ double quality_max = opts[1].second->max;
+ ConfigOptionMode quality_mode = opts[1].second->mode;
+
+ auto* closing_d_cfg = static_cast<const ConfigOptionFloat*>(opts[2].first);
+ float closing_d = closing_d_cfg->value;
+ double closing_d_min = opts[2].second->min;
+ double closing_d_max = opts[2].second->max;
+ ConfigOptionMode closing_d_mode = opts[2].second->mode;
+
+ m_desc["offset"] = _(opts[0].second->label) + ":";
+ m_desc["quality"] = _(opts[1].second->label) + ":";
+ m_desc["closing_distance"] = _(opts[2].second->label) + ":";
+
+
+RENDER_AGAIN:
+ const float approx_height = m_imgui->scaled(20.0f);
+ y = std::min(y, bottom_limit - approx_height);
+ m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
+
+ m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
+
+ // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
+ const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
+ m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(0.5f);
+
+ const float settings_sliders_left =
+ std::max(std::max({m_imgui->calc_text_size(m_desc.at("offset")).x,
+ m_imgui->calc_text_size(m_desc.at("quality")).x,
+ m_imgui->calc_text_size(m_desc.at("closing_distance")).x,
+ m_imgui->calc_text_size(m_desc.at("hole_diameter")).x,
+ m_imgui->calc_text_size(m_desc.at("hole_depth")).x}) + m_imgui->scaled(0.5f), clipping_slider_left);
+
+ const float diameter_slider_left = settings_sliders_left; //m_imgui->calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f);
+ const float minimal_slider_width = m_imgui->scaled(4.f);
+
+ const float button_preview_width = m_imgui->calc_button_size(m_desc.at("preview")).x;
+
+ float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left});
+ window_width = std::max(window_width, button_preview_width);
+
+ if (m_imgui->button(m_desc["preview"]))
+ hollow_mesh();
+
+ bool config_changed = false;
+
+ ImGui::Separator();
+
+ {
+ auto opts = get_config_options({"hollowing_enable"});
+ m_enable_hollowing = static_cast<const ConfigOptionBool*>(opts[0].first)->value;
+ if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) {
+ mo->config.set("hollowing_enable", m_enable_hollowing);
+ wxGetApp().obj_list()->update_and_show_object_settings_item();
+ config_changed = true;
+ }
+ }
+
+ m_imgui->disabled_begin(! m_enable_hollowing);
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("offset"));
+ ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
+ ImGui::PushItemWidth(window_width - settings_sliders_left);
+ m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip));
+
+ bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider
+ bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider
+ bool slider_released =m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider
+
+ if (current_mode >= quality_mode) {
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("quality"));
+ ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
+ m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip));
+
+ slider_clicked |= m_imgui->get_last_slider_status().clicked;
+ slider_edited |= m_imgui->get_last_slider_status().edited;
+ slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
+ }
+
+ if (current_mode >= closing_d_mode) {
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("closing_distance"));
+ ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
+ m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm", 1.0f, true, _L(opts[2].second->tooltip));
+
+ slider_clicked |= m_imgui->get_last_slider_status().clicked;
+ slider_edited |= m_imgui->get_last_slider_status().edited;
+ slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
+ }
+
+ if (slider_clicked) {
+ m_offset_stash = offset;
+ m_quality_stash = quality;
+ m_closing_d_stash = closing_d;
+ }
+ if (slider_edited || slider_released) {
+ if (slider_released) {
+ mo->config.set("hollowing_min_thickness", m_offset_stash);
+ mo->config.set("hollowing_quality", m_quality_stash);
+ mo->config.set("hollowing_closing_distance", m_closing_d_stash);
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change")));
+ }
+ mo->config.set("hollowing_min_thickness", offset);
+ mo->config.set("hollowing_quality", quality);
+ mo->config.set("hollowing_closing_distance", closing_d);
+ if (slider_released) {
+ wxGetApp().obj_list()->update_and_show_object_settings_item();
+ config_changed = true;
+ }
+ }
+
+ m_imgui->disabled_end();
+
+ bool force_refresh = false;
+ bool remove_selected = false;
+ bool remove_all = false;
+
+ ImGui::Separator();
+
+ float diameter_upper_cap = 60.;
+ if (m_new_hole_radius * 2.f > diameter_upper_cap)
+ m_new_hole_radius = diameter_upper_cap / 2.f;
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("hole_diameter"));
+ ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x);
+ ImGui::PushItemWidth(window_width - diameter_slider_left);
+
+ float diam = 2.f * m_new_hole_radius;
+ m_imgui->slider_float("##hole_diameter", &diam, 1.f, 25.f, "%.1f mm", 1.f, false);
+ // Let's clamp the value (which could have been entered by keyboard) to a larger range
+ // than the slider. This allows entering off-scale values and still protects against
+ //complete non-sense.
+ diam = std::clamp(diam, 0.1f, diameter_upper_cap);
+ m_new_hole_radius = diam / 2.f;
+ bool clicked = m_imgui->get_last_slider_status().clicked;
+ bool edited = m_imgui->get_last_slider_status().edited;
+ bool deactivated = m_imgui->get_last_slider_status().deactivated_after_edit;
+
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc["hole_depth"]);
+ ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x);
+ m_imgui->slider_float("##hole_depth", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false);
+ // Same as above:
+ m_new_hole_height = std::clamp(m_new_hole_height, 0.f, 100.f);
+
+ clicked |= m_imgui->get_last_slider_status().clicked;
+ edited |= m_imgui->get_last_slider_status().edited;
+ deactivated |= m_imgui->get_last_slider_status().deactivated_after_edit;;
+
+ // Following is a nasty way to:
+ // - save the initial value of the slider before one starts messing with it
+ // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
+ // - take correct undo/redo snapshot after the user is done with moving the slider
+ if (! m_selection_empty) {
+ if (clicked) {
+ m_holes_stash = mo->sla_drain_holes;
+ }
+ if (edited) {
+ for (size_t idx=0; idx<m_selected.size(); ++idx)
+ if (m_selected[idx]) {
+ mo->sla_drain_holes[idx].radius = m_new_hole_radius;
+ mo->sla_drain_holes[idx].height = m_new_hole_height;
+ }
+ }
+ if (deactivated) {
+ // momentarily restore the old value to take snapshot
+ sla::DrainHoles new_holes = mo->sla_drain_holes;
+ mo->sla_drain_holes = m_holes_stash;
+ float backup_rad = m_new_hole_radius;
+ float backup_hei = m_new_hole_height;
+ for (size_t i=0; i<m_holes_stash.size(); ++i) {
+ if (m_selected[i]) {
+ m_new_hole_radius = m_holes_stash[i].radius;
+ m_new_hole_height = m_holes_stash[i].height;
+ break;
+ }
+ }
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Change drainage hole diameter")));
+ m_new_hole_radius = backup_rad;
+ m_new_hole_height = backup_hei;
+ mo->sla_drain_holes = new_holes;
+ }
+ }
+
+ m_imgui->disabled_begin(m_selection_empty);
+ remove_selected = m_imgui->button(m_desc.at("remove_selected"));
+ m_imgui->disabled_end();
+
+ m_imgui->disabled_begin(mo->sla_drain_holes.empty());
+ remove_all = m_imgui->button(m_desc.at("remove_all"));
+ m_imgui->disabled_end();
+
+ // Following is rendered in both editing and non-editing mode:
+ // m_imgui->text("");
+ ImGui::Separator();
+ if (m_c->object_clipper()->get_position() == 0.f) {
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("clipping_of_view"));
+ }
+ else {
+ if (m_imgui->button(m_desc.at("reset_direction"))) {
+ wxGetApp().CallAfter([this](){
+ m_c->object_clipper()->set_position(-1., false);
+ });
+ }
+ }
+
+ ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
+ ImGui::PushItemWidth(window_width - settings_sliders_left);
+ float clp_dist = m_c->object_clipper()->get_position();
+ if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
+ m_c->object_clipper()->set_position(clp_dist, true);
+
+ // make sure supports are shown/hidden as appropriate
+ bool show_sups = m_c->instances_hider()->are_supports_shown();
+ if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) {
+ m_c->instances_hider()->show_supports(show_sups);
+ force_refresh = true;
+ }
+
+ m_imgui->end();
+
+
+ if (remove_selected || remove_all) {
+ force_refresh = false;
+ m_parent.set_as_dirty();
+
+ if (remove_all) {
+ select_point(AllPoints);
+ delete_selected_points();
+ }
+ if (remove_selected)
+ delete_selected_points();
+
+ if (first_run) {
+ first_run = false;
+ goto RENDER_AGAIN;
+ }
+ }
+
+ if (force_refresh)
+ m_parent.set_as_dirty();
+
+ if (config_changed)
+ m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
+}
+
+bool GLGizmoHollow::on_is_activable() const
+{
+ const Selection& selection = m_parent.get_selection();
+
+ if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA
+ || !selection.is_from_single_instance())
+ return false;
+
+ // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
+ const Selection::IndicesList& list = selection.get_volume_idxs();
+ for (const auto& idx : list)
+ if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0)
+ return false;
+
+ return true;
+}
+
+bool GLGizmoHollow::on_is_selectable() const
+{
+ return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA);
+}
+
+std::string GLGizmoHollow::on_get_name() const
+{
+ return _u8L("Hollow and drill");
+}
+
+
+CommonGizmosDataID GLGizmoHollow::on_get_requirements() const
+{
+ return CommonGizmosDataID(
+ int(CommonGizmosDataID::SelectionInfo)
+ | int(CommonGizmosDataID::InstancesHider)
+ | int(CommonGizmosDataID::Raycaster)
+ | int(CommonGizmosDataID::HollowedMesh)
+ | int(CommonGizmosDataID::ObjectClipper)
+ | int(CommonGizmosDataID::SupportsClipper));
+}
+
+
+void GLGizmoHollow::on_set_state()
+{
+ if (m_state == m_old_state)
+ return;
+
+ if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off
+ m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
+ m_old_state = m_state;
+}
+
+
+
+void GLGizmoHollow::on_start_dragging()
+{
+ if (m_hover_id != -1) {
+ select_point(NoPoints);
+ select_point(m_hover_id);
+ m_hole_before_drag = m_c->selection_info()->model_object()->sla_drain_holes[m_hover_id].pos;
+ }
+ else
+ m_hole_before_drag = Vec3f::Zero();
+}
+
+
+void GLGizmoHollow::on_stop_dragging()
+{
+ sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
+ if (m_hover_id != -1) {
+ Vec3f backup = drain_holes[m_hover_id].pos;
+
+ if (m_hole_before_drag != Vec3f::Zero() // some point was touched
+ && backup != m_hole_before_drag) // and it was moved, not just selected
+ {
+ drain_holes[m_hover_id].pos = m_hole_before_drag;
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move drainage hole")));
+ drain_holes[m_hover_id].pos = backup;
+ }
+ }
+ m_hole_before_drag = Vec3f::Zero();
+}
+
+
+void GLGizmoHollow::on_dragging(const UpdateData &data)
+{
+ assert(m_hover_id != -1);
+ std::pair<Vec3f, Vec3f> pos_and_normal;
+ if (!unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
+ return;
+ sla::DrainHoles &drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
+ drain_holes[m_hover_id].pos = pos_and_normal.first;
+ drain_holes[m_hover_id].normal = -pos_and_normal.second;
+}
+
+
+void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar)
+{
+ ar(m_new_hole_radius,
+ m_new_hole_height,
+ m_selected,
+ m_selection_empty
+ );
+}
+
+
+
+void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const
+{
+ ar(m_new_hole_radius,
+ m_new_hole_height,
+ m_selected,
+ m_selection_empty
+ );
+}
+
+
+
+void GLGizmoHollow::select_point(int i)
+{
+ const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
+
+ if (i == AllPoints || i == NoPoints) {
+ m_selected.assign(m_selected.size(), i == AllPoints);
+ m_selection_empty = (i == NoPoints);
+
+ if (i == AllPoints) {
+ m_new_hole_radius = drain_holes[0].radius;
+ m_new_hole_height = drain_holes[0].height;
+ }
+ }
+ else {
+ while (size_t(i) >= m_selected.size())
+ m_selected.push_back(false);
+ m_selected[i] = true;
+ m_selection_empty = false;
+ m_new_hole_radius = drain_holes[i].radius;
+ m_new_hole_height = drain_holes[i].height;
+ }
+}
+
+
+void GLGizmoHollow::unselect_point(int i)
+{
+ m_selected[i] = false;
+ m_selection_empty = true;
+ for (const bool sel : m_selected) {
+ if (sel) {
+ m_selection_empty = false;
+ break;
+ }
+ }
+}
+
+void GLGizmoHollow::reload_cache()
+{
+ m_selected.clear();
+ m_selected.assign(m_c->selection_info()->model_object()->sla_drain_holes.size(), false);
+}
+
+
+void GLGizmoHollow::on_set_hover_id()
+{
+ if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id)
+ m_hover_id = -1;
+}
+
+
+
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
index 9dfb8bc9e..79aed06b9 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
@@ -2,6 +2,9 @@
#include "GLGizmoMove.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
+#if ENABLE_WORLD_COORDINATE
+#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
+#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_GL_SHADERS_ATTRIBUTES
#include "slic3r/GUI/Plater.hpp"
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
@@ -21,18 +24,29 @@ GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filenam
std::string GLGizmoMove3D::get_tooltip() const
{
+#if ENABLE_WORLD_COORDINATE
+ if (m_hover_id == 0)
+ return "X: " + format(m_displacement.x(), 2);
+ else if (m_hover_id == 1)
+ return "Y: " + format(m_displacement.y(), 2);
+ else if (m_hover_id == 2)
+ return "Z: " + format(m_displacement.z(), 2);
+ else
+ return "";
+#else
const Selection& selection = m_parent.get_selection();
- bool show_position = selection.is_single_full_instance();
+ const bool show_position = selection.is_single_full_instance();
const Vec3d& position = selection.get_bounding_box().center();
if (m_hover_id == 0 || m_grabbers[0].dragging)
- return "X: " + format(show_position ? position(0) : m_displacement(0), 2);
+ return "X: " + format(show_position ? position.x() : m_displacement.x(), 2);
else if (m_hover_id == 1 || m_grabbers[1].dragging)
- return "Y: " + format(show_position ? position(1) : m_displacement(1), 2);
+ return "Y: " + format(show_position ? position.y() : m_displacement.y(), 2);
else if (m_hover_id == 2 || m_grabbers[2].dragging)
- return "Z: " + format(show_position ? position(2) : m_displacement(2), 2);
+ return "Z: " + format(show_position ? position.z() : m_displacement.z(), 2);
else
return "";
+#endif // ENABLE_WORLD_COORDINATE
}
bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) {
@@ -40,9 +54,7 @@ bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) {
}
void GLGizmoMove3D::data_changed() {
- const Selection &selection = m_parent.get_selection();
- bool is_wipe_tower = selection.is_wipe_tower();
- m_grabbers[2].enabled = !is_wipe_tower;
+ m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower();
}
bool GLGizmoMove3D::on_init()
@@ -79,11 +91,29 @@ void GLGizmoMove3D::on_start_dragging()
assert(m_hover_id != -1);
m_displacement = Vec3d::Zero();
+#if ENABLE_WORLD_COORDINATE
+ const Selection& selection = m_parent.get_selection();
+ const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
+ if (coordinates_type == ECoordinatesType::World)
+ m_starting_drag_position = m_center + m_grabbers[m_hover_id].center;
+ else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
+ const GLVolume& v = *selection.get_first_volume();
+ m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center;
+ }
+ else {
+ const GLVolume& v = *selection.get_first_volume();
+ m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center;
+ }
+ m_starting_box_center = m_center;
+ m_starting_box_bottom_center = m_center;
+ m_starting_box_bottom_center.z() = m_bounding_box.min.z();
+#else
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_starting_drag_position = m_grabbers[m_hover_id].center;
m_starting_box_center = box.center();
m_starting_box_bottom_center = box.center();
- m_starting_box_bottom_center(2) = box.min(2);
+ m_starting_box_bottom_center.z() = box.min.z();
+#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoMove3D::on_stop_dragging()
@@ -102,7 +132,19 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data)
m_displacement.z() = calc_projection(data);
Selection &selection = m_parent.get_selection();
+#if ENABLE_WORLD_COORDINATE
+ TransformationType trafo_type;
+ trafo_type.set_relative();
+ switch (wxGetApp().obj_manipul()->get_coordinates_type())
+ {
+ case ECoordinatesType::Instance: { trafo_type.set_instance(); break; }
+ case ECoordinatesType::Local: { trafo_type.set_local(); break; }
+ default: { break; }
+ }
+ selection.translate(m_displacement, trafo_type);
+#else
selection.translate(m_displacement);
+#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoMove3D::on_render()
@@ -112,11 +154,39 @@ void GLGizmoMove3D::on_render()
m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 18.0));
#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
- const Selection& selection = m_parent.get_selection();
-
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
+#if ENABLE_WORLD_COORDINATE
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPushMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+ calc_selection_box_and_center();
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d base_matrix = local_transform(m_parent.get_selection());
+ for (int i = 0; i < 3; ++i) {
+ m_grabbers[i].matrix = base_matrix;
+ }
+#else
+ transform_to_local(m_parent.get_selection());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ const Vec3d zero = Vec3d::Zero();
+ const Vec3d half_box_size = 0.5 * m_bounding_box.size();
+
+ // x axis
+ m_grabbers[0].center = { half_box_size.x() + Offset, 0.0, 0.0 };
+ m_grabbers[0].color = AXES_COLOR[0];
+
+ // y axis
+ m_grabbers[1].center = { 0.0, half_box_size.y() + Offset, 0.0 };
+ m_grabbers[1].color = AXES_COLOR[1];
+
+ // z axis
+ m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset };
+ m_grabbers[2].color = AXES_COLOR[2];
+#else
+ const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
const Vec3d& center = box.center();
@@ -131,14 +201,24 @@ void GLGizmoMove3D::on_render()
// z axis
m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset };
m_grabbers[2].color = AXES_COLOR[2];
+#endif // ENABLE_WORLD_COORDINATE
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
#if ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_WORLD_COORDINATE
+ auto render_grabber_connection = [this, &zero](unsigned int id) {
+#else
auto render_grabber_connection = [this, &center](unsigned int id) {
+#endif // ENABLE_WORLD_COORDINATE
if (m_grabbers[id].enabled) {
+#if ENABLE_WORLD_COORDINATE
+ if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(m_grabbers[id].center)) {
+ m_grabber_connections[id].old_center = m_grabbers[id].center;
+#else
if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(center)) {
m_grabber_connections[id].old_center = center;
+#endif // ENABLE_WORLD_COORDINATE
m_grabber_connections[id].model.reset();
GLModel::Geometry init_data;
@@ -148,7 +228,11 @@ void GLGizmoMove3D::on_render()
init_data.reserve_indices(2);
// vertices
+#if ENABLE_WORLD_COORDINATE
+ init_data.add_vertex((Vec3f)zero.cast<float>());
+#else
init_data.add_vertex((Vec3f)center.cast<float>());
+#endif // ENABLE_WORLD_COORDINATE
init_data.add_vertex((Vec3f)m_grabbers[id].center.cast<float>());
// indices
@@ -171,7 +255,11 @@ void GLGizmoMove3D::on_render()
#if ENABLE_GL_SHADERS_ATTRIBUTES
const Camera& camera = wxGetApp().plater()->get_camera();
+#if ENABLE_WORLD_COORDINATE
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix);
+#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
@@ -183,7 +271,11 @@ void GLGizmoMove3D::on_render()
if (m_grabbers[i].enabled) {
glsafe(::glColor4fv(AXES_COLOR[i].data()));
::glBegin(GL_LINES);
+#if ENABLE_WORLD_COORDINATE
+ ::glVertex3dv(zero.data());
+#else
::glVertex3dv(center.data());
+#endif // ENABLE_WORLD_COORDINATE
::glVertex3dv(m_grabbers[i].center.data());
glsafe(::glEnd());
}
@@ -196,6 +288,19 @@ void GLGizmoMove3D::on_render()
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
// draw grabbers
+#if ENABLE_WORLD_COORDINATE
+ render_grabbers(m_bounding_box);
+#if !ENABLE_GIZMO_GRABBER_REFACTOR
+ for (unsigned int i = 0; i < 3; ++i) {
+ if (m_grabbers[i].enabled)
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ render_grabber_extension((Axis)i, base_matrix, m_bounding_box, false);
+#else
+ render_grabber_extension((Axis)i, m_bounding_box, false);
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ }
+#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
+#else
render_grabbers(box);
#if !ENABLE_GIZMO_GRABBER_REFACTOR
for (unsigned int i = 0; i < 3; ++i) {
@@ -203,6 +308,7 @@ void GLGizmoMove3D::on_render()
render_grabber_extension((Axis)i, box, false);
}
#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
+#endif // ENABLE_WORLD_COORDINATE
}
else {
// draw axis
@@ -213,7 +319,11 @@ void GLGizmoMove3D::on_render()
#if ENABLE_GL_SHADERS_ATTRIBUTES
const Camera& camera = wxGetApp().plater()->get_camera();
+#if ENABLE_WORLD_COORDINATE
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix);
+#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
@@ -225,7 +335,11 @@ void GLGizmoMove3D::on_render()
#else
glsafe(::glColor4fv(AXES_COLOR[m_hover_id].data()));
::glBegin(GL_LINES);
+#if ENABLE_WORLD_COORDINATE
+ ::glVertex3dv(zero.data());
+#else
::glVertex3dv(center.data());
+#endif // ENABLE_WORLD_COORDINATE
::glVertex3dv(m_grabbers[m_hover_id].center.data());
glsafe(::glEnd());
@@ -235,20 +349,65 @@ void GLGizmoMove3D::on_render()
shader->start_using();
shader->set_uniform("emission_factor", 0.1f);
// draw grabber
- const float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0);
+#if ENABLE_WORLD_COORDINATE
+ const Vec3d box_size = m_bounding_box.size();
+#else
+ const Vec3d box_size = box.size();
+#endif // ENABLE_WORLD_COORDINATE
+ const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0);
m_grabbers[m_hover_id].render(true, mean_size);
shader->stop_using();
}
#if !ENABLE_GIZMO_GRABBER_REFACTOR
+#if ENABLE_WORLD_COORDINATE
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ render_grabber_extension((Axis)m_hover_id, base_matrix, m_bounding_box, false);
+#else
+ render_grabber_extension((Axis)m_hover_id, m_bounding_box, false);
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#else
render_grabber_extension((Axis)m_hover_id, box, false);
+#endif // ENABLE_WORLD_COORDINATE
#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
}
+
+#if ENABLE_WORLD_COORDINATE
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoMove3D::on_render_for_picking()
{
glsafe(::glDisable(GL_DEPTH_TEST));
+#if ENABLE_WORLD_COORDINATE
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d base_matrix = local_transform(m_parent.get_selection());
+ for (int i = 0; i < 3; ++i) {
+ m_grabbers[i].matrix = base_matrix;
+ }
+#else
+ glsafe(::glPushMatrix());
+ transform_to_local(m_parent.get_selection());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ render_grabbers_for_picking(m_bounding_box);
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+#if !ENABLE_GIZMO_GRABBER_REFACTOR
+ render_grabber_extension(X, base_matrix, m_bounding_box, true);
+ render_grabber_extension(Y, base_matrix, m_bounding_box, true);
+ render_grabber_extension(Z, base_matrix, m_bounding_box, true);
+#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
+#else
+#if !ENABLE_GIZMO_GRABBER_REFACTOR
+ render_grabber_extension(X, m_bounding_box, true);
+ render_grabber_extension(Y, m_bounding_box, true);
+ render_grabber_extension(Z, m_bounding_box, true);
+#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
+ glsafe(::glPopMatrix());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#else
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
render_grabbers_for_picking(box);
#if !ENABLE_GIZMO_GRABBER_REFACTOR
@@ -256,23 +415,24 @@ void GLGizmoMove3D::on_render_for_picking()
render_grabber_extension(Y, box, true);
render_grabber_extension(Z, box, true);
#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
+#endif // ENABLE_WORLD_COORDINATE
}
double GLGizmoMove3D::calc_projection(const UpdateData& data) const
{
double projection = 0.0;
- Vec3d starting_vec = m_starting_drag_position - m_starting_box_center;
- double len_starting_vec = starting_vec.norm();
+ const Vec3d starting_vec = m_starting_drag_position - m_starting_box_center;
+ const double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 0.0) {
- Vec3d mouse_dir = data.mouse_ray.unit_vector();
+ const Vec3d mouse_dir = data.mouse_ray.unit_vector();
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
- Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
+ const Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
// vector from the starting position to the found intersection
- Vec3d inters_vec = inters - m_starting_drag_position;
+ const Vec3d inters_vec = inters - m_starting_drag_position;
// finds projection of the vector along the staring direction
projection = inters_vec.dot(starting_vec.normalized());
@@ -285,9 +445,14 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const
}
#if !ENABLE_GIZMO_GRABBER_REFACTOR
+#if ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES
+void GLGizmoMove3D::render_grabber_extension(Axis axis, const Transform3d& base_matrix, const BoundingBoxf3& box, bool picking)
+#else
void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking)
+#endif // ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES
{
- const float mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 3.0);
+ const Vec3d box_size = box.size();
+ const float mean_size = float((box_size.x() + box_size.y() + box_size.z()) / 3.0);
const double size = m_dragging ? double(m_grabbers[axis].get_dragging_half_size(mean_size)) : double(m_grabbers[axis].get_half_size(mean_size));
#if ENABLE_LEGACY_OPENGL_REMOVAL
@@ -312,7 +477,7 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box
#if ENABLE_GL_SHADERS_ATTRIBUTES
const Camera& camera = wxGetApp().plater()->get_camera();
- Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform(m_grabbers[axis].center);
+ Transform3d view_model_matrix = camera.get_view_matrix() * base_matrix * Geometry::assemble_transform(m_grabbers[axis].center);
if (axis == X)
view_model_matrix = view_model_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY());
else if (axis == Y)
@@ -345,5 +510,62 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box
}
#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
+#if ENABLE_WORLD_COORDINATE
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const
+{
+ Transform3d ret = Geometry::assemble_transform(m_center);
+ if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
+ const GLVolume& v = *selection.get_first_volume();
+ Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix();
+ if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
+ orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix();
+ ret = ret * orient_matrix;
+ }
+ return ret;
+}
+#else
+void GLGizmoMove3D::transform_to_local(const Selection& selection) const
+{
+ glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z()));
+
+ if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
+ const GLVolume& v = *selection.get_first_volume();
+ Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true);
+ if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
+ orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true);
+ glsafe(::glMultMatrixd(orient_matrix.data()));
+ }
+}
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+void GLGizmoMove3D::calc_selection_box_and_center()
+{
+ const Selection& selection = m_parent.get_selection();
+ const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
+ if (coordinates_type == ECoordinatesType::World) {
+ m_bounding_box = selection.get_bounding_box();
+ m_center = m_bounding_box.center();
+ }
+ else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
+ const GLVolume& v = *selection.get_first_volume();
+ m_bounding_box = v.transformed_convex_hull_bounding_box(
+ v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix());
+ m_center = v.world_matrix() * m_bounding_box.center();
+ }
+ else {
+ m_bounding_box.reset();
+ const Selection::IndicesList& ids = selection.get_volume_idxs();
+ for (unsigned int id : ids) {
+ const GLVolume& v = *selection.get_volume(id);
+ m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
+ }
+ const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation();
+ m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix());
+ m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center();
+ }
+}
+#endif // ENABLE_WORLD_COORDINATE
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
index 6a618c3e4..dc5618cc4 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
@@ -7,11 +7,19 @@
namespace Slic3r {
namespace GUI {
+#if ENABLE_WORLD_COORDINATE
+class Selection;
+#endif // ENABLE_WORLD_COORDINATE
+
class GLGizmoMove3D : public GLGizmoBase
{
static const double Offset;
Vec3d m_displacement{ Vec3d::Zero() };
+#if ENABLE_WORLD_COORDINATE
+ Vec3d m_center{ Vec3d::Zero() };
+ BoundingBoxf3 m_bounding_box;
+#endif // ENABLE_WORLD_COORDINATE
double m_snap_step{ 1.0 };
Vec3d m_starting_drag_position{ Vec3d::Zero() };
Vec3d m_starting_box_center{ Vec3d::Zero() };
@@ -49,7 +57,6 @@ public:
/// Detect reduction of move for wipetover on selection change
/// </summary>
void data_changed() override;
-
protected:
bool on_init() override;
std::string on_get_name() const override;
@@ -62,11 +69,25 @@ protected:
private:
double calc_projection(const UpdateData& data) const;
+#if ENABLE_WORLD_COORDINATE
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ Transform3d local_transform(const Selection& selection) const;
+#else
+ void transform_to_local(const Selection& selection) const;
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ void calc_selection_box_and_center();
+#endif // ENABLE_WORLD_COORDINATE
#if !ENABLE_GIZMO_GRABBER_REFACTOR
+#if ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES
+ void render_grabber_extension(Axis axis, const Transform3d& base_matrix, const BoundingBoxf3& box, bool picking);
+#else
void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking);
+#endif // ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES
#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
};
+
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
index 0cba59c6d..4e03abb33 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
@@ -1,1383 +1,1407 @@
-// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
-#include "GLGizmoPainterBase.hpp"
-#include "slic3r/GUI/GLCanvas3D.hpp"
-#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
-
-#include <GL/glew.h>
-
-#include "slic3r/GUI/GUI_App.hpp"
-#include "slic3r/GUI/Camera.hpp"
-#include "slic3r/GUI/Plater.hpp"
-#include "slic3r/GUI/OpenGLManager.hpp"
-#include "slic3r/Utils/UndoRedo.hpp"
-#include "libslic3r/Model.hpp"
-#include "libslic3r/PresetBundle.hpp"
-#include "libslic3r/TriangleMesh.hpp"
-
-#include <memory>
-#include <optional>
-
-namespace Slic3r::GUI {
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-std::shared_ptr<GLModel> GLGizmoPainterBase::s_sphere = nullptr;
-#else
-std::shared_ptr<GLIndexedVertexArray> GLGizmoPainterBase::s_sphere = nullptr;
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
-GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
- : GLGizmoBase(parent, icon_filename, sprite_id)
-{
-}
-
-GLGizmoPainterBase::~GLGizmoPainterBase()
-{
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- if (s_sphere != nullptr)
- s_sphere.reset();
-#else
- if (s_sphere != nullptr && s_sphere->has_VBOs())
- s_sphere->release_geometry();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-}
-
-void GLGizmoPainterBase::data_changed()
-{
- if (m_state != On)
- return;
-
- const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
- const Selection & selection = m_parent.get_selection();
- if (mo && selection.is_from_single_instance()
- && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size))
- {
- update_from_model_object();
- m_old_mo_id = mo->id();
- m_old_volumes_size = mo->volumes.size();
- m_schedule_update = false;
- }
-}
-
-GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const
-{
- ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}};
- // Take care of the clipping plane. The normal of the clipping plane is
- // saved with opposite sign than we need to pass to OpenGL (FIXME)
- if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) {
- const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane();
- for (size_t i = 0; i < 3; ++i)
- clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]);
- clp_data_out.clp_dataf[3] = float(clp->get_data()[3]);
- }
-
- // z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
- if (m_c->get_canvas()->get_use_clipping_planes()) {
- const std::array<ClippingPlane, 2> &clps = m_c->get_canvas()->get_clipping_planes();
- clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])};
- }
-
- return clp_data_out;
-}
-
-void GLGizmoPainterBase::render_triangles(const Selection& selection) const
-{
- auto* shader = wxGetApp().get_shader("gouraud");
- if (! shader)
- return;
- shader->start_using();
- shader->set_uniform("slope.actived", false);
- shader->set_uniform("print_volume.type", 0);
- shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf);
- ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
-
- const ModelObject *mo = m_c->selection_info()->model_object();
- int mesh_id = -1;
- for (const ModelVolume* mv : mo->volumes) {
- if (! mv->is_model_part())
- continue;
-
- ++mesh_id;
-
- const Transform3d trafo_matrix =
- mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() *
- mv->get_matrix();
-
- bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
- if (is_left_handed)
- glsafe(::glFrontFace(GL_CW));
-
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Camera& camera = wxGetApp().plater()->get_camera();
- const Transform3d matrix = camera.get_view_matrix() * trafo_matrix;
- shader->set_uniform("view_model_matrix", matrix);
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
- shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
-#else
- glsafe(::glPushMatrix());
- glsafe(::glMultMatrixd(trafo_matrix.data()));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
- // For printers with multiple extruders, it is necessary to pass trafo_matrix
- // to the shader input variable print_box.volume_world_matrix before
- // rendering the painted triangles. When this matrix is not set, the
- // wrong transformation matrix is used for "Clipping of view".
- shader->set_uniform("volume_world_matrix", trafo_matrix);
-
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix);
-#else
- m_triangle_selectors[mesh_id]->render(m_imgui);
-
- glsafe(::glPopMatrix());
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- if (is_left_handed)
- glsafe(::glFrontFace(GL_CCW));
- }
-}
-
-void GLGizmoPainterBase::render_cursor()
-{
- // First check that the mouse pointer is on an object.
- const ModelObject* mo = m_c->selection_info()->model_object();
- const Selection& selection = m_parent.get_selection();
- const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
- const Camera& camera = wxGetApp().plater()->get_camera();
-
- // Precalculate transformations of individual meshes.
- std::vector<Transform3d> trafo_matrices;
- for (const ModelVolume* mv : mo->volumes) {
- if (mv->is_model_part())
- trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix());
- }
- // Raycast and return if there's no hit.
- update_raycast_cache(m_parent.get_local_mouse_position(), camera, trafo_matrices);
- if (m_rr.mesh_id == -1)
- return;
-
- if (m_tool_type == ToolType::BRUSH) {
- if (m_cursor_type == TriangleSelector::SPHERE)
- render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
- else if (m_cursor_type == TriangleSelector::CIRCLE)
- render_cursor_circle();
- }
-}
-
-void GLGizmoPainterBase::render_cursor_circle()
-{
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- const Camera &camera = wxGetApp().plater()->get_camera();
- const float zoom = float(camera.get_zoom());
- const float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-
- const Size cnv_size = m_parent.get_canvas_size();
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const float cnv_width = float(cnv_size.get_width());
- const float cnv_height = float(cnv_size.get_height());
- if (cnv_width == 0.0f || cnv_height == 0.0f)
- return;
-
- const float cnv_inv_width = 1.0f / cnv_width;
- const float cnv_inv_height = 1.0f / cnv_height;
-
- const Vec2d center = m_parent.get_local_mouse_position();
- const float radius = m_cursor_radius * float(wxGetApp().plater()->get_camera().get_zoom());
-#else
- const float cnv_half_width = 0.5f * float(cnv_size.get_width());
- const float cnv_half_height = 0.5f * float(cnv_size.get_height());
- if (cnv_half_width == 0.0f || cnv_half_height == 0.0f)
- return;
- const Vec2d mouse_pos(m_parent.get_local_mouse_position().x(), m_parent.get_local_mouse_position().y());
- Vec2d center(mouse_pos.x() - cnv_half_width, cnv_half_height - mouse_pos.y());
- center = center * inv_zoom;
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
- glsafe(::glLineWidth(1.5f));
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- static const std::array<float, 3> color = { 0.f, 1.f, 0.3f };
- glsafe(::glColor3fv(color.data()));
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
- glsafe(::glDisable(GL_DEPTH_TEST));
-
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPushMatrix());
- glsafe(::glLoadIdentity());
- // ensure that the circle is renderered inside the frustrum
- glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5)));
- // ensure that the overlay fits the frustrum near z plane
- const double gui_scale = camera.get_gui_scale();
- glsafe(::glScaled(gui_scale, gui_scale, 1.0));
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-
- glsafe(::glPushAttrib(GL_ENABLE_BIT));
- glsafe(::glLineStipple(4, 0xAAAA));
- glsafe(::glEnable(GL_LINE_STIPPLE));
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - radius) > EPSILON) {
- m_old_cursor_radius = radius;
-#else
- if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - m_cursor_radius) > EPSILON) {
- m_old_cursor_radius = m_cursor_radius;
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- m_old_center = center;
- m_circle.reset();
-
- GLModel::Geometry init_data;
- static const unsigned int StepsCount = 32;
- static const float StepSize = 2.0f * float(PI) / float(StepsCount);
- init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 };
- init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f };
- init_data.reserve_vertices(StepsCount);
- init_data.reserve_indices(StepsCount);
-
- // vertices + indices
- for (unsigned int i = 0; i < StepsCount; ++i) {
- const float angle = float(i) * StepSize;
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- init_data.add_vertex(Vec2f(2.0f * ((center.x() + ::cos(angle) * radius) * cnv_inv_width - 0.5f),
- -2.0f * ((center.y() + ::sin(angle) * radius) * cnv_inv_height - 0.5f)));
-#else
- init_data.add_vertex(Vec2f(center.x() + ::cos(angle) * m_cursor_radius, center.y() + ::sin(angle) * m_cursor_radius));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- init_data.add_index(i);
- }
-
- m_circle.init_from(std::move(init_data));
- }
-
- GLShaderProgram* shader = GUI::wxGetApp().get_shader("flat");
- if (shader != nullptr) {
- shader->start_using();
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- shader->set_uniform("view_model_matrix", Transform3d::Identity());
- shader->set_uniform("projection_matrix", Transform3d::Identity());
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- m_circle.render();
- shader->stop_using();
- }
-#else
- ::glBegin(GL_LINE_LOOP);
- for (double angle=0; angle<2*M_PI; angle+=M_PI/20.)
- ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle)));
- glsafe(::glEnd());
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- glsafe(::glPopAttrib());
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glEnable(GL_DEPTH_TEST));
-}
-
-
-void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
-{
- if (s_sphere == nullptr) {
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- s_sphere = std::make_shared<GLModel>();
- s_sphere->init_from(its_make_sphere(1.0, double(PI) / 12.0));
-#else
- s_sphere = std::make_shared<GLIndexedVertexArray>();
- s_sphere->load_its_flat_shading(its_make_sphere(1.0, double(PI) / 12.0));
- s_sphere->finalize_geometry(true);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLShaderProgram* shader = wxGetApp().get_shader("flat");
- if (shader == nullptr)
- return;
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse();
- const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed();
-
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPushMatrix());
- glsafe(::glMultMatrixd(trafo.data()));
- // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
- glsafe(::glTranslatef(m_rr.hit.x(), m_rr.hit.y(), m_rr.hit.z()));
- glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data()));
- glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius));
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-
- if (is_left_handed)
- glFrontFace(GL_CW);
-
- ColorRGBA render_color = { 0.0f, 0.0f, 0.0f, 0.25f };
- if (m_button_down == Button::Left)
- render_color = this->get_cursor_sphere_left_button_color();
- else if (m_button_down == Button::Right)
- render_color = this->get_cursor_sphere_right_button_color();
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- shader->start_using();
-
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Camera& camera = wxGetApp().plater()->get_camera();
- Transform3d view_model_matrix = camera.get_view_matrix() * trafo *
- Geometry::assemble_transform(m_rr.hit.cast<double>()) * complete_scaling_matrix_inverse *
- Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), m_cursor_radius * Vec3d::Ones());
-
- shader->set_uniform("view_model_matrix", view_model_matrix);
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
- assert(s_sphere != nullptr);
- s_sphere->set_color(render_color);
-#else
- glsafe(::glColor4fv(render_color.data()));
-
- assert(s_sphere != nullptr);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- s_sphere->render();
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- shader->stop_using();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- if (is_left_handed)
- glFrontFace(GL_CCW);
-
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-}
-
-
-bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const
-{
- if (m_c->object_clipper()->get_position() == 0.)
- return false;
-
- auto sel_info = m_c->selection_info();
- Vec3d transformed_point = trafo * point;
- transformed_point(2) += sel_info->get_sla_shift();
- return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
-}
-
-// Interpolate points between the previous and current mouse positions, which are then projected onto the object.
-// Returned projected mouse positions are grouped by mesh_idx. It may contain multiple std::vector<GLGizmoPainterBase::ProjectedMousePosition>
-// with the same mesh_idx, but all items in std::vector<GLGizmoPainterBase::ProjectedMousePosition> always have the same mesh_idx.
-std::vector<std::vector<GLGizmoPainterBase::ProjectedMousePosition>> GLGizmoPainterBase::get_projected_mouse_positions(const Vec2d &mouse_position, const double resolution, const std::vector<Transform3d> &trafo_matrices) const
-{
- // List of mouse positions that will be used as seeds for painting.
- std::vector<Vec2d> mouse_positions{mouse_position};
- if (m_last_mouse_click != Vec2d::Zero()) {
- // In case current mouse position is far from the last one,
- // add several positions from between into the list, so there
- // are no gaps in the painted region.
- if (size_t patches_in_between = size_t((mouse_position - m_last_mouse_click).norm() / resolution); patches_in_between > 0) {
- const Vec2d diff = (m_last_mouse_click - mouse_position) / (patches_in_between + 1);
- for (size_t patch_idx = 1; patch_idx <= patches_in_between; ++patch_idx)
- mouse_positions.emplace_back(mouse_position + patch_idx * diff);
- mouse_positions.emplace_back(m_last_mouse_click);
- }
- }
-
- const Camera &camera = wxGetApp().plater()->get_camera();
- std::vector<ProjectedMousePosition> mesh_hit_points;
- mesh_hit_points.reserve(mouse_positions.size());
-
- // In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't.
- for (const Vec2d &mp : mouse_positions) {
- update_raycast_cache(mp, camera, trafo_matrices);
- mesh_hit_points.push_back({m_rr.hit, m_rr.mesh_id, m_rr.facet});
- if (m_rr.mesh_id == -1)
- break;
- }
-
- // Divide mesh_hit_points into groups with the same mesh_idx. It may contain multiple groups with the same mesh_idx.
- std::vector<std::vector<ProjectedMousePosition>> mesh_hit_points_by_mesh;
- for (size_t prev_mesh_hit_point = 0, curr_mesh_hit_point = 0; curr_mesh_hit_point < mesh_hit_points.size(); ++curr_mesh_hit_point) {
- size_t next_mesh_hit_point = curr_mesh_hit_point + 1;
- if (next_mesh_hit_point >= mesh_hit_points.size() || mesh_hit_points[curr_mesh_hit_point].mesh_idx != mesh_hit_points[next_mesh_hit_point].mesh_idx) {
- mesh_hit_points_by_mesh.emplace_back();
- mesh_hit_points_by_mesh.back().insert(mesh_hit_points_by_mesh.back().end(), mesh_hit_points.begin() + int(prev_mesh_hit_point), mesh_hit_points.begin() + int(next_mesh_hit_point));
- prev_mesh_hit_point = next_mesh_hit_point;
- }
- }
-
- auto on_same_facet = [](std::vector<ProjectedMousePosition> &hit_points) -> bool {
- for (const ProjectedMousePosition &mesh_hit_point : hit_points)
- if (mesh_hit_point.facet_idx != hit_points.front().facet_idx)
- return false;
- return true;
- };
-
- struct Plane
- {
- Vec3d origin;
- Vec3d first_axis;
- Vec3d second_axis;
- };
- auto find_plane = [](std::vector<ProjectedMousePosition> &hit_points) -> std::optional<Plane> {
- assert(hit_points.size() >= 3);
- for (size_t third_idx = 2; third_idx < hit_points.size(); ++third_idx) {
- const Vec3d &first_point = hit_points[third_idx - 2].mesh_hit.cast<double>();
- const Vec3d &second_point = hit_points[third_idx - 1].mesh_hit.cast<double>();
- const Vec3d &third_point = hit_points[third_idx].mesh_hit.cast<double>();
-
- const Vec3d first_vec = first_point - second_point;
- const Vec3d second_vec = third_point - second_point;
-
- // If three points aren't collinear, then there exists only one plane going through all points.
- if (first_vec.cross(second_vec).squaredNorm() > sqr(EPSILON)) {
- const Vec3d first_axis_vec_n = first_vec.normalized();
- // Make second_vec perpendicular to first_axis_vec_n using Gram–Schmidt orthogonalization process
- const Vec3d second_axis_vec_n = (second_vec - (first_vec.dot(second_vec) / first_vec.dot(first_vec)) * first_vec).normalized();
- return Plane{second_point, first_axis_vec_n, second_axis_vec_n};
- }
- }
-
- return std::nullopt;
- };
-
- for(std::vector<ProjectedMousePosition> &hit_points : mesh_hit_points_by_mesh) {
- assert(!hit_points.empty());
- if (hit_points.back().mesh_idx == -1)
- break;
-
- if (hit_points.size() <= 2)
- continue;
-
- if (on_same_facet(hit_points)) {
- hit_points = {hit_points.front(), hit_points.back()};
- } else if (std::optional<Plane> plane = find_plane(hit_points); plane) {
- Polyline polyline;
- polyline.points.reserve(hit_points.size());
- // Project hit_points into its plane to simplified them in the next step.
- for (auto &hit_point : hit_points) {
- const Vec3d &point = hit_point.mesh_hit.cast<double>();
- const double x_cord = plane->first_axis.dot(point - plane->origin);
- const double y_cord = plane->second_axis.dot(point - plane->origin);
- polyline.points.emplace_back(scale_(x_cord), scale_(y_cord));
- }
-
- polyline.simplify(scale_(m_cursor_radius) / 10.);
-
- const int mesh_idx = hit_points.front().mesh_idx;
- std::vector<ProjectedMousePosition> new_hit_points;
- new_hit_points.reserve(polyline.points.size());
- // Project 2D simplified hit_points beck to 3D.
- for (const Point &point : polyline.points) {
- const double x_cord = unscale<double>(point.x());
- const double y_cord = unscale<double>(point.y());
- const Vec3d new_hit_point = plane->origin + x_cord * plane->first_axis + y_cord * plane->second_axis;
- const int facet_idx = m_c->raycaster()->raycasters()[mesh_idx]->get_closest_facet(new_hit_point.cast<float>());
- new_hit_points.push_back({new_hit_point.cast<float>(), mesh_idx, size_t(facet_idx)});
- }
-
- hit_points = new_hit_points;
- } else {
- hit_points = {hit_points.front(), hit_points.back()};
- }
- }
-
- return mesh_hit_points_by_mesh;
-}
-
-// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
-// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
-// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
-// concludes that the event was not intended for it, it should return false.
-bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
-{
- if (action == SLAGizmoEventType::MouseWheelUp
- || action == SLAGizmoEventType::MouseWheelDown) {
- if (control_down) {
- double pos = m_c->object_clipper()->get_position();
- pos = action == SLAGizmoEventType::MouseWheelDown
- ? std::max(0., pos - 0.01)
- : std::min(1., pos + 0.01);
- m_c->object_clipper()->set_position(pos, true);
- return true;
- }
- else if (alt_down) {
- if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) {
- m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - this->get_cursor_radius_step(), this->get_cursor_radius_min())
- : std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max());
- m_parent.set_as_dirty();
- return true;
- } else if (m_tool_type == ToolType::SMART_FILL) {
- m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin)
- : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax);
- m_parent.set_as_dirty();
- if (m_rr.mesh_id != -1) {
- const Selection &selection = m_parent.get_selection();
- const ModelObject *mo = m_c->selection_info()->model_object();
- const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
- const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true);
- const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix();
- m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle,
- m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
- m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
- m_seed_fill_last_mesh_id = m_rr.mesh_id;
- }
- return true;
- }
-
- return false;
- }
- }
-
- if (action == SLAGizmoEventType::ResetClippingPlane) {
- m_c->object_clipper()->set_position(-1., false);
- return true;
- }
-
- if (action == SLAGizmoEventType::LeftDown
- || action == SLAGizmoEventType::RightDown
- || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) {
-
- if (m_triangle_selectors.empty())
- return false;
-
- EnforcerBlockerType new_state = EnforcerBlockerType::NONE;
- if (! shift_down) {
- if (action == SLAGizmoEventType::Dragging)
- new_state = m_button_down == Button::Left ? this->get_left_button_state_type() : this->get_right_button_state_type();
- else
- new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type();
- }
-
- const Camera &camera = wxGetApp().plater()->get_camera();
- const Selection &selection = m_parent.get_selection();
- const ModelObject *mo = m_c->selection_info()->model_object();
- const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
- const Transform3d instance_trafo = mi->get_transformation().get_matrix();
- const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true);
-
- // Precalculate transformations of individual meshes.
- std::vector<Transform3d> trafo_matrices;
- std::vector<Transform3d> trafo_matrices_not_translate;
- for (const ModelVolume *mv : mo->volumes)
- if (mv->is_model_part()) {
- trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
- trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
- }
-
- std::vector<std::vector<ProjectedMousePosition>> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices);
- m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved
-
- for (const std::vector<ProjectedMousePosition> &projected_mouse_positions : projected_mouse_positions_by_mesh) {
- assert(!projected_mouse_positions.empty());
- const int mesh_idx = projected_mouse_positions.front().mesh_idx;
- const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
-
- // The mouse button click detection is enabled when there is a valid hit.
- // Missing the object entirely
- // shall not capture the mouse.
- if (mesh_idx != -1)
- if (m_button_down == Button::None)
- m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
-
- // In case we have no valid hit, we can return. The event will be stopped when
- // dragging while painting (to prevent scene rotations and moving the object)
- if (mesh_idx == -1)
- return dragging_while_painting;
-
- const Transform3d &trafo_matrix = trafo_matrices[mesh_idx];
- const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx];
-
- // Calculate direction from camera to the hit (in mesh coords):
- Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
-
- assert(mesh_idx < int(m_triangle_selectors.size()));
- const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
- if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
- for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) {
- assert(projected_mouse_position.mesh_idx == mesh_idx);
- const Vec3f mesh_hit = projected_mouse_position.mesh_hit;
- const int facet_idx = int(projected_mouse_position.facet_idx);
- m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state);
- if (m_tool_type == ToolType::SMART_FILL)
- m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle,
- m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
- else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
- m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true);
- else if (m_tool_type == ToolType::BUCKET_FILL)
- m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true);
-
- m_seed_fill_last_mesh_id = -1;
- }
- } else if (m_tool_type == ToolType::BRUSH) {
- assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE);
-
- if (projected_mouse_positions.size() == 1) {
- const ProjectedMousePosition &first_position = projected_mouse_positions.front();
- std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit,
- camera_pos, m_cursor_radius,
- m_cursor_type, trafo_matrix, clp);
- m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate,
- m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
- } else {
- for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) {
- auto second_position_it = first_position_it + 1;
- std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp);
- m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
- }
- }
- }
-
- m_triangle_selectors[mesh_idx]->request_update_render_data();
- m_last_mouse_click = mouse_position;
- }
-
- return true;
- }
-
- if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) {
- if (m_triangle_selectors.empty())
- return false;
-
- const Camera &camera = wxGetApp().plater()->get_camera();
- const Selection &selection = m_parent.get_selection();
- const ModelObject *mo = m_c->selection_info()->model_object();
- const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
- const Transform3d instance_trafo = mi->get_transformation().get_matrix();
- const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true);
-
- // Precalculate transformations of individual meshes.
- std::vector<Transform3d> trafo_matrices;
- std::vector<Transform3d> trafo_matrices_not_translate;
- for (const ModelVolume *mv : mo->volumes)
- if (mv->is_model_part()) {
- trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
- trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
- }
-
- // Now "click" into all the prepared points and spill paint around them.
- update_raycast_cache(mouse_position, camera, trafo_matrices);
-
- auto seed_fill_unselect_all = [this]() {
- for (auto &triangle_selector : m_triangle_selectors) {
- triangle_selector->seed_fill_unselect_all_triangles();
- triangle_selector->request_update_render_data();
- }
- };
-
- if (m_rr.mesh_id == -1) {
- // Clean selected by seed fill for all triangles in all meshes when a mouse isn't pointing on any mesh.
- seed_fill_unselect_all();
- m_seed_fill_last_mesh_id = -1;
-
- // In case we have no valid hit, we can return.
- return false;
- }
-
- // The mouse moved from one object's volume to another one. So it is needed to unselect all triangles selected by seed fill.
- if(m_rr.mesh_id != m_seed_fill_last_mesh_id)
- seed_fill_unselect_all();
-
- const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id];
- const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id];
-
- assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
- const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
- if (m_tool_type == ToolType::SMART_FILL)
- m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle,
- m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
- else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
- m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false);
- else if (m_tool_type == ToolType::BUCKET_FILL)
- m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true);
- m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
- m_seed_fill_last_mesh_id = m_rr.mesh_id;
- return true;
- }
-
- if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
- && m_button_down != Button::None) {
- // Take snapshot and update ModelVolume data.
- wxString action_name = this->handle_snapshot_action_name(shift_down, m_button_down);
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name, UndoRedo::SnapshotType::GizmoAction);
- update_model_object();
-
- m_button_down = Button::None;
- m_last_mouse_click = Vec2d::Zero();
- return true;
- }
-
- return false;
-}
-
-bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event)
-{
- // wxCoord == int --> wx/types.h
- Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
- Vec2d mouse_pos = mouse_coord.cast<double>();
-
- if (mouse_event.Moving()) {
- gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false);
- return false;
- }
-
- // when control is down we allow scene pan and rotation even when clicking
- // over some object
- bool control_down = mouse_event.CmdDown();
- bool grabber_contains_mouse = (get_hover_id() != -1);
-
- const Selection &selection = m_parent.get_selection();
- int selected_object_idx = selection.get_object_idx();
- if (mouse_event.LeftDown()) {
- if ((!control_down || grabber_contains_mouse) &&
- gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false))
- // the gizmo got the event and took some action, there is no need
- // to do anything more
- return true;
- } else if (mouse_event.RightDown()){
- if (!control_down && selected_object_idx != -1 &&
- gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false))
- // event was taken care of
- return true;
- } else if (mouse_event.Dragging()) {
- if (m_parent.get_move_volume_id() != -1)
- // don't allow dragging objects with the Sla gizmo on
- return true;
- if (!control_down && gizmo_event(SLAGizmoEventType::Dragging,
- mouse_pos, mouse_event.ShiftDown(),
- mouse_event.AltDown(), false)) {
- // the gizmo got the event and took some action, no need to do
- // anything more here
- m_parent.set_as_dirty();
- return true;
- }
- if(control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown()))
- {
- // CTRL has been pressed while already dragging -> stop current action
- if (mouse_event.LeftIsDown())
- gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
- else if (mouse_event.RightIsDown())
- gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
- return false;
- }
- } else if (mouse_event.LeftUp()) {
- if (!m_parent.is_mouse_dragging()) {
- // in case SLA/FDM gizmo is selected, we just pass the LeftUp
- // event and stop processing - neither object moving or selecting
- // is suppressed in that case
- gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down);
- return true;
- }
- } else if (mouse_event.RightUp()) {
- if (!m_parent.is_mouse_dragging()) {
- gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down);
- return true;
- }
- }
- return false;
-}
-
-void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position,
- const Camera& camera,
- const std::vector<Transform3d>& trafo_matrices) const
-{
- if (m_rr.mouse_position == mouse_position) {
- // Same query as last time - the answer is already in the cache.
- return;
- }
-
- Vec3f normal = Vec3f::Zero();
- Vec3f hit = Vec3f::Zero();
- size_t facet = 0;
- Vec3f closest_hit = Vec3f::Zero();
- double closest_hit_squared_distance = std::numeric_limits<double>::max();
- size_t closest_facet = 0;
- int closest_hit_mesh_id = -1;
-
- // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
- for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) {
-
- if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
- mouse_position,
- trafo_matrices[mesh_id],
- camera,
- hit,
- normal,
- m_c->object_clipper()->get_clipping_plane(),
- &facet))
- {
- // In case this hit is clipped, skip it.
- if (is_mesh_point_clipped(hit.cast<double>(), trafo_matrices[mesh_id]))
- continue;
-
- // Is this hit the closest to the camera so far?
- double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm();
- if (hit_squared_distance < closest_hit_squared_distance) {
- closest_hit_squared_distance = hit_squared_distance;
- closest_facet = facet;
- closest_hit_mesh_id = mesh_id;
- closest_hit = hit;
- }
- }
- }
-
- m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet};
-}
-
-bool GLGizmoPainterBase::on_is_activable() const
-{
- const Selection& selection = m_parent.get_selection();
-
- if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF
- || !selection.is_single_full_instance() || wxGetApp().get_mode() == comSimple)
- return false;
-
- // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
- const Selection::IndicesList& list = selection.get_volume_idxs();
- return std::all_of(list.cbegin(), list.cend(), [&selection](unsigned int idx) { return !selection.get_volume(idx)->is_outside; });
-}
-
-bool GLGizmoPainterBase::on_is_selectable() const
-{
- return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF
- && wxGetApp().get_mode() != comSimple );
-}
-
-
-CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const
-{
- return CommonGizmosDataID(
- int(CommonGizmosDataID::SelectionInfo)
- | int(CommonGizmosDataID::InstancesHider)
- | int(CommonGizmosDataID::Raycaster)
- | int(CommonGizmosDataID::ObjectClipper));
-}
-
-
-void GLGizmoPainterBase::on_set_state()
-{
- if (m_state == m_old_state)
- return;
-
- if (m_state == On && m_old_state != On) { // the gizmo was just turned on
- on_opening();
- }
- if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
- // we are actually shutting down
- on_shutdown();
- m_old_mo_id = -1;
- //m_iva.release_geometry();
- m_triangle_selectors.clear();
- }
- m_old_state = m_state;
-}
-
-
-
-void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&)
-{
- // We should update the gizmo from current ModelObject, but it is not
- // possible at this point. That would require having updated selection and
- // common gizmos data, which is not done at this point. Instead, save
- // a flag to do the update in set_painter_gizmo_data, which will be called
- // soon after.
- m_schedule_update = true;
-}
-
-TriangleSelector::ClippingPlane GLGizmoPainterBase::get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const {
- const ::Slic3r::GUI::ClippingPlane *const clipping_plane = m_c->object_clipper()->get_clipping_plane();
- if (clipping_plane == nullptr || !clipping_plane->is_active())
- return {};
-
- const Vec3d clp_normal = clipping_plane->get_normal();
- const double clp_offset = clipping_plane->get_offset();
-
- const Transform3d trafo_normal = Transform3d(trafo.linear().transpose());
- const Transform3d trafo_inv = trafo.inverse();
-
- Vec3d point_on_plane = clp_normal * clp_offset;
- Vec3d point_on_plane_transformed = trafo_inv * point_on_plane;
- Vec3d normal_transformed = trafo_normal * clp_normal;
- auto offset_transformed = float(point_on_plane_transformed.dot(normal_transformed));
-
- return TriangleSelector::ClippingPlane({float(normal_transformed.x()), float(normal_transformed.y()), float(normal_transformed.z()), offset_transformed});
-}
-
-ColorRGBA TriangleSelectorGUI::get_seed_fill_color(const ColorRGBA& base_color)
-{
- return saturate(base_color, 0.75f);
-}
-
-#if ENABLE_GL_SHADERS_ATTRIBUTES
-void TriangleSelectorGUI::render(ImGuiWrapper* imgui, const Transform3d& matrix)
-#else
-void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-{
- static const ColorRGBA enforcers_color = { 0.47f, 0.47f, 1.0f, 1.0f };
- static const ColorRGBA blockers_color = { 1.0f, 0.44f, 0.44f, 1.0f };
-
- if (m_update_render_data) {
- update_render_data();
- m_update_render_data = false;
- }
-
- auto* shader = wxGetApp().get_current_shader();
- if (! shader)
- return;
-
- assert(shader->get_name() == "gouraud");
-
- for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color),
- std::make_pair(&m_iva_blockers, blockers_color)}) {
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- iva.first->set_color(iva.second);
- iva.first->render();
-#else
- if (iva.first->has_VBOs()) {
- shader->set_uniform("uniform_color", iva.second);
- iva.first->render();
- }
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- for (auto& iva : m_iva_seed_fills) {
- size_t color_idx = &iva - &m_iva_seed_fills.front();
- const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color :
- color_idx == 2 ? blockers_color :
- GLVolume::NEUTRAL_COLOR);
- iva.set_color(color);
- iva.render();
- }
-#else
- for (auto& iva : m_iva_seed_fills)
- if (iva.has_VBOs()) {
- size_t color_idx = &iva - &m_iva_seed_fills.front();
- const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color :
- color_idx == 2 ? blockers_color :
- GLVolume::NEUTRAL_COLOR);
- shader->set_uniform("uniform_color", color);
- iva.render();
- }
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- render_paint_contour(matrix);
-#else
- render_paint_contour();
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-#else
- if (m_paint_contour.has_VBO()) {
- ScopeGuard guard_gouraud([shader]() { shader->start_using(); });
- shader->stop_using();
-
- auto *contour_shader = wxGetApp().get_shader("mm_contour");
- contour_shader->start_using();
- contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001);
- m_paint_contour.render();
- contour_shader->stop_using();
- }
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
-#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
- if (imgui)
- render_debug(imgui);
- else
- assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
-#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
-}
-
-void TriangleSelectorGUI::update_render_data()
-{
- int enf_cnt = 0;
- int blc_cnt = 0;
- std::vector<int> seed_fill_cnt(m_iva_seed_fills.size(), 0);
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) {
- iva->reset();
- }
-
- for (auto& iva : m_iva_seed_fills) {
- iva.reset();
- }
-
- GLModel::Geometry iva_enforcers_data;
- iva_enforcers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
- GLModel::Geometry iva_blockers_data;
- iva_blockers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
- std::array<GLModel::Geometry, 3> iva_seed_fills_data;
- for (auto& data : iva_seed_fills_data)
- data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
-#else
- for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
- iva->release_geometry();
-
- for (auto &iva : m_iva_seed_fills)
- iva.release_geometry();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- // small value used to offset triangles along their normal to avoid z-fighting
- static const float offset = 0.001f;
-
- for (const Triangle &tr : m_triangles) {
- if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill()))
- continue;
-
- int tr_state = int(tr.get_state());
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] :
- tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data :
- iva_blockers_data;
-#else
- GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] :
- tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers :
- m_iva_blockers;
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] :
- tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt :
- blc_cnt;
- const Vec3f &v0 = m_vertices[tr.verts_idxs[0]].v;
- const Vec3f &v1 = m_vertices[tr.verts_idxs[1]].v;
- const Vec3f &v2 = m_vertices[tr.verts_idxs[2]].v;
- //FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort
- // or the current implementation may be more cache friendly.
- const Vec3f n = (v1 - v0).cross(v2 - v1).normalized();
- // small value used to offset triangles along their normal to avoid z-fighting
- const Vec3f offset_n = offset * n;
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- iva.add_vertex(v0 + offset_n, n);
- iva.add_vertex(v1 + offset_n, n);
- iva.add_vertex(v2 + offset_n, n);
- iva.add_triangle((unsigned int)cnt, (unsigned int)cnt + 1, (unsigned int)cnt + 2);
-#else
- iva.push_geometry(v0 + offset_n, n);
- iva.push_geometry(v1 + offset_n, n);
- iva.push_geometry(v2 + offset_n, n);
- iva.push_triangle(cnt, cnt + 1, cnt + 2);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- cnt += 3;
- }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- if (!iva_enforcers_data.is_empty())
- m_iva_enforcers.init_from(std::move(iva_enforcers_data));
- if (!iva_blockers_data.is_empty())
- m_iva_blockers.init_from(std::move(iva_blockers_data));
- for (size_t i = 0; i < m_iva_seed_fills.size(); ++i) {
- if (!iva_seed_fills_data[i].is_empty())
- m_iva_seed_fills[i].init_from(std::move(iva_seed_fills_data[i]));
- }
-
- update_paint_contour();
-#else
- for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
- iva->finalize_geometry(true);
-
- for (auto &iva : m_iva_seed_fills)
- iva.finalize_geometry(true);
-
- m_paint_contour.release_geometry();
- std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
- m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6);
- for (const Vec2i &edge : contour_edges) {
- m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x());
- m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y());
- m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z());
-
- m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x());
- m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y());
- m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z());
- }
-
- m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0);
- std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0);
- m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size();
-
- m_paint_contour.finalize_geometry();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-}
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
-void GLPaintContour::render() const
-{
- assert(this->m_contour_VBO_id != 0);
- assert(this->m_contour_EBO_id != 0);
-
- glsafe(::glLineWidth(4.0f));
-
- glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id));
- glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
-
- glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
-
- if (this->contour_indices_size > 0) {
- glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id));
- glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr));
- glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
- }
-
- glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
-
- glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
-}
-
-void GLPaintContour::finalize_geometry()
-{
- assert(this->m_contour_VBO_id == 0);
- assert(this->m_contour_EBO_id == 0);
-
- if (!this->contour_vertices.empty()) {
- glsafe(::glGenBuffers(1, &this->m_contour_VBO_id));
- glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id));
- glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW));
- glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
- this->contour_vertices.clear();
- }
-
- if (!this->contour_indices.empty()) {
- glsafe(::glGenBuffers(1, &this->m_contour_EBO_id));
- glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id));
- glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW));
- glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
- this->contour_indices.clear();
- }
-}
-
-void GLPaintContour::release_geometry()
-{
- if (this->m_contour_VBO_id) {
- glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id));
- this->m_contour_VBO_id = 0;
- }
- if (this->m_contour_EBO_id) {
- glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id));
- this->m_contour_EBO_id = 0;
- }
- this->clear();
-}
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
-#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
-void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
-{
- imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"),
- ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
- static float edge_limit = 1.f;
- imgui->text("Edge limit (mm): ");
- imgui->slider_float("", &edge_limit, 0.1f, 8.f);
- set_edge_limit(edge_limit);
- imgui->checkbox("Show split triangles: ", m_show_triangles);
- imgui->checkbox("Show invalid triangles: ", m_show_invalid);
-
- int valid_triangles = m_triangles.size() - m_invalid_triangles;
- imgui->text("Valid triangles: " + std::to_string(valid_triangles) +
- "/" + std::to_string(m_triangles.size()));
- imgui->text("Vertices: " + std::to_string(m_vertices.size()));
- if (imgui->button("Force garbage collection"))
- garbage_collect();
-
- if (imgui->button("Serialize - deserialize")) {
- auto map = serialize();
- deserialize(map);
- }
-
- imgui->end();
-
- if (! m_show_triangles)
- return;
-
- enum vtype {
- ORIGINAL = 0,
- SPLIT,
- INVALID
- };
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- for (auto& va : m_varrays)
- va.reset();
-#else
- for (auto& va : m_varrays)
- va.release_geometry();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- std::array<int, 3> cnts;
-
- ::glScalef(1.01f, 1.01f, 1.01f);
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- std::array<GLModel::Geometry, 3> varrays_data;
- for (auto& data : varrays_data)
- data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT };
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- for (int tr_id=0; tr_id<int(m_triangles.size()); ++tr_id) {
- const Triangle& tr = m_triangles[tr_id];
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLModel::Geometry* va = nullptr;
-#else
- GLIndexedVertexArray* va = nullptr;
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- int* cnt = nullptr;
- if (tr_id < m_orig_size_indices) {
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- va = &varrays_data[ORIGINAL];
-#else
- va = &m_varrays[ORIGINAL];
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- cnt = &cnts[ORIGINAL];
- }
- else if (tr.valid()) {
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- va = &varrays_data[SPLIT];
-#else
- va = &m_varrays[SPLIT];
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- cnt = &cnts[SPLIT];
- }
- else {
- if (! m_show_invalid)
- continue;
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- va = &varrays_data[INVALID];
-#else
- va = &m_varrays[INVALID];
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- cnt = &cnts[INVALID];
- }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- for (int i = 0; i < 3; ++i) {
- va->add_vertex(m_vertices[tr.verts_idxs[i]].v, Vec3f(0.0f, 0.0f, 1.0f));
- }
- va->add_uint_triangle((unsigned int)*cnt, (unsigned int)*cnt + 1, (unsigned int)*cnt + 2);
-#else
- for (int i = 0; i < 3; ++i)
- va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
- double(m_vertices[tr.verts_idxs[i]].v[1]),
- double(m_vertices[tr.verts_idxs[i]].v[2]),
- 0., 0., 1.);
- va->push_triangle(*cnt,
- *cnt + 1,
- *cnt + 2);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- *cnt += 3;
- }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- for (int i = 0; i < 3; ++i) {
- if (!varrays_data[i].is_empty())
- m_varrays[i].init_from(std::move(varrays_data[i]));
- }
-#else
-// for (auto* iva : { &m_iva_enforcers, &m_iva_blockers })
-// iva->finalize_geometry(true);
-//
-// for (auto& iva : m_iva_seed_fills)
-// iva.finalize_geometry(true);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
- if (curr_shader != nullptr)
- curr_shader->stop_using();
-
- GLShaderProgram* shader = wxGetApp().get_shader("flat");
- if (shader != nullptr) {
- shader->start_using();
-
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Camera& camera = wxGetApp().plater()->get_camera();
- shader->set_uniform("view_model_matrix", camera.get_view_matrix());
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
- for (vtype i : {ORIGINAL, SPLIT, INVALID}) {
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLModel& va = m_varrays[i];
- switch (i) {
- case ORIGINAL: va.set_color({ 0.0f, 0.0f, 1.0f, 1.0f }); break;
- case SPLIT: va.set_color({ 1.0f, 0.0f, 0.0f, 1.0f }); break;
- case INVALID: va.set_color({ 1.0f, 1.0f, 0.0f, 1.0f }); break;
- }
- va.render();
-#else
- GLIndexedVertexArray& va = m_varrays[i];
- va.finalize_geometry(true);
- if (va.has_VBOs()) {
- switch (i) {
- case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break;
- case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break;
- case INVALID : ::glColor3f(1.f, 1.f, 0.f); break;
- }
- va.render();
- }
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- }
- ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- shader->stop_using();
- }
-
- if (curr_shader != nullptr)
- curr_shader->start_using();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-}
-#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-void TriangleSelectorGUI::update_paint_contour()
-{
- m_paint_contour.reset();
-
- GLModel::Geometry init_data;
- const std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
- init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
- init_data.reserve_vertices(2 * contour_edges.size());
- init_data.reserve_indices(2 * contour_edges.size());
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- init_data.color = ColorRGBA::WHITE();
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-//
- // vertices + indices
- unsigned int vertices_count = 0;
- for (const Vec2i& edge : contour_edges) {
- init_data.add_vertex(m_vertices[edge(0)].v);
- init_data.add_vertex(m_vertices[edge(1)].v);
- vertices_count += 2;
- init_data.add_line(vertices_count - 2, vertices_count - 1);
- }
-
- if (!init_data.is_empty())
- m_paint_contour.init_from(std::move(init_data));
-}
-
-#if ENABLE_GL_SHADERS_ATTRIBUTES
-void TriangleSelectorGUI::render_paint_contour(const Transform3d& matrix)
-#else
-void TriangleSelectorGUI::render_paint_contour()
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-{
- auto* curr_shader = wxGetApp().get_current_shader();
- if (curr_shader != nullptr)
- curr_shader->stop_using();
-
- auto* contour_shader = wxGetApp().get_shader("mm_contour");
- if (contour_shader != nullptr) {
- contour_shader->start_using();
-
- contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001);
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Camera& camera = wxGetApp().plater()->get_camera();
- contour_shader->set_uniform("view_model_matrix", camera.get_view_matrix() * matrix);
- contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix());
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
- m_paint_contour.render();
- contour_shader->stop_using();
- }
-
- if (curr_shader != nullptr)
- curr_shader->start_using();
-}
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
-} // namespace Slic3r::GUI
+// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
+#include "GLGizmoPainterBase.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
+
+#include <GL/glew.h>
+
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/Camera.hpp"
+#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/OpenGLManager.hpp"
+#include "slic3r/Utils/UndoRedo.hpp"
+#include "libslic3r/Model.hpp"
+#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/TriangleMesh.hpp"
+
+#include <memory>
+#include <optional>
+
+namespace Slic3r::GUI {
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+std::shared_ptr<GLModel> GLGizmoPainterBase::s_sphere = nullptr;
+#else
+std::shared_ptr<GLIndexedVertexArray> GLGizmoPainterBase::s_sphere = nullptr;
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
+ : GLGizmoBase(parent, icon_filename, sprite_id)
+{
+}
+
+GLGizmoPainterBase::~GLGizmoPainterBase()
+{
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ if (s_sphere != nullptr)
+ s_sphere.reset();
+#else
+ if (s_sphere != nullptr && s_sphere->has_VBOs())
+ s_sphere->release_geometry();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+}
+
+void GLGizmoPainterBase::data_changed()
+{
+ if (m_state != On)
+ return;
+
+ const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
+ const Selection & selection = m_parent.get_selection();
+ if (mo && selection.is_from_single_instance()
+ && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size))
+ {
+ update_from_model_object();
+ m_old_mo_id = mo->id();
+ m_old_volumes_size = mo->volumes.size();
+ m_schedule_update = false;
+ }
+}
+
+GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const
+{
+ ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}};
+ // Take care of the clipping plane. The normal of the clipping plane is
+ // saved with opposite sign than we need to pass to OpenGL (FIXME)
+ if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) {
+ const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane();
+ for (size_t i = 0; i < 3; ++i)
+ clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]);
+ clp_data_out.clp_dataf[3] = float(clp->get_data()[3]);
+ }
+
+ // z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
+ if (m_c->get_canvas()->get_use_clipping_planes()) {
+ const std::array<ClippingPlane, 2> &clps = m_c->get_canvas()->get_clipping_planes();
+ clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])};
+ }
+
+ return clp_data_out;
+}
+
+void GLGizmoPainterBase::render_triangles(const Selection& selection) const
+{
+ auto* shader = wxGetApp().get_shader("gouraud");
+ if (! shader)
+ return;
+ shader->start_using();
+ shader->set_uniform("slope.actived", false);
+ shader->set_uniform("print_volume.type", 0);
+ shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf);
+ ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
+
+ const ModelObject *mo = m_c->selection_info()->model_object();
+ int mesh_id = -1;
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+
+ ++mesh_id;
+
+ const Transform3d trafo_matrix =
+ mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() *
+ mv->get_matrix();
+
+ bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
+ if (is_left_handed)
+ glsafe(::glFrontFace(GL_CW));
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ const Transform3d matrix = camera.get_view_matrix() * trafo_matrix;
+ shader->set_uniform("view_model_matrix", matrix);
+ shader->set_uniform("projection_matrix", camera.get_projection_matrix());
+ shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
+#else
+ glsafe(::glPushMatrix());
+ glsafe(::glMultMatrixd(trafo_matrix.data()));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ // For printers with multiple extruders, it is necessary to pass trafo_matrix
+ // to the shader input variable print_box.volume_world_matrix before
+ // rendering the painted triangles. When this matrix is not set, the
+ // wrong transformation matrix is used for "Clipping of view".
+ shader->set_uniform("volume_world_matrix", trafo_matrix);
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix);
+#else
+ m_triangle_selectors[mesh_id]->render(m_imgui);
+
+ glsafe(::glPopMatrix());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ if (is_left_handed)
+ glsafe(::glFrontFace(GL_CCW));
+ }
+}
+
+void GLGizmoPainterBase::render_cursor()
+{
+ // First check that the mouse pointer is on an object.
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ const Selection& selection = m_parent.get_selection();
+ const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
+ const Camera& camera = wxGetApp().plater()->get_camera();
+
+ // Precalculate transformations of individual meshes.
+ std::vector<Transform3d> trafo_matrices;
+ for (const ModelVolume* mv : mo->volumes) {
+ if (mv->is_model_part())
+ trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix());
+ }
+ // Raycast and return if there's no hit.
+ update_raycast_cache(m_parent.get_local_mouse_position(), camera, trafo_matrices);
+ if (m_rr.mesh_id == -1)
+ return;
+
+ if (m_tool_type == ToolType::BRUSH) {
+ if (m_cursor_type == TriangleSelector::SPHERE)
+ render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
+ else if (m_cursor_type == TriangleSelector::CIRCLE)
+ render_cursor_circle();
+ }
+}
+
+void GLGizmoPainterBase::render_cursor_circle()
+{
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ const Camera &camera = wxGetApp().plater()->get_camera();
+ const float zoom = float(camera.get_zoom());
+ const float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+
+ const Size cnv_size = m_parent.get_canvas_size();
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const float cnv_width = float(cnv_size.get_width());
+ const float cnv_height = float(cnv_size.get_height());
+ if (cnv_width == 0.0f || cnv_height == 0.0f)
+ return;
+
+ const float cnv_inv_width = 1.0f / cnv_width;
+ const float cnv_inv_height = 1.0f / cnv_height;
+
+ const Vec2d center = m_parent.get_local_mouse_position();
+ const float radius = m_cursor_radius * float(wxGetApp().plater()->get_camera().get_zoom());
+#else
+ const float cnv_half_width = 0.5f * float(cnv_size.get_width());
+ const float cnv_half_height = 0.5f * float(cnv_size.get_height());
+ if (cnv_half_width == 0.0f || cnv_half_height == 0.0f)
+ return;
+ const Vec2d mouse_pos(m_parent.get_local_mouse_position().x(), m_parent.get_local_mouse_position().y());
+ Vec2d center(mouse_pos.x() - cnv_half_width, cnv_half_height - mouse_pos.y());
+ center = center * inv_zoom;
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ glsafe(::glLineWidth(1.5f));
+#if !ENABLE_LEGACY_OPENGL_REMOVAL
+ static const std::array<float, 3> color = { 0.f, 1.f, 0.3f };
+ glsafe(::glColor3fv(color.data()));
+#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
+ glsafe(::glDisable(GL_DEPTH_TEST));
+
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPushMatrix());
+ glsafe(::glLoadIdentity());
+ // ensure that the circle is renderered inside the frustrum
+ glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5)));
+ // ensure that the overlay fits the frustrum near z plane
+ const double gui_scale = camera.get_gui_scale();
+ glsafe(::glScaled(gui_scale, gui_scale, 1.0));
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+
+ glsafe(::glPushAttrib(GL_ENABLE_BIT));
+ glsafe(::glLineStipple(4, 0xAAAA));
+ glsafe(::glEnable(GL_LINE_STIPPLE));
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - radius) > EPSILON) {
+ m_old_cursor_radius = radius;
+#else
+ if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - m_cursor_radius) > EPSILON) {
+ m_old_cursor_radius = m_cursor_radius;
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ m_old_center = center;
+ m_circle.reset();
+
+ GLModel::Geometry init_data;
+ static const unsigned int StepsCount = 32;
+ static const float StepSize = 2.0f * float(PI) / float(StepsCount);
+ init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 };
+ init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f };
+ init_data.reserve_vertices(StepsCount);
+ init_data.reserve_indices(StepsCount);
+
+ // vertices + indices
+ for (unsigned int i = 0; i < StepsCount; ++i) {
+ const float angle = float(i) * StepSize;
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ init_data.add_vertex(Vec2f(2.0f * ((center.x() + ::cos(angle) * radius) * cnv_inv_width - 0.5f),
+ -2.0f * ((center.y() + ::sin(angle) * radius) * cnv_inv_height - 0.5f)));
+#else
+ init_data.add_vertex(Vec2f(center.x() + ::cos(angle) * m_cursor_radius, center.y() + ::sin(angle) * m_cursor_radius));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ init_data.add_index(i);
+ }
+
+ m_circle.init_from(std::move(init_data));
+ }
+
+ GLShaderProgram* shader = GUI::wxGetApp().get_shader("flat");
+ if (shader != nullptr) {
+ shader->start_using();
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ shader->set_uniform("view_model_matrix", Transform3d::Identity());
+ shader->set_uniform("projection_matrix", Transform3d::Identity());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ m_circle.render();
+ shader->stop_using();
+ }
+#else
+ ::glBegin(GL_LINE_LOOP);
+ for (double angle=0; angle<2*M_PI; angle+=M_PI/20.)
+ ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle)));
+ glsafe(::glEnd());
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ glsafe(::glPopAttrib());
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glEnable(GL_DEPTH_TEST));
+}
+
+
+void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
+{
+ if (s_sphere == nullptr) {
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ s_sphere = std::make_shared<GLModel>();
+ s_sphere->init_from(its_make_sphere(1.0, double(PI) / 12.0));
+#else
+ s_sphere = std::make_shared<GLIndexedVertexArray>();
+ s_sphere->load_its_flat_shading(its_make_sphere(1.0, double(PI) / 12.0));
+ s_sphere->finalize_geometry(true);
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ }
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLShaderProgram* shader = wxGetApp().get_shader("flat");
+ if (shader == nullptr)
+ return;
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_scaling_factor_matrix().inverse();
+#else
+ const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse();
+#endif // ENABLE_WORLD_COORDINATE
+ const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed();
+
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPushMatrix());
+ glsafe(::glMultMatrixd(trafo.data()));
+ // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
+ glsafe(::glTranslatef(m_rr.hit.x(), m_rr.hit.y(), m_rr.hit.z()));
+ glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data()));
+ glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius));
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+
+ if (is_left_handed)
+ glFrontFace(GL_CW);
+
+ ColorRGBA render_color = { 0.0f, 0.0f, 0.0f, 0.25f };
+ if (m_button_down == Button::Left)
+ render_color = this->get_cursor_sphere_left_button_color();
+ else if (m_button_down == Button::Right)
+ render_color = this->get_cursor_sphere_right_button_color();
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ shader->start_using();
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ Transform3d view_model_matrix = camera.get_view_matrix() * trafo *
+ Geometry::assemble_transform(m_rr.hit.cast<double>()) * complete_scaling_matrix_inverse *
+ Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), m_cursor_radius * Vec3d::Ones());
+
+ shader->set_uniform("view_model_matrix", view_model_matrix);
+ shader->set_uniform("projection_matrix", camera.get_projection_matrix());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ assert(s_sphere != nullptr);
+ s_sphere->set_color(render_color);
+#else
+ glsafe(::glColor4fv(render_color.data()));
+
+ assert(s_sphere != nullptr);
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ s_sphere->render();
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ shader->stop_using();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ if (is_left_handed)
+ glFrontFace(GL_CCW);
+
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+}
+
+
+bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const
+{
+ if (m_c->object_clipper()->get_position() == 0.)
+ return false;
+
+ auto sel_info = m_c->selection_info();
+ Vec3d transformed_point = trafo * point;
+ transformed_point(2) += sel_info->get_sla_shift();
+ return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
+}
+
+// Interpolate points between the previous and current mouse positions, which are then projected onto the object.
+// Returned projected mouse positions are grouped by mesh_idx. It may contain multiple std::vector<GLGizmoPainterBase::ProjectedMousePosition>
+// with the same mesh_idx, but all items in std::vector<GLGizmoPainterBase::ProjectedMousePosition> always have the same mesh_idx.
+std::vector<std::vector<GLGizmoPainterBase::ProjectedMousePosition>> GLGizmoPainterBase::get_projected_mouse_positions(const Vec2d &mouse_position, const double resolution, const std::vector<Transform3d> &trafo_matrices) const
+{
+ // List of mouse positions that will be used as seeds for painting.
+ std::vector<Vec2d> mouse_positions{mouse_position};
+ if (m_last_mouse_click != Vec2d::Zero()) {
+ // In case current mouse position is far from the last one,
+ // add several positions from between into the list, so there
+ // are no gaps in the painted region.
+ if (size_t patches_in_between = size_t((mouse_position - m_last_mouse_click).norm() / resolution); patches_in_between > 0) {
+ const Vec2d diff = (m_last_mouse_click - mouse_position) / (patches_in_between + 1);
+ for (size_t patch_idx = 1; patch_idx <= patches_in_between; ++patch_idx)
+ mouse_positions.emplace_back(mouse_position + patch_idx * diff);
+ mouse_positions.emplace_back(m_last_mouse_click);
+ }
+ }
+
+ const Camera &camera = wxGetApp().plater()->get_camera();
+ std::vector<ProjectedMousePosition> mesh_hit_points;
+ mesh_hit_points.reserve(mouse_positions.size());
+
+ // In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't.
+ for (const Vec2d &mp : mouse_positions) {
+ update_raycast_cache(mp, camera, trafo_matrices);
+ mesh_hit_points.push_back({m_rr.hit, m_rr.mesh_id, m_rr.facet});
+ if (m_rr.mesh_id == -1)
+ break;
+ }
+
+ // Divide mesh_hit_points into groups with the same mesh_idx. It may contain multiple groups with the same mesh_idx.
+ std::vector<std::vector<ProjectedMousePosition>> mesh_hit_points_by_mesh;
+ for (size_t prev_mesh_hit_point = 0, curr_mesh_hit_point = 0; curr_mesh_hit_point < mesh_hit_points.size(); ++curr_mesh_hit_point) {
+ size_t next_mesh_hit_point = curr_mesh_hit_point + 1;
+ if (next_mesh_hit_point >= mesh_hit_points.size() || mesh_hit_points[curr_mesh_hit_point].mesh_idx != mesh_hit_points[next_mesh_hit_point].mesh_idx) {
+ mesh_hit_points_by_mesh.emplace_back();
+ mesh_hit_points_by_mesh.back().insert(mesh_hit_points_by_mesh.back().end(), mesh_hit_points.begin() + int(prev_mesh_hit_point), mesh_hit_points.begin() + int(next_mesh_hit_point));
+ prev_mesh_hit_point = next_mesh_hit_point;
+ }
+ }
+
+ auto on_same_facet = [](std::vector<ProjectedMousePosition> &hit_points) -> bool {
+ for (const ProjectedMousePosition &mesh_hit_point : hit_points)
+ if (mesh_hit_point.facet_idx != hit_points.front().facet_idx)
+ return false;
+ return true;
+ };
+
+ struct Plane
+ {
+ Vec3d origin;
+ Vec3d first_axis;
+ Vec3d second_axis;
+ };
+ auto find_plane = [](std::vector<ProjectedMousePosition> &hit_points) -> std::optional<Plane> {
+ assert(hit_points.size() >= 3);
+ for (size_t third_idx = 2; third_idx < hit_points.size(); ++third_idx) {
+ const Vec3d &first_point = hit_points[third_idx - 2].mesh_hit.cast<double>();
+ const Vec3d &second_point = hit_points[third_idx - 1].mesh_hit.cast<double>();
+ const Vec3d &third_point = hit_points[third_idx].mesh_hit.cast<double>();
+
+ const Vec3d first_vec = first_point - second_point;
+ const Vec3d second_vec = third_point - second_point;
+
+ // If three points aren't collinear, then there exists only one plane going through all points.
+ if (first_vec.cross(second_vec).squaredNorm() > sqr(EPSILON)) {
+ const Vec3d first_axis_vec_n = first_vec.normalized();
+ // Make second_vec perpendicular to first_axis_vec_n using Gram–Schmidt orthogonalization process
+ const Vec3d second_axis_vec_n = (second_vec - (first_vec.dot(second_vec) / first_vec.dot(first_vec)) * first_vec).normalized();
+ return Plane{second_point, first_axis_vec_n, second_axis_vec_n};
+ }
+ }
+
+ return std::nullopt;
+ };
+
+ for(std::vector<ProjectedMousePosition> &hit_points : mesh_hit_points_by_mesh) {
+ assert(!hit_points.empty());
+ if (hit_points.back().mesh_idx == -1)
+ break;
+
+ if (hit_points.size() <= 2)
+ continue;
+
+ if (on_same_facet(hit_points)) {
+ hit_points = {hit_points.front(), hit_points.back()};
+ } else if (std::optional<Plane> plane = find_plane(hit_points); plane) {
+ Polyline polyline;
+ polyline.points.reserve(hit_points.size());
+ // Project hit_points into its plane to simplified them in the next step.
+ for (auto &hit_point : hit_points) {
+ const Vec3d &point = hit_point.mesh_hit.cast<double>();
+ const double x_cord = plane->first_axis.dot(point - plane->origin);
+ const double y_cord = plane->second_axis.dot(point - plane->origin);
+ polyline.points.emplace_back(scale_(x_cord), scale_(y_cord));
+ }
+
+ polyline.simplify(scale_(m_cursor_radius) / 10.);
+
+ const int mesh_idx = hit_points.front().mesh_idx;
+ std::vector<ProjectedMousePosition> new_hit_points;
+ new_hit_points.reserve(polyline.points.size());
+ // Project 2D simplified hit_points beck to 3D.
+ for (const Point &point : polyline.points) {
+ const double x_cord = unscale<double>(point.x());
+ const double y_cord = unscale<double>(point.y());
+ const Vec3d new_hit_point = plane->origin + x_cord * plane->first_axis + y_cord * plane->second_axis;
+ const int facet_idx = m_c->raycaster()->raycasters()[mesh_idx]->get_closest_facet(new_hit_point.cast<float>());
+ new_hit_points.push_back({new_hit_point.cast<float>(), mesh_idx, size_t(facet_idx)});
+ }
+
+ hit_points = new_hit_points;
+ } else {
+ hit_points = {hit_points.front(), hit_points.back()};
+ }
+ }
+
+ return mesh_hit_points_by_mesh;
+}
+
+// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
+// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
+// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
+// concludes that the event was not intended for it, it should return false.
+bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
+{
+ if (action == SLAGizmoEventType::MouseWheelUp
+ || action == SLAGizmoEventType::MouseWheelDown) {
+ if (control_down) {
+ double pos = m_c->object_clipper()->get_position();
+ pos = action == SLAGizmoEventType::MouseWheelDown
+ ? std::max(0., pos - 0.01)
+ : std::min(1., pos + 0.01);
+ m_c->object_clipper()->set_position(pos, true);
+ return true;
+ }
+ else if (alt_down) {
+ if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) {
+ m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - this->get_cursor_radius_step(), this->get_cursor_radius_min())
+ : std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max());
+ m_parent.set_as_dirty();
+ return true;
+ } else if (m_tool_type == ToolType::SMART_FILL) {
+ m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin)
+ : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax);
+ m_parent.set_as_dirty();
+ if (m_rr.mesh_id != -1) {
+ const Selection &selection = m_parent.get_selection();
+ const ModelObject *mo = m_c->selection_info()->model_object();
+ const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset();
+#else
+ const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true);
+#endif // ENABLE_WORLD_COORDINATE
+ const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix();
+ m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle,
+ m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
+ m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
+ m_seed_fill_last_mesh_id = m_rr.mesh_id;
+ }
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ if (action == SLAGizmoEventType::ResetClippingPlane) {
+ m_c->object_clipper()->set_position(-1., false);
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::LeftDown
+ || action == SLAGizmoEventType::RightDown
+ || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) {
+
+ if (m_triangle_selectors.empty())
+ return false;
+
+ EnforcerBlockerType new_state = EnforcerBlockerType::NONE;
+ if (! shift_down) {
+ if (action == SLAGizmoEventType::Dragging)
+ new_state = m_button_down == Button::Left ? this->get_left_button_state_type() : this->get_right_button_state_type();
+ else
+ new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type();
+ }
+
+ const Camera &camera = wxGetApp().plater()->get_camera();
+ const Selection &selection = m_parent.get_selection();
+ const ModelObject *mo = m_c->selection_info()->model_object();
+ const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
+ const Transform3d instance_trafo = mi->get_transformation().get_matrix();
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix_no_offset();
+#else
+ const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true);
+#endif // ENABLE_WORLD_COORDINATE
+
+ // Precalculate transformations of individual meshes.
+ std::vector<Transform3d> trafo_matrices;
+ std::vector<Transform3d> trafo_matrices_not_translate;
+ for (const ModelVolume *mv : mo->volumes)
+ if (mv->is_model_part()) {
+ trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
+#if ENABLE_WORLD_COORDINATE
+ trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset());
+#else
+ trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
+#endif // ENABLE_WORLD_COORDINATE
+ }
+
+ std::vector<std::vector<ProjectedMousePosition>> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices);
+ m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved
+
+ for (const std::vector<ProjectedMousePosition> &projected_mouse_positions : projected_mouse_positions_by_mesh) {
+ assert(!projected_mouse_positions.empty());
+ const int mesh_idx = projected_mouse_positions.front().mesh_idx;
+ const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
+
+ // The mouse button click detection is enabled when there is a valid hit.
+ // Missing the object entirely
+ // shall not capture the mouse.
+ if (mesh_idx != -1)
+ if (m_button_down == Button::None)
+ m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
+
+ // In case we have no valid hit, we can return. The event will be stopped when
+ // dragging while painting (to prevent scene rotations and moving the object)
+ if (mesh_idx == -1)
+ return dragging_while_painting;
+
+ const Transform3d &trafo_matrix = trafo_matrices[mesh_idx];
+ const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx];
+
+ // Calculate direction from camera to the hit (in mesh coords):
+ Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
+
+ assert(mesh_idx < int(m_triangle_selectors.size()));
+ const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
+ if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
+ for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) {
+ assert(projected_mouse_position.mesh_idx == mesh_idx);
+ const Vec3f mesh_hit = projected_mouse_position.mesh_hit;
+ const int facet_idx = int(projected_mouse_position.facet_idx);
+ m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state);
+ if (m_tool_type == ToolType::SMART_FILL)
+ m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle,
+ m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
+ else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
+ m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true);
+ else if (m_tool_type == ToolType::BUCKET_FILL)
+ m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true);
+
+ m_seed_fill_last_mesh_id = -1;
+ }
+ } else if (m_tool_type == ToolType::BRUSH) {
+ assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE);
+
+ if (projected_mouse_positions.size() == 1) {
+ const ProjectedMousePosition &first_position = projected_mouse_positions.front();
+ std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit,
+ camera_pos, m_cursor_radius,
+ m_cursor_type, trafo_matrix, clp);
+ m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate,
+ m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
+ } else {
+ for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) {
+ auto second_position_it = first_position_it + 1;
+ std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp);
+ m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
+ }
+ }
+ }
+
+ m_triangle_selectors[mesh_idx]->request_update_render_data();
+ m_last_mouse_click = mouse_position;
+ }
+
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) {
+ if (m_triangle_selectors.empty())
+ return false;
+
+ const Camera &camera = wxGetApp().plater()->get_camera();
+ const Selection &selection = m_parent.get_selection();
+ const ModelObject *mo = m_c->selection_info()->model_object();
+ const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
+ const Transform3d instance_trafo = mi->get_transformation().get_matrix();
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix_no_offset();
+#else
+ const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true);
+#endif // ENABLE_WORLD_COORDINATE
+
+ // Precalculate transformations of individual meshes.
+ std::vector<Transform3d> trafo_matrices;
+ std::vector<Transform3d> trafo_matrices_not_translate;
+ for (const ModelVolume *mv : mo->volumes)
+ if (mv->is_model_part()) {
+ trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
+#if ENABLE_WORLD_COORDINATE
+ trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset());
+#else
+ trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
+#endif // ENABLE_WORLD_COORDINATE
+ }
+
+ // Now "click" into all the prepared points and spill paint around them.
+ update_raycast_cache(mouse_position, camera, trafo_matrices);
+
+ auto seed_fill_unselect_all = [this]() {
+ for (auto &triangle_selector : m_triangle_selectors) {
+ triangle_selector->seed_fill_unselect_all_triangles();
+ triangle_selector->request_update_render_data();
+ }
+ };
+
+ if (m_rr.mesh_id == -1) {
+ // Clean selected by seed fill for all triangles in all meshes when a mouse isn't pointing on any mesh.
+ seed_fill_unselect_all();
+ m_seed_fill_last_mesh_id = -1;
+
+ // In case we have no valid hit, we can return.
+ return false;
+ }
+
+ // The mouse moved from one object's volume to another one. So it is needed to unselect all triangles selected by seed fill.
+ if(m_rr.mesh_id != m_seed_fill_last_mesh_id)
+ seed_fill_unselect_all();
+
+ const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id];
+ const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id];
+
+ assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
+ const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
+ if (m_tool_type == ToolType::SMART_FILL)
+ m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle,
+ m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
+ else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
+ m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false);
+ else if (m_tool_type == ToolType::BUCKET_FILL)
+ m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true);
+ m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
+ m_seed_fill_last_mesh_id = m_rr.mesh_id;
+ return true;
+ }
+
+ if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
+ && m_button_down != Button::None) {
+ // Take snapshot and update ModelVolume data.
+ wxString action_name = this->handle_snapshot_action_name(shift_down, m_button_down);
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name, UndoRedo::SnapshotType::GizmoAction);
+ update_model_object();
+
+ m_button_down = Button::None;
+ m_last_mouse_click = Vec2d::Zero();
+ return true;
+ }
+
+ return false;
+}
+
+bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event)
+{
+ // wxCoord == int --> wx/types.h
+ Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
+ Vec2d mouse_pos = mouse_coord.cast<double>();
+
+ if (mouse_event.Moving()) {
+ gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false);
+ return false;
+ }
+
+ // when control is down we allow scene pan and rotation even when clicking
+ // over some object
+ bool control_down = mouse_event.CmdDown();
+ bool grabber_contains_mouse = (get_hover_id() != -1);
+
+ const Selection &selection = m_parent.get_selection();
+ int selected_object_idx = selection.get_object_idx();
+ if (mouse_event.LeftDown()) {
+ if ((!control_down || grabber_contains_mouse) &&
+ gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false))
+ // the gizmo got the event and took some action, there is no need
+ // to do anything more
+ return true;
+ } else if (mouse_event.RightDown()){
+ if (!control_down && selected_object_idx != -1 &&
+ gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false))
+ // event was taken care of
+ return true;
+ } else if (mouse_event.Dragging()) {
+ if (m_parent.get_move_volume_id() != -1)
+ // don't allow dragging objects with the Sla gizmo on
+ return true;
+ if (!control_down && gizmo_event(SLAGizmoEventType::Dragging,
+ mouse_pos, mouse_event.ShiftDown(),
+ mouse_event.AltDown(), false)) {
+ // the gizmo got the event and took some action, no need to do
+ // anything more here
+ m_parent.set_as_dirty();
+ return true;
+ }
+ if(control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown()))
+ {
+ // CTRL has been pressed while already dragging -> stop current action
+ if (mouse_event.LeftIsDown())
+ gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
+ else if (mouse_event.RightIsDown())
+ gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
+ return false;
+ }
+ } else if (mouse_event.LeftUp()) {
+ if (!m_parent.is_mouse_dragging()) {
+ // in case SLA/FDM gizmo is selected, we just pass the LeftUp
+ // event and stop processing - neither object moving or selecting
+ // is suppressed in that case
+ gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down);
+ return true;
+ }
+ } else if (mouse_event.RightUp()) {
+ if (!m_parent.is_mouse_dragging()) {
+ gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down);
+ return true;
+ }
+ }
+ return false;
+}
+
+void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position,
+ const Camera& camera,
+ const std::vector<Transform3d>& trafo_matrices) const
+{
+ if (m_rr.mouse_position == mouse_position) {
+ // Same query as last time - the answer is already in the cache.
+ return;
+ }
+
+ Vec3f normal = Vec3f::Zero();
+ Vec3f hit = Vec3f::Zero();
+ size_t facet = 0;
+ Vec3f closest_hit = Vec3f::Zero();
+ double closest_hit_squared_distance = std::numeric_limits<double>::max();
+ size_t closest_facet = 0;
+ int closest_hit_mesh_id = -1;
+
+ // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
+ for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) {
+
+ if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
+ mouse_position,
+ trafo_matrices[mesh_id],
+ camera,
+ hit,
+ normal,
+ m_c->object_clipper()->get_clipping_plane(),
+ &facet))
+ {
+ // In case this hit is clipped, skip it.
+ if (is_mesh_point_clipped(hit.cast<double>(), trafo_matrices[mesh_id]))
+ continue;
+
+ // Is this hit the closest to the camera so far?
+ double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm();
+ if (hit_squared_distance < closest_hit_squared_distance) {
+ closest_hit_squared_distance = hit_squared_distance;
+ closest_facet = facet;
+ closest_hit_mesh_id = mesh_id;
+ closest_hit = hit;
+ }
+ }
+ }
+
+ m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet};
+}
+
+bool GLGizmoPainterBase::on_is_activable() const
+{
+ const Selection& selection = m_parent.get_selection();
+
+ if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF
+ || !selection.is_single_full_instance() || wxGetApp().get_mode() == comSimple)
+ return false;
+
+ // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
+ const Selection::IndicesList& list = selection.get_volume_idxs();
+ return std::all_of(list.cbegin(), list.cend(), [&selection](unsigned int idx) { return !selection.get_volume(idx)->is_outside; });
+}
+
+bool GLGizmoPainterBase::on_is_selectable() const
+{
+ return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF
+ && wxGetApp().get_mode() != comSimple );
+}
+
+
+CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const
+{
+ return CommonGizmosDataID(
+ int(CommonGizmosDataID::SelectionInfo)
+ | int(CommonGizmosDataID::InstancesHider)
+ | int(CommonGizmosDataID::Raycaster)
+ | int(CommonGizmosDataID::ObjectClipper));
+}
+
+
+void GLGizmoPainterBase::on_set_state()
+{
+ if (m_state == m_old_state)
+ return;
+
+ if (m_state == On && m_old_state != On) { // the gizmo was just turned on
+ on_opening();
+ }
+ if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
+ // we are actually shutting down
+ on_shutdown();
+ m_old_mo_id = -1;
+ //m_iva.release_geometry();
+ m_triangle_selectors.clear();
+ }
+ m_old_state = m_state;
+}
+
+
+
+void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&)
+{
+ // We should update the gizmo from current ModelObject, but it is not
+ // possible at this point. That would require having updated selection and
+ // common gizmos data, which is not done at this point. Instead, save
+ // a flag to do the update in set_painter_gizmo_data, which will be called
+ // soon after.
+ m_schedule_update = true;
+}
+
+TriangleSelector::ClippingPlane GLGizmoPainterBase::get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const {
+ const ::Slic3r::GUI::ClippingPlane *const clipping_plane = m_c->object_clipper()->get_clipping_plane();
+ if (clipping_plane == nullptr || !clipping_plane->is_active())
+ return {};
+
+ const Vec3d clp_normal = clipping_plane->get_normal();
+ const double clp_offset = clipping_plane->get_offset();
+
+ const Transform3d trafo_normal = Transform3d(trafo.linear().transpose());
+ const Transform3d trafo_inv = trafo.inverse();
+
+ Vec3d point_on_plane = clp_normal * clp_offset;
+ Vec3d point_on_plane_transformed = trafo_inv * point_on_plane;
+ Vec3d normal_transformed = trafo_normal * clp_normal;
+ auto offset_transformed = float(point_on_plane_transformed.dot(normal_transformed));
+
+ return TriangleSelector::ClippingPlane({float(normal_transformed.x()), float(normal_transformed.y()), float(normal_transformed.z()), offset_transformed});
+}
+
+ColorRGBA TriangleSelectorGUI::get_seed_fill_color(const ColorRGBA& base_color)
+{
+ return saturate(base_color, 0.75f);
+}
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+void TriangleSelectorGUI::render(ImGuiWrapper* imgui, const Transform3d& matrix)
+#else
+void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+{
+ static const ColorRGBA enforcers_color = { 0.47f, 0.47f, 1.0f, 1.0f };
+ static const ColorRGBA blockers_color = { 1.0f, 0.44f, 0.44f, 1.0f };
+
+ if (m_update_render_data) {
+ update_render_data();
+ m_update_render_data = false;
+ }
+
+ auto* shader = wxGetApp().get_current_shader();
+ if (! shader)
+ return;
+
+ assert(shader->get_name() == "gouraud");
+
+ for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color),
+ std::make_pair(&m_iva_blockers, blockers_color)}) {
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ iva.first->set_color(iva.second);
+ iva.first->render();
+#else
+ if (iva.first->has_VBOs()) {
+ shader->set_uniform("uniform_color", iva.second);
+ iva.first->render();
+ }
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ }
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ for (auto& iva : m_iva_seed_fills) {
+ size_t color_idx = &iva - &m_iva_seed_fills.front();
+ const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color :
+ color_idx == 2 ? blockers_color :
+ GLVolume::NEUTRAL_COLOR);
+ iva.set_color(color);
+ iva.render();
+ }
+#else
+ for (auto& iva : m_iva_seed_fills)
+ if (iva.has_VBOs()) {
+ size_t color_idx = &iva - &m_iva_seed_fills.front();
+ const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color :
+ color_idx == 2 ? blockers_color :
+ GLVolume::NEUTRAL_COLOR);
+ shader->set_uniform("uniform_color", color);
+ iva.render();
+ }
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ render_paint_contour(matrix);
+#else
+ render_paint_contour();
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#else
+ if (m_paint_contour.has_VBO()) {
+ ScopeGuard guard_gouraud([shader]() { shader->start_using(); });
+ shader->stop_using();
+
+ auto *contour_shader = wxGetApp().get_shader("mm_contour");
+ contour_shader->start_using();
+ contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001);
+ m_paint_contour.render();
+ contour_shader->stop_using();
+ }
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
+ if (imgui)
+ render_debug(imgui);
+ else
+ assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
+#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
+}
+
+void TriangleSelectorGUI::update_render_data()
+{
+ int enf_cnt = 0;
+ int blc_cnt = 0;
+ std::vector<int> seed_fill_cnt(m_iva_seed_fills.size(), 0);
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) {
+ iva->reset();
+ }
+
+ for (auto& iva : m_iva_seed_fills) {
+ iva.reset();
+ }
+
+ GLModel::Geometry iva_enforcers_data;
+ iva_enforcers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
+ GLModel::Geometry iva_blockers_data;
+ iva_blockers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
+ std::array<GLModel::Geometry, 3> iva_seed_fills_data;
+ for (auto& data : iva_seed_fills_data)
+ data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
+#else
+ for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
+ iva->release_geometry();
+
+ for (auto &iva : m_iva_seed_fills)
+ iva.release_geometry();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ // small value used to offset triangles along their normal to avoid z-fighting
+ static const float offset = 0.001f;
+
+ for (const Triangle &tr : m_triangles) {
+ if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill()))
+ continue;
+
+ int tr_state = int(tr.get_state());
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] :
+ tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data :
+ iva_blockers_data;
+#else
+ GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] :
+ tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers :
+ m_iva_blockers;
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] :
+ tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt :
+ blc_cnt;
+ const Vec3f &v0 = m_vertices[tr.verts_idxs[0]].v;
+ const Vec3f &v1 = m_vertices[tr.verts_idxs[1]].v;
+ const Vec3f &v2 = m_vertices[tr.verts_idxs[2]].v;
+ //FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort
+ // or the current implementation may be more cache friendly.
+ const Vec3f n = (v1 - v0).cross(v2 - v1).normalized();
+ // small value used to offset triangles along their normal to avoid z-fighting
+ const Vec3f offset_n = offset * n;
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ iva.add_vertex(v0 + offset_n, n);
+ iva.add_vertex(v1 + offset_n, n);
+ iva.add_vertex(v2 + offset_n, n);
+ iva.add_triangle((unsigned int)cnt, (unsigned int)cnt + 1, (unsigned int)cnt + 2);
+#else
+ iva.push_geometry(v0 + offset_n, n);
+ iva.push_geometry(v1 + offset_n, n);
+ iva.push_geometry(v2 + offset_n, n);
+ iva.push_triangle(cnt, cnt + 1, cnt + 2);
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ cnt += 3;
+ }
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ if (!iva_enforcers_data.is_empty())
+ m_iva_enforcers.init_from(std::move(iva_enforcers_data));
+ if (!iva_blockers_data.is_empty())
+ m_iva_blockers.init_from(std::move(iva_blockers_data));
+ for (size_t i = 0; i < m_iva_seed_fills.size(); ++i) {
+ if (!iva_seed_fills_data[i].is_empty())
+ m_iva_seed_fills[i].init_from(std::move(iva_seed_fills_data[i]));
+ }
+
+ update_paint_contour();
+#else
+ for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
+ iva->finalize_geometry(true);
+
+ for (auto &iva : m_iva_seed_fills)
+ iva.finalize_geometry(true);
+
+ m_paint_contour.release_geometry();
+ std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
+ m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6);
+ for (const Vec2i &edge : contour_edges) {
+ m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x());
+ m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y());
+ m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z());
+
+ m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x());
+ m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y());
+ m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z());
+ }
+
+ m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0);
+ std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0);
+ m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size();
+
+ m_paint_contour.finalize_geometry();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+}
+
+#if !ENABLE_LEGACY_OPENGL_REMOVAL
+void GLPaintContour::render() const
+{
+ assert(this->m_contour_VBO_id != 0);
+ assert(this->m_contour_EBO_id != 0);
+
+ glsafe(::glLineWidth(4.0f));
+
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id));
+ glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
+
+ glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
+
+ if (this->contour_indices_size > 0) {
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id));
+ glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr));
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+ }
+
+ glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
+
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+}
+
+void GLPaintContour::finalize_geometry()
+{
+ assert(this->m_contour_VBO_id == 0);
+ assert(this->m_contour_EBO_id == 0);
+
+ if (!this->contour_vertices.empty()) {
+ glsafe(::glGenBuffers(1, &this->m_contour_VBO_id));
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id));
+ glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW));
+ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
+ this->contour_vertices.clear();
+ }
+
+ if (!this->contour_indices.empty()) {
+ glsafe(::glGenBuffers(1, &this->m_contour_EBO_id));
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id));
+ glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW));
+ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+ this->contour_indices.clear();
+ }
+}
+
+void GLPaintContour::release_geometry()
+{
+ if (this->m_contour_VBO_id) {
+ glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id));
+ this->m_contour_VBO_id = 0;
+ }
+ if (this->m_contour_EBO_id) {
+ glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id));
+ this->m_contour_EBO_id = 0;
+ }
+ this->clear();
+}
+#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
+
+#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
+void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
+{
+ imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"),
+ ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
+ static float edge_limit = 1.f;
+ imgui->text("Edge limit (mm): ");
+ imgui->slider_float("", &edge_limit, 0.1f, 8.f);
+ set_edge_limit(edge_limit);
+ imgui->checkbox("Show split triangles: ", m_show_triangles);
+ imgui->checkbox("Show invalid triangles: ", m_show_invalid);
+
+ int valid_triangles = m_triangles.size() - m_invalid_triangles;
+ imgui->text("Valid triangles: " + std::to_string(valid_triangles) +
+ "/" + std::to_string(m_triangles.size()));
+ imgui->text("Vertices: " + std::to_string(m_vertices.size()));
+ if (imgui->button("Force garbage collection"))
+ garbage_collect();
+
+ if (imgui->button("Serialize - deserialize")) {
+ auto map = serialize();
+ deserialize(map);
+ }
+
+ imgui->end();
+
+ if (! m_show_triangles)
+ return;
+
+ enum vtype {
+ ORIGINAL = 0,
+ SPLIT,
+ INVALID
+ };
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ for (auto& va : m_varrays)
+ va.reset();
+#else
+ for (auto& va : m_varrays)
+ va.release_geometry();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ std::array<int, 3> cnts;
+
+ ::glScalef(1.01f, 1.01f, 1.01f);
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ std::array<GLModel::Geometry, 3> varrays_data;
+ for (auto& data : varrays_data)
+ data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT };
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ for (int tr_id=0; tr_id<int(m_triangles.size()); ++tr_id) {
+ const Triangle& tr = m_triangles[tr_id];
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLModel::Geometry* va = nullptr;
+#else
+ GLIndexedVertexArray* va = nullptr;
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ int* cnt = nullptr;
+ if (tr_id < m_orig_size_indices) {
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ va = &varrays_data[ORIGINAL];
+#else
+ va = &m_varrays[ORIGINAL];
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ cnt = &cnts[ORIGINAL];
+ }
+ else if (tr.valid()) {
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ va = &varrays_data[SPLIT];
+#else
+ va = &m_varrays[SPLIT];
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ cnt = &cnts[SPLIT];
+ }
+ else {
+ if (! m_show_invalid)
+ continue;
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ va = &varrays_data[INVALID];
+#else
+ va = &m_varrays[INVALID];
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ cnt = &cnts[INVALID];
+ }
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ for (int i = 0; i < 3; ++i) {
+ va->add_vertex(m_vertices[tr.verts_idxs[i]].v, Vec3f(0.0f, 0.0f, 1.0f));
+ }
+ va->add_uint_triangle((unsigned int)*cnt, (unsigned int)*cnt + 1, (unsigned int)*cnt + 2);
+#else
+ for (int i = 0; i < 3; ++i)
+ va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
+ double(m_vertices[tr.verts_idxs[i]].v[1]),
+ double(m_vertices[tr.verts_idxs[i]].v[2]),
+ 0., 0., 1.);
+ va->push_triangle(*cnt,
+ *cnt + 1,
+ *cnt + 2);
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ *cnt += 3;
+ }
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ for (int i = 0; i < 3; ++i) {
+ if (!varrays_data[i].is_empty())
+ m_varrays[i].init_from(std::move(varrays_data[i]));
+ }
+#else
+// for (auto* iva : { &m_iva_enforcers, &m_iva_blockers })
+// iva->finalize_geometry(true);
+//
+// for (auto& iva : m_iva_seed_fills)
+// iva.finalize_geometry(true);
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
+ if (curr_shader != nullptr)
+ curr_shader->stop_using();
+
+ GLShaderProgram* shader = wxGetApp().get_shader("flat");
+ if (shader != nullptr) {
+ shader->start_using();
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+ shader->set_uniform("projection_matrix", camera.get_projection_matrix());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
+ for (vtype i : {ORIGINAL, SPLIT, INVALID}) {
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLModel& va = m_varrays[i];
+ switch (i) {
+ case ORIGINAL: va.set_color({ 0.0f, 0.0f, 1.0f, 1.0f }); break;
+ case SPLIT: va.set_color({ 1.0f, 0.0f, 0.0f, 1.0f }); break;
+ case INVALID: va.set_color({ 1.0f, 1.0f, 0.0f, 1.0f }); break;
+ }
+ va.render();
+#else
+ GLIndexedVertexArray& va = m_varrays[i];
+ va.finalize_geometry(true);
+ if (va.has_VBOs()) {
+ switch (i) {
+ case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break;
+ case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break;
+ case INVALID : ::glColor3f(1.f, 1.f, 0.f); break;
+ }
+ va.render();
+ }
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ }
+ ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ shader->stop_using();
+ }
+
+ if (curr_shader != nullptr)
+ curr_shader->start_using();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+}
+#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+void TriangleSelectorGUI::update_paint_contour()
+{
+ m_paint_contour.reset();
+
+ GLModel::Geometry init_data;
+ const std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
+ init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
+ init_data.reserve_vertices(2 * contour_edges.size());
+ init_data.reserve_indices(2 * contour_edges.size());
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ init_data.color = ColorRGBA::WHITE();
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+//
+ // vertices + indices
+ unsigned int vertices_count = 0;
+ for (const Vec2i& edge : contour_edges) {
+ init_data.add_vertex(m_vertices[edge(0)].v);
+ init_data.add_vertex(m_vertices[edge(1)].v);
+ vertices_count += 2;
+ init_data.add_line(vertices_count - 2, vertices_count - 1);
+ }
+
+ if (!init_data.is_empty())
+ m_paint_contour.init_from(std::move(init_data));
+}
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+void TriangleSelectorGUI::render_paint_contour(const Transform3d& matrix)
+#else
+void TriangleSelectorGUI::render_paint_contour()
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+{
+ auto* curr_shader = wxGetApp().get_current_shader();
+ if (curr_shader != nullptr)
+ curr_shader->stop_using();
+
+ auto* contour_shader = wxGetApp().get_shader("mm_contour");
+ if (contour_shader != nullptr) {
+ contour_shader->start_using();
+
+ contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001);
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ contour_shader->set_uniform("view_model_matrix", camera.get_view_matrix() * matrix);
+ contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ m_paint_contour.render();
+ contour_shader->stop_using();
+ }
+
+ if (curr_shader != nullptr)
+ curr_shader->start_using();
+}
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
index 640199019..e877fa9f3 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
@@ -2,15 +2,18 @@
#include "GLGizmoRotate.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
-
-#include <GL/glew.h>
+#if ENABLE_WORLD_COORDINATE
+#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
+#endif // ENABLE_WORLD_COORDINATE
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
+
#include "libslic3r/PresetBundle.hpp"
-#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
+#include <GL/glew.h>
namespace Slic3r {
namespace GUI {
@@ -99,6 +102,9 @@ bool GLGizmoRotate::on_init()
void GLGizmoRotate::on_start_dragging()
{
+#if ENABLE_WORLD_COORDINATE
+ init_data_from_selection(m_parent.get_selection());
+#else
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_center = box.center();
m_radius = Offset + box.radius();
@@ -106,6 +112,7 @@ void GLGizmoRotate::on_start_dragging()
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
m_snap_fine_in_radius = m_radius;
m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth;
+#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoRotate::on_dragging(const UpdateData &data)
@@ -151,15 +158,21 @@ void GLGizmoRotate::on_render()
#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
const Selection& selection = m_parent.get_selection();
+#if !ENABLE_WORLD_COORDINATE
const BoundingBoxf3& box = selection.get_bounding_box();
+#endif // !ENABLE_WORLD_COORDINATE
if (m_hover_id != 0 && !m_grabbers.front().dragging) {
+#if ENABLE_WORLD_COORDINATE
+ init_data_from_selection(selection);
+#else
m_center = box.center();
m_radius = Offset + box.radius();
m_snap_coarse_in_radius = m_radius / 3.0f;
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
m_snap_fine_in_radius = m_radius;
m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth);
+#endif // ENABLE_WORLD_COORDINATE
}
const double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset);
@@ -223,10 +236,17 @@ void GLGizmoRotate::on_render()
render_angle();
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_WORLD_COORDINATE
+ render_grabber(m_bounding_box);
+#if !ENABLE_GIZMO_GRABBER_REFACTOR
+ render_grabber_extension(m_bounding_box, false);
+#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
+#else
render_grabber(box);
#if !ENABLE_GIZMO_GRABBER_REFACTOR
render_grabber_extension(box, false);
#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
+#endif // ENABLE_WORLD_COORDINATE
#if !ENABLE_GL_SHADERS_ATTRIBUTES
glsafe(::glPopMatrix());
@@ -246,17 +266,73 @@ void GLGizmoRotate::on_render_for_picking()
transform_to_local(selection);
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#if ENABLE_WORLD_COORDINATE
+ render_grabbers_for_picking(m_bounding_box);
+#if !ENABLE_GIZMO_GRABBER_REFACTOR
+ render_grabber_extension(m_bounding_box, true);
+#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
+#else
const BoundingBoxf3& box = selection.get_bounding_box();
render_grabbers_for_picking(box);
#if !ENABLE_GIZMO_GRABBER_REFACTOR
render_grabber_extension(box, true);
#endif // !ENABLE_GIZMO_GRABBER_REFACTOR
+#endif // ENABLE_WORLD_COORDINATE
#if !ENABLE_GL_SHADERS_ATTRIBUTES
glsafe(::glPopMatrix());
#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
}
+#if ENABLE_WORLD_COORDINATE
+void GLGizmoRotate::init_data_from_selection(const Selection& selection)
+{
+ ECoordinatesType coordinates_type;
+ if (selection.is_wipe_tower())
+ coordinates_type = ECoordinatesType::Local;
+ else
+ coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
+ if (coordinates_type == ECoordinatesType::World) {
+ m_bounding_box = selection.get_bounding_box();
+ m_center = m_bounding_box.center();
+ }
+ else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
+ const GLVolume& v = *selection.get_first_volume();
+ m_bounding_box = v.transformed_convex_hull_bounding_box(
+ v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix());
+ m_center = v.world_matrix() * m_bounding_box.center();
+ }
+ else {
+ m_bounding_box.reset();
+ const Selection::IndicesList& ids = selection.get_volume_idxs();
+ for (unsigned int id : ids) {
+ const GLVolume& v = *selection.get_volume(id);
+ m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
+ }
+ const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation();
+ m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix());
+ m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center();
+ }
+
+ m_radius = Offset + m_bounding_box.radius();
+ m_snap_coarse_in_radius = m_radius / 3.0f;
+ m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
+ m_snap_fine_in_radius = m_radius;
+ m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth;
+
+ if (coordinates_type == ECoordinatesType::World)
+ m_orient_matrix = Transform3d::Identity();
+ else if (coordinates_type == ECoordinatesType::Local && (selection.is_wipe_tower() || selection.is_single_volume_or_modifier())) {
+ const GLVolume& v = *selection.get_first_volume();
+ m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix();
+ }
+ else {
+ const GLVolume& v = *selection.get_first_volume();
+ m_orient_matrix = v.get_instance_transformation().get_rotation_matrix();
+ }
+}
+#endif // ENABLE_WORLD_COORDINATE
+
void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit)
{
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
@@ -317,10 +393,10 @@ void GLGizmoRotate::render_circle() const
#else
::glBegin(GL_LINE_LOOP);
for (unsigned int i = 0; i < ScaleStepsCount; ++i) {
- float angle = (float)i * ScaleStepRad;
- float x = ::cos(angle) * m_radius;
- float y = ::sin(angle) * m_radius;
- float z = 0.0f;
+ const float angle = float(i) * ScaleStepRad;
+ const float x = ::cos(angle) * m_radius;
+ const float y = ::sin(angle) * m_radius;
+ const float z = 0.0f;
::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
}
glsafe(::glEnd());
@@ -519,10 +595,10 @@ void GLGizmoRotate::render_angle() const
#else
::glBegin(GL_LINE_STRIP);
for (unsigned int i = 0; i <= AngleResolution; ++i) {
- float angle = (float)i * step_angle;
- float x = ::cos(angle) * ex_radius;
- float y = ::sin(angle) * ex_radius;
- float z = 0.0f;
+ const float angle = float(i) * step_angle;
+ const float x = ::cos(angle) * ex_radius;
+ const float y = ::sin(angle) * ex_radius;
+ const float z = 0.0f;
::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
}
glsafe(::glEnd());
@@ -661,12 +737,20 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const
{
case X:
{
- ret = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.5 * PI, 0.0)) * Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, -0.5 * PI));
+#if ENABLE_WORLD_COORDINATE
+ ret = Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ());
+#else
+ ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ());
+#endif // ENABLE_WORLD_COORDINATE
break;
}
case Y:
{
- ret = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, -0.5 * PI)) * Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, -0.5 * PI, 0.0));
+#if ENABLE_WORLD_COORDINATE
+ ret = Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY());
+#else
+ ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY());
+#endif // ENABLE_WORLD_COORDINATE
break;
}
default:
@@ -677,20 +761,28 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const
}
}
+#if ENABLE_WORLD_COORDINATE
+ return Geometry::translation_transform(m_center) * m_orient_matrix * ret;
+#else
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes())
- ret = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true) * ret;
+ ret = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true) * ret;
return Geometry::assemble_transform(m_center) * ret;
+#endif // ENABLE_WORLD_COORDINATE
}
#else
void GLGizmoRotate::transform_to_local(const Selection& selection) const
{
glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z()));
+#if ENABLE_WORLD_COORDINATE
+ glsafe(::glMultMatrixd(m_orient_matrix.data()));
+#else
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) {
- const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true);
+ const Transform3d orient_matrix = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
+#endif // ENABLE_WORLD_COORDINATE
switch (m_axis)
{
@@ -744,8 +836,12 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons
}
}
+#if ENABLE_WORLD_COORDINATE
+ m = m * m_orient_matrix.inverse();
+#else
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes())
- m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse();
+ m = m * selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true).inverse();
+#endif // ENABLE_WORLD_COORDINATE
m.translate(-m_center);
@@ -766,31 +862,51 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event)
{
if (mouse_event.Dragging() && m_dragging) {
// Apply new temporary rotations
- TransformationType transformation_type(
- TransformationType::World_Relative_Joint);
- if (mouse_event.AltDown()) transformation_type.set_independent();
+#if ENABLE_WORLD_COORDINATE
+ TransformationType transformation_type;
+ if (m_parent.get_selection().is_wipe_tower())
+ transformation_type = TransformationType::Instance_Relative_Joint;
+ else {
+ switch (wxGetApp().obj_manipul()->get_coordinates_type())
+ {
+ default:
+ case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; }
+ case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; }
+ case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; }
+ }
+ }
+#else
+ TransformationType transformation_type(TransformationType::World_Relative_Joint);
+#endif // ENABLE_WORLD_COORDINATE
+ if (mouse_event.AltDown())
+ transformation_type.set_independent();
m_parent.get_selection().rotate(get_rotation(), transformation_type);
}
return use_grabbers(mouse_event);
}
void GLGizmoRotate3D::data_changed() {
- const Selection &selection = m_parent.get_selection();
- bool is_wipe_tower = selection.is_wipe_tower();
- if (is_wipe_tower) {
- DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
- float wipe_tower_rotation_angle =
- dynamic_cast<const ConfigOptionFloat *>(
- config.option("wipe_tower_rotation_angle"))
- ->value;
+ if (m_parent.get_selection().is_wipe_tower()) {
+#if !ENABLE_WORLD_COORDINATE
+ const DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
+ const float wipe_tower_rotation_angle =
+ dynamic_cast<const ConfigOptionFloat*>(
+ config.option("wipe_tower_rotation_angle"))->value;
set_rotation(Vec3d(0., 0., (M_PI / 180.) * wipe_tower_rotation_angle));
+#endif // !ENABLE_WORLD_COORDINATE
m_gizmos[0].disable_grabber();
m_gizmos[1].disable_grabber();
- } else {
+ }
+ else {
+#if !ENABLE_WORLD_COORDINATE
set_rotation(Vec3d::Zero());
+#endif // !ENABLE_WORLD_COORDINATE
m_gizmos[0].enable_grabber();
m_gizmos[1].enable_grabber();
}
+#if ENABLE_WORLD_COORDINATE
+ set_rotation(Vec3d::Zero());
+#endif // ENABLE_WORLD_COORDINATE
}
bool GLGizmoRotate3D::on_init()
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
index f4594bc33..9b0417aaf 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
@@ -34,6 +34,10 @@ private:
float m_snap_coarse_out_radius{ 0.0f };
float m_snap_fine_in_radius{ 0.0f };
float m_snap_fine_out_radius{ 0.0f };
+#if ENABLE_WORLD_COORDINATE
+ BoundingBoxf3 m_bounding_box;
+ Transform3d m_orient_matrix{ Transform3d::Identity() };
+#endif // ENABLE_WORLD_COORDINATE
#if !ENABLE_GIZMO_GRABBER_REFACTOR
GLModel m_cone;
@@ -119,6 +123,10 @@ private:
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const;
+
+#if ENABLE_WORLD_COORDINATE
+ void init_data_from_selection(const Selection& selection);
+#endif // ENABLE_WORLD_COORDINATE
};
class GLGizmoRotate3D : public GLGizmoBase
@@ -129,7 +137,7 @@ public:
GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); }
- void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); }
+ void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation.x()); m_gizmos[Y].set_angle(rotation.y()); m_gizmos[Z].set_angle(rotation.z()); }
std::string get_tooltip() const override {
std::string tooltip = m_gizmos[X].get_tooltip();
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
index 26c9251b4..98c9ffeef 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
@@ -2,19 +2,22 @@
#include "GLGizmoScale.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
+#if ENABLE_WORLD_COORDINATE
+#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
+#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_GL_SHADERS_ATTRIBUTES
#include "slic3r/GUI/Plater.hpp"
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
#include <GL/glew.h>
-#include <wx/utils.h>
+#include <wx/utils.h>
namespace Slic3r {
namespace GUI {
-const float GLGizmoScale3D::Offset = 5.0f;
+const double GLGizmoScale3D::Offset = 5.0;
GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
@@ -38,16 +41,17 @@ GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filen
std::string GLGizmoScale3D::get_tooltip() const
{
+#if ENABLE_WORLD_COORDINATE
+ const Vec3d scale = 100.0 * m_scale;
+#else
const Selection& selection = m_parent.get_selection();
- bool single_instance = selection.is_single_full_instance();
- bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
-
- Vec3f scale = 100.0f * Vec3f::Ones();
- if (single_instance)
- scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>();
- else if (single_volume)
- scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast<float>();
+ Vec3d scale = 100.0 * Vec3d::Ones();
+ if (selection.is_single_full_instance())
+ scale = 100.0 * selection.get_first_volume()->get_instance_scaling_factor();
+ else if (selection.is_single_modifier() || selection.is_single_volume())
+ scale = 100.0 * selection.get_first_volume()->get_volume_scaling_factor();
+#endif // ENABLE_WORLD_COORDINATE
if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging)
return "X: " + format(scale.x(), 4) + "%";
@@ -72,12 +76,28 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event)
if (mouse_event.Dragging()) {
if (m_dragging) {
// Apply new temporary scale factors
+#if ENABLE_WORLD_COORDINATE
+ TransformationType transformation_type;
+ if (wxGetApp().obj_manipul()->is_local_coordinates())
+ transformation_type.set_local();
+ else if (wxGetApp().obj_manipul()->is_instance_coordinates())
+ transformation_type.set_instance();
+
+ transformation_type.set_relative();
+#else
TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
- if (mouse_event.AltDown()) transformation_type.set_independent();
+#endif // ENABLE_WORLD_COORDINATE
+
+ if (mouse_event.AltDown())
+ transformation_type.set_independent();
- Selection &selection = m_parent.get_selection();
- selection.scale(get_scale(), transformation_type);
+#if ENABLE_WORLD_COORDINATE
+ m_parent.get_selection().scale_and_translate(m_scale, m_offset, transformation_type);
+#else
+ Selection& selection = m_parent.get_selection();
+ selection.scale(m_scale, transformation_type);
if (mouse_event.CmdDown()) selection.translate(m_offset, true);
+#endif // ENABLE_WORLD_COORDINATE
}
}
return use_grabbers(mouse_event);
@@ -85,26 +105,29 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event)
void GLGizmoScale3D::data_changed()
{
- const Selection &selection = m_parent.get_selection();
- bool enable_scale_xyz = selection.is_single_full_instance() ||
- selection.is_single_volume() ||
- selection.is_single_modifier();
+#if ENABLE_WORLD_COORDINATE
+ set_scale(Vec3d::Ones());
+#else
+ const Selection& selection = m_parent.get_selection();
+ bool enable_scale_xyz = selection.is_single_full_instance() ||
+ selection.is_single_volume() ||
+ selection.is_single_modifier();
+
for (unsigned int i = 0; i < 6; ++i)
m_grabbers[i].enabled = enable_scale_xyz;
if (enable_scale_xyz) {
// all volumes in the selection belongs to the same instance, any of
// them contains the needed data, so we take the first
- const GLVolume *volume = selection.get_volume(*selection.get_volume_idxs().begin());
- if (selection.is_single_full_instance()) {
+ const GLVolume* volume = selection.get_first_volume();
+ if (selection.is_single_full_instance())
set_scale(volume->get_instance_scaling_factor());
- } else if (selection.is_single_volume() ||
- selection.is_single_modifier()) {
+ else if (selection.is_single_volume() || selection.is_single_modifier())
set_scale(volume->get_volume_scaling_factor());
- }
- } else {
- set_scale(Vec3d::Ones());
}
+ else
+ set_scale(Vec3d::Ones());
+#endif // ENABLE_WORLD_COORDINATE
}
bool GLGizmoScale3D::on_init()
@@ -113,15 +136,17 @@ bool GLGizmoScale3D::on_init()
m_grabbers.push_back(Grabber());
}
+#if !ENABLE_WORLD_COORDINATE
double half_pi = 0.5 * (double)PI;
// x axis
- m_grabbers[0].angles(1) = half_pi;
- m_grabbers[1].angles(1) = half_pi;
+ m_grabbers[0].angles.y() = half_pi;
+ m_grabbers[1].angles.y() = half_pi;
// y axis
- m_grabbers[2].angles(0) = half_pi;
- m_grabbers[3].angles(0) = half_pi;
+ m_grabbers[2].angles.x() = half_pi;
+ m_grabbers[3].angles.x() = half_pi;
+#endif // !ENABLE_WORLD_COORDINATE
m_shortcut_key = WXK_CONTROL_S;
@@ -142,9 +167,15 @@ bool GLGizmoScale3D::on_is_activable() const
void GLGizmoScale3D::on_start_dragging()
{
assert(m_hover_id != -1);
- m_starting.drag_position = m_grabbers[m_hover_id].center;
m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL);
- m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box();
+#if ENABLE_WORLD_COORDINATE
+ m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center;
+ m_starting.box = m_bounding_box;
+ m_starting.center = m_center;
+ m_starting.instance_center = m_instance_center;
+#else
+ m_starting.drag_position = m_grabbers[m_hover_id].center;
+ m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_bounding_box : m_parent.get_selection().get_bounding_box();
const Vec3d& center = m_starting.box.center();
m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z());
@@ -153,6 +184,7 @@ void GLGizmoScale3D::on_start_dragging()
m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z());
m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z());
m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z());
+#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoScale3D::on_stop_dragging() {
@@ -175,78 +207,156 @@ void GLGizmoScale3D::on_render()
{
const Selection& selection = m_parent.get_selection();
- bool single_instance = selection.is_single_full_instance();
- bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
-
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
- m_box.reset();
+ m_bounding_box.reset();
+#if ENABLE_WORLD_COORDINATE
+ m_grabbers_transform = Transform3d::Identity();
+ m_center = Vec3d::Zero();
+ m_instance_center = Vec3d::Zero();
+ if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) {
+#else
m_transform = Transform3d::Identity();
// Transforms grabbers' offsets to world refefence system
Transform3d offsets_transform = Transform3d::Identity();
m_offsets_transform = Transform3d::Identity();
Vec3d angles = Vec3d::Zero();
- if (single_instance) {
+ if (selection.is_single_full_instance()) {
+#endif // ENABLE_WORLD_COORDINATE
// calculate bounding box in instance local reference system
const Selection::IndicesList& idxs = selection.get_volume_idxs();
for (unsigned int idx : idxs) {
- const GLVolume* vol = selection.get_volume(idx);
- m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix()));
+ const GLVolume& v = *selection.get_volume(idx);
+ m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
}
+#if ENABLE_WORLD_COORDINATE
+ m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix());
+#endif // ENABLE_WORLD_COORDINATE
+
// gets transform from first selected volume
- const GLVolume* v = selection.get_volume(*idxs.begin());
- m_transform = v->get_instance_transformation().get_matrix();
+ const GLVolume& v = *selection.get_first_volume();
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d inst_trafo = v.get_instance_transformation().get_matrix_no_scaling_factor();
+ m_grabbers_transform = inst_trafo * Geometry::translation_transform(m_bounding_box.center());
+ m_center = inst_trafo * m_bounding_box.center();
+ m_instance_center = v.get_instance_offset();
+ }
+ else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) {
+#else
+ m_transform = v.get_instance_transformation().get_matrix();
+
// gets angles from first selected volume
- angles = v->get_instance_rotation();
+ angles = v.get_instance_rotation();
// consider rotation+mirror only components of the transform for offsets
- offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror());
+ offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror());
m_offsets_transform = offsets_transform;
}
- else if (single_volume) {
- const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
- m_box = v->bounding_box();
- m_transform = v->world_matrix();
+ else if (selection.is_single_modifier() || selection.is_single_volume()) {
+#endif // ENABLE_WORLD_COORDINATE
+ const GLVolume& v = *selection.get_first_volume();
+#if ENABLE_WORLD_COORDINATE
+ m_bounding_box.merge(v.transformed_convex_hull_bounding_box(
+ v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_matrix_no_offset()));
+ Geometry::Transformation trafo(v.get_instance_transformation().get_rotation_matrix());
+ trafo.set_offset(v.world_matrix().translation());
+ m_grabbers_transform = trafo.get_matrix();
+ m_center = v.world_matrix() * m_bounding_box.center();
+ m_instance_center = m_center;
+ }
+ else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) {
+ const GLVolume& v = *selection.get_first_volume();
+ m_bounding_box.merge(v.transformed_convex_hull_bounding_box(
+ v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()));
+ Geometry::Transformation trafo(v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix());
+ trafo.set_offset(v.world_matrix().translation());
+ m_grabbers_transform = trafo.get_matrix();
+ m_center = v.world_matrix() * m_bounding_box.center();
+ m_instance_center = m_center;
+ }
+ else {
+ m_bounding_box = selection.get_bounding_box();
+ m_grabbers_transform = Geometry::assemble_transform(m_bounding_box.center());
+ m_center = m_bounding_box.center();
+ m_instance_center = selection.is_single_full_instance() ? selection.get_first_volume()->get_instance_offset() : m_center;
+ }
+#else
+ m_bounding_box = v.bounding_box();
+ m_transform = v.world_matrix();
angles = Geometry::extract_euler_angles(m_transform);
// consider rotation+mirror only components of the transform for offsets
- offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror());
- m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v->get_volume_rotation(), Vec3d::Ones(), v->get_volume_mirror());
+ offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror());
+ m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation(), Vec3d::Ones(), v.get_volume_mirror());
}
else
- m_box = selection.get_bounding_box();
+ m_bounding_box = selection.get_bounding_box();
- const Vec3d& center = m_box.center();
+ const Vec3d& center = m_bounding_box.center();
const Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0);
const Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0);
const Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset);
const bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL));
+#endif // ENABLE_WORLD_COORDINATE
+#if ENABLE_WORLD_COORDINATE
// x axis
- m_grabbers[0].center = m_transform * Vec3d(m_box.min.x(), center.y(), center.z()) - offset_x;
+ const Vec3d box_half_size = 0.5 * m_bounding_box.size();
+ bool use_constrain = wxGetKeyState(WXK_CONTROL) && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier());
+
+ m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 };
+ m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0];
+ m_grabbers[1].center = { box_half_size.x() + Offset, 0.0, 0.0 };
+ m_grabbers[1].color = (use_constrain && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0];
+
+ // y axis
+ m_grabbers[2].center = { 0.0, -(box_half_size.y() + Offset), 0.0 };
+ m_grabbers[2].color = (use_constrain && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1];
+ m_grabbers[3].center = { 0.0, box_half_size.y() + Offset, 0.0 };
+ m_grabbers[3].color = (use_constrain && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1];
+
+ // z axis
+ m_grabbers[4].center = { 0.0, 0.0, -(box_half_size.z() + Offset) };
+ m_grabbers[4].color = (use_constrain && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2];
+ m_grabbers[5].center = { 0.0, 0.0, box_half_size.z() + Offset };
+ m_grabbers[5].color = (use_constrain && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2];
+
+ // uniform
+ m_grabbers[6].center = { -(box_half_size.x() + Offset), -(box_half_size.y() + Offset), 0.0 };
+ m_grabbers[6].color = (use_constrain && m_hover_id == 8) ? CONSTRAINED_COLOR : m_highlight_color;
+ m_grabbers[7].center = { box_half_size.x() + Offset, -(box_half_size.y() + Offset), 0.0 };
+ m_grabbers[7].color = (use_constrain && m_hover_id == 9) ? CONSTRAINED_COLOR : m_highlight_color;
+ m_grabbers[8].center = { box_half_size.x() + Offset, box_half_size.y() + Offset, 0.0 };
+ m_grabbers[8].color = (use_constrain && m_hover_id == 6) ? CONSTRAINED_COLOR : m_highlight_color;
+ m_grabbers[9].center = { -(box_half_size.x() + Offset), box_half_size.y() + Offset, 0.0 };
+ m_grabbers[9].color = (use_constrain && m_hover_id == 7) ? CONSTRAINED_COLOR : m_highlight_color;
+#else
+ // x axis
+ m_grabbers[0].center = m_transform * Vec3d(m_bounding_box.min.x(), center.y(), center.z()) - offset_x;
m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0];
- m_grabbers[1].center = m_transform * Vec3d(m_box.max.x(), center.y(), center.z()) + offset_x;
+ m_grabbers[1].center = m_transform * Vec3d(m_bounding_box.max.x(), center.y(), center.z()) + offset_x;
m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0];
// y axis
- m_grabbers[2].center = m_transform * Vec3d(center.x(), m_box.min.y(), center.z()) - offset_y;
+ m_grabbers[2].center = m_transform * Vec3d(center.x(), m_bounding_box.min.y(), center.z()) - offset_y;
m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1];
- m_grabbers[3].center = m_transform * Vec3d(center.x(), m_box.max.y(), center.z()) + offset_y;
+ m_grabbers[3].center = m_transform * Vec3d(center.x(), m_bounding_box.max.y(), center.z()) + offset_y;
m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1];
// z axis
- m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_box.min.z()) - offset_z;
+ m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_bounding_box.min.z()) - offset_z;
m_grabbers[4].color = (ctrl_down && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2];
- m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_box.max.z()) + offset_z;
+ m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_bounding_box.max.z()) + offset_z;
m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2];
// uniform
- m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y;
- m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y;
- m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y;
- m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y;
+ m_grabbers[6].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.min.y(), center.z()) - offset_x - offset_y;
+ m_grabbers[7].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.min.y(), center.z()) + offset_x - offset_y;
+ m_grabbers[8].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.max.y(), center.z()) + offset_x + offset_y;
+ m_grabbers[9].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.max.y(), center.z()) - offset_x + offset_y;
+
for (int i = 6; i < 10; ++i) {
m_grabbers[i].color = m_highlight_color;
}
@@ -255,12 +365,25 @@ void GLGizmoScale3D::on_render()
for (int i = 0; i < 10; ++i) {
m_grabbers[i].angles = angles;
}
+#endif // ENABLE_WORLD_COORDINATE
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
+#if ENABLE_WORLD_COORDINATE
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d base_matrix = local_transform(selection);
+ for (int i = 0; i < 10; ++i) {
+ m_grabbers[i].matrix = base_matrix;
+ }
+#else
+ glsafe(::glPushMatrix());
+ transform_to_local(selection);
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ const float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0);
+#else
const BoundingBoxf3& selection_box = selection.get_bounding_box();
-
const float grabber_mean_size = (float)((selection_box.size().x() + selection_box.size().y() + selection_box.size().z()) / 3.0);
+#endif // ENABLE_WORLD_COORDINATE
if (m_hover_id == -1) {
#if ENABLE_LEGACY_OPENGL_REMOVAL
@@ -270,7 +393,11 @@ void GLGizmoScale3D::on_render()
shader->start_using();
#if ENABLE_GL_SHADERS_ATTRIBUTES
const Camera& camera = wxGetApp().plater()->get_camera();
+#if ENABLE_WORLD_COORDINATE
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix);
+#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
if (m_grabbers[0].enabled && m_grabbers[1].enabled)
@@ -317,7 +444,11 @@ void GLGizmoScale3D::on_render()
shader->start_using();
#if ENABLE_GL_SHADERS_ATTRIBUTES
const Camera& camera = wxGetApp().plater()->get_camera();
+#if ENABLE_WORLD_COORDINATE
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix);
+#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
render_grabbers_connection(0, 1, m_grabbers[0].color);
@@ -328,7 +459,7 @@ void GLGizmoScale3D::on_render()
shader = wxGetApp().get_shader("gouraud_light");
#else
// draw connection
- glsafe(::glColor4fv(m_grabbers[0].color.data()));
+ glsafe(::glColor4fv(AXES_COLOR[0].data()));
render_grabbers_connection(0, 1);
// draw grabbers
@@ -350,7 +481,11 @@ void GLGizmoScale3D::on_render()
shader->start_using();
#if ENABLE_GL_SHADERS_ATTRIBUTES
const Camera& camera = wxGetApp().plater()->get_camera();
+#if ENABLE_WORLD_COORDINATE
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix);
+#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
render_grabbers_connection(2, 3, m_grabbers[2].color);
@@ -361,7 +496,7 @@ void GLGizmoScale3D::on_render()
shader = wxGetApp().get_shader("gouraud_light");
#else
// draw connection
- glsafe(::glColor4fv(m_grabbers[2].color.data()));
+ glsafe(::glColor4fv(AXES_COLOR[1].data()));
render_grabbers_connection(2, 3);
// draw grabbers
@@ -383,7 +518,11 @@ void GLGizmoScale3D::on_render()
shader->start_using();
#if ENABLE_GL_SHADERS_ATTRIBUTES
const Camera& camera = wxGetApp().plater()->get_camera();
+#if ENABLE_WORLD_COORDINATE
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix);
+#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
render_grabbers_connection(4, 5, m_grabbers[4].color);
@@ -394,7 +533,7 @@ void GLGizmoScale3D::on_render()
shader = wxGetApp().get_shader("gouraud_light");
#else
// draw connection
- glsafe(::glColor4fv(m_grabbers[4].color.data()));
+ glsafe(::glColor4fv(AXES_COLOR[2].data()));
render_grabbers_connection(4, 5);
// draw grabbers
@@ -416,7 +555,11 @@ void GLGizmoScale3D::on_render()
shader->start_using();
#if ENABLE_GL_SHADERS_ATTRIBUTES
const Camera& camera = wxGetApp().plater()->get_camera();
+#if ENABLE_WORLD_COORDINATE
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix);
+#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
render_grabbers_connection(6, 7, m_drag_color);
@@ -448,12 +591,34 @@ void GLGizmoScale3D::on_render()
shader->stop_using();
}
}
+
+#if ENABLE_WORLD_COORDINATE
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoScale3D::on_render_for_picking()
{
glsafe(::glDisable(GL_DEPTH_TEST));
+#if ENABLE_WORLD_COORDINATE
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d base_matrix = local_transform(m_parent.get_selection());
+ for (int i = 0; i < 10; ++i) {
+ m_grabbers[i].matrix = base_matrix;
+ }
+#else
+ glsafe(::glPushMatrix());
+ transform_to_local(m_parent.get_selection());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ render_grabbers_for_picking(m_bounding_box);
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#else
render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
+#endif // ENABLE_WORLD_COORDINATE
}
#if ENABLE_LEGACY_OPENGL_REMOVAL
@@ -509,13 +674,67 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int
}
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_WORLD_COORDINATE
void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
{
double ratio = calc_ratio(data);
if (ratio > 0.0) {
+ Vec3d curr_scale = m_scale;
+ Vec3d starting_scale = m_starting.scale;
+ const Selection& selection = m_parent.get_selection();
+ const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
+
+ curr_scale(axis) = starting_scale(axis) * ratio;
+ m_scale = curr_scale;
+
+ if (m_starting.ctrl_down && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) {
+ double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis);
+
+ if (m_hover_id == 2 * axis)
+ local_offset *= -1.0;
+
+ Vec3d center_offset = m_starting.instance_center - m_starting.center; // world coordinates (== Vec3d::Zero() for single volume selection)
+ if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local)
+ // from world coordinates to instance coordinates
+ center_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix().inverse() * center_offset;
+
+ local_offset += (ratio - 1.0) * center_offset(axis);
+
+ switch (axis)
+ {
+ case X: { m_offset = local_offset * Vec3d::UnitX(); break; }
+ case Y: { m_offset = local_offset * Vec3d::UnitY(); break; }
+ case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; }
+ default: { m_offset = Vec3d::Zero(); break; }
+ }
+
+ if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local)
+ // from instance coordinates to world coordinates
+ m_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix() * m_offset;
+
+ if (selection.is_single_volume_or_modifier()) {
+ if (coordinates_type == ECoordinatesType::Instance)
+ m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * m_offset;
+ else if (coordinates_type == ECoordinatesType::Local) {
+ m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() *
+ selection.get_first_volume()->get_volume_transformation().get_rotation_matrix() * m_offset;
+ }
+ }
+ }
+ else
+ m_offset = Vec3d::Zero();
+ }
+}
+#else
+void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
+{
+ const double ratio = calc_ratio(data);
+ if (ratio > 0.0) {
m_scale(axis) = m_starting.scale(axis) * ratio;
+
if (m_starting.ctrl_down) {
double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis);
+
if (m_hover_id == 2 * axis)
local_offset *= -1.0;
@@ -534,36 +753,86 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
m_offset = Vec3d::Zero();
}
}
+#endif // ENABLE_WORLD_COORDINATE
+
+#if ENABLE_WORLD_COORDINATE
+void GLGizmoScale3D::do_scale_uniform(const UpdateData & data)
+{
+ const double ratio = calc_ratio(data);
+ if (ratio > 0.0) {
+ m_scale = m_starting.scale * ratio;
+ const Selection& selection = m_parent.get_selection();
+ const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
+ if (m_starting.ctrl_down && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) {
+ m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size();
+
+ if (m_hover_id == 6 || m_hover_id == 9)
+ m_offset.x() *= -1.0;
+ if (m_hover_id == 6 || m_hover_id == 7)
+ m_offset.y() *= -1.0;
+
+ Vec3d center_offset = m_starting.instance_center - m_starting.center; // world coordinates (== Vec3d::Zero() for single volume selection)
+
+ if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local)
+ // from world coordinates to instance coordinates
+ center_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix().inverse() * center_offset;
+
+ m_offset += (ratio - 1.0) * center_offset;
+
+ if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local)
+ // from instance coordinates to world coordinates
+ m_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix() * m_offset;
+
+ if (selection.is_single_volume_or_modifier()) {
+ if (coordinates_type == ECoordinatesType::Instance)
+ m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * m_offset;
+ else if (coordinates_type == ECoordinatesType::Local) {
+ m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() *
+ selection.get_first_volume()->get_volume_transformation().get_rotation_matrix() * m_offset;
+ }
+ }
+ }
+ else
+ m_offset = Vec3d::Zero();
+ }
+}
+#else
void GLGizmoScale3D::do_scale_uniform(const UpdateData& data)
{
- double ratio = calc_ratio(data);
+ const double ratio = calc_ratio(data);
if (ratio > 0.0) {
m_scale = m_starting.scale * ratio;
m_offset = Vec3d::Zero();
}
}
+#endif // ENABLE_WORLD_COORDINATE
double GLGizmoScale3D::calc_ratio(const UpdateData& data) const
{
double ratio = 0.0;
- Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.box.center();
+#if ENABLE_WORLD_COORDINATE
+ const Vec3d starting_vec = m_starting.drag_position - m_starting.center;
+#else
+ const Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.box.center();
+ const Vec3d starting_vec = m_starting.drag_position - pivot;
+#endif // ENABLE_WORLD_COORDINATE
+
+ const double len_starting_vec = starting_vec.norm();
- Vec3d starting_vec = m_starting.drag_position - pivot;
- double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 0.0) {
- Vec3d mouse_dir = data.mouse_ray.unit_vector();
+ const Vec3d mouse_dir = data.mouse_ray.unit_vector();
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
- Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
+ const Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
// vector from the starting position to the found intersection
- Vec3d inters_vec = inters - m_starting.drag_position;
+ const Vec3d inters_vec = inters - m_starting.drag_position;
// finds projection of the vector along the staring direction
- double proj = inters_vec.dot(starting_vec.normalized());
+ const double proj = inters_vec.dot(starting_vec.normalized());
ratio = (len_starting_vec + proj) / len_starting_vec;
}
@@ -574,5 +843,34 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const
return ratio;
}
+#if ENABLE_WORLD_COORDINATE
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+Transform3d GLGizmoScale3D::local_transform(const Selection& selection) const
+{
+ Transform3d ret = Geometry::assemble_transform(m_center);
+ if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
+ const GLVolume& v = *selection.get_first_volume();
+ Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix();
+ if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
+ orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix();
+ ret = ret * orient_matrix;
+ }
+ return ret;
+}
+#else
+void GLGizmoScale3D::transform_to_local(const Selection& selection) const
+{
+ glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z()));
+
+ if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
+ Transform3d orient_matrix = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true);
+ if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
+ orient_matrix = orient_matrix * selection.get_first_volume()->get_volume_transformation().get_matrix(true, false, true, true);
+ glsafe(::glMultMatrixd(orient_matrix.data()));
+ }
+}
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_WORLD_COORDINATE
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
index f4efe052a..fb4ff09b6 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
@@ -3,31 +3,46 @@
#include "GLGizmoBase.hpp"
+#if !ENABLE_WORLD_COORDINATE
#include "libslic3r/BoundingBox.hpp"
-
+#endif // !ENABLE_WORLD_COORDINATE
namespace Slic3r {
namespace GUI {
+#if ENABLE_WORLD_COORDINATE
+class Selection;
+#endif // ENABLE_WORLD_COORDINATE
+
class GLGizmoScale3D : public GLGizmoBase
{
- static const float Offset;
+ static const double Offset;
struct StartingData
{
- Vec3d scale;
- Vec3d drag_position;
+ bool ctrl_down{ false };
+ Vec3d scale{ Vec3d::Ones() };
+ Vec3d drag_position{ Vec3d::Zero() };
+#if ENABLE_WORLD_COORDINATE
+ Vec3d center{ Vec3d::Zero() };
+ Vec3d instance_center{ Vec3d::Zero() };
+#endif // ENABLE_WORLD_COORDINATE
BoundingBoxf3 box;
- Vec3d pivots[6];
- bool ctrl_down;
-
- StartingData() : scale(Vec3d::Ones()), drag_position(Vec3d::Zero()), ctrl_down(false) { for (int i = 0; i < 5; ++i) { pivots[i] = Vec3d::Zero(); } }
+#if !ENABLE_WORLD_COORDINATE
+ std::array<Vec3d, 6> pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() };
+#endif // !ENABLE_WORLD_COORDINATE
};
- mutable BoundingBoxf3 m_box;
- mutable Transform3d m_transform;
+ BoundingBoxf3 m_bounding_box;
+#if ENABLE_WORLD_COORDINATE
+ Transform3d m_grabbers_transform;
+ Vec3d m_center{ Vec3d::Zero() };
+ Vec3d m_instance_center{ Vec3d::Zero() };
+#else
+ Transform3d m_transform;
// Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes)
- mutable Transform3d m_offsets_transform;
+ Transform3d m_offsets_transform;
+#endif // ENABLE_WORLD_COORDINATE
Vec3d m_scale{ Vec3d::Ones() };
Vec3d m_offset{ Vec3d::Zero() };
double m_snap_step{ 0.05 };
@@ -54,7 +69,11 @@ public:
void set_snap_step(double step) { m_snap_step = step; }
const Vec3d& get_scale() const { return m_scale; }
+#if ENABLE_WORLD_COORDINATE
+ void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; m_offset = Vec3d::Zero(); }
+#else
void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; }
+#endif // ENABLE_WORLD_COORDINATE
std::string get_tooltip() const override;
@@ -87,6 +106,13 @@ private:
void do_scale_uniform(const UpdateData& data);
double calc_ratio(const UpdateData& data) const;
+#if ENABLE_WORLD_COORDINATE
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ Transform3d local_transform(const Selection& selection) const;
+#else
+ void transform_to_local(const Selection& selection) const;
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_WORLD_COORDINATE
};
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
index 7c61673b4..987fd325f 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
@@ -1,1370 +1,1374 @@
-// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
-#include "GLGizmoSlaSupports.hpp"
-#include "slic3r/GUI/GLCanvas3D.hpp"
-#include "slic3r/GUI/Camera.hpp"
-#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
-#include "slic3r/GUI/MainFrame.hpp"
-#include "slic3r/Utils/UndoRedo.hpp"
-
-#include <GL/glew.h>
-
-#include <wx/msgdlg.h>
-#include <wx/settings.h>
-#include <wx/stattext.h>
-
-#include "slic3r/GUI/GUI_App.hpp"
-#include "slic3r/GUI/GUI.hpp"
-#include "slic3r/GUI/GUI_ObjectSettings.hpp"
-#include "slic3r/GUI/GUI_ObjectList.hpp"
-#include "slic3r/GUI/Plater.hpp"
-#include "slic3r/GUI/NotificationManager.hpp"
-#include "slic3r/GUI/MsgDialog.hpp"
-#include "libslic3r/PresetBundle.hpp"
-#include "libslic3r/SLAPrint.hpp"
-
-
-namespace Slic3r {
-namespace GUI {
-
-GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
- : GLGizmoBase(parent, icon_filename, sprite_id)
-{}
-
-bool GLGizmoSlaSupports::on_init()
-{
- m_shortcut_key = WXK_CONTROL_L;
-
- m_desc["head_diameter"] = _L("Head diameter") + ": ";
- m_desc["lock_supports"] = _L("Lock supports under new islands");
- m_desc["remove_selected"] = _L("Remove selected points");
- m_desc["remove_all"] = _L("Remove all points");
- m_desc["apply_changes"] = _L("Apply changes");
- m_desc["discard_changes"] = _L("Discard changes");
- m_desc["minimal_distance"] = _L("Minimal points distance") + ": ";
- m_desc["points_density"] = _L("Support points density") + ": ";
- m_desc["auto_generate"] = _L("Auto-generate points");
- m_desc["manual_editing"] = _L("Manual editing");
- m_desc["clipping_of_view"] = _L("Clipping of view")+ ": ";
- m_desc["reset_direction"] = _L("Reset direction");
-
- m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24));
- m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.));
- m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.));
-
- return true;
-}
-
-void GLGizmoSlaSupports::data_changed()
-{
- if (! m_c->selection_info())
- return;
-
- ModelObject* mo = m_c->selection_info()->model_object();
-
- if (m_state == On && mo && mo->id() != m_old_mo_id) {
- disable_editing_mode();
- reload_cache();
- m_old_mo_id = mo->id();
- m_c->instances_hider()->show_supports(true);
- }
-
- // If we triggered autogeneration before, check backend and fetch results if they are there
- if (mo) {
- if (mo->sla_points_status == sla::PointsStatus::Generating)
- get_data_from_backend();
- }
-}
-
-
-
-void GLGizmoSlaSupports::on_render()
-{
- if (!m_cone.is_initialized())
- m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0));
- if (!m_sphere.is_initialized())
- m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0));
- if (!m_cylinder.is_initialized())
- m_cylinder.init_from(its_make_cylinder(1.0, 1.0, double(PI) / 12.0));
-
- ModelObject* mo = m_c->selection_info()->model_object();
- const Selection& selection = m_parent.get_selection();
-
- // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off
- if (m_state == On
- && (mo != selection.get_model()->objects[selection.get_object_idx()]
- || m_c->selection_info()->get_active_instance() != selection.get_instance_idx())) {
- m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
- return;
- }
-
- glsafe(::glEnable(GL_BLEND));
- glsafe(::glEnable(GL_DEPTH_TEST));
-
- if (selection.is_from_single_instance())
- render_points(selection, false);
-
- m_selection_rectangle.render(m_parent);
- m_c->object_clipper()->render_cut();
- m_c->supports_clipper()->render_cut();
-
- glsafe(::glDisable(GL_BLEND));
-}
-
-
-void GLGizmoSlaSupports::on_render_for_picking()
-{
- const Selection& selection = m_parent.get_selection();
- //glsafe(::glEnable(GL_DEPTH_TEST));
- render_points(selection, true);
-}
-
-void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
-{
- const size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size();
-
- const bool has_points = (cache_size != 0);
- const bool has_holes = (! m_c->hollowed_mesh()->get_hollowed_mesh()
- && ! m_c->selection_info()->model_object()->sla_drain_holes.empty());
-
- if (! has_points && ! has_holes)
- return;
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light");
- if (shader == nullptr)
- return;
-
- shader->start_using();
- ScopeGuard guard([shader]() { shader->stop_using(); });
-#else
- GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light");
- if (shader != nullptr)
- shader->start_using();
- ScopeGuard guard([shader]() {
- if (shader != nullptr)
- shader->stop_using();
- });
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
- Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix());
- const Transform3d& instance_scaling_matrix_inverse = transformation.get_matrix(true, true, false, true).inverse();
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * transformation.get_matrix();
- const Camera& camera = wxGetApp().plater()->get_camera();
- const Transform3d& view_matrix = camera.get_view_matrix();
- const Transform3d& projection_matrix = camera.get_projection_matrix();
-
- shader->set_uniform("projection_matrix", projection_matrix);
-#else
- const Transform3d& instance_matrix = transformation.get_matrix();
- const float z_shift = m_c->selection_info()->get_sla_shift();
- glsafe(::glPushMatrix());
- glsafe(::glTranslated(0.0, 0.0, z_shift));
- glsafe(::glMultMatrixd(instance_matrix.data()));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
- ColorRGBA render_color;
- for (size_t i = 0; i < cache_size; ++i) {
- const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i];
- const bool point_selected = m_editing_mode ? m_editing_cache[i].selected : false;
-
- if (is_mesh_point_clipped(support_point.pos.cast<double>()))
- continue;
-
- // First decide about the color of the point.
- if (picking)
- render_color = picking_color_component(i);
- else {
- if (size_t(m_hover_id) == i && m_editing_mode) // ignore hover state unless editing mode is active
- render_color = { 0.f, 1.f, 1.f, 1.f };
- else { // neigher hover nor picking
- bool supports_new_island = m_lock_unique_islands && support_point.is_new_island;
- if (m_editing_mode) {
- if (point_selected)
- render_color = { 1.f, 0.3f, 0.3f, 1.f};
- else
- if (supports_new_island)
- render_color = { 0.3f, 0.3f, 1.f, 1.f };
- else
- render_color = { 0.7f, 0.7f, 0.7f, 1.f };
- }
- else
- render_color = { 0.5f, 0.5f, 0.5f, 1.f };
- }
- }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_cone.set_color(render_color);
- m_sphere.set_color(render_color);
- if (!picking)
-#else
- m_cone.set_color(-1, render_color);
- m_sphere.set_color(-1, render_color);
- if (shader && !picking)
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- shader->set_uniform("emission_factor", 0.5f);
-
- // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Transform3d support_matrix = Geometry::assemble_transform(support_point.pos.cast<double>()) * instance_scaling_matrix_inverse;
-#else
- glsafe(::glPushMatrix());
- glsafe(::glTranslatef(support_point.pos.x(), support_point.pos.y(), support_point.pos.z()));
- glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data()));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
- if (vol->is_left_handed())
- glFrontFace(GL_CW);
-
- // Matrices set, we can render the point mark now.
- // If in editing mode, we'll also render a cone pointing to the sphere.
- if (m_editing_mode) {
- // in case the normal is not yet cached, find and cache it
- if (m_editing_cache[i].normal == Vec3f::Zero())
- m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
-
- Eigen::Quaterniond q;
- q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast<double>());
- const Eigen::AngleAxisd aa(q);
- const double cone_radius = 0.25; // mm
- const double cone_height = 0.75;
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix * Transform3d(aa.toRotationMatrix()) *
- Geometry::assemble_transform((cone_height + support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ(),
- Vec3d(PI, 0.0, 0.0), Vec3d(cone_radius, cone_radius, cone_height));
-
- shader->set_uniform("view_model_matrix", view_model_matrix);
- shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
-#else
- glsafe(::glPushMatrix());
- glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z()));
- glsafe(::glTranslatef(0.f, 0.f, cone_height + support_point.head_front_radius * RenderPointScale));
- glsafe(::glRotated(180., 1., 0., 0.));
- glsafe(::glScaled(cone_radius, cone_radius, cone_height));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- m_cone.render();
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
- }
-
- const double radius = (double)support_point.head_front_radius * RenderPointScale;
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix *
- Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), radius * Vec3d::Ones());
-
- shader->set_uniform("view_model_matrix", view_model_matrix);
- shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
-#else
- glsafe(::glPushMatrix());
- glsafe(::glScaled(radius, radius, radius));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- m_sphere.render();
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-
- if (vol->is_left_handed())
- glFrontFace(GL_CCW);
-
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
- }
-
- // Now render the drain holes:
- if (has_holes && ! picking) {
- render_color = { 0.7f, 0.7f, 0.7f, 0.7f };
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_cylinder.set_color(render_color);
-#else
- m_cylinder.set_color(-1, render_color);
- if (shader != nullptr)
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- shader->set_uniform("emission_factor", 0.5f);
- for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) {
- if (is_mesh_point_clipped(drain_hole.pos.cast<double>()))
- continue;
-
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast<double>()) * instance_scaling_matrix_inverse;
-#else
- // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
- glsafe(::glPushMatrix());
- glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z()));
- glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data()));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
- if (vol->is_left_handed())
- glFrontFace(GL_CW);
-
- // Matrices set, we can render the point mark now.
- Eigen::Quaterniond q;
- q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast<double>());
- const Eigen::AngleAxisd aa(q);
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) *
- Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength));
-
- shader->set_uniform("view_model_matrix", view_model_matrix);
- shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
-#else
- glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z()));
- glsafe(::glTranslated(0., 0., -drain_hole.height));
- glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength));
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- m_cylinder.render();
-
- if (vol->is_left_handed())
- glFrontFace(GL_CCW);
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
- }
- }
-
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-}
-
-
-
-bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
-{
- if (m_c->object_clipper()->get_position() == 0.)
- return false;
-
- auto sel_info = m_c->selection_info();
- int active_inst = m_c->selection_info()->get_active_instance();
- const ModelInstance* mi = sel_info->model_object()->instances[active_inst];
- const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix();
-
- Vec3d transformed_point = trafo * point;
- transformed_point(2) += sel_info->get_sla_shift();
- return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
-}
-
-
-
-// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
-// Return false if no intersection was found, true otherwise.
-bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
-{
- if (! m_c->raycaster()->raycaster())
- return false;
-
- const Camera& camera = wxGetApp().plater()->get_camera();
- const Selection& selection = m_parent.get_selection();
- const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
- Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation();
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
-
- double clp_dist = m_c->object_clipper()->get_position();
- const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
-
- // The raycaster query
- Vec3f hit;
- Vec3f normal;
- if (m_c->raycaster()->raycaster()->unproject_on_mesh(
- mouse_pos,
- trafo.get_matrix(),
- camera,
- hit,
- normal,
- clp_dist != 0. ? clp : nullptr))
- {
- // Check whether the hit is in a hole
- bool in_hole = false;
- // In case the hollowed and drilled mesh is available, we can allow
- // placing points in holes, because they should never end up
- // on surface that's been drilled away.
- if (! m_c->hollowed_mesh()->get_hollowed_mesh()) {
- sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
- for (const sla::DrainHole& hole : drain_holes) {
- if (hole.is_inside(hit)) {
- in_hole = true;
- break;
- }
- }
- }
- if (! in_hole) {
- // Return both the point and the facet normal.
- pos_and_normal = std::make_pair(hit, normal);
- return true;
- }
- }
-
- return false;
-}
-
-// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
-// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
-// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
-// concludes that the event was not intended for it, it should return false.
-bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
-{
- ModelObject* mo = m_c->selection_info()->model_object();
- int active_inst = m_c->selection_info()->get_active_instance();
-
- if (m_editing_mode) {
-
- // left down with shift - show the selection rectangle:
- if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) {
- if (m_hover_id == -1) {
- if (shift_down || alt_down) {
- m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect);
- }
- }
- else {
- if (m_editing_cache[m_hover_id].selected)
- unselect_point(m_hover_id);
- else {
- if (!alt_down)
- select_point(m_hover_id);
- }
- }
-
- return true;
- }
-
- // left down without selection rectangle - place point on the mesh:
- if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) {
- // If any point is in hover state, this should initiate its move - return control back to GLCanvas:
- if (m_hover_id != -1)
- return false;
-
- // If there is some selection, don't add new point and deselect everything instead.
- if (m_selection_empty) {
- std::pair<Vec3f, Vec3f> pos_and_normal;
- if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point"));
- m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second);
- m_parent.set_as_dirty();
- m_wait_for_up_event = true;
- }
- else
- return false;
- }
- else
- select_point(NoPoints);
-
- return true;
- }
-
- // left up with selection rectangle - select points inside the rectangle:
- if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) {
- // Is this a selection or deselection rectangle?
- GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
-
- // First collect positions of all the points in world coordinates.
- Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation();
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
- std::vector<Vec3d> points;
- for (unsigned int i=0; i<m_editing_cache.size(); ++i)
- points.push_back(trafo.get_matrix() * m_editing_cache[i].support_point.pos.cast<double>());
-
- // Now ask the rectangle which of the points are inside.
- std::vector<Vec3f> points_inside;
- std::vector<unsigned int> points_idxs = m_selection_rectangle.stop_dragging(m_parent, points);
- for (size_t idx : points_idxs)
- points_inside.push_back(points[idx].cast<float>());
-
- // Only select/deselect points that are actually visible. We want to check not only
- // the point itself, but also the center of base of its cone, so the points don't hide
- // under every miniature irregularity on the model. Remember the actual number and
- // append the cone bases.
- size_t orig_pts_num = points_inside.size();
- for (size_t idx : points_idxs)
- points_inside.emplace_back((trafo.get_matrix().cast<float>() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast<float>());
-
- for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs(
- trafo, wxGetApp().plater()->get_camera(), points_inside,
- m_c->object_clipper()->get_clipping_plane()))
- {
- if (idx >= orig_pts_num) // this is a cone-base, get index of point it belongs to
- idx -= orig_pts_num;
- if (rectangle_status == GLSelectionRectangle::EState::Deselect)
- unselect_point(points_idxs[idx]);
- else
- select_point(points_idxs[idx]);
- }
- return true;
- }
-
- // left up with no selection rectangle
- if (action == SLAGizmoEventType::LeftUp) {
- if (m_wait_for_up_event) {
- m_wait_for_up_event = false;
- return true;
- }
- }
-
- // dragging the selection rectangle:
- if (action == SLAGizmoEventType::Dragging) {
- if (m_wait_for_up_event)
- return true; // point has been placed and the button not released yet
- // this prevents GLCanvas from starting scene rotation
-
- if (m_selection_rectangle.is_dragging()) {
- m_selection_rectangle.dragging(mouse_position);
- return true;
- }
-
- return false;
- }
-
- if (action == SLAGizmoEventType::Delete) {
- // delete key pressed
- delete_selected_points();
- return true;
- }
-
- if (action == SLAGizmoEventType::ApplyChanges) {
- editing_mode_apply_changes();
- return true;
- }
-
- if (action == SLAGizmoEventType::DiscardChanges) {
- ask_about_changes_call_after([this](){ editing_mode_apply_changes(); },
- [this](){ editing_mode_discard_changes(); });
- return true;
- }
-
- if (action == SLAGizmoEventType::RightDown) {
- if (m_hover_id != -1) {
- select_point(NoPoints);
- select_point(m_hover_id);
- delete_selected_points();
- return true;
- }
- return false;
- }
-
- if (action == SLAGizmoEventType::SelectAll) {
- select_point(AllPoints);
- return true;
- }
- }
-
- if (!m_editing_mode) {
- if (action == SLAGizmoEventType::AutomaticGeneration) {
- auto_generate();
- return true;
- }
-
- if (action == SLAGizmoEventType::ManualEditing) {
- switch_to_editing_mode();
- return true;
- }
- }
-
- if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
- double pos = m_c->object_clipper()->get_position();
- pos = std::min(1., pos + 0.01);
- m_c->object_clipper()->set_position(pos, true);
- return true;
- }
-
- if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
- double pos = m_c->object_clipper()->get_position();
- pos = std::max(0., pos - 0.01);
- m_c->object_clipper()->set_position(pos, true);
- return true;
- }
-
- if (action == SLAGizmoEventType::ResetClippingPlane) {
- m_c->object_clipper()->set_position(-1., false);
- return true;
- }
-
- return false;
-}
-
-void GLGizmoSlaSupports::delete_selected_points(bool force)
-{
- if (! m_editing_mode) {
- std::cout << "DEBUGGING: delete_selected_points called out of editing mode!" << std::endl;
- std::abort();
- }
-
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point"));
-
- for (unsigned int idx=0; idx<m_editing_cache.size(); ++idx) {
- if (m_editing_cache[idx].selected && (!m_editing_cache[idx].support_point.is_new_island || !m_lock_unique_islands || force)) {
- m_editing_cache.erase(m_editing_cache.begin() + (idx--));
- }
- }
-
- select_point(NoPoints);
-}
-
-std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vector<std::string>& keys) const
-{
- std::vector<const ConfigOption*> out;
- const ModelObject* mo = m_c->selection_info()->model_object();
-
- if (! mo)
- return out;
-
- const DynamicPrintConfig& object_cfg = mo->config.get();
- const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
- std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
-
- for (const std::string& key : keys) {
- if (object_cfg.has(key))
- out.push_back(object_cfg.option(key));
- else
- if (print_cfg.has(key))
- out.push_back(print_cfg.option(key));
- else { // we must get it from defaults
- if (default_cfg == nullptr)
- default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys));
- out.push_back(default_cfg->option(key));
- }
- }
-
- return out;
-}
-
-
-
-/*
-void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& idxs) const
-{
- if (aabb->is_leaf()) { // this is a facet
- // corner.dot(normal) - offset
- idxs.push_back(aabb->m_primitive);
- }
- else { // not a leaf
- using CornerType = Eigen::AlignedBox<float, 3>::CornerType;
- bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0))));
- for (unsigned int i=1; i<8; ++i)
- if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) {
- find_intersecting_facets(aabb->m_left, normal, offset, idxs);
- find_intersecting_facets(aabb->m_right, normal, offset, idxs);
- }
- }
-}
-
-
-
-void GLGizmoSlaSupports::make_line_segments() const
-{
- TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh);
- Vec3f normal(0.f, 1.f, 1.f);
- double d = 0.;
-
- std::vector<IntersectionLine> lines;
- find_intersections(&m_AABB, normal, d, lines);
- ExPolygons expolys;
- tms.make_expolygons_simple(lines, &expolys);
-
- SVG svg("slice_loops.svg", get_extents(expolys));
- svg.draw(expolys);
- //for (const IntersectionLine &l : lines[i])
- // svg.draw(l, "red", 0);
- //svg.draw_outline(expolygons, "black", "blue", 0);
- svg.Close();
-}
-*/
-
-
-void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit)
-{
- static float last_y = 0.0f;
- static float last_h = 0.0f;
-
- ModelObject* mo = m_c->selection_info()->model_object();
-
- if (! mo)
- return;
-
- bool first_run = true; // This is a hack to redraw the button when all points are removed,
- // so it is not delayed until the background process finishes.
-RENDER_AGAIN:
- //m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
- //const ImVec2 window_size(m_imgui->scaled(18.f, 16.f));
- //ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) ));
- //ImGui::SetNextWindowSize(ImVec2(window_size));
-
- m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
-
- // adjust window position to avoid overlap the view toolbar
- float win_h = ImGui::GetWindowHeight();
- y = std::min(y, bottom_limit - win_h);
- ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
- if ((last_h != win_h) || (last_y != y))
- {
- // ask canvas for another frame to render the window in the correct position
- m_imgui->set_requires_extra_frame();
- if (last_h != win_h)
- last_h = win_h;
- if (last_y != y)
- last_y = y;
- }
-
- // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
-
- const float settings_sliders_left = std::max(m_imgui->calc_text_size(m_desc.at("minimal_distance")).x, m_imgui->calc_text_size(m_desc.at("points_density")).x) + m_imgui->scaled(1.f);
- const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
- const float diameter_slider_left = m_imgui->calc_text_size(m_desc.at("head_diameter")).x + m_imgui->scaled(1.f);
- const float minimal_slider_width = m_imgui->scaled(4.f);
- const float buttons_width_approx = m_imgui->calc_text_size(m_desc.at("apply_changes")).x + m_imgui->calc_text_size(m_desc.at("discard_changes")).x + m_imgui->scaled(1.5f);
- const float lock_supports_width_approx = m_imgui->calc_text_size(m_desc.at("lock_supports")).x + m_imgui->scaled(2.f);
-
- float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left);
- window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx);
-
- bool force_refresh = false;
- bool remove_selected = false;
- bool remove_all = false;
-
- if (m_editing_mode) {
-
- float diameter_upper_cap = static_cast<ConfigOptionFloat*>(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value;
- if (m_new_point_head_diameter > diameter_upper_cap)
- m_new_point_head_diameter = diameter_upper_cap;
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("head_diameter"));
- ImGui::SameLine(diameter_slider_left);
- ImGui::PushItemWidth(window_width - diameter_slider_left);
-
- // Following is a nasty way to:
- // - save the initial value of the slider before one starts messing with it
- // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
- // - take correct undo/redo snapshot after the user is done with moving the slider
- float initial_value = m_new_point_head_diameter;
- m_imgui->slider_float("##head_diameter", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f");
- if (m_imgui->get_last_slider_status().clicked) {
- if (m_old_point_head_diameter == 0.f)
- m_old_point_head_diameter = initial_value;
- }
- if (m_imgui->get_last_slider_status().edited) {
- for (auto& cache_entry : m_editing_cache)
- if (cache_entry.selected)
- cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f;
- }
- if (m_imgui->get_last_slider_status().deactivated_after_edit) {
- // momentarily restore the old value to take snapshot
- for (auto& cache_entry : m_editing_cache)
- if (cache_entry.selected)
- cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f;
- float backup = m_new_point_head_diameter;
- m_new_point_head_diameter = m_old_point_head_diameter;
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change point head diameter"));
- m_new_point_head_diameter = backup;
- for (auto& cache_entry : m_editing_cache)
- if (cache_entry.selected)
- cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f;
- m_old_point_head_diameter = 0.f;
- }
-
- bool changed = m_lock_unique_islands;
- m_imgui->checkbox(m_desc.at("lock_supports"), m_lock_unique_islands);
- force_refresh |= changed != m_lock_unique_islands;
-
- m_imgui->disabled_begin(m_selection_empty);
- remove_selected = m_imgui->button(m_desc.at("remove_selected"));
- m_imgui->disabled_end();
-
- m_imgui->disabled_begin(m_editing_cache.empty());
- remove_all = m_imgui->button(m_desc.at("remove_all"));
- m_imgui->disabled_end();
-
- m_imgui->text(" "); // vertical gap
-
- if (m_imgui->button(m_desc.at("apply_changes"))) {
- editing_mode_apply_changes();
- force_refresh = true;
- }
- ImGui::SameLine();
- bool discard_changes = m_imgui->button(m_desc.at("discard_changes"));
- if (discard_changes) {
- editing_mode_discard_changes();
- force_refresh = true;
- }
- }
- else { // not in editing mode:
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("minimal_distance"));
- ImGui::SameLine(settings_sliders_left);
- ImGui::PushItemWidth(window_width - settings_sliders_left);
-
- std::vector<const ConfigOption*> opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"});
- float density = static_cast<const ConfigOptionInt*>(opts[0])->value;
- float minimal_point_distance = static_cast<const ConfigOptionFloat*>(opts[1])->value;
-
- m_imgui->slider_float("##minimal_point_distance", &minimal_point_distance, 0.f, 20.f, "%.f mm");
- bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider
- bool slider_edited = m_imgui->get_last_slider_status().edited; // someone is dragging the slider
- bool slider_released = m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider
-
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("points_density"));
- ImGui::SameLine(settings_sliders_left);
-
- m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%");
- slider_clicked |= m_imgui->get_last_slider_status().clicked;
- slider_edited |= m_imgui->get_last_slider_status().edited;
- slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
-
- if (slider_clicked) { // stash the values of the settings so we know what to revert to after undo
- m_minimal_point_distance_stash = minimal_point_distance;
- m_density_stash = density;
- }
- if (slider_edited) {
- mo->config.set("support_points_minimal_distance", minimal_point_distance);
- mo->config.set("support_points_density_relative", (int)density);
- }
- if (slider_released) {
- mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash);
- mo->config.set("support_points_density_relative", (int)m_density_stash);
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change"));
- mo->config.set("support_points_minimal_distance", minimal_point_distance);
- mo->config.set("support_points_density_relative", (int)density);
- wxGetApp().obj_list()->update_and_show_object_settings_item();
- }
-
- bool generate = m_imgui->button(m_desc.at("auto_generate"));
-
- if (generate)
- auto_generate();
-
- ImGui::Separator();
- if (m_imgui->button(m_desc.at("manual_editing")))
- switch_to_editing_mode();
-
- m_imgui->disabled_begin(m_normal_cache.empty());
- remove_all = m_imgui->button(m_desc.at("remove_all"));
- m_imgui->disabled_end();
-
- // m_imgui->text("");
- // m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) :
- // (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) :
- // (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) :
- // (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS"))));
- }
-
-
- // Following is rendered in both editing and non-editing mode:
- ImGui::Separator();
- if (m_c->object_clipper()->get_position() == 0.f) {
- ImGui::AlignTextToFramePadding();
- m_imgui->text(m_desc.at("clipping_of_view"));
- }
- else {
- if (m_imgui->button(m_desc.at("reset_direction"))) {
- wxGetApp().CallAfter([this](){
- m_c->object_clipper()->set_position(-1., false);
- });
- }
- }
-
- ImGui::SameLine(clipping_slider_left);
- ImGui::PushItemWidth(window_width - clipping_slider_left);
- float clp_dist = m_c->object_clipper()->get_position();
- if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
- m_c->object_clipper()->set_position(clp_dist, true);
-
-
- if (m_imgui->button("?")) {
- wxGetApp().CallAfter([]() {
- SlaGizmoHelpDialog help_dlg;
- help_dlg.ShowModal();
- });
- }
-
- m_imgui->end();
-
- if (remove_selected || remove_all) {
- force_refresh = false;
- m_parent.set_as_dirty();
- bool was_in_editing = m_editing_mode;
- if (! was_in_editing)
- switch_to_editing_mode();
- if (remove_all) {
- select_point(AllPoints);
- delete_selected_points(true); // true - delete regardless of locked status
- }
- if (remove_selected)
- delete_selected_points(false); // leave locked points
- if (! was_in_editing)
- editing_mode_apply_changes();
-
- if (first_run) {
- first_run = false;
- goto RENDER_AGAIN;
- }
- }
-
- if (force_refresh)
- m_parent.set_as_dirty();
-}
-
-bool GLGizmoSlaSupports::on_is_activable() const
-{
- const Selection& selection = m_parent.get_selection();
-
- if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA
- || !selection.is_from_single_instance())
- return false;
-
- // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
- const Selection::IndicesList& list = selection.get_volume_idxs();
- for (const auto& idx : list)
- if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0)
- return false;
-
- return true;
-}
-
-bool GLGizmoSlaSupports::on_is_selectable() const
-{
- return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA);
-}
-
-std::string GLGizmoSlaSupports::on_get_name() const
-{
- return _u8L("SLA Support Points");
-}
-
-CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const
-{
- return CommonGizmosDataID(
- int(CommonGizmosDataID::SelectionInfo)
- | int(CommonGizmosDataID::InstancesHider)
- | int(CommonGizmosDataID::Raycaster)
- | int(CommonGizmosDataID::HollowedMesh)
- | int(CommonGizmosDataID::ObjectClipper)
- | int(CommonGizmosDataID::SupportsClipper));
-}
-
-
-
-void GLGizmoSlaSupports::ask_about_changes_call_after(std::function<void()> on_yes, std::function<void()> on_no)
-{
- wxGetApp().CallAfter([on_yes, on_no]() {
- // Following is called through CallAfter, because otherwise there was a problem
- // on OSX with the wxMessageDialog being shown several times when clicked into.
- MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually "
- "edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL );
- int ret = dlg.ShowModal();
- if (ret == wxID_YES)
- on_yes();
- else if (ret == wxID_NO)
- on_no();
- });
-}
-
-
-void GLGizmoSlaSupports::on_set_state()
-{
- if (m_state == m_old_state)
- return;
-
- if (m_state == On && m_old_state != On) { // the gizmo was just turned on
- // Set default head diameter from config.
- const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
- m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
- }
- if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
- bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable();
- if (will_ask) {
- ask_about_changes_call_after([this](){ editing_mode_apply_changes(); },
- [this](){ editing_mode_discard_changes(); });
- // refuse to be turned off so the gizmo is active when the CallAfter is executed
- m_state = m_old_state;
- }
- else {
- // we are actually shutting down
- disable_editing_mode(); // so it is not active next time the gizmo opens
- m_old_mo_id = -1;
- }
- }
- m_old_state = m_state;
-}
-
-
-
-void GLGizmoSlaSupports::on_start_dragging()
-{
- if (m_hover_id != -1) {
- select_point(NoPoints);
- select_point(m_hover_id);
- m_point_before_drag = m_editing_cache[m_hover_id];
- }
- else
- m_point_before_drag = CacheEntry();
-}
-
-
-void GLGizmoSlaSupports::on_stop_dragging()
-{
- if (m_hover_id != -1) {
- CacheEntry backup = m_editing_cache[m_hover_id];
-
- if (m_point_before_drag.support_point.pos != Vec3f::Zero() // some point was touched
- && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected
- {
- m_editing_cache[m_hover_id] = m_point_before_drag;
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point"));
- m_editing_cache[m_hover_id] = backup;
- }
- }
- m_point_before_drag = CacheEntry();
-}
-
-void GLGizmoSlaSupports::on_dragging(const UpdateData &data)
-{
- assert(m_hover_id != -1);
- if (!m_editing_mode) return;
- if (m_editing_cache[m_hover_id].support_point.is_new_island && m_lock_unique_islands)
- return;
-
- std::pair<Vec3f, Vec3f> pos_and_normal;
- if (!unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
- return;
-
- m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first;
- m_editing_cache[m_hover_id].support_point.is_new_island = false;
- m_editing_cache[m_hover_id].normal = pos_and_normal.second;
-}
-
-void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
-{
- ar(m_new_point_head_diameter,
- m_normal_cache,
- m_editing_cache,
- m_selection_empty
- );
-}
-
-
-
-void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const
-{
- ar(m_new_point_head_diameter,
- m_normal_cache,
- m_editing_cache,
- m_selection_empty
- );
-}
-
-
-
-void GLGizmoSlaSupports::select_point(int i)
-{
- if (! m_editing_mode) {
- std::cout << "DEBUGGING: select_point called when out of editing mode!" << std::endl;
- std::abort();
- }
-
- if (i == AllPoints || i == NoPoints) {
- for (auto& point_and_selection : m_editing_cache)
- point_and_selection.selected = ( i == AllPoints );
- m_selection_empty = (i == NoPoints);
-
- if (i == AllPoints)
- m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f;
- }
- else {
- m_editing_cache[i].selected = true;
- m_selection_empty = false;
- m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f;
- }
-}
-
-
-void GLGizmoSlaSupports::unselect_point(int i)
-{
- if (! m_editing_mode) {
- std::cout << "DEBUGGING: unselect_point called when out of editing mode!" << std::endl;
- std::abort();
- }
-
- m_editing_cache[i].selected = false;
- m_selection_empty = true;
- for (const CacheEntry& ce : m_editing_cache) {
- if (ce.selected) {
- m_selection_empty = false;
- break;
- }
- }
-}
-
-
-
-
-void GLGizmoSlaSupports::editing_mode_discard_changes()
-{
- if (! m_editing_mode) {
- std::cout << "DEBUGGING: editing_mode_discard_changes called when out of editing mode!" << std::endl;
- std::abort();
- }
- select_point(NoPoints);
- disable_editing_mode();
-}
-
-
-
-void GLGizmoSlaSupports::editing_mode_apply_changes()
-{
- // If there are no changes, don't touch the front-end. The data in the cache could have been
- // taken from the backend and copying them to ModelObject would needlessly invalidate them.
- disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken
-
- if (unsaved_changes()) {
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit"));
-
- m_normal_cache.clear();
- for (const CacheEntry& ce : m_editing_cache)
- m_normal_cache.push_back(ce.support_point);
-
- ModelObject* mo = m_c->selection_info()->model_object();
- mo->sla_points_status = sla::PointsStatus::UserModified;
- mo->sla_support_points.clear();
- mo->sla_support_points = m_normal_cache;
-
- reslice_SLA_supports();
- }
-}
-
-
-
-void GLGizmoSlaSupports::reload_cache()
-{
- const ModelObject* mo = m_c->selection_info()->model_object();
- m_normal_cache.clear();
- if (mo->sla_points_status == sla::PointsStatus::AutoGenerated || mo->sla_points_status == sla::PointsStatus::Generating)
- get_data_from_backend();
- else
- for (const sla::SupportPoint& point : mo->sla_support_points)
- m_normal_cache.emplace_back(point);
-}
-
-
-bool GLGizmoSlaSupports::has_backend_supports() const
-{
- const ModelObject* mo = m_c->selection_info()->model_object();
- if (! mo)
- return false;
-
- // find SlaPrintObject with this ID
- for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
- if (po->model_object()->id() == mo->id())
- return po->is_step_done(slaposSupportPoints);
- }
- return false;
-}
-
-void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const
-{
- wxGetApp().CallAfter([this, postpone_error_messages]() {
- wxGetApp().plater()->reslice_SLA_supports(
- *m_c->selection_info()->model_object(), postpone_error_messages);
- });
-}
-
-bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event){
- if (mouse_event.Moving()) return false;
- if (use_grabbers(mouse_event)) return true;
-
- // wxCoord == int --> wx/types.h
- Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
- Vec2d mouse_pos = mouse_coord.cast<double>();
-
- static bool pending_right_up = false;
- if (mouse_event.LeftDown()) {
- bool grabber_contains_mouse = (get_hover_id() != -1);
- bool control_down = mouse_event.CmdDown();
- if ((!control_down || grabber_contains_mouse) &&
- gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false))
- return true;
- } else if (mouse_event.Dragging()) {
- bool control_down = mouse_event.CmdDown();
- if (m_parent.get_move_volume_id() != -1) {
- // don't allow dragging objects with the Sla gizmo on
- return true;
- } else if (!control_down &&
- gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) {
- // the gizmo got the event and took some action, no need to do
- // anything more here
- m_parent.set_as_dirty();
- return true;
- } else if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())){
- // CTRL has been pressed while already dragging -> stop current action
- if (mouse_event.LeftIsDown())
- gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
- else if (mouse_event.RightIsDown())
- pending_right_up = false;
- }
- } else if (mouse_event.LeftUp() && !m_parent.is_mouse_dragging()) {
- // in case SLA/FDM gizmo is selected, we just pass the LeftUp event
- // and stop processing - neither object moving or selecting is
- // suppressed in that case
- gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown());
- return true;
- }else if (mouse_event.RightDown()){
- if (m_parent.get_selection().get_object_idx() != -1 &&
- gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) {
- // we need to set the following right up as processed to avoid showing
- // the context menu if the user release the mouse over the object
- pending_right_up = true;
- // event was taken care of by the SlaSupports gizmo
- return true;
- }
- } else if (pending_right_up && mouse_event.RightUp()) {
- pending_right_up = false;
- return true;
- }
- return false;
-}
-
-void GLGizmoSlaSupports::get_data_from_backend()
-{
- if (! has_backend_supports())
- return;
- ModelObject* mo = m_c->selection_info()->model_object();
-
- // find the respective SLAPrintObject, we need a pointer to it
- for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
- if (po->model_object()->id() == mo->id()) {
- m_normal_cache.clear();
- const std::vector<sla::SupportPoint>& points = po->get_support_points();
- auto mat = (po->trafo() * po->model_object()->volumes.front()->get_transformation().get_matrix()).inverse().cast<float>();
- for (unsigned int i=0; i<points.size();++i)
- m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island));
-
- mo->sla_points_status = sla::PointsStatus::AutoGenerated;
- break;
- }
- }
-
- // We don't copy the data into ModelObject, as this would stop the background processing.
-}
-
-
-
-void GLGizmoSlaSupports::auto_generate()
-{
- //wxMessageDialog dlg(GUI::wxGetApp().plater(),
- MessageDialog dlg(GUI::wxGetApp().plater(),
- _L("Autogeneration will erase all manually edited points.") + "\n\n" +
- _L("Are you sure you want to do it?") + "\n",
- _L("Warning"), wxICON_WARNING | wxYES | wxNO);
-
- ModelObject* mo = m_c->selection_info()->model_object();
-
- if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points"));
- wxGetApp().CallAfter([this]() { reslice_SLA_supports(); });
- mo->sla_points_status = sla::PointsStatus::Generating;
- }
-}
-
-
-
-void GLGizmoSlaSupports::switch_to_editing_mode()
-{
- wxGetApp().plater()->enter_gizmos_stack();
- m_editing_mode = true;
- m_editing_cache.clear();
- for (const sla::SupportPoint& sp : m_normal_cache)
- m_editing_cache.emplace_back(sp);
- select_point(NoPoints);
-
- m_c->instances_hider()->show_supports(false);
- m_parent.set_as_dirty();
-}
-
-
-void GLGizmoSlaSupports::disable_editing_mode()
-{
- if (m_editing_mode) {
- m_editing_mode = false;
- wxGetApp().plater()->leave_gizmos_stack();
- m_c->instances_hider()->show_supports(true);
- m_parent.set_as_dirty();
- }
- wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode);
-}
-
-
-
-bool GLGizmoSlaSupports::unsaved_changes() const
-{
- if (m_editing_cache.size() != m_normal_cache.size())
- return true;
-
- for (size_t i=0; i<m_editing_cache.size(); ++i)
- if (m_editing_cache[i].support_point != m_normal_cache[i])
- return true;
-
- return false;
-}
-
-SlaGizmoHelpDialog::SlaGizmoHelpDialog()
-: wxDialog(nullptr, wxID_ANY, _L("SLA gizmo keyboard shortcuts"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
-{
- SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
- const wxString ctrl = GUI::shortkey_ctrl_prefix();
- const wxString alt = GUI::shortkey_alt_prefix();
-
-
- // fonts
- const wxFont& font = wxGetApp().small_font();
- const wxFont& bold_font = wxGetApp().bold_font();
-
- auto note_text = new wxStaticText(this, wxID_ANY, _L("Note: some shortcuts work in (non)editing mode only."));
- note_text->SetFont(font);
-
- auto vsizer = new wxBoxSizer(wxVERTICAL);
- auto gridsizer = new wxFlexGridSizer(2, 5, 15);
- auto hsizer = new wxBoxSizer(wxHORIZONTAL);
-
- hsizer->AddSpacer(20);
- hsizer->Add(vsizer);
- hsizer->AddSpacer(20);
-
- vsizer->AddSpacer(20);
- vsizer->Add(note_text, 1, wxALIGN_CENTRE_HORIZONTAL);
- vsizer->AddSpacer(20);
- vsizer->Add(gridsizer);
- vsizer->AddSpacer(20);
-
- std::vector<std::pair<wxString, wxString>> shortcuts;
- shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add point")));
- shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove point")));
- shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move point")));
- shortcuts.push_back(std::make_pair(ctrl+_L("Left click"), _L("Add point to selection")));
- shortcuts.push_back(std::make_pair(alt+_L("Left click"), _L("Remove point from selection")));
- shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle")));
- shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _L("Deselect by rectangle")));
- shortcuts.push_back(std::make_pair(ctrl+"A", _L("Select all points")));
- shortcuts.push_back(std::make_pair("Delete", _L("Remove selected points")));
- shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane")));
- shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane")));
- shortcuts.push_back(std::make_pair("Enter", _L("Apply changes")));
- shortcuts.push_back(std::make_pair("Esc", _L("Discard changes")));
- shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode")));
- shortcuts.push_back(std::make_pair("A", _L("Auto-generate points")));
-
- for (const auto& pair : shortcuts) {
- auto shortcut = new wxStaticText(this, wxID_ANY, pair.first);
- auto desc = new wxStaticText(this, wxID_ANY, pair.second);
- shortcut->SetFont(bold_font);
- desc->SetFont(font);
- gridsizer->Add(shortcut, -1, wxALIGN_CENTRE_VERTICAL);
- gridsizer->Add(desc, -1, wxALIGN_CENTRE_VERTICAL);
- }
-
- SetSizer(hsizer);
- hsizer->SetSizeHints(this);
-}
-
-
-
-} // namespace GUI
-} // namespace Slic3r
+// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
+#include "GLGizmoSlaSupports.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/Camera.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
+#include "slic3r/GUI/MainFrame.hpp"
+#include "slic3r/Utils/UndoRedo.hpp"
+
+#include <GL/glew.h>
+
+#include <wx/msgdlg.h>
+#include <wx/settings.h>
+#include <wx/stattext.h>
+
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/GUI_ObjectSettings.hpp"
+#include "slic3r/GUI/GUI_ObjectList.hpp"
+#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/NotificationManager.hpp"
+#include "slic3r/GUI/MsgDialog.hpp"
+#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/SLAPrint.hpp"
+
+
+namespace Slic3r {
+namespace GUI {
+
+GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
+ : GLGizmoBase(parent, icon_filename, sprite_id)
+{}
+
+bool GLGizmoSlaSupports::on_init()
+{
+ m_shortcut_key = WXK_CONTROL_L;
+
+ m_desc["head_diameter"] = _L("Head diameter") + ": ";
+ m_desc["lock_supports"] = _L("Lock supports under new islands");
+ m_desc["remove_selected"] = _L("Remove selected points");
+ m_desc["remove_all"] = _L("Remove all points");
+ m_desc["apply_changes"] = _L("Apply changes");
+ m_desc["discard_changes"] = _L("Discard changes");
+ m_desc["minimal_distance"] = _L("Minimal points distance") + ": ";
+ m_desc["points_density"] = _L("Support points density") + ": ";
+ m_desc["auto_generate"] = _L("Auto-generate points");
+ m_desc["manual_editing"] = _L("Manual editing");
+ m_desc["clipping_of_view"] = _L("Clipping of view")+ ": ";
+ m_desc["reset_direction"] = _L("Reset direction");
+
+ m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24));
+ m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.));
+ m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.));
+
+ return true;
+}
+
+void GLGizmoSlaSupports::data_changed()
+{
+ if (! m_c->selection_info())
+ return;
+
+ ModelObject* mo = m_c->selection_info()->model_object();
+
+ if (m_state == On && mo && mo->id() != m_old_mo_id) {
+ disable_editing_mode();
+ reload_cache();
+ m_old_mo_id = mo->id();
+ m_c->instances_hider()->show_supports(true);
+ }
+
+ // If we triggered autogeneration before, check backend and fetch results if they are there
+ if (mo) {
+ if (mo->sla_points_status == sla::PointsStatus::Generating)
+ get_data_from_backend();
+ }
+}
+
+
+
+void GLGizmoSlaSupports::on_render()
+{
+ if (!m_cone.is_initialized())
+ m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0));
+ if (!m_sphere.is_initialized())
+ m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0));
+ if (!m_cylinder.is_initialized())
+ m_cylinder.init_from(its_make_cylinder(1.0, 1.0, double(PI) / 12.0));
+
+ ModelObject* mo = m_c->selection_info()->model_object();
+ const Selection& selection = m_parent.get_selection();
+
+ // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off
+ if (m_state == On
+ && (mo != selection.get_model()->objects[selection.get_object_idx()]
+ || m_c->selection_info()->get_active_instance() != selection.get_instance_idx())) {
+ m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
+ return;
+ }
+
+ glsafe(::glEnable(GL_BLEND));
+ glsafe(::glEnable(GL_DEPTH_TEST));
+
+ if (selection.is_from_single_instance())
+ render_points(selection, false);
+
+ m_selection_rectangle.render(m_parent);
+ m_c->object_clipper()->render_cut();
+ m_c->supports_clipper()->render_cut();
+
+ glsafe(::glDisable(GL_BLEND));
+}
+
+
+void GLGizmoSlaSupports::on_render_for_picking()
+{
+ const Selection& selection = m_parent.get_selection();
+ //glsafe(::glEnable(GL_DEPTH_TEST));
+ render_points(selection, true);
+}
+
+void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
+{
+ const size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size();
+
+ const bool has_points = (cache_size != 0);
+ const bool has_holes = (! m_c->hollowed_mesh()->get_hollowed_mesh()
+ && ! m_c->selection_info()->model_object()->sla_drain_holes.empty());
+
+ if (! has_points && ! has_holes)
+ return;
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light");
+ if (shader == nullptr)
+ return;
+
+ shader->start_using();
+ ScopeGuard guard([shader]() { shader->stop_using(); });
+#else
+ GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light");
+ if (shader != nullptr)
+ shader->start_using();
+ ScopeGuard guard([shader]() {
+ if (shader != nullptr)
+ shader->stop_using();
+ });
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ const GLVolume* vol = selection.get_first_volume();
+ Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix());
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse();
+#else
+ const Transform3d& instance_scaling_matrix_inverse = transformation.get_matrix(true, true, false, true).inverse();
+#endif // ENABLE_WORLD_COORDINATE
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * transformation.get_matrix();
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ const Transform3d& view_matrix = camera.get_view_matrix();
+ const Transform3d& projection_matrix = camera.get_projection_matrix();
+
+ shader->set_uniform("projection_matrix", projection_matrix);
+#else
+ const Transform3d& instance_matrix = transformation.get_matrix();
+ const float z_shift = m_c->selection_info()->get_sla_shift();
+ glsafe(::glPushMatrix());
+ glsafe(::glTranslated(0.0, 0.0, z_shift));
+ glsafe(::glMultMatrixd(instance_matrix.data()));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ ColorRGBA render_color;
+ for (size_t i = 0; i < cache_size; ++i) {
+ const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i];
+ const bool point_selected = m_editing_mode ? m_editing_cache[i].selected : false;
+
+ if (is_mesh_point_clipped(support_point.pos.cast<double>()))
+ continue;
+
+ // First decide about the color of the point.
+ if (picking)
+ render_color = picking_color_component(i);
+ else {
+ if (size_t(m_hover_id) == i && m_editing_mode) // ignore hover state unless editing mode is active
+ render_color = { 0.f, 1.f, 1.f, 1.f };
+ else { // neigher hover nor picking
+ bool supports_new_island = m_lock_unique_islands && support_point.is_new_island;
+ if (m_editing_mode) {
+ if (point_selected)
+ render_color = { 1.f, 0.3f, 0.3f, 1.f};
+ else
+ if (supports_new_island)
+ render_color = { 0.3f, 0.3f, 1.f, 1.f };
+ else
+ render_color = { 0.7f, 0.7f, 0.7f, 1.f };
+ }
+ else
+ render_color = { 0.5f, 0.5f, 0.5f, 1.f };
+ }
+ }
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_cone.set_color(render_color);
+ m_sphere.set_color(render_color);
+ if (!picking)
+#else
+ m_cone.set_color(-1, render_color);
+ m_sphere.set_color(-1, render_color);
+ if (shader && !picking)
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ shader->set_uniform("emission_factor", 0.5f);
+
+ // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d support_matrix = Geometry::assemble_transform(support_point.pos.cast<double>()) * instance_scaling_matrix_inverse;
+#else
+ glsafe(::glPushMatrix());
+ glsafe(::glTranslatef(support_point.pos.x(), support_point.pos.y(), support_point.pos.z()));
+ glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data()));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ if (vol->is_left_handed())
+ glFrontFace(GL_CW);
+
+ // Matrices set, we can render the point mark now.
+ // If in editing mode, we'll also render a cone pointing to the sphere.
+ if (m_editing_mode) {
+ // in case the normal is not yet cached, find and cache it
+ if (m_editing_cache[i].normal == Vec3f::Zero())
+ m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
+
+ Eigen::Quaterniond q;
+ q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast<double>());
+ const Eigen::AngleAxisd aa(q);
+ const double cone_radius = 0.25; // mm
+ const double cone_height = 0.75;
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix * Transform3d(aa.toRotationMatrix()) *
+ Geometry::assemble_transform((cone_height + support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ(),
+ Vec3d(PI, 0.0, 0.0), Vec3d(cone_radius, cone_radius, cone_height));
+
+ shader->set_uniform("view_model_matrix", view_model_matrix);
+ shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
+#else
+ glsafe(::glPushMatrix());
+ glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z()));
+ glsafe(::glTranslatef(0.f, 0.f, cone_height + support_point.head_front_radius * RenderPointScale));
+ glsafe(::glRotated(180., 1., 0., 0.));
+ glsafe(::glScaled(cone_radius, cone_radius, cone_height));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ m_cone.render();
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+ }
+
+ const double radius = (double)support_point.head_front_radius * RenderPointScale;
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix *
+ Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), radius * Vec3d::Ones());
+
+ shader->set_uniform("view_model_matrix", view_model_matrix);
+ shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
+#else
+ glsafe(::glPushMatrix());
+ glsafe(::glScaled(radius, radius, radius));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ m_sphere.render();
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+
+ if (vol->is_left_handed())
+ glFrontFace(GL_CCW);
+
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+ }
+
+ // Now render the drain holes:
+ if (has_holes && ! picking) {
+ render_color = { 0.7f, 0.7f, 0.7f, 0.7f };
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_cylinder.set_color(render_color);
+#else
+ m_cylinder.set_color(-1, render_color);
+ if (shader != nullptr)
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ shader->set_uniform("emission_factor", 0.5f);
+ for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) {
+ if (is_mesh_point_clipped(drain_hole.pos.cast<double>()))
+ continue;
+
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast<double>()) * instance_scaling_matrix_inverse;
+#else
+ // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
+ glsafe(::glPushMatrix());
+ glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z()));
+ glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data()));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+ if (vol->is_left_handed())
+ glFrontFace(GL_CW);
+
+ // Matrices set, we can render the point mark now.
+ Eigen::Quaterniond q;
+ q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast<double>());
+ const Eigen::AngleAxisd aa(q);
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) *
+ Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength));
+
+ shader->set_uniform("view_model_matrix", view_model_matrix);
+ shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose());
+#else
+ glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z()));
+ glsafe(::glTranslated(0., 0., -drain_hole.height));
+ glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength));
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ m_cylinder.render();
+
+ if (vol->is_left_handed())
+ glFrontFace(GL_CCW);
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+ }
+ }
+
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+}
+
+
+
+bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
+{
+ if (m_c->object_clipper()->get_position() == 0.)
+ return false;
+
+ auto sel_info = m_c->selection_info();
+ int active_inst = m_c->selection_info()->get_active_instance();
+ const ModelInstance* mi = sel_info->model_object()->instances[active_inst];
+ const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix();
+
+ Vec3d transformed_point = trafo * point;
+ transformed_point(2) += sel_info->get_sla_shift();
+ return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
+}
+
+
+
+// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
+// Return false if no intersection was found, true otherwise.
+bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
+{
+ if (! m_c->raycaster()->raycaster())
+ return false;
+
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ const Selection& selection = m_parent.get_selection();
+ const GLVolume* volume = selection.get_first_volume();
+ Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation();
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
+
+ double clp_dist = m_c->object_clipper()->get_position();
+ const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
+
+ // The raycaster query
+ Vec3f hit;
+ Vec3f normal;
+ if (m_c->raycaster()->raycaster()->unproject_on_mesh(
+ mouse_pos,
+ trafo.get_matrix(),
+ camera,
+ hit,
+ normal,
+ clp_dist != 0. ? clp : nullptr))
+ {
+ // Check whether the hit is in a hole
+ bool in_hole = false;
+ // In case the hollowed and drilled mesh is available, we can allow
+ // placing points in holes, because they should never end up
+ // on surface that's been drilled away.
+ if (! m_c->hollowed_mesh()->get_hollowed_mesh()) {
+ sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
+ for (const sla::DrainHole& hole : drain_holes) {
+ if (hole.is_inside(hit)) {
+ in_hole = true;
+ break;
+ }
+ }
+ }
+ if (! in_hole) {
+ // Return both the point and the facet normal.
+ pos_and_normal = std::make_pair(hit, normal);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
+// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
+// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
+// concludes that the event was not intended for it, it should return false.
+bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
+{
+ ModelObject* mo = m_c->selection_info()->model_object();
+ int active_inst = m_c->selection_info()->get_active_instance();
+
+ if (m_editing_mode) {
+
+ // left down with shift - show the selection rectangle:
+ if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) {
+ if (m_hover_id == -1) {
+ if (shift_down || alt_down) {
+ m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect);
+ }
+ }
+ else {
+ if (m_editing_cache[m_hover_id].selected)
+ unselect_point(m_hover_id);
+ else {
+ if (!alt_down)
+ select_point(m_hover_id);
+ }
+ }
+
+ return true;
+ }
+
+ // left down without selection rectangle - place point on the mesh:
+ if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) {
+ // If any point is in hover state, this should initiate its move - return control back to GLCanvas:
+ if (m_hover_id != -1)
+ return false;
+
+ // If there is some selection, don't add new point and deselect everything instead.
+ if (m_selection_empty) {
+ std::pair<Vec3f, Vec3f> pos_and_normal;
+ if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point"));
+ m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second);
+ m_parent.set_as_dirty();
+ m_wait_for_up_event = true;
+ }
+ else
+ return false;
+ }
+ else
+ select_point(NoPoints);
+
+ return true;
+ }
+
+ // left up with selection rectangle - select points inside the rectangle:
+ if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) {
+ // Is this a selection or deselection rectangle?
+ GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
+
+ // First collect positions of all the points in world coordinates.
+ Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation();
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift()));
+ std::vector<Vec3d> points;
+ for (unsigned int i=0; i<m_editing_cache.size(); ++i)
+ points.push_back(trafo.get_matrix() * m_editing_cache[i].support_point.pos.cast<double>());
+
+ // Now ask the rectangle which of the points are inside.
+ std::vector<Vec3f> points_inside;
+ std::vector<unsigned int> points_idxs = m_selection_rectangle.stop_dragging(m_parent, points);
+ for (size_t idx : points_idxs)
+ points_inside.push_back(points[idx].cast<float>());
+
+ // Only select/deselect points that are actually visible. We want to check not only
+ // the point itself, but also the center of base of its cone, so the points don't hide
+ // under every miniature irregularity on the model. Remember the actual number and
+ // append the cone bases.
+ size_t orig_pts_num = points_inside.size();
+ for (size_t idx : points_idxs)
+ points_inside.emplace_back((trafo.get_matrix().cast<float>() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast<float>());
+
+ for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs(
+ trafo, wxGetApp().plater()->get_camera(), points_inside,
+ m_c->object_clipper()->get_clipping_plane()))
+ {
+ if (idx >= orig_pts_num) // this is a cone-base, get index of point it belongs to
+ idx -= orig_pts_num;
+ if (rectangle_status == GLSelectionRectangle::EState::Deselect)
+ unselect_point(points_idxs[idx]);
+ else
+ select_point(points_idxs[idx]);
+ }
+ return true;
+ }
+
+ // left up with no selection rectangle
+ if (action == SLAGizmoEventType::LeftUp) {
+ if (m_wait_for_up_event) {
+ m_wait_for_up_event = false;
+ return true;
+ }
+ }
+
+ // dragging the selection rectangle:
+ if (action == SLAGizmoEventType::Dragging) {
+ if (m_wait_for_up_event)
+ return true; // point has been placed and the button not released yet
+ // this prevents GLCanvas from starting scene rotation
+
+ if (m_selection_rectangle.is_dragging()) {
+ m_selection_rectangle.dragging(mouse_position);
+ return true;
+ }
+
+ return false;
+ }
+
+ if (action == SLAGizmoEventType::Delete) {
+ // delete key pressed
+ delete_selected_points();
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::ApplyChanges) {
+ editing_mode_apply_changes();
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::DiscardChanges) {
+ ask_about_changes_call_after([this](){ editing_mode_apply_changes(); },
+ [this](){ editing_mode_discard_changes(); });
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::RightDown) {
+ if (m_hover_id != -1) {
+ select_point(NoPoints);
+ select_point(m_hover_id);
+ delete_selected_points();
+ return true;
+ }
+ return false;
+ }
+
+ if (action == SLAGizmoEventType::SelectAll) {
+ select_point(AllPoints);
+ return true;
+ }
+ }
+
+ if (!m_editing_mode) {
+ if (action == SLAGizmoEventType::AutomaticGeneration) {
+ auto_generate();
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::ManualEditing) {
+ switch_to_editing_mode();
+ return true;
+ }
+ }
+
+ if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
+ double pos = m_c->object_clipper()->get_position();
+ pos = std::min(1., pos + 0.01);
+ m_c->object_clipper()->set_position(pos, true);
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
+ double pos = m_c->object_clipper()->get_position();
+ pos = std::max(0., pos - 0.01);
+ m_c->object_clipper()->set_position(pos, true);
+ return true;
+ }
+
+ if (action == SLAGizmoEventType::ResetClippingPlane) {
+ m_c->object_clipper()->set_position(-1., false);
+ return true;
+ }
+
+ return false;
+}
+
+void GLGizmoSlaSupports::delete_selected_points(bool force)
+{
+ if (! m_editing_mode) {
+ std::cout << "DEBUGGING: delete_selected_points called out of editing mode!" << std::endl;
+ std::abort();
+ }
+
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point"));
+
+ for (unsigned int idx=0; idx<m_editing_cache.size(); ++idx) {
+ if (m_editing_cache[idx].selected && (!m_editing_cache[idx].support_point.is_new_island || !m_lock_unique_islands || force)) {
+ m_editing_cache.erase(m_editing_cache.begin() + (idx--));
+ }
+ }
+
+ select_point(NoPoints);
+}
+
+std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vector<std::string>& keys) const
+{
+ std::vector<const ConfigOption*> out;
+ const ModelObject* mo = m_c->selection_info()->model_object();
+
+ if (! mo)
+ return out;
+
+ const DynamicPrintConfig& object_cfg = mo->config.get();
+ const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
+ std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
+
+ for (const std::string& key : keys) {
+ if (object_cfg.has(key))
+ out.push_back(object_cfg.option(key));
+ else
+ if (print_cfg.has(key))
+ out.push_back(print_cfg.option(key));
+ else { // we must get it from defaults
+ if (default_cfg == nullptr)
+ default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys));
+ out.push_back(default_cfg->option(key));
+ }
+ }
+
+ return out;
+}
+
+
+
+/*
+void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& idxs) const
+{
+ if (aabb->is_leaf()) { // this is a facet
+ // corner.dot(normal) - offset
+ idxs.push_back(aabb->m_primitive);
+ }
+ else { // not a leaf
+ using CornerType = Eigen::AlignedBox<float, 3>::CornerType;
+ bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0))));
+ for (unsigned int i=1; i<8; ++i)
+ if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) {
+ find_intersecting_facets(aabb->m_left, normal, offset, idxs);
+ find_intersecting_facets(aabb->m_right, normal, offset, idxs);
+ }
+ }
+}
+
+
+
+void GLGizmoSlaSupports::make_line_segments() const
+{
+ TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh);
+ Vec3f normal(0.f, 1.f, 1.f);
+ double d = 0.;
+
+ std::vector<IntersectionLine> lines;
+ find_intersections(&m_AABB, normal, d, lines);
+ ExPolygons expolys;
+ tms.make_expolygons_simple(lines, &expolys);
+
+ SVG svg("slice_loops.svg", get_extents(expolys));
+ svg.draw(expolys);
+ //for (const IntersectionLine &l : lines[i])
+ // svg.draw(l, "red", 0);
+ //svg.draw_outline(expolygons, "black", "blue", 0);
+ svg.Close();
+}
+*/
+
+
+void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit)
+{
+ static float last_y = 0.0f;
+ static float last_h = 0.0f;
+
+ ModelObject* mo = m_c->selection_info()->model_object();
+
+ if (! mo)
+ return;
+
+ bool first_run = true; // This is a hack to redraw the button when all points are removed,
+ // so it is not delayed until the background process finishes.
+RENDER_AGAIN:
+ //m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
+ //const ImVec2 window_size(m_imgui->scaled(18.f, 16.f));
+ //ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) ));
+ //ImGui::SetNextWindowSize(ImVec2(window_size));
+
+ m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
+
+ // adjust window position to avoid overlap the view toolbar
+ float win_h = ImGui::GetWindowHeight();
+ y = std::min(y, bottom_limit - win_h);
+ ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
+ if ((last_h != win_h) || (last_y != y))
+ {
+ // ask canvas for another frame to render the window in the correct position
+ m_imgui->set_requires_extra_frame();
+ if (last_h != win_h)
+ last_h = win_h;
+ if (last_y != y)
+ last_y = y;
+ }
+
+ // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
+
+ const float settings_sliders_left = std::max(m_imgui->calc_text_size(m_desc.at("minimal_distance")).x, m_imgui->calc_text_size(m_desc.at("points_density")).x) + m_imgui->scaled(1.f);
+ const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
+ const float diameter_slider_left = m_imgui->calc_text_size(m_desc.at("head_diameter")).x + m_imgui->scaled(1.f);
+ const float minimal_slider_width = m_imgui->scaled(4.f);
+ const float buttons_width_approx = m_imgui->calc_text_size(m_desc.at("apply_changes")).x + m_imgui->calc_text_size(m_desc.at("discard_changes")).x + m_imgui->scaled(1.5f);
+ const float lock_supports_width_approx = m_imgui->calc_text_size(m_desc.at("lock_supports")).x + m_imgui->scaled(2.f);
+
+ float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left);
+ window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx);
+
+ bool force_refresh = false;
+ bool remove_selected = false;
+ bool remove_all = false;
+
+ if (m_editing_mode) {
+
+ float diameter_upper_cap = static_cast<ConfigOptionFloat*>(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value;
+ if (m_new_point_head_diameter > diameter_upper_cap)
+ m_new_point_head_diameter = diameter_upper_cap;
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("head_diameter"));
+ ImGui::SameLine(diameter_slider_left);
+ ImGui::PushItemWidth(window_width - diameter_slider_left);
+
+ // Following is a nasty way to:
+ // - save the initial value of the slider before one starts messing with it
+ // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
+ // - take correct undo/redo snapshot after the user is done with moving the slider
+ float initial_value = m_new_point_head_diameter;
+ m_imgui->slider_float("##head_diameter", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f");
+ if (m_imgui->get_last_slider_status().clicked) {
+ if (m_old_point_head_diameter == 0.f)
+ m_old_point_head_diameter = initial_value;
+ }
+ if (m_imgui->get_last_slider_status().edited) {
+ for (auto& cache_entry : m_editing_cache)
+ if (cache_entry.selected)
+ cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f;
+ }
+ if (m_imgui->get_last_slider_status().deactivated_after_edit) {
+ // momentarily restore the old value to take snapshot
+ for (auto& cache_entry : m_editing_cache)
+ if (cache_entry.selected)
+ cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f;
+ float backup = m_new_point_head_diameter;
+ m_new_point_head_diameter = m_old_point_head_diameter;
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change point head diameter"));
+ m_new_point_head_diameter = backup;
+ for (auto& cache_entry : m_editing_cache)
+ if (cache_entry.selected)
+ cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f;
+ m_old_point_head_diameter = 0.f;
+ }
+
+ bool changed = m_lock_unique_islands;
+ m_imgui->checkbox(m_desc.at("lock_supports"), m_lock_unique_islands);
+ force_refresh |= changed != m_lock_unique_islands;
+
+ m_imgui->disabled_begin(m_selection_empty);
+ remove_selected = m_imgui->button(m_desc.at("remove_selected"));
+ m_imgui->disabled_end();
+
+ m_imgui->disabled_begin(m_editing_cache.empty());
+ remove_all = m_imgui->button(m_desc.at("remove_all"));
+ m_imgui->disabled_end();
+
+ m_imgui->text(" "); // vertical gap
+
+ if (m_imgui->button(m_desc.at("apply_changes"))) {
+ editing_mode_apply_changes();
+ force_refresh = true;
+ }
+ ImGui::SameLine();
+ bool discard_changes = m_imgui->button(m_desc.at("discard_changes"));
+ if (discard_changes) {
+ editing_mode_discard_changes();
+ force_refresh = true;
+ }
+ }
+ else { // not in editing mode:
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("minimal_distance"));
+ ImGui::SameLine(settings_sliders_left);
+ ImGui::PushItemWidth(window_width - settings_sliders_left);
+
+ std::vector<const ConfigOption*> opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"});
+ float density = static_cast<const ConfigOptionInt*>(opts[0])->value;
+ float minimal_point_distance = static_cast<const ConfigOptionFloat*>(opts[1])->value;
+
+ m_imgui->slider_float("##minimal_point_distance", &minimal_point_distance, 0.f, 20.f, "%.f mm");
+ bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider
+ bool slider_edited = m_imgui->get_last_slider_status().edited; // someone is dragging the slider
+ bool slider_released = m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider
+
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("points_density"));
+ ImGui::SameLine(settings_sliders_left);
+
+ m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%");
+ slider_clicked |= m_imgui->get_last_slider_status().clicked;
+ slider_edited |= m_imgui->get_last_slider_status().edited;
+ slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
+
+ if (slider_clicked) { // stash the values of the settings so we know what to revert to after undo
+ m_minimal_point_distance_stash = minimal_point_distance;
+ m_density_stash = density;
+ }
+ if (slider_edited) {
+ mo->config.set("support_points_minimal_distance", minimal_point_distance);
+ mo->config.set("support_points_density_relative", (int)density);
+ }
+ if (slider_released) {
+ mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash);
+ mo->config.set("support_points_density_relative", (int)m_density_stash);
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change"));
+ mo->config.set("support_points_minimal_distance", minimal_point_distance);
+ mo->config.set("support_points_density_relative", (int)density);
+ wxGetApp().obj_list()->update_and_show_object_settings_item();
+ }
+
+ bool generate = m_imgui->button(m_desc.at("auto_generate"));
+
+ if (generate)
+ auto_generate();
+
+ ImGui::Separator();
+ if (m_imgui->button(m_desc.at("manual_editing")))
+ switch_to_editing_mode();
+
+ m_imgui->disabled_begin(m_normal_cache.empty());
+ remove_all = m_imgui->button(m_desc.at("remove_all"));
+ m_imgui->disabled_end();
+
+ // m_imgui->text("");
+ // m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) :
+ // (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) :
+ // (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) :
+ // (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS"))));
+ }
+
+
+ // Following is rendered in both editing and non-editing mode:
+ ImGui::Separator();
+ if (m_c->object_clipper()->get_position() == 0.f) {
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_desc.at("clipping_of_view"));
+ }
+ else {
+ if (m_imgui->button(m_desc.at("reset_direction"))) {
+ wxGetApp().CallAfter([this](){
+ m_c->object_clipper()->set_position(-1., false);
+ });
+ }
+ }
+
+ ImGui::SameLine(clipping_slider_left);
+ ImGui::PushItemWidth(window_width - clipping_slider_left);
+ float clp_dist = m_c->object_clipper()->get_position();
+ if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
+ m_c->object_clipper()->set_position(clp_dist, true);
+
+
+ if (m_imgui->button("?")) {
+ wxGetApp().CallAfter([]() {
+ SlaGizmoHelpDialog help_dlg;
+ help_dlg.ShowModal();
+ });
+ }
+
+ m_imgui->end();
+
+ if (remove_selected || remove_all) {
+ force_refresh = false;
+ m_parent.set_as_dirty();
+ bool was_in_editing = m_editing_mode;
+ if (! was_in_editing)
+ switch_to_editing_mode();
+ if (remove_all) {
+ select_point(AllPoints);
+ delete_selected_points(true); // true - delete regardless of locked status
+ }
+ if (remove_selected)
+ delete_selected_points(false); // leave locked points
+ if (! was_in_editing)
+ editing_mode_apply_changes();
+
+ if (first_run) {
+ first_run = false;
+ goto RENDER_AGAIN;
+ }
+ }
+
+ if (force_refresh)
+ m_parent.set_as_dirty();
+}
+
+bool GLGizmoSlaSupports::on_is_activable() const
+{
+ const Selection& selection = m_parent.get_selection();
+
+ if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA
+ || !selection.is_from_single_instance())
+ return false;
+
+ // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
+ const Selection::IndicesList& list = selection.get_volume_idxs();
+ for (const auto& idx : list)
+ if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0)
+ return false;
+
+ return true;
+}
+
+bool GLGizmoSlaSupports::on_is_selectable() const
+{
+ return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA);
+}
+
+std::string GLGizmoSlaSupports::on_get_name() const
+{
+ return _u8L("SLA Support Points");
+}
+
+CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const
+{
+ return CommonGizmosDataID(
+ int(CommonGizmosDataID::SelectionInfo)
+ | int(CommonGizmosDataID::InstancesHider)
+ | int(CommonGizmosDataID::Raycaster)
+ | int(CommonGizmosDataID::HollowedMesh)
+ | int(CommonGizmosDataID::ObjectClipper)
+ | int(CommonGizmosDataID::SupportsClipper));
+}
+
+
+
+void GLGizmoSlaSupports::ask_about_changes_call_after(std::function<void()> on_yes, std::function<void()> on_no)
+{
+ wxGetApp().CallAfter([on_yes, on_no]() {
+ // Following is called through CallAfter, because otherwise there was a problem
+ // on OSX with the wxMessageDialog being shown several times when clicked into.
+ MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually "
+ "edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL );
+ int ret = dlg.ShowModal();
+ if (ret == wxID_YES)
+ on_yes();
+ else if (ret == wxID_NO)
+ on_no();
+ });
+}
+
+
+void GLGizmoSlaSupports::on_set_state()
+{
+ if (m_state == m_old_state)
+ return;
+
+ if (m_state == On && m_old_state != On) { // the gizmo was just turned on
+ // Set default head diameter from config.
+ const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
+ m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
+ }
+ if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
+ bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable();
+ if (will_ask) {
+ ask_about_changes_call_after([this](){ editing_mode_apply_changes(); },
+ [this](){ editing_mode_discard_changes(); });
+ // refuse to be turned off so the gizmo is active when the CallAfter is executed
+ m_state = m_old_state;
+ }
+ else {
+ // we are actually shutting down
+ disable_editing_mode(); // so it is not active next time the gizmo opens
+ m_old_mo_id = -1;
+ }
+ }
+ m_old_state = m_state;
+}
+
+
+
+void GLGizmoSlaSupports::on_start_dragging()
+{
+ if (m_hover_id != -1) {
+ select_point(NoPoints);
+ select_point(m_hover_id);
+ m_point_before_drag = m_editing_cache[m_hover_id];
+ }
+ else
+ m_point_before_drag = CacheEntry();
+}
+
+
+void GLGizmoSlaSupports::on_stop_dragging()
+{
+ if (m_hover_id != -1) {
+ CacheEntry backup = m_editing_cache[m_hover_id];
+
+ if (m_point_before_drag.support_point.pos != Vec3f::Zero() // some point was touched
+ && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected
+ {
+ m_editing_cache[m_hover_id] = m_point_before_drag;
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point"));
+ m_editing_cache[m_hover_id] = backup;
+ }
+ }
+ m_point_before_drag = CacheEntry();
+}
+
+void GLGizmoSlaSupports::on_dragging(const UpdateData &data)
+{
+ assert(m_hover_id != -1);
+ if (!m_editing_mode) return;
+ if (m_editing_cache[m_hover_id].support_point.is_new_island && m_lock_unique_islands)
+ return;
+
+ std::pair<Vec3f, Vec3f> pos_and_normal;
+ if (!unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
+ return;
+
+ m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first;
+ m_editing_cache[m_hover_id].support_point.is_new_island = false;
+ m_editing_cache[m_hover_id].normal = pos_and_normal.second;
+}
+
+void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
+{
+ ar(m_new_point_head_diameter,
+ m_normal_cache,
+ m_editing_cache,
+ m_selection_empty
+ );
+}
+
+
+
+void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const
+{
+ ar(m_new_point_head_diameter,
+ m_normal_cache,
+ m_editing_cache,
+ m_selection_empty
+ );
+}
+
+
+
+void GLGizmoSlaSupports::select_point(int i)
+{
+ if (! m_editing_mode) {
+ std::cout << "DEBUGGING: select_point called when out of editing mode!" << std::endl;
+ std::abort();
+ }
+
+ if (i == AllPoints || i == NoPoints) {
+ for (auto& point_and_selection : m_editing_cache)
+ point_and_selection.selected = ( i == AllPoints );
+ m_selection_empty = (i == NoPoints);
+
+ if (i == AllPoints)
+ m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f;
+ }
+ else {
+ m_editing_cache[i].selected = true;
+ m_selection_empty = false;
+ m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f;
+ }
+}
+
+
+void GLGizmoSlaSupports::unselect_point(int i)
+{
+ if (! m_editing_mode) {
+ std::cout << "DEBUGGING: unselect_point called when out of editing mode!" << std::endl;
+ std::abort();
+ }
+
+ m_editing_cache[i].selected = false;
+ m_selection_empty = true;
+ for (const CacheEntry& ce : m_editing_cache) {
+ if (ce.selected) {
+ m_selection_empty = false;
+ break;
+ }
+ }
+}
+
+
+
+
+void GLGizmoSlaSupports::editing_mode_discard_changes()
+{
+ if (! m_editing_mode) {
+ std::cout << "DEBUGGING: editing_mode_discard_changes called when out of editing mode!" << std::endl;
+ std::abort();
+ }
+ select_point(NoPoints);
+ disable_editing_mode();
+}
+
+
+
+void GLGizmoSlaSupports::editing_mode_apply_changes()
+{
+ // If there are no changes, don't touch the front-end. The data in the cache could have been
+ // taken from the backend and copying them to ModelObject would needlessly invalidate them.
+ disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken
+
+ if (unsaved_changes()) {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit"));
+
+ m_normal_cache.clear();
+ for (const CacheEntry& ce : m_editing_cache)
+ m_normal_cache.push_back(ce.support_point);
+
+ ModelObject* mo = m_c->selection_info()->model_object();
+ mo->sla_points_status = sla::PointsStatus::UserModified;
+ mo->sla_support_points.clear();
+ mo->sla_support_points = m_normal_cache;
+
+ reslice_SLA_supports();
+ }
+}
+
+
+
+void GLGizmoSlaSupports::reload_cache()
+{
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ m_normal_cache.clear();
+ if (mo->sla_points_status == sla::PointsStatus::AutoGenerated || mo->sla_points_status == sla::PointsStatus::Generating)
+ get_data_from_backend();
+ else
+ for (const sla::SupportPoint& point : mo->sla_support_points)
+ m_normal_cache.emplace_back(point);
+}
+
+
+bool GLGizmoSlaSupports::has_backend_supports() const
+{
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ if (! mo)
+ return false;
+
+ // find SlaPrintObject with this ID
+ for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
+ if (po->model_object()->id() == mo->id())
+ return po->is_step_done(slaposSupportPoints);
+ }
+ return false;
+}
+
+void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const
+{
+ wxGetApp().CallAfter([this, postpone_error_messages]() {
+ wxGetApp().plater()->reslice_SLA_supports(
+ *m_c->selection_info()->model_object(), postpone_error_messages);
+ });
+}
+
+bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event){
+ if (mouse_event.Moving()) return false;
+ if (use_grabbers(mouse_event)) return true;
+
+ // wxCoord == int --> wx/types.h
+ Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
+ Vec2d mouse_pos = mouse_coord.cast<double>();
+
+ static bool pending_right_up = false;
+ if (mouse_event.LeftDown()) {
+ bool grabber_contains_mouse = (get_hover_id() != -1);
+ bool control_down = mouse_event.CmdDown();
+ if ((!control_down || grabber_contains_mouse) &&
+ gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false))
+ return true;
+ } else if (mouse_event.Dragging()) {
+ bool control_down = mouse_event.CmdDown();
+ if (m_parent.get_move_volume_id() != -1) {
+ // don't allow dragging objects with the Sla gizmo on
+ return true;
+ } else if (!control_down &&
+ gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) {
+ // the gizmo got the event and took some action, no need to do
+ // anything more here
+ m_parent.set_as_dirty();
+ return true;
+ } else if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())){
+ // CTRL has been pressed while already dragging -> stop current action
+ if (mouse_event.LeftIsDown())
+ gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
+ else if (mouse_event.RightIsDown())
+ pending_right_up = false;
+ }
+ } else if (mouse_event.LeftUp() && !m_parent.is_mouse_dragging()) {
+ // in case SLA/FDM gizmo is selected, we just pass the LeftUp event
+ // and stop processing - neither object moving or selecting is
+ // suppressed in that case
+ gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown());
+ return true;
+ }else if (mouse_event.RightDown()){
+ if (m_parent.get_selection().get_object_idx() != -1 &&
+ gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) {
+ // we need to set the following right up as processed to avoid showing
+ // the context menu if the user release the mouse over the object
+ pending_right_up = true;
+ // event was taken care of by the SlaSupports gizmo
+ return true;
+ }
+ } else if (pending_right_up && mouse_event.RightUp()) {
+ pending_right_up = false;
+ return true;
+ }
+ return false;
+}
+
+void GLGizmoSlaSupports::get_data_from_backend()
+{
+ if (! has_backend_supports())
+ return;
+ ModelObject* mo = m_c->selection_info()->model_object();
+
+ // find the respective SLAPrintObject, we need a pointer to it
+ for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
+ if (po->model_object()->id() == mo->id()) {
+ m_normal_cache.clear();
+ const std::vector<sla::SupportPoint>& points = po->get_support_points();
+ auto mat = (po->trafo() * po->model_object()->volumes.front()->get_transformation().get_matrix()).inverse().cast<float>();
+ for (unsigned int i=0; i<points.size();++i)
+ m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island));
+
+ mo->sla_points_status = sla::PointsStatus::AutoGenerated;
+ break;
+ }
+ }
+
+ // We don't copy the data into ModelObject, as this would stop the background processing.
+}
+
+
+
+void GLGizmoSlaSupports::auto_generate()
+{
+ //wxMessageDialog dlg(GUI::wxGetApp().plater(),
+ MessageDialog dlg(GUI::wxGetApp().plater(),
+ _L("Autogeneration will erase all manually edited points.") + "\n\n" +
+ _L("Are you sure you want to do it?") + "\n",
+ _L("Warning"), wxICON_WARNING | wxYES | wxNO);
+
+ ModelObject* mo = m_c->selection_info()->model_object();
+
+ if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points"));
+ wxGetApp().CallAfter([this]() { reslice_SLA_supports(); });
+ mo->sla_points_status = sla::PointsStatus::Generating;
+ }
+}
+
+
+
+void GLGizmoSlaSupports::switch_to_editing_mode()
+{
+ wxGetApp().plater()->enter_gizmos_stack();
+ m_editing_mode = true;
+ m_editing_cache.clear();
+ for (const sla::SupportPoint& sp : m_normal_cache)
+ m_editing_cache.emplace_back(sp);
+ select_point(NoPoints);
+
+ m_c->instances_hider()->show_supports(false);
+ m_parent.set_as_dirty();
+}
+
+
+void GLGizmoSlaSupports::disable_editing_mode()
+{
+ if (m_editing_mode) {
+ m_editing_mode = false;
+ wxGetApp().plater()->leave_gizmos_stack();
+ m_c->instances_hider()->show_supports(true);
+ m_parent.set_as_dirty();
+ }
+ wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode);
+}
+
+
+
+bool GLGizmoSlaSupports::unsaved_changes() const
+{
+ if (m_editing_cache.size() != m_normal_cache.size())
+ return true;
+
+ for (size_t i=0; i<m_editing_cache.size(); ++i)
+ if (m_editing_cache[i].support_point != m_normal_cache[i])
+ return true;
+
+ return false;
+}
+
+SlaGizmoHelpDialog::SlaGizmoHelpDialog()
+: wxDialog(nullptr, wxID_ANY, _L("SLA gizmo keyboard shortcuts"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
+{
+ SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+ const wxString ctrl = GUI::shortkey_ctrl_prefix();
+ const wxString alt = GUI::shortkey_alt_prefix();
+
+
+ // fonts
+ const wxFont& font = wxGetApp().small_font();
+ const wxFont& bold_font = wxGetApp().bold_font();
+
+ auto note_text = new wxStaticText(this, wxID_ANY, _L("Note: some shortcuts work in (non)editing mode only."));
+ note_text->SetFont(font);
+
+ auto vsizer = new wxBoxSizer(wxVERTICAL);
+ auto gridsizer = new wxFlexGridSizer(2, 5, 15);
+ auto hsizer = new wxBoxSizer(wxHORIZONTAL);
+
+ hsizer->AddSpacer(20);
+ hsizer->Add(vsizer);
+ hsizer->AddSpacer(20);
+
+ vsizer->AddSpacer(20);
+ vsizer->Add(note_text, 1, wxALIGN_CENTRE_HORIZONTAL);
+ vsizer->AddSpacer(20);
+ vsizer->Add(gridsizer);
+ vsizer->AddSpacer(20);
+
+ std::vector<std::pair<wxString, wxString>> shortcuts;
+ shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add point")));
+ shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove point")));
+ shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move point")));
+ shortcuts.push_back(std::make_pair(ctrl+_L("Left click"), _L("Add point to selection")));
+ shortcuts.push_back(std::make_pair(alt+_L("Left click"), _L("Remove point from selection")));
+ shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle")));
+ shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _L("Deselect by rectangle")));
+ shortcuts.push_back(std::make_pair(ctrl+"A", _L("Select all points")));
+ shortcuts.push_back(std::make_pair("Delete", _L("Remove selected points")));
+ shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane")));
+ shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane")));
+ shortcuts.push_back(std::make_pair("Enter", _L("Apply changes")));
+ shortcuts.push_back(std::make_pair("Esc", _L("Discard changes")));
+ shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode")));
+ shortcuts.push_back(std::make_pair("A", _L("Auto-generate points")));
+
+ for (const auto& pair : shortcuts) {
+ auto shortcut = new wxStaticText(this, wxID_ANY, pair.first);
+ auto desc = new wxStaticText(this, wxID_ANY, pair.second);
+ shortcut->SetFont(bold_font);
+ desc->SetFont(font);
+ gridsizer->Add(shortcut, -1, wxALIGN_CENTRE_VERTICAL);
+ gridsizer->Add(desc, -1, wxALIGN_CENTRE_VERTICAL);
+ }
+
+ SetSizer(hsizer);
+ hsizer->SetSizeHints(this);
+}
+
+
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp
index f1156f937..a77c1dd30 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp
@@ -1,567 +1,567 @@
-#include "GLGizmosCommon.hpp"
-
-#include <cassert>
-
-#include "slic3r/GUI/GLCanvas3D.hpp"
-#include "libslic3r/SLAPrint.hpp"
-#include "slic3r/GUI/GUI_App.hpp"
-#include "slic3r/GUI/Camera.hpp"
-#include "slic3r/GUI/Plater.hpp"
-
-#include "libslic3r/PresetBundle.hpp"
-
-#include <GL/glew.h>
-
-namespace Slic3r {
-namespace GUI {
-
-using namespace CommonGizmosDataObjects;
-
-CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas)
- : m_canvas(canvas)
-{
- using c = CommonGizmosDataID;
- m_data[c::SelectionInfo].reset( new SelectionInfo(this));
- m_data[c::InstancesHider].reset( new InstancesHider(this));
- m_data[c::HollowedMesh].reset( new HollowedMesh(this));
- m_data[c::Raycaster].reset( new Raycaster(this));
- m_data[c::ObjectClipper].reset( new ObjectClipper(this));
- m_data[c::SupportsClipper].reset( new SupportsClipper(this));
-
-}
-
-void CommonGizmosDataPool::update(CommonGizmosDataID required)
-{
- assert(check_dependencies(required));
- for (auto& [id, data] : m_data) {
- if (int(required) & int(CommonGizmosDataID(id)))
- data->update();
- else
- if (data->is_valid())
- data->release();
-
- }
-}
-
-
-SelectionInfo* CommonGizmosDataPool::selection_info() const
-{
- SelectionInfo* sel_info = dynamic_cast<SelectionInfo*>(m_data.at(CommonGizmosDataID::SelectionInfo).get());
- assert(sel_info);
- return sel_info->is_valid() ? sel_info : nullptr;
-}
-
-
-InstancesHider* CommonGizmosDataPool::instances_hider() const
-{
- InstancesHider* inst_hider = dynamic_cast<InstancesHider*>(m_data.at(CommonGizmosDataID::InstancesHider).get());
- assert(inst_hider);
- return inst_hider->is_valid() ? inst_hider : nullptr;
-}
-
-HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const
-{
- HollowedMesh* hol_mesh = dynamic_cast<HollowedMesh*>(m_data.at(CommonGizmosDataID::HollowedMesh).get());
- assert(hol_mesh);
- return hol_mesh->is_valid() ? hol_mesh : nullptr;
-}
-
-Raycaster* CommonGizmosDataPool::raycaster() const
-{
- Raycaster* rc = dynamic_cast<Raycaster*>(m_data.at(CommonGizmosDataID::Raycaster).get());
- assert(rc);
- return rc->is_valid() ? rc : nullptr;
-}
-
-ObjectClipper* CommonGizmosDataPool::object_clipper() const
-{
- ObjectClipper* oc = dynamic_cast<ObjectClipper*>(m_data.at(CommonGizmosDataID::ObjectClipper).get());
- // ObjectClipper is used from outside the gizmos to report current clipping plane.
- // This function can be called when oc is nullptr.
- return (oc && oc->is_valid()) ? oc : nullptr;
-}
-
-SupportsClipper* CommonGizmosDataPool::supports_clipper() const
-{
- SupportsClipper* sc = dynamic_cast<SupportsClipper*>(m_data.at(CommonGizmosDataID::SupportsClipper).get());
- assert(sc);
- return sc->is_valid() ? sc : nullptr;
-}
-
-#ifndef NDEBUG
-// Check the required resources one by one and return true if all
-// dependencies are met.
-bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const
-{
- // This should iterate over currently required data. Each of them should
- // be asked about its dependencies and it must check that all dependencies
- // are also in required and before the current one.
- for (auto& [id, data] : m_data) {
- // in case we don't use this, the deps are irrelevant
- if (! (int(required) & int(CommonGizmosDataID(id))))
- continue;
-
-
- CommonGizmosDataID deps = data->get_dependencies();
- assert(int(deps) == (int(deps) & int(required)));
- }
-
-
- return true;
-}
-#endif // NDEBUG
-
-
-
-
-void SelectionInfo::on_update()
-{
- const Selection& selection = get_pool()->get_canvas()->get_selection();
- if (selection.is_single_full_instance()) {
- m_model_object = selection.get_model()->objects[selection.get_object_idx()];
- m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
- }
- else
- m_model_object = nullptr;
-}
-
-void SelectionInfo::on_release()
-{
- m_model_object = nullptr;
-}
-
-int SelectionInfo::get_active_instance() const
-{
- const Selection& selection = get_pool()->get_canvas()->get_selection();
- return selection.get_instance_idx();
-}
-
-
-
-
-
-void InstancesHider::on_update()
-{
- const ModelObject* mo = get_pool()->selection_info()->model_object();
- int active_inst = get_pool()->selection_info()->get_active_instance();
- GLCanvas3D* canvas = get_pool()->get_canvas();
-
- if (mo && active_inst != -1) {
- canvas->toggle_model_objects_visibility(false);
- canvas->toggle_model_objects_visibility(true, mo, active_inst);
- canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst);
- canvas->set_use_clipping_planes(true);
- // Some objects may be sinking, do not show whatever is below the bed.
- canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
- canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits<double>::max()));
-
-
- std::vector<const TriangleMesh*> meshes;
- for (const ModelVolume* mv : mo->volumes)
- meshes.push_back(&mv->mesh());
-
- if (meshes != m_old_meshes) {
- m_clippers.clear();
- for (const TriangleMesh* mesh : meshes) {
- m_clippers.emplace_back(new MeshClipper);
- m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
- m_clippers.back()->set_mesh(*mesh);
- }
- m_old_meshes = meshes;
- }
- }
- else
- canvas->toggle_model_objects_visibility(true);
-}
-
-void InstancesHider::on_release()
-{
- get_pool()->get_canvas()->toggle_model_objects_visibility(true);
- get_pool()->get_canvas()->set_use_clipping_planes(false);
- m_old_meshes.clear();
- m_clippers.clear();
-}
-
-void InstancesHider::show_supports(bool show) {
- if (m_show_supports != show) {
- m_show_supports = show;
- on_update();
- }
-}
-
-void InstancesHider::render_cut() const
-{
- const SelectionInfo* sel_info = get_pool()->selection_info();
- const ModelObject* mo = sel_info->model_object();
- Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
-
- size_t clipper_id = 0;
- for (const ModelVolume* mv : mo->volumes) {
- Geometry::Transformation vol_trafo = mv->get_transformation();
- Geometry::Transformation trafo = inst_trafo * vol_trafo;
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
-
- auto& clipper = m_clippers[clipper_id];
- clipper->set_transformation(trafo);
- const ObjectClipper* obj_clipper = get_pool()->object_clipper();
- if (obj_clipper->is_valid() && obj_clipper->get_clipping_plane()
- && obj_clipper->get_position() != 0.) {
- ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane();
- clp.set_normal(-clp.get_normal());
- clipper->set_limiting_plane(clp);
- }
- else
- clipper->set_limiting_plane(ClippingPlane::ClipsNothing());
-
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPushMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- if (mv->is_model_part())
- glsafe(::glColor3f(0.8f, 0.3f, 0.0f));
- else {
- const ColorRGBA color = color_from_model_volume(*mv);
- glsafe(::glColor4fv(color.data()));
- }
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
- glsafe(::glPushAttrib(GL_DEPTH_TEST));
- glsafe(::glDisable(GL_DEPTH_TEST));
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv));
-#else
- clipper->render_cut();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- glsafe(::glPopAttrib());
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-
- ++clipper_id;
- }
-}
-
-
-
-void HollowedMesh::on_update()
-{
- const ModelObject* mo = get_pool()->selection_info()->model_object();
- bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA;
- if (! mo || ! is_sla)
- return;
-
- const GLCanvas3D* canvas = get_pool()->get_canvas();
- const PrintObjects& print_objects = canvas->sla_print()->objects();
- const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size()))
- ? print_objects[m_print_object_idx]
- : nullptr;
-
- // Find the respective SLAPrintObject.
- if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) {
- m_print_objects_count = print_objects.size();
- m_print_object_idx = -1;
- for (const SLAPrintObject* po : print_objects) {
- ++m_print_object_idx;
- if (po->model_object()->id() == mo->id()) {
- print_object = po;
- break;
- }
- }
- }
-
- // If there is a valid SLAPrintObject, check state of Hollowing step.
- if (print_object) {
- if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) {
- size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp;
- if (timestamp > m_old_hollowing_timestamp) {
- const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice();
- if (! backend_mesh.empty()) {
- m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh));
- Transform3d trafo_inv = (canvas->sla_print()->sla_trafo(*mo) * print_object->model_object()->volumes.front()->get_transformation().get_matrix()).inverse();
- m_hollowed_mesh_transformed->transform(trafo_inv);
- m_drainholes = print_object->model_object()->sla_drain_holes;
- m_old_hollowing_timestamp = timestamp;
-
- indexed_triangle_set interior = print_object->hollowed_interior_mesh();
- its_flip_triangles(interior);
- m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(std::move(interior));
- m_hollowed_interior_transformed->transform(trafo_inv);
- }
- else {
- m_hollowed_mesh_transformed.reset(nullptr);
- }
- }
- }
- else
- m_hollowed_mesh_transformed.reset(nullptr);
- }
-}
-
-
-void HollowedMesh::on_release()
-{
- m_hollowed_mesh_transformed.reset();
- m_old_hollowing_timestamp = 0;
- m_print_object_idx = -1;
-}
-
-
-const TriangleMesh* HollowedMesh::get_hollowed_mesh() const
-{
- return m_hollowed_mesh_transformed.get();
-}
-
-const TriangleMesh* HollowedMesh::get_hollowed_interior() const
-{
- return m_hollowed_interior_transformed.get();
-}
-
-
-
-
-void Raycaster::on_update()
-{
- wxBusyCursor wait;
- const ModelObject* mo = get_pool()->selection_info()->model_object();
-
- if (! mo)
- return;
-
- std::vector<const TriangleMesh*> meshes;
- const std::vector<ModelVolume*>& mvs = mo->volumes;
- if (mvs.size() == 1) {
- assert(mvs.front()->is_model_part());
- const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh();
- if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh())
- meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh());
- }
- if (meshes.empty()) {
- for (const ModelVolume* mv : mvs) {
- if (mv->is_model_part())
- meshes.push_back(&mv->mesh());
- }
- }
-
- if (meshes != m_old_meshes) {
- m_raycasters.clear();
- for (const TriangleMesh* mesh : meshes)
- m_raycasters.emplace_back(new MeshRaycaster(*mesh));
- m_old_meshes = meshes;
- }
-}
-
-void Raycaster::on_release()
-{
- m_raycasters.clear();
- m_old_meshes.clear();
-}
-
-std::vector<const MeshRaycaster*> Raycaster::raycasters() const
-{
- std::vector<const MeshRaycaster*> mrcs;
- for (const auto& raycaster_unique_ptr : m_raycasters)
- mrcs.push_back(raycaster_unique_ptr.get());
- return mrcs;
-}
-
-
-
-
-
-void ObjectClipper::on_update()
-{
- const ModelObject* mo = get_pool()->selection_info()->model_object();
- if (! mo)
- return;
-
- // which mesh should be cut?
- std::vector<const TriangleMesh*> meshes;
- bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh();
- if (has_hollowed)
- meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh());
-
- if (meshes.empty())
- for (const ModelVolume* mv : mo->volumes)
- meshes.push_back(&mv->mesh());
-
- if (meshes != m_old_meshes) {
- m_clippers.clear();
- for (const TriangleMesh* mesh : meshes) {
- m_clippers.emplace_back(new MeshClipper);
- m_clippers.back()->set_mesh(*mesh);
- }
- m_old_meshes = meshes;
-
- if (has_hollowed)
- m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior());
-
- m_active_inst_bb_radius =
- mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius();
- }
-}
-
-
-void ObjectClipper::on_release()
-{
- m_clippers.clear();
- m_old_meshes.clear();
- m_clp.reset();
- m_clp_ratio = 0.;
-
-}
-
-void ObjectClipper::render_cut() const
-{
- if (m_clp_ratio == 0.)
- return;
- const SelectionInfo* sel_info = get_pool()->selection_info();
- const ModelObject* mo = sel_info->model_object();
- const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
-
- size_t clipper_id = 0;
- for (const ModelVolume* mv : mo->volumes) {
- const Geometry::Transformation vol_trafo = mv->get_transformation();
- Geometry::Transformation trafo = inst_trafo * vol_trafo;
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
-
- auto& clipper = m_clippers[clipper_id];
- clipper->set_plane(*m_clp);
- clipper->set_transformation(trafo);
- clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPushMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f });
-#else
- glsafe(::glColor3f(1.0f, 0.37f, 0.0f));
- clipper->render_cut();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-
- ++clipper_id;
- }
-}
-
-
-void ObjectClipper::set_position(double pos, bool keep_normal)
-{
- const ModelObject* mo = get_pool()->selection_info()->model_object();
- int active_inst = get_pool()->selection_info()->get_active_instance();
- double z_shift = get_pool()->selection_info()->get_sla_shift();
-
- Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward();
- const Vec3d& center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift);
- float dist = normal.dot(center);
-
- if (pos < 0.)
- pos = m_clp_ratio;
-
- m_clp_ratio = pos;
- m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2*m_active_inst_bb_radius)));
- get_pool()->get_canvas()->set_as_dirty();
-}
-
-
-
-void SupportsClipper::on_update()
-{
- const ModelObject* mo = get_pool()->selection_info()->model_object();
- bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA;
- if (! mo || ! is_sla)
- return;
-
- const GLCanvas3D* canvas = get_pool()->get_canvas();
- const PrintObjects& print_objects = canvas->sla_print()->objects();
- const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size()))
- ? print_objects[m_print_object_idx]
- : nullptr;
-
- // Find the respective SLAPrintObject.
- if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) {
- m_print_objects_count = print_objects.size();
- m_print_object_idx = -1;
- for (const SLAPrintObject* po : print_objects) {
- ++m_print_object_idx;
- if (po->model_object()->id() == mo->id()) {
- print_object = po;
- break;
- }
- }
- }
-
- if (print_object
- && print_object->is_step_done(slaposSupportTree)
- && ! print_object->support_mesh().empty())
- {
- // If the supports are already calculated, save the timestamp of the respective step
- // so we can later tell they were recalculated.
- size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
- if (! m_clipper || timestamp != m_old_timestamp) {
- // The timestamp has changed.
- m_clipper.reset(new MeshClipper);
- // The mesh should already have the shared vertices calculated.
- m_clipper->set_mesh(print_object->support_mesh());
- m_old_timestamp = timestamp;
- }
- }
- else
- // The supports are not valid. We better dump the cached data.
- m_clipper.reset();
-}
-
-
-void SupportsClipper::on_release()
-{
- m_clipper.reset();
- m_old_timestamp = 0;
- m_print_object_idx = -1;
-}
-
-void SupportsClipper::render_cut() const
-{
- const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper();
- if (ocl->get_position() == 0.
- || ! get_pool()->instances_hider()->are_supports_shown()
- || ! m_clipper)
- return;
-
- const SelectionInfo* sel_info = get_pool()->selection_info();
- const ModelObject* mo = sel_info->model_object();
- const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
- //Geometry::Transformation vol_trafo = mo->volumes.front()->get_transformation();
- Geometry::Transformation trafo = inst_trafo;// * vol_trafo;
- trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
-
-
- // Get transformation of supports
- Geometry::Transformation supports_trafo = trafo;
- supports_trafo.set_scaling_factor(Vec3d::Ones());
- supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift()));
- supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2)));
- // I don't know why, but following seems to be correct.
- supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2),
- 1,
- 1.));
-
- m_clipper->set_plane(*ocl->get_clipping_plane());
- m_clipper->set_transformation(supports_trafo);
-
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPushMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f });
-#else
- glsafe(::glColor3f(1.0f, 0.f, 0.37f));
- m_clipper->render_cut();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
- glsafe(::glPopMatrix());
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
-}
-
-
-} // namespace GUI
-} // namespace Slic3r
+#include "GLGizmosCommon.hpp"
+
+#include <cassert>
+
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "libslic3r/SLAPrint.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/Camera.hpp"
+#include "slic3r/GUI/Plater.hpp"
+
+#include "libslic3r/PresetBundle.hpp"
+
+#include <GL/glew.h>
+
+namespace Slic3r {
+namespace GUI {
+
+using namespace CommonGizmosDataObjects;
+
+CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas)
+ : m_canvas(canvas)
+{
+ using c = CommonGizmosDataID;
+ m_data[c::SelectionInfo].reset( new SelectionInfo(this));
+ m_data[c::InstancesHider].reset( new InstancesHider(this));
+ m_data[c::HollowedMesh].reset( new HollowedMesh(this));
+ m_data[c::Raycaster].reset( new Raycaster(this));
+ m_data[c::ObjectClipper].reset( new ObjectClipper(this));
+ m_data[c::SupportsClipper].reset( new SupportsClipper(this));
+
+}
+
+void CommonGizmosDataPool::update(CommonGizmosDataID required)
+{
+ assert(check_dependencies(required));
+ for (auto& [id, data] : m_data) {
+ if (int(required) & int(CommonGizmosDataID(id)))
+ data->update();
+ else
+ if (data->is_valid())
+ data->release();
+
+ }
+}
+
+
+SelectionInfo* CommonGizmosDataPool::selection_info() const
+{
+ SelectionInfo* sel_info = dynamic_cast<SelectionInfo*>(m_data.at(CommonGizmosDataID::SelectionInfo).get());
+ assert(sel_info);
+ return sel_info->is_valid() ? sel_info : nullptr;
+}
+
+
+InstancesHider* CommonGizmosDataPool::instances_hider() const
+{
+ InstancesHider* inst_hider = dynamic_cast<InstancesHider*>(m_data.at(CommonGizmosDataID::InstancesHider).get());
+ assert(inst_hider);
+ return inst_hider->is_valid() ? inst_hider : nullptr;
+}
+
+HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const
+{
+ HollowedMesh* hol_mesh = dynamic_cast<HollowedMesh*>(m_data.at(CommonGizmosDataID::HollowedMesh).get());
+ assert(hol_mesh);
+ return hol_mesh->is_valid() ? hol_mesh : nullptr;
+}
+
+Raycaster* CommonGizmosDataPool::raycaster() const
+{
+ Raycaster* rc = dynamic_cast<Raycaster*>(m_data.at(CommonGizmosDataID::Raycaster).get());
+ assert(rc);
+ return rc->is_valid() ? rc : nullptr;
+}
+
+ObjectClipper* CommonGizmosDataPool::object_clipper() const
+{
+ ObjectClipper* oc = dynamic_cast<ObjectClipper*>(m_data.at(CommonGizmosDataID::ObjectClipper).get());
+ // ObjectClipper is used from outside the gizmos to report current clipping plane.
+ // This function can be called when oc is nullptr.
+ return (oc && oc->is_valid()) ? oc : nullptr;
+}
+
+SupportsClipper* CommonGizmosDataPool::supports_clipper() const
+{
+ SupportsClipper* sc = dynamic_cast<SupportsClipper*>(m_data.at(CommonGizmosDataID::SupportsClipper).get());
+ assert(sc);
+ return sc->is_valid() ? sc : nullptr;
+}
+
+#ifndef NDEBUG
+// Check the required resources one by one and return true if all
+// dependencies are met.
+bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const
+{
+ // This should iterate over currently required data. Each of them should
+ // be asked about its dependencies and it must check that all dependencies
+ // are also in required and before the current one.
+ for (auto& [id, data] : m_data) {
+ // in case we don't use this, the deps are irrelevant
+ if (! (int(required) & int(CommonGizmosDataID(id))))
+ continue;
+
+
+ CommonGizmosDataID deps = data->get_dependencies();
+ assert(int(deps) == (int(deps) & int(required)));
+ }
+
+
+ return true;
+}
+#endif // NDEBUG
+
+
+
+
+void SelectionInfo::on_update()
+{
+ const Selection& selection = get_pool()->get_canvas()->get_selection();
+ if (selection.is_single_full_instance()) {
+ m_model_object = selection.get_model()->objects[selection.get_object_idx()];
+ m_z_shift = selection.get_first_volume()->get_sla_shift_z();
+ }
+ else
+ m_model_object = nullptr;
+}
+
+void SelectionInfo::on_release()
+{
+ m_model_object = nullptr;
+}
+
+int SelectionInfo::get_active_instance() const
+{
+ const Selection& selection = get_pool()->get_canvas()->get_selection();
+ return selection.get_instance_idx();
+}
+
+
+
+
+
+void InstancesHider::on_update()
+{
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+ int active_inst = get_pool()->selection_info()->get_active_instance();
+ GLCanvas3D* canvas = get_pool()->get_canvas();
+
+ if (mo && active_inst != -1) {
+ canvas->toggle_model_objects_visibility(false);
+ canvas->toggle_model_objects_visibility(true, mo, active_inst);
+ canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst);
+ canvas->set_use_clipping_planes(true);
+ // Some objects may be sinking, do not show whatever is below the bed.
+ canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
+ canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits<double>::max()));
+
+
+ std::vector<const TriangleMesh*> meshes;
+ for (const ModelVolume* mv : mo->volumes)
+ meshes.push_back(&mv->mesh());
+
+ if (meshes != m_old_meshes) {
+ m_clippers.clear();
+ for (const TriangleMesh* mesh : meshes) {
+ m_clippers.emplace_back(new MeshClipper);
+ m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
+ m_clippers.back()->set_mesh(*mesh);
+ }
+ m_old_meshes = meshes;
+ }
+ }
+ else
+ canvas->toggle_model_objects_visibility(true);
+}
+
+void InstancesHider::on_release()
+{
+ get_pool()->get_canvas()->toggle_model_objects_visibility(true);
+ get_pool()->get_canvas()->set_use_clipping_planes(false);
+ m_old_meshes.clear();
+ m_clippers.clear();
+}
+
+void InstancesHider::show_supports(bool show) {
+ if (m_show_supports != show) {
+ m_show_supports = show;
+ on_update();
+ }
+}
+
+void InstancesHider::render_cut() const
+{
+ const SelectionInfo* sel_info = get_pool()->selection_info();
+ const ModelObject* mo = sel_info->model_object();
+ Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
+
+ size_t clipper_id = 0;
+ for (const ModelVolume* mv : mo->volumes) {
+ Geometry::Transformation vol_trafo = mv->get_transformation();
+ Geometry::Transformation trafo = inst_trafo * vol_trafo;
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
+
+ auto& clipper = m_clippers[clipper_id];
+ clipper->set_transformation(trafo);
+ const ObjectClipper* obj_clipper = get_pool()->object_clipper();
+ if (obj_clipper->is_valid() && obj_clipper->get_clipping_plane()
+ && obj_clipper->get_position() != 0.) {
+ ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane();
+ clp.set_normal(-clp.get_normal());
+ clipper->set_limiting_plane(clp);
+ }
+ else
+ clipper->set_limiting_plane(ClippingPlane::ClipsNothing());
+
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPushMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#if !ENABLE_LEGACY_OPENGL_REMOVAL
+ if (mv->is_model_part())
+ glsafe(::glColor3f(0.8f, 0.3f, 0.0f));
+ else {
+ const ColorRGBA color = color_from_model_volume(*mv);
+ glsafe(::glColor4fv(color.data()));
+ }
+#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
+ glsafe(::glPushAttrib(GL_DEPTH_TEST));
+ glsafe(::glDisable(GL_DEPTH_TEST));
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv));
+#else
+ clipper->render_cut();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+ glsafe(::glPopAttrib());
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+
+ ++clipper_id;
+ }
+}
+
+
+
+void HollowedMesh::on_update()
+{
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+ bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA;
+ if (! mo || ! is_sla)
+ return;
+
+ const GLCanvas3D* canvas = get_pool()->get_canvas();
+ const PrintObjects& print_objects = canvas->sla_print()->objects();
+ const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size()))
+ ? print_objects[m_print_object_idx]
+ : nullptr;
+
+ // Find the respective SLAPrintObject.
+ if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) {
+ m_print_objects_count = print_objects.size();
+ m_print_object_idx = -1;
+ for (const SLAPrintObject* po : print_objects) {
+ ++m_print_object_idx;
+ if (po->model_object()->id() == mo->id()) {
+ print_object = po;
+ break;
+ }
+ }
+ }
+
+ // If there is a valid SLAPrintObject, check state of Hollowing step.
+ if (print_object) {
+ if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) {
+ size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp;
+ if (timestamp > m_old_hollowing_timestamp) {
+ const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice();
+ if (! backend_mesh.empty()) {
+ m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh));
+ Transform3d trafo_inv = (canvas->sla_print()->sla_trafo(*mo) * print_object->model_object()->volumes.front()->get_transformation().get_matrix()).inverse();
+ m_hollowed_mesh_transformed->transform(trafo_inv);
+ m_drainholes = print_object->model_object()->sla_drain_holes;
+ m_old_hollowing_timestamp = timestamp;
+
+ indexed_triangle_set interior = print_object->hollowed_interior_mesh();
+ its_flip_triangles(interior);
+ m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(std::move(interior));
+ m_hollowed_interior_transformed->transform(trafo_inv);
+ }
+ else {
+ m_hollowed_mesh_transformed.reset(nullptr);
+ }
+ }
+ }
+ else
+ m_hollowed_mesh_transformed.reset(nullptr);
+ }
+}
+
+
+void HollowedMesh::on_release()
+{
+ m_hollowed_mesh_transformed.reset();
+ m_old_hollowing_timestamp = 0;
+ m_print_object_idx = -1;
+}
+
+
+const TriangleMesh* HollowedMesh::get_hollowed_mesh() const
+{
+ return m_hollowed_mesh_transformed.get();
+}
+
+const TriangleMesh* HollowedMesh::get_hollowed_interior() const
+{
+ return m_hollowed_interior_transformed.get();
+}
+
+
+
+
+void Raycaster::on_update()
+{
+ wxBusyCursor wait;
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+
+ if (! mo)
+ return;
+
+ std::vector<const TriangleMesh*> meshes;
+ const std::vector<ModelVolume*>& mvs = mo->volumes;
+ if (mvs.size() == 1) {
+ assert(mvs.front()->is_model_part());
+ const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh();
+ if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh())
+ meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh());
+ }
+ if (meshes.empty()) {
+ for (const ModelVolume* mv : mvs) {
+ if (mv->is_model_part())
+ meshes.push_back(&mv->mesh());
+ }
+ }
+
+ if (meshes != m_old_meshes) {
+ m_raycasters.clear();
+ for (const TriangleMesh* mesh : meshes)
+ m_raycasters.emplace_back(new MeshRaycaster(*mesh));
+ m_old_meshes = meshes;
+ }
+}
+
+void Raycaster::on_release()
+{
+ m_raycasters.clear();
+ m_old_meshes.clear();
+}
+
+std::vector<const MeshRaycaster*> Raycaster::raycasters() const
+{
+ std::vector<const MeshRaycaster*> mrcs;
+ for (const auto& raycaster_unique_ptr : m_raycasters)
+ mrcs.push_back(raycaster_unique_ptr.get());
+ return mrcs;
+}
+
+
+
+
+
+void ObjectClipper::on_update()
+{
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+ if (! mo)
+ return;
+
+ // which mesh should be cut?
+ std::vector<const TriangleMesh*> meshes;
+ bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh();
+ if (has_hollowed)
+ meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh());
+
+ if (meshes.empty())
+ for (const ModelVolume* mv : mo->volumes)
+ meshes.push_back(&mv->mesh());
+
+ if (meshes != m_old_meshes) {
+ m_clippers.clear();
+ for (const TriangleMesh* mesh : meshes) {
+ m_clippers.emplace_back(new MeshClipper);
+ m_clippers.back()->set_mesh(*mesh);
+ }
+ m_old_meshes = meshes;
+
+ if (has_hollowed)
+ m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior());
+
+ m_active_inst_bb_radius =
+ mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius();
+ }
+}
+
+
+void ObjectClipper::on_release()
+{
+ m_clippers.clear();
+ m_old_meshes.clear();
+ m_clp.reset();
+ m_clp_ratio = 0.;
+
+}
+
+void ObjectClipper::render_cut() const
+{
+ if (m_clp_ratio == 0.)
+ return;
+ const SelectionInfo* sel_info = get_pool()->selection_info();
+ const ModelObject* mo = sel_info->model_object();
+ const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
+
+ size_t clipper_id = 0;
+ for (const ModelVolume* mv : mo->volumes) {
+ const Geometry::Transformation vol_trafo = mv->get_transformation();
+ Geometry::Transformation trafo = inst_trafo * vol_trafo;
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
+
+ auto& clipper = m_clippers[clipper_id];
+ clipper->set_plane(*m_clp);
+ clipper->set_transformation(trafo);
+ clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPushMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f });
+#else
+ glsafe(::glColor3f(1.0f, 0.37f, 0.0f));
+ clipper->render_cut();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+
+ ++clipper_id;
+ }
+}
+
+
+void ObjectClipper::set_position(double pos, bool keep_normal)
+{
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+ int active_inst = get_pool()->selection_info()->get_active_instance();
+ double z_shift = get_pool()->selection_info()->get_sla_shift();
+
+ Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward();
+ const Vec3d& center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift);
+ float dist = normal.dot(center);
+
+ if (pos < 0.)
+ pos = m_clp_ratio;
+
+ m_clp_ratio = pos;
+ m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2*m_active_inst_bb_radius)));
+ get_pool()->get_canvas()->set_as_dirty();
+}
+
+
+
+void SupportsClipper::on_update()
+{
+ const ModelObject* mo = get_pool()->selection_info()->model_object();
+ bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA;
+ if (! mo || ! is_sla)
+ return;
+
+ const GLCanvas3D* canvas = get_pool()->get_canvas();
+ const PrintObjects& print_objects = canvas->sla_print()->objects();
+ const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size()))
+ ? print_objects[m_print_object_idx]
+ : nullptr;
+
+ // Find the respective SLAPrintObject.
+ if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) {
+ m_print_objects_count = print_objects.size();
+ m_print_object_idx = -1;
+ for (const SLAPrintObject* po : print_objects) {
+ ++m_print_object_idx;
+ if (po->model_object()->id() == mo->id()) {
+ print_object = po;
+ break;
+ }
+ }
+ }
+
+ if (print_object
+ && print_object->is_step_done(slaposSupportTree)
+ && ! print_object->support_mesh().empty())
+ {
+ // If the supports are already calculated, save the timestamp of the respective step
+ // so we can later tell they were recalculated.
+ size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
+ if (! m_clipper || timestamp != m_old_timestamp) {
+ // The timestamp has changed.
+ m_clipper.reset(new MeshClipper);
+ // The mesh should already have the shared vertices calculated.
+ m_clipper->set_mesh(print_object->support_mesh());
+ m_old_timestamp = timestamp;
+ }
+ }
+ else
+ // The supports are not valid. We better dump the cached data.
+ m_clipper.reset();
+}
+
+
+void SupportsClipper::on_release()
+{
+ m_clipper.reset();
+ m_old_timestamp = 0;
+ m_print_object_idx = -1;
+}
+
+void SupportsClipper::render_cut() const
+{
+ const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper();
+ if (ocl->get_position() == 0.
+ || ! get_pool()->instances_hider()->are_supports_shown()
+ || ! m_clipper)
+ return;
+
+ const SelectionInfo* sel_info = get_pool()->selection_info();
+ const ModelObject* mo = sel_info->model_object();
+ const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
+ //Geometry::Transformation vol_trafo = mo->volumes.front()->get_transformation();
+ Geometry::Transformation trafo = inst_trafo;// * vol_trafo;
+ trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
+
+
+ // Get transformation of supports
+ Geometry::Transformation supports_trafo = trafo;
+ supports_trafo.set_scaling_factor(Vec3d::Ones());
+ supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift()));
+ supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2)));
+ // I don't know why, but following seems to be correct.
+ supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2),
+ 1,
+ 1.));
+
+ m_clipper->set_plane(*ocl->get_clipping_plane());
+ m_clipper->set_transformation(supports_trafo);
+
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPushMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f });
+#else
+ glsafe(::glColor3f(1.0f, 0.f, 0.37f));
+ m_clipper->render_cut();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+}
+
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index 21b55ea69..39a1cba8e 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -408,7 +408,8 @@ bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) {
// at this moment is enebled to process mouse move under gizmo
// tools bar e.g. Do not interupt dragging.
return false;
- } else if (mc.exist_tooltip) {
+ }
+ else if (mc.exist_tooltip) {
// first move out of gizmo tool bar - unselect tooltip
mc.exist_tooltip = false;
update_hover_state(Undefined);
@@ -423,10 +424,12 @@ bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) {
mc.left = true;
open_gizmo(gizmo);
return true;
- } else if (mouse_event.RightDown()) {
+ }
+ else if (mouse_event.RightDown()) {
mc.right = true;
return true;
- } else if (mouse_event.MiddleDown()) {
+ }
+ else if (mouse_event.MiddleDown()) {
mc.middle = true;
return true;
}
@@ -441,14 +444,17 @@ bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) {
update_hover_state(Undefined);
}
// draging start on toolbar so no propagation into scene
- return true;
- } else if (mc.left && mouse_event.LeftUp()) {
+ return true;
+ }
+ else if (mc.left && mouse_event.LeftUp()) {
mc.left = false;
return true;
- } else if (mc.right && mouse_event.RightUp()) {
+ }
+ else if (mc.right && mouse_event.RightUp()) {
mc.right = false;
return true;
- } else if (mc.middle && mouse_event.MiddleUp()) {
+ }
+ else if (mc.middle && mouse_event.MiddleUp()) {
mc.middle = false;
return true;
}
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
index 187afd889..2e9e6bb65 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
@@ -205,7 +205,7 @@ public:
bool handle_shortcut(int key);
bool is_dragging() const;
-
+
ClippingPlane get_clipping_plane() const;
bool wants_reslice_supports_on_undo() const;
diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp
index bbdcdeb34..7782b8d75 100644
--- a/src/slic3r/GUI/MeshUtils.cpp
+++ b/src/slic3r/GUI/MeshUtils.cpp
@@ -1,370 +1,378 @@
-#include "MeshUtils.hpp"
-
-#include "libslic3r/Tesselate.hpp"
-#include "libslic3r/TriangleMesh.hpp"
-#include "libslic3r/TriangleMeshSlicer.hpp"
-#include "libslic3r/ClipperUtils.hpp"
-#include "libslic3r/Model.hpp"
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-#include "slic3r/GUI/GUI_App.hpp"
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-#include "slic3r/GUI/Camera.hpp"
-#if ENABLE_GL_SHADERS_ATTRIBUTES
-#include "slic3r/GUI/Plater.hpp"
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
-
-#include <GL/glew.h>
-
-#include <igl/unproject.h>
-
-
-namespace Slic3r {
-namespace GUI {
-
-void MeshClipper::set_plane(const ClippingPlane& plane)
-{
- if (m_plane != plane) {
- m_plane = plane;
- m_triangles_valid = false;
- }
-}
-
-
-void MeshClipper::set_limiting_plane(const ClippingPlane& plane)
-{
- if (m_limiting_plane != plane) {
- m_limiting_plane = plane;
- m_triangles_valid = false;
- }
-}
-
-
-
-void MeshClipper::set_mesh(const TriangleMesh& mesh)
-{
- if (m_mesh != &mesh) {
- m_mesh = &mesh;
- m_triangles_valid = false;
- m_triangles2d.resize(0);
- }
-}
-
-void MeshClipper::set_negative_mesh(const TriangleMesh& mesh)
-{
- if (m_negative_mesh != &mesh) {
- m_negative_mesh = &mesh;
- m_triangles_valid = false;
- m_triangles2d.resize(0);
- }
-}
-
-
-
-void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
-{
- if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) {
- m_trafo = trafo;
- m_triangles_valid = false;
- m_triangles2d.resize(0);
- }
-}
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-void MeshClipper::render_cut(const ColorRGBA& color)
-#else
-void MeshClipper::render_cut()
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-{
- if (! m_triangles_valid)
- recalculate_triangles();
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- if (m_model.vertices_count() == 0 || m_model.indices_count() == 0)
- return;
-
- GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
- if (curr_shader != nullptr)
- curr_shader->stop_using();
-
- GLShaderProgram* shader = wxGetApp().get_shader("flat");
- if (shader != nullptr) {
- shader->start_using();
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- const Camera& camera = wxGetApp().plater()->get_camera();
- shader->set_uniform("view_model_matrix", camera.get_view_matrix());
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
- m_model.set_color(color);
- m_model.render();
- shader->stop_using();
- }
-
- if (curr_shader != nullptr)
- curr_shader->start_using();
-#else
- if (m_vertex_array.has_VBOs())
- m_vertex_array.render();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-}
-
-
-
-void MeshClipper::recalculate_triangles()
-{
- const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>();
- // Calculate clipping plane normal in mesh coordinates.
- const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>();
- const Vec3d up = up_noscale.cast<double>().cwiseProduct(m_trafo.get_scaling_factor());
- // Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
- const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
-
- // Now do the cutting
- MeshSlicingParams slicing_params;
- slicing_params.trafo.rotate(Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(up, Vec3d::UnitZ()));
-
- ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params));
-
- if (m_negative_mesh && !m_negative_mesh->empty()) {
- const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params));
- expolys = diff_ex(expolys, neg_expolys);
- }
-
- // Triangulate and rotate the cut into world coords:
- Eigen::Quaterniond q;
- q.setFromTwoVectors(Vec3d::UnitZ(), up);
- Transform3d tr = Transform3d::Identity();
- tr.rotate(q);
- tr = m_trafo.get_matrix() * tr;
-
- if (m_limiting_plane != ClippingPlane::ClipsNothing())
- {
- // Now remove whatever ended up below the limiting plane (e.g. sinking objects).
- // First transform the limiting plane from world to mesh coords.
- // Note that inverse of tr transforms the plane from world to horizontal.
- const Vec3d normal_old = m_limiting_plane.get_normal().normalized();
- const Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized();
-
- // normal_new should now be the plane normal in mesh coords. To find the offset,
- // transform a point and set offset so it belongs to the transformed plane.
- Vec3d pt = Vec3d::Zero();
- const double plane_offset = m_limiting_plane.get_data()[3];
- if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57
- pt.z() = - plane_offset / normal_old.z();
- else if (std::abs(normal_old.y()) > 0.5)
- pt.y() = - plane_offset / normal_old.y();
- else
- pt.x() = - plane_offset / normal_old.x();
- pt = tr.inverse() * pt;
- const double offset = -(normal_new.dot(pt));
-
- if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) {
- // The cuts are parallel, show all or nothing.
- if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh)
- expolys.clear();
- } else {
- // The cut is a horizontal plane defined by z=height_mesh.
- // ax+by+e=0 is the line of intersection with the limiting plane.
- // Normalized so a^2 + b^2 = 1.
- const double len = std::hypot(normal_new.x(), normal_new.y());
- if (len == 0.)
- return;
- const double a = normal_new.x() / len;
- const double b = normal_new.y() / len;
- const double e = (normal_new.z() * height_mesh + offset) / len;
-
- // We need a half-plane to limit the cut. Get angle of the intersecting line.
- double angle = (b != 0.0) ? std::atan(-a / b) : ((a < 0.0) ? -0.5 * M_PI : 0.5 * M_PI);
- if (b > 0) // select correct half-plane
- angle += M_PI;
-
- // We'll take a big rectangle above x-axis and rotate and translate
- // it so it lies on our line. This will be the figure to subtract
- // from the cut. The coordinates must not overflow after the transform,
- // make the rectangle a bit smaller.
- const coord_t size = (std::numeric_limits<coord_t>::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4;
- Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})};
- ep.front().rotate(angle);
- ep.front().translate(scale_(-e * a), scale_(-e * b));
- expolys = diff_ex(expolys, ep);
- }
- }
-
- m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.);
-
- tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_model.reset();
-
- GLModel::Geometry init_data;
- init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
- init_data.reserve_vertices(m_triangles2d.size());
- init_data.reserve_indices(m_triangles2d.size());
-
- // vertices + indices
- for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) {
- init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
- init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
- init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
- const size_t idx = it - m_triangles2d.cbegin();
- init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2);
- }
-
- if (!init_data.is_empty())
- m_model.init_from(std::move(init_data));
-#else
- m_vertex_array.release_geometry();
- for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) {
- m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up);
- m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up);
- m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up);
- const size_t idx = it - m_triangles2d.cbegin();
- m_vertex_array.push_triangle(idx, idx+1, idx+2);
- }
- m_vertex_array.finalize_geometry(true);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- m_triangles_valid = true;
-}
-
-
-Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const
-{
- return m_normals[facet_idx];
-}
-
-void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
- Vec3d& point, Vec3d& direction) const
-{
- Matrix4d modelview = camera.get_view_matrix().matrix();
- Matrix4d projection= camera.get_projection_matrix().matrix();
- Vec4i viewport(camera.get_viewport().data());
-
- Vec3d pt1;
- Vec3d pt2;
- igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 0.),
- modelview, projection, viewport, pt1);
- igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 1.),
- modelview, projection, viewport, pt2);
-
- Transform3d inv = trafo.inverse();
- pt1 = inv * pt1;
- pt2 = inv * pt2;
-
- point = pt1;
- direction = pt2-pt1;
-}
-
-
-bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
- Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane,
- size_t* facet_idx) const
-{
- Vec3d point;
- Vec3d direction;
- line_from_mouse_pos(mouse_pos, trafo, camera, point, direction);
-
- std::vector<sla::IndexedMesh::hit_result> hits = m_emesh.query_ray_hits(point, direction);
-
- if (hits.empty())
- return false; // no intersection found
-
- unsigned i = 0;
-
- // Remove points that are obscured or cut by the clipping plane.
- // Also, remove anything below the bed (sinking objects).
- for (i=0; i<hits.size(); ++i) {
- Vec3d transformed_hit = trafo * hits[i].position();
- if (transformed_hit.z() >= SINKING_Z_THRESHOLD &&
- (! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit)))
- break;
- }
-
- if (i==hits.size() || (hits.size()-i) % 2 != 0) {
- // All hits are either clipped, or there is an odd number of unclipped
- // hits - meaning the nearest must be from inside the mesh.
- return false;
- }
-
- // Now stuff the points in the provided vector and calculate normals if asked about them:
- position = hits[i].position().cast<float>();
- normal = hits[i].normal().cast<float>();
-
- if (facet_idx)
- *facet_idx = hits[i].face();
-
- return true;
-}
-
-
-std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector<Vec3f>& points,
- const ClippingPlane* clipping_plane) const
-{
- std::vector<unsigned> out;
-
- const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true);
- Vec3d direction_to_camera = -camera.get_dir_forward();
- Vec3d direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera).normalized().eval();
- direction_to_camera_mesh = direction_to_camera_mesh.cwiseProduct(trafo.get_scaling_factor());
- const Transform3d inverse_trafo = trafo.get_matrix().inverse();
-
- for (size_t i=0; i<points.size(); ++i) {
- const Vec3f& pt = points[i];
- if (clipping_plane && clipping_plane->is_point_clipped(pt.cast<double>()))
- continue;
-
- bool is_obscured = false;
- // Cast a ray in the direction of the camera and look for intersection with the mesh:
- std::vector<sla::IndexedMesh::hit_result> hits;
- // Offset the start of the ray by EPSILON to account for numerical inaccuracies.
- hits = m_emesh.query_ray_hits((inverse_trafo * pt.cast<double>() + direction_to_camera_mesh * EPSILON),
- direction_to_camera_mesh);
-
- if (! hits.empty()) {
- // If the closest hit facet normal points in the same direction as the ray,
- // we are looking through the mesh and should therefore discard the point:
- if (hits.front().normal().dot(direction_to_camera_mesh.cast<double>()) > 0)
- is_obscured = true;
-
- // Eradicate all hits that the caller wants to ignore
- for (unsigned j=0; j<hits.size(); ++j) {
- if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * hits[j].position())) {
- hits.erase(hits.begin()+j);
- --j;
- }
- }
-
- // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction.
- // Also, the threshold is in mesh coordinates, not in actual dimensions.
- if (! hits.empty())
- is_obscured = true;
- }
- if (! is_obscured)
- out.push_back(i);
- }
- return out;
-}
-
-
-Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
-{
- int idx = 0;
- Vec3d closest_point;
- m_emesh.squared_distance(point.cast<double>(), idx, closest_point);
- if (normal)
- *normal = m_normals[idx];
-
- return closest_point.cast<float>();
-}
-
-int MeshRaycaster::get_closest_facet(const Vec3f &point) const
-{
- int facet_idx = 0;
- Vec3d closest_point;
- m_emesh.squared_distance(point.cast<double>(), facet_idx, closest_point);
- return facet_idx;
-}
-
-} // namespace GUI
-} // namespace Slic3r
+#include "MeshUtils.hpp"
+
+#include "libslic3r/Tesselate.hpp"
+#include "libslic3r/TriangleMesh.hpp"
+#include "libslic3r/TriangleMeshSlicer.hpp"
+#include "libslic3r/ClipperUtils.hpp"
+#include "libslic3r/Model.hpp"
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+#include "slic3r/GUI/GUI_App.hpp"
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#include "slic3r/GUI/Camera.hpp"
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+#include "slic3r/GUI/Plater.hpp"
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+
+#include <GL/glew.h>
+
+#include <igl/unproject.h>
+
+
+namespace Slic3r {
+namespace GUI {
+
+void MeshClipper::set_plane(const ClippingPlane& plane)
+{
+ if (m_plane != plane) {
+ m_plane = plane;
+ m_triangles_valid = false;
+ }
+}
+
+
+void MeshClipper::set_limiting_plane(const ClippingPlane& plane)
+{
+ if (m_limiting_plane != plane) {
+ m_limiting_plane = plane;
+ m_triangles_valid = false;
+ }
+}
+
+
+
+void MeshClipper::set_mesh(const TriangleMesh& mesh)
+{
+ if (m_mesh != &mesh) {
+ m_mesh = &mesh;
+ m_triangles_valid = false;
+ m_triangles2d.resize(0);
+ }
+}
+
+void MeshClipper::set_negative_mesh(const TriangleMesh& mesh)
+{
+ if (m_negative_mesh != &mesh) {
+ m_negative_mesh = &mesh;
+ m_triangles_valid = false;
+ m_triangles2d.resize(0);
+ }
+}
+
+
+
+void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
+{
+ if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) {
+ m_trafo = trafo;
+ m_triangles_valid = false;
+ m_triangles2d.resize(0);
+ }
+}
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+void MeshClipper::render_cut(const ColorRGBA& color)
+#else
+void MeshClipper::render_cut()
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+{
+ if (! m_triangles_valid)
+ recalculate_triangles();
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ if (m_model.vertices_count() == 0 || m_model.indices_count() == 0)
+ return;
+
+ GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
+ if (curr_shader != nullptr)
+ curr_shader->stop_using();
+
+ GLShaderProgram* shader = wxGetApp().get_shader("flat");
+ if (shader != nullptr) {
+ shader->start_using();
+#if ENABLE_GL_SHADERS_ATTRIBUTES
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+ shader->set_uniform("projection_matrix", camera.get_projection_matrix());
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+ m_model.set_color(color);
+ m_model.render();
+ shader->stop_using();
+ }
+
+ if (curr_shader != nullptr)
+ curr_shader->start_using();
+#else
+ if (m_vertex_array.has_VBOs())
+ m_vertex_array.render();
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+}
+
+
+
+void MeshClipper::recalculate_triangles()
+{
+#if ENABLE_WORLD_COORDINATE
+ const Transform3f instance_matrix_no_translation_no_scaling = m_trafo.get_rotation_matrix().cast<float>();
+#else
+ const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>();
+#endif // ENABLE_WORLD_COORDINATE
+ // Calculate clipping plane normal in mesh coordinates.
+ const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>();
+ const Vec3d up = up_noscale.cast<double>().cwiseProduct(m_trafo.get_scaling_factor());
+ // Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
+ const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
+
+ // Now do the cutting
+ MeshSlicingParams slicing_params;
+ slicing_params.trafo.rotate(Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(up, Vec3d::UnitZ()));
+
+ ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params));
+
+ if (m_negative_mesh && !m_negative_mesh->empty()) {
+ const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params));
+ expolys = diff_ex(expolys, neg_expolys);
+ }
+
+ // Triangulate and rotate the cut into world coords:
+ Eigen::Quaterniond q;
+ q.setFromTwoVectors(Vec3d::UnitZ(), up);
+ Transform3d tr = Transform3d::Identity();
+ tr.rotate(q);
+ tr = m_trafo.get_matrix() * tr;
+
+ if (m_limiting_plane != ClippingPlane::ClipsNothing())
+ {
+ // Now remove whatever ended up below the limiting plane (e.g. sinking objects).
+ // First transform the limiting plane from world to mesh coords.
+ // Note that inverse of tr transforms the plane from world to horizontal.
+ const Vec3d normal_old = m_limiting_plane.get_normal().normalized();
+ const Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized();
+
+ // normal_new should now be the plane normal in mesh coords. To find the offset,
+ // transform a point and set offset so it belongs to the transformed plane.
+ Vec3d pt = Vec3d::Zero();
+ const double plane_offset = m_limiting_plane.get_data()[3];
+ if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57
+ pt.z() = - plane_offset / normal_old.z();
+ else if (std::abs(normal_old.y()) > 0.5)
+ pt.y() = - plane_offset / normal_old.y();
+ else
+ pt.x() = - plane_offset / normal_old.x();
+ pt = tr.inverse() * pt;
+ const double offset = -(normal_new.dot(pt));
+
+ if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) {
+ // The cuts are parallel, show all or nothing.
+ if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh)
+ expolys.clear();
+ } else {
+ // The cut is a horizontal plane defined by z=height_mesh.
+ // ax+by+e=0 is the line of intersection with the limiting plane.
+ // Normalized so a^2 + b^2 = 1.
+ const double len = std::hypot(normal_new.x(), normal_new.y());
+ if (len == 0.)
+ return;
+ const double a = normal_new.x() / len;
+ const double b = normal_new.y() / len;
+ const double e = (normal_new.z() * height_mesh + offset) / len;
+
+ // We need a half-plane to limit the cut. Get angle of the intersecting line.
+ double angle = (b != 0.0) ? std::atan(-a / b) : ((a < 0.0) ? -0.5 * M_PI : 0.5 * M_PI);
+ if (b > 0) // select correct half-plane
+ angle += M_PI;
+
+ // We'll take a big rectangle above x-axis and rotate and translate
+ // it so it lies on our line. This will be the figure to subtract
+ // from the cut. The coordinates must not overflow after the transform,
+ // make the rectangle a bit smaller.
+ const coord_t size = (std::numeric_limits<coord_t>::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4;
+ Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})};
+ ep.front().rotate(angle);
+ ep.front().translate(scale_(-e * a), scale_(-e * b));
+ expolys = diff_ex(expolys, ep);
+ }
+ }
+
+ m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.);
+
+ tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting
+
+#if ENABLE_LEGACY_OPENGL_REMOVAL
+ m_model.reset();
+
+ GLModel::Geometry init_data;
+ init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
+ init_data.reserve_vertices(m_triangles2d.size());
+ init_data.reserve_indices(m_triangles2d.size());
+
+ // vertices + indices
+ for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) {
+ init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
+ init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
+ init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
+ const size_t idx = it - m_triangles2d.cbegin();
+ init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2);
+ }
+
+ if (!init_data.is_empty())
+ m_model.init_from(std::move(init_data));
+#else
+ m_vertex_array.release_geometry();
+ for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) {
+ m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up);
+ m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up);
+ m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up);
+ const size_t idx = it - m_triangles2d.cbegin();
+ m_vertex_array.push_triangle(idx, idx+1, idx+2);
+ }
+ m_vertex_array.finalize_geometry(true);
+#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+
+ m_triangles_valid = true;
+}
+
+
+Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const
+{
+ return m_normals[facet_idx];
+}
+
+void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
+ Vec3d& point, Vec3d& direction) const
+{
+ Matrix4d modelview = camera.get_view_matrix().matrix();
+ Matrix4d projection= camera.get_projection_matrix().matrix();
+ Vec4i viewport(camera.get_viewport().data());
+
+ Vec3d pt1;
+ Vec3d pt2;
+ igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 0.),
+ modelview, projection, viewport, pt1);
+ igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 1.),
+ modelview, projection, viewport, pt2);
+
+ Transform3d inv = trafo.inverse();
+ pt1 = inv * pt1;
+ pt2 = inv * pt2;
+
+ point = pt1;
+ direction = pt2-pt1;
+}
+
+
+bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
+ Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane,
+ size_t* facet_idx) const
+{
+ Vec3d point;
+ Vec3d direction;
+ line_from_mouse_pos(mouse_pos, trafo, camera, point, direction);
+
+ std::vector<sla::IndexedMesh::hit_result> hits = m_emesh.query_ray_hits(point, direction);
+
+ if (hits.empty())
+ return false; // no intersection found
+
+ unsigned i = 0;
+
+ // Remove points that are obscured or cut by the clipping plane.
+ // Also, remove anything below the bed (sinking objects).
+ for (i=0; i<hits.size(); ++i) {
+ Vec3d transformed_hit = trafo * hits[i].position();
+ if (transformed_hit.z() >= SINKING_Z_THRESHOLD &&
+ (! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit)))
+ break;
+ }
+
+ if (i==hits.size() || (hits.size()-i) % 2 != 0) {
+ // All hits are either clipped, or there is an odd number of unclipped
+ // hits - meaning the nearest must be from inside the mesh.
+ return false;
+ }
+
+ // Now stuff the points in the provided vector and calculate normals if asked about them:
+ position = hits[i].position().cast<float>();
+ normal = hits[i].normal().cast<float>();
+
+ if (facet_idx)
+ *facet_idx = hits[i].face();
+
+ return true;
+}
+
+
+std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector<Vec3f>& points,
+ const ClippingPlane* clipping_plane) const
+{
+ std::vector<unsigned> out;
+
+#if ENABLE_WORLD_COORDINATE
+ const Transform3d instance_matrix_no_translation_no_scaling = trafo.get_rotation_matrix();
+#else
+ const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true);
+#endif // ENABLE_WORLD_COORDINATE
+ Vec3d direction_to_camera = -camera.get_dir_forward();
+ Vec3d direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera).normalized().eval();
+ direction_to_camera_mesh = direction_to_camera_mesh.cwiseProduct(trafo.get_scaling_factor());
+ const Transform3d inverse_trafo = trafo.get_matrix().inverse();
+
+ for (size_t i=0; i<points.size(); ++i) {
+ const Vec3f& pt = points[i];
+ if (clipping_plane && clipping_plane->is_point_clipped(pt.cast<double>()))
+ continue;
+
+ bool is_obscured = false;
+ // Cast a ray in the direction of the camera and look for intersection with the mesh:
+ std::vector<sla::IndexedMesh::hit_result> hits;
+ // Offset the start of the ray by EPSILON to account for numerical inaccuracies.
+ hits = m_emesh.query_ray_hits((inverse_trafo * pt.cast<double>() + direction_to_camera_mesh * EPSILON),
+ direction_to_camera_mesh);
+
+ if (! hits.empty()) {
+ // If the closest hit facet normal points in the same direction as the ray,
+ // we are looking through the mesh and should therefore discard the point:
+ if (hits.front().normal().dot(direction_to_camera_mesh.cast<double>()) > 0)
+ is_obscured = true;
+
+ // Eradicate all hits that the caller wants to ignore
+ for (unsigned j=0; j<hits.size(); ++j) {
+ if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * hits[j].position())) {
+ hits.erase(hits.begin()+j);
+ --j;
+ }
+ }
+
+ // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction.
+ // Also, the threshold is in mesh coordinates, not in actual dimensions.
+ if (! hits.empty())
+ is_obscured = true;
+ }
+ if (! is_obscured)
+ out.push_back(i);
+ }
+ return out;
+}
+
+
+Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
+{
+ int idx = 0;
+ Vec3d closest_point;
+ m_emesh.squared_distance(point.cast<double>(), idx, closest_point);
+ if (normal)
+ *normal = m_normals[idx];
+
+ return closest_point.cast<float>();
+}
+
+int MeshRaycaster::get_closest_facet(const Vec3f &point) const
+{
+ int facet_idx = 0;
+ Vec3d closest_point;
+ m_emesh.squared_distance(point.cast<double>(), facet_idx, closest_point);
+ return facet_idx;
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 30c30d2ec..d23a3dd13 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -57,6 +57,9 @@
#include "GUI_ObjectManipulation.hpp"
#include "GUI_ObjectLayers.hpp"
#include "GUI_Utils.hpp"
+#if ENABLE_WORLD_COORDINATE
+#include "GUI_Geometry.hpp"
+#endif // ENABLE_WORLD_COORDINATE
#include "GUI_Factories.hpp"
#include "wxExtensions.hpp"
#include "MainFrame.hpp"
@@ -1514,6 +1517,11 @@ void Sidebar::update_mode()
wxWindowUpdateLocker noUpdates(this);
+#if ENABLE_WORLD_COORDINATE
+ if (m_mode == comSimple)
+ p->object_manipulation->set_coordinates_type(ECoordinatesType::World);
+#endif // ENABLE_WORLD_COORDINATE
+
p->object_list->get_sizer()->Show(m_mode > comSimple);
p->object_list->unselect_objects();
@@ -2077,6 +2085,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this);
view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this);
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); });
+#if ENABLE_WORLD_COORDINATE
+ view3D_canvas->Bind(EVT_GLCANVAS_RESET_SKEW, [this](SimpleEvent&) { update(); });
+#endif // ENABLE_WORLD_COORDINATE
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); });
view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool>& evt) { this->sidebar->enable_buttons(evt.data); });
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this);
@@ -2922,7 +2933,7 @@ int Plater::priv::get_selected_volume_idx() const
if ((0 > idx) || (idx > 1000))
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
return-1;
- const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
+ const GLVolume* v = selection.get_first_volume();
if (model.objects[idx]->volumes.size() > 1)
return v->volume_idx();
return -1;
@@ -3484,7 +3495,11 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
new_volume->set_type(old_volume->type());
new_volume->set_material_id(old_volume->material_id());
new_volume->set_transformation(old_volume->get_transformation());
+#if ENABLE_WORLD_COORDINATE
+ new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
+#else
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
+#endif // ENABLE_WORLD_COORDINATE
assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters);
if (old_volume->source.is_converted_from_inches)
new_volume->convert_from_imperial_units();
@@ -3521,7 +3536,7 @@ void Plater::priv::replace_with_stl()
if (selection.is_wipe_tower() || get_selection().get_volume_idxs().size() != 1)
return;
- const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
+ const GLVolume* v = selection.get_first_volume();
int object_idx = v->object_idx();
int volume_idx = v->volume_idx();
@@ -3839,10 +3854,16 @@ void Plater::priv::reload_from_disk()
new_volume->config.apply(old_volume->config);
new_volume->set_type(old_volume->type());
new_volume->set_material_id(old_volume->material_id());
+#if ENABLE_WORLD_COORDINATE
+ new_volume->set_transformation(Geometry::translation_transform(old_volume->source.transform.get_offset()) *
+ old_volume->get_transformation().get_matrix_no_offset() * old_volume->source.transform.get_matrix_no_offset());
+ new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
+#else
new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) *
old_volume->get_transformation().get_matrix(true) *
old_volume->source.transform.get_matrix(true));
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
+#endif // ENABLE_WORLD_COORDINATE
new_volume->source.object_idx = old_volume->source.object_idx;
new_volume->source.volume_idx = old_volume->source.volume_idx;
assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters);
@@ -4448,8 +4469,12 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
const bool is_some_full_instances = selection.is_single_full_instance() ||
selection.is_single_full_object() ||
selection.is_multiple_full_instance();
+#if ENABLE_WORLD_COORDINATE
+ const bool is_part = selection.is_single_volume_or_modifier();
+#else
const bool is_part = selection.is_single_volume() || selection.is_single_modifier();
- menu = is_some_full_instances ? menus.object_menu() :
+#endif // ENABLE_WORLD_COORDINATE
+ menu = is_some_full_instances ? menus.object_menu() :
is_part ? menus.part_menu() : menus.multi_selection_menu();
}
}
@@ -6030,7 +6055,7 @@ void Plater::export_stl_obj(bool extended, bool selection_only)
if (selection.get_mode() == Selection::Instance)
mesh = mesh_to_export(*model_object, (selection.is_single_full_object() && model_object->instances.size() > 1) ? -1 : selection.get_instance_idx());
else {
- const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
+ const GLVolume* volume = selection.get_first_volume();
mesh = model_object->volumes[volume->volume_idx()]->mesh();
mesh.transform(volume->get_volume_transformation().get_matrix(), true);
}
diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp
index e7c4e1763..271435c99 100644
--- a/src/slic3r/GUI/Selection.cpp
+++ b/src/slic3r/GUI/Selection.cpp
@@ -7,9 +7,14 @@
#include "GUI.hpp"
#include "GUI_ObjectManipulation.hpp"
#include "GUI_ObjectList.hpp"
-#include "Gizmos/GLGizmoBase.hpp"
#include "Camera.hpp"
#include "Plater.hpp"
+#if ENABLE_WORLD_COORDINATE
+#include "MsgDialog.hpp"
+#endif // ENABLE_WORLD_COORDINATE
+
+#include "Gizmos/GLGizmoBase.hpp"
+
#include "slic3r/Utils/UndoRedo.hpp"
#include "libslic3r/LocalesUtils.hpp"
@@ -29,28 +34,24 @@ static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5
namespace Slic3r {
namespace GUI {
-Selection::VolumeCache::TransformCache::TransformCache()
- : position(Vec3d::Zero())
- , rotation(Vec3d::Zero())
- , scaling_factor(Vec3d::Ones())
- , mirror(Vec3d::Ones())
- , rotation_matrix(Transform3d::Identity())
- , scale_matrix(Transform3d::Identity())
- , mirror_matrix(Transform3d::Identity())
- , full_matrix(Transform3d::Identity())
-{
-}
-
Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform)
: position(transform.get_offset())
, rotation(transform.get_rotation())
, scaling_factor(transform.get_scaling_factor())
, mirror(transform.get_mirror())
, full_matrix(transform.get_matrix())
-{
+#if ENABLE_WORLD_COORDINATE
+ , transform(transform)
+ , rotation_matrix(transform.get_rotation_matrix())
+ , scale_matrix(transform.get_scaling_factor_matrix())
+ , mirror_matrix(transform.get_mirror_matrix())
+#endif // ENABLE_WORLD_COORDINATE
+{
+#if !ENABLE_WORLD_COORDINATE
rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation);
- scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor);
- mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror);
+ scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor);
+ mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror);
+#endif // !ENABLE_WORLD_COORDINATE
}
Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform)
@@ -117,6 +118,12 @@ Selection::Selection()
, m_scale_factor(1.0f)
{
this->set_bounding_boxes_dirty();
+#if ENABLE_WORLD_COORDINATE
+ m_axes.set_stem_radius(0.15f);
+ m_axes.set_stem_length(3.0f);
+ m_axes.set_tip_radius(0.45f);
+ m_axes.set_tip_length(1.5f);
+#endif // ENABLE_WORLD_COORDINATE
}
@@ -591,6 +598,7 @@ bool Selection::matches(const std::vector<unsigned int>& volume_idxs) const
return count == (unsigned int)m_list.size();
}
+#if !ENABLE_WORLD_COORDINATE
bool Selection::requires_uniform_scale() const
{
if (is_single_full_instance() || is_single_modifier() || is_single_volume())
@@ -598,6 +606,7 @@ bool Selection::requires_uniform_scale() const
return true;
}
+#endif // !ENABLE_WORLD_COORDINATE
int Selection::get_object_idx() const
{
@@ -642,6 +651,8 @@ const BoundingBoxf3& Selection::get_bounding_box() const
const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const
{
+ assert(is_single_full_instance());
+
if (!m_unscaled_instance_bounding_box.has_value()) {
std::optional<BoundingBoxf3>* bbox = const_cast<std::optional<BoundingBoxf3>*>(&m_unscaled_instance_bounding_box);
*bbox = BoundingBoxf3();
@@ -650,7 +661,11 @@ const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const
const GLVolume& volume = *(*m_volumes)[i];
if (volume.is_modifier)
continue;
+#if ENABLE_WORLD_COORDINATE
+ Transform3d trafo = volume.get_instance_transformation().get_matrix_no_scaling_factor() * volume.get_volume_transformation().get_matrix();
+#else
Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix();
+#endif // ENABLE_WORLD_COORDINATE
trafo.translation().z() += volume.get_sla_shift_z();
(*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo));
}
@@ -661,6 +676,8 @@ const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const
const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const
{
+ assert(is_single_full_instance());
+
if (!m_scaled_instance_bounding_box.has_value()) {
std::optional<BoundingBoxf3>* bbox = const_cast<std::optional<BoundingBoxf3>*>(&m_scaled_instance_bounding_box);
*bbox = BoundingBoxf3();
@@ -669,7 +686,7 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const
const GLVolume& volume = *(*m_volumes)[i];
if (volume.is_modifier)
continue;
- Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix();
+ Transform3d trafo = volume.get_instance_transformation().get_matrix() * volume.get_volume_transformation().get_matrix();
trafo.translation().z() += volume.get_sla_shift_z();
(*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo));
}
@@ -678,6 +695,65 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const
return *m_scaled_instance_bounding_box;
}
+#if ENABLE_WORLD_COORDINATE
+const BoundingBoxf3& Selection::get_full_unscaled_instance_bounding_box() const
+{
+ assert(is_single_full_instance());
+
+ if (!m_full_unscaled_instance_bounding_box.has_value()) {
+ std::optional<BoundingBoxf3>* bbox = const_cast<std::optional<BoundingBoxf3>*>(&m_full_unscaled_instance_bounding_box);
+ *bbox = BoundingBoxf3();
+ if (m_valid) {
+ for (unsigned int i : m_list) {
+ const GLVolume& volume = *(*m_volumes)[i];
+ Transform3d trafo = volume.get_instance_transformation().get_matrix_no_scaling_factor() * volume.get_volume_transformation().get_matrix();
+ trafo.translation().z() += volume.get_sla_shift_z();
+ (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo));
+ }
+ }
+ }
+ return *m_full_unscaled_instance_bounding_box;
+}
+
+const BoundingBoxf3& Selection::get_full_scaled_instance_bounding_box() const
+{
+ assert(is_single_full_instance());
+
+ if (!m_full_scaled_instance_bounding_box.has_value()) {
+ std::optional<BoundingBoxf3>* bbox = const_cast<std::optional<BoundingBoxf3>*>(&m_full_scaled_instance_bounding_box);
+ *bbox = BoundingBoxf3();
+ if (m_valid) {
+ for (unsigned int i : m_list) {
+ const GLVolume& volume = *(*m_volumes)[i];
+ Transform3d trafo = volume.get_instance_transformation().get_matrix() * volume.get_volume_transformation().get_matrix();
+ trafo.translation().z() += volume.get_sla_shift_z();
+ (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo));
+ }
+ }
+ }
+ return *m_full_scaled_instance_bounding_box;
+}
+
+const BoundingBoxf3& Selection::get_full_unscaled_instance_local_bounding_box() const
+{
+ assert(is_single_full_instance());
+
+ if (!m_full_unscaled_instance_local_bounding_box.has_value()) {
+ std::optional<BoundingBoxf3>* bbox = const_cast<std::optional<BoundingBoxf3>*>(&m_full_unscaled_instance_local_bounding_box);
+ *bbox = BoundingBoxf3();
+ if (m_valid) {
+ for (unsigned int i : m_list) {
+ const GLVolume& volume = *(*m_volumes)[i];
+ Transform3d trafo = volume.get_volume_transformation().get_matrix();
+ trafo.translation().z() += volume.get_sla_shift_z();
+ (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo));
+ }
+ }
+ }
+ return *m_full_unscaled_instance_local_bounding_box;
+}
+#endif // ENABLE_WORLD_COORDINATE
+
void Selection::setup_cache()
{
if (!m_valid)
@@ -686,6 +762,47 @@ void Selection::setup_cache()
set_caches();
}
+#if ENABLE_WORLD_COORDINATE
+void Selection::translate(const Vec3d& displacement, TransformationType transformation_type)
+{
+ if (!m_valid)
+ return;
+
+ assert(transformation_type.relative());
+
+ for (unsigned int i : m_list) {
+ GLVolume& v = *(*m_volumes)[i];
+ const VolumeCache& volume_data = m_cache.volumes_data[i];
+ if (m_mode == Instance && !is_wipe_tower()) {
+ assert(is_from_fully_selected_instance(i));
+ if (transformation_type.world())
+ v.set_instance_transformation(Geometry::translation_transform(displacement) * volume_data.get_instance_full_matrix());
+ else if (transformation_type.local()) {
+ const Vec3d world_displacement = volume_data.get_instance_rotation_matrix() * displacement;
+ v.set_instance_transformation(Geometry::translation_transform(world_displacement) * volume_data.get_instance_full_matrix());
+ }
+ else
+ assert(false);
+ }
+ else {
+ const Vec3d offset = transformation_type.local() ?
+ (Vec3d)(volume_data.get_volume_transform().get_rotation_matrix() * displacement) : displacement;
+ transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(offset));
+ }
+ }
+
+#if !DISABLE_INSTANCES_SYNCH
+ if (m_mode == Instance)
+ synchronize_unselected_instances(SyncRotationType::NONE);
+ else if (m_mode == Volume)
+ synchronize_unselected_volumes();
+#endif // !DISABLE_INSTANCES_SYNCH
+
+ ensure_not_below_bed();
+ set_bounding_boxes_dirty();
+ wxGetApp().plater()->canvas3D()->requires_check_outside_state();
+}
+#else
void Selection::translate(const Vec3d& displacement, bool local)
{
if (!m_valid)
@@ -716,7 +833,7 @@ void Selection::translate(const Vec3d& displacement, bool local)
#if !DISABLE_INSTANCES_SYNCH
if (translation_type == Instance)
- synchronize_unselected_instances(SYNC_ROTATION_NONE);
+ synchronize_unselected_instances(SyncRotationType::NONE);
else if (translation_type == Volume)
synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH
@@ -725,8 +842,75 @@ void Selection::translate(const Vec3d& displacement, bool local)
set_bounding_boxes_dirty();
wxGetApp().plater()->canvas3D()->requires_check_outside_state();
}
+#endif // ENABLE_WORLD_COORDINATE
// Rotate an object around one of the axes. Only one rotation component is expected to be changing.
+#if ENABLE_WORLD_COORDINATE
+void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type)
+{
+ if (!m_valid)
+ return;
+
+ assert(transformation_type.relative() || (transformation_type.absolute() && transformation_type.local()));
+
+ const Transform3d rotation_matrix = Geometry::rotation_transform(rotation);
+
+ for (unsigned int i : m_list) {
+ GLVolume& v = *(*m_volumes)[i];
+ const VolumeCache& volume_data = m_cache.volumes_data[i];
+ const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform();
+ if (m_mode == Instance && !is_wipe_tower()) {
+ assert(is_from_fully_selected_instance(i));
+ Transform3d new_rotation_matrix = Transform3d::Identity();
+ if (transformation_type.absolute())
+ new_rotation_matrix = rotation_matrix;
+ else {
+ if (transformation_type.world())
+ new_rotation_matrix = rotation_matrix * inst_trafo.get_rotation_matrix();
+ else if (transformation_type.local())
+ new_rotation_matrix = inst_trafo.get_rotation_matrix() * rotation_matrix;
+ else
+ assert(false);
+ }
+
+ const Vec3d new_offset = transformation_type.independent() ? inst_trafo.get_offset() :
+ m_cache.dragging_center + new_rotation_matrix * inst_trafo.get_rotation_matrix().inverse() *
+ (inst_trafo.get_offset() - m_cache.dragging_center);
+ v.set_instance_transformation(Geometry::assemble_transform(Geometry::translation_transform(new_offset), new_rotation_matrix,
+ inst_trafo.get_scaling_factor_matrix(), inst_trafo.get_mirror_matrix()));
+ }
+ else {
+ if (transformation_type.absolute()) {
+ const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform();
+ v.set_volume_transformation(Geometry::assemble_transform(volume_trafo.get_offset_matrix(), Geometry::rotation_transform(rotation),
+ volume_trafo.get_scaling_factor_matrix(), volume_trafo.get_mirror_matrix()));
+ }
+ else
+ transform_volume_relative(v, volume_data, transformation_type, Geometry::rotation_transform(rotation));
+ }
+ }
+
+#if !DISABLE_INSTANCES_SYNCH
+ if (m_mode == Instance) {
+ int rot_axis_max = 0;
+ rotation.cwiseAbs().maxCoeff(&rot_axis_max);
+ SyncRotationType synch;
+ if (transformation_type.world() && rot_axis_max == 2)
+ synch = SyncRotationType::NONE;
+ else if (transformation_type.local())
+ synch = SyncRotationType::FULL;
+ else
+ synch = SyncRotationType::GENERAL;
+ synchronize_unselected_instances(synch);
+ }
+ else if (m_mode == Volume)
+ synchronize_unselected_volumes();
+#endif // !DISABLE_INSTANCES_SYNCH
+
+ set_bounding_boxes_dirty();
+ wxGetApp().plater()->canvas3D()->requires_check_outside_state();
+}
+#else
void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type)
{
if (!m_valid)
@@ -769,11 +953,11 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
const GLVolume &first_volume = *(*m_volumes)[first_volume_idx];
const Vec3d &rotation = first_volume.get_instance_rotation();
const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation());
- volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff));
+ volume.set_instance_rotation(Vec3d(rotation.x(), rotation.y(), rotation.z() + z_diff));
}
else {
// extracts rotations from the composed transformation
- Vec3d new_rotation = transformation_type.world() ?
+ const Vec3d new_rotation = transformation_type.world() ?
Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) :
transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation();
if (rot_axis_max == 2 && transformation_type.joint()) {
@@ -781,9 +965,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation);
volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center));
}
- else if (!(m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center).isApprox(Vec3d::Zero()))
- volume.set_instance_offset(m_cache.dragging_center + Geometry::assemble_transform(Vec3d::Zero(), new_rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center));
-
volume.set_instance_rotation(new_rotation);
object_instance_first[volume.object_idx()] = i;
}
@@ -802,14 +983,13 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
v.set_volume_rotation(new_rotation);
}
}
- else
- {
+ else {
if (m_mode == Instance)
rotate_instance(v, i);
else if (m_mode == Volume) {
// extracts rotations from the composed transformation
- Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
- Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
+ const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
+ const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
if (transformation_type.joint()) {
const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center;
const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot);
@@ -821,12 +1001,12 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
}
}
- #if !DISABLE_INSTANCES_SYNCH
+#if !DISABLE_INSTANCES_SYNCH
if (m_mode == Instance)
- synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL);
+ synchronize_unselected_instances((rot_axis_max == 2) ? SyncRotationType::NONE : SyncRotationType::GENERAL);
else if (m_mode == Volume)
synchronize_unselected_volumes();
- #endif // !DISABLE_INSTANCES_SYNCH
+#endif // !DISABLE_INSTANCES_SYNCH
}
else { // it's the wipe tower that's selected and being rotated
GLVolume& volume = *((*m_volumes)[*m_list.begin()]); // the wipe tower is always alone in the selection
@@ -834,7 +1014,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
// make sure the wipe tower rotates around its center, not origin
// we can assume that only Z rotation changes
const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset();
- const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local;
+ const Vec3d center_local_new = Eigen::AngleAxisd(rotation.z()-volume.get_volume_rotation().z(), Vec3d::UnitZ()) * center_local;
volume.set_volume_rotation(rotation);
volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new);
}
@@ -842,6 +1022,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
set_bounding_boxes_dirty();
wxGetApp().plater()->canvas3D()->requires_check_outside_state();
}
+#endif // ENABLE_WORLD_COORDINATE
void Selection::flattening_rotate(const Vec3d& normal)
{
@@ -856,25 +1037,39 @@ void Selection::flattening_rotate(const Vec3d& normal)
for (unsigned int i : m_list) {
GLVolume& v = *(*m_volumes)[i];
// Normal transformed from the object coordinate space to the world coordinate space.
- const auto &voldata = m_cache.volumes_data[i];
+#if ENABLE_WORLD_COORDINATE
+ const Geometry::Transformation& old_inst_trafo = v.get_instance_transformation();
+ const Vec3d tnormal = old_inst_trafo.get_matrix().matrix().block(0, 0, 3, 3).inverse().transpose() * normal;
+ // Additional rotation to align tnormal with the down vector in the world coordinate space.
+ const Transform3d rotation_matrix = Transform3d(Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ()));
+ v.set_instance_transformation(old_inst_trafo.get_offset_matrix() * rotation_matrix * old_inst_trafo.get_matrix_no_offset());
+#else
+ const auto& voldata = m_cache.volumes_data[i];
Vec3d tnormal = (Geometry::assemble_transform(
Vec3d::Zero(), voldata.get_instance_rotation(),
voldata.get_instance_scaling_factor().cwiseInverse(), voldata.get_instance_mirror()) * normal).normalized();
// Additional rotation to align tnormal with the down vector in the world coordinate space.
- auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, - Vec3d::UnitZ());
+ auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ());
v.set_instance_rotation(Geometry::extract_euler_angles(extra_rotation.toRotationMatrix() * m_cache.volumes_data[i].get_instance_rotation_matrix()));
+#endif // ENABLE_WORLD_COORDINATE
}
#if !DISABLE_INSTANCES_SYNCH
// Apply the same transformation also to other instances,
// but respect their possibly diffrent z-rotation.
if (m_mode == Instance)
- synchronize_unselected_instances(SYNC_ROTATION_GENERAL);
+ synchronize_unselected_instances(SyncRotationType::GENERAL);
#endif // !DISABLE_INSTANCES_SYNCH
this->set_bounding_boxes_dirty();
}
+#if ENABLE_WORLD_COORDINATE
+void Selection::scale(const Vec3d& scale, TransformationType transformation_type)
+{
+ scale_and_translate(scale, Vec3d::Zero(), transformation_type);
+}
+#else
void Selection::scale(const Vec3d& scale, TransformationType transformation_type)
{
if (!m_valid)
@@ -884,10 +1079,10 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
GLVolume &v = *(*m_volumes)[i];
if (is_single_full_instance()) {
if (transformation_type.relative()) {
- Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale);
- Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3);
+ const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale);
+ const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3);
// extracts scaling factors from the composed transformation
- Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
+ const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
if (transformation_type.joint())
v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center));
@@ -907,22 +1102,22 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
else if (is_single_volume() || is_single_modifier())
v.set_volume_scaling_factor(scale);
else {
- Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale);
+ const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale);
if (m_mode == Instance) {
- Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3);
+ const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3);
// extracts scaling factors from the composed transformation
- Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
+ const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
if (transformation_type.joint())
v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center));
v.set_instance_scaling_factor(new_scale);
}
else if (m_mode == Volume) {
- Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3);
+ const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3);
// extracts scaling factors from the composed transformation
- Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
+ const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
if (transformation_type.joint()) {
- Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center);
+ const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center);
v.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset);
}
v.set_volume_scaling_factor(new_scale);
@@ -932,7 +1127,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
#if !DISABLE_INSTANCES_SYNCH
if (m_mode == Instance)
- synchronize_unselected_instances(SYNC_ROTATION_NONE);
+ synchronize_unselected_instances(SyncRotationType::NONE);
else if (m_mode == Volume)
synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH
@@ -941,6 +1136,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
set_bounding_boxes_dirty();
wxGetApp().plater()->canvas3D()->requires_check_outside_state();
}
+#endif // ENABLE_WORLD_COORDINATE
void Selection::scale_to_fit_print_volume(const BuildVolume& volume)
{
@@ -963,7 +1159,13 @@ void Selection::scale_to_fit_print_volume(const BuildVolume& volume)
// center selection on print bed
setup_cache();
offset.z() = -get_bounding_box().min.z();
+#if ENABLE_WORLD_COORDINATE
+ TransformationType trafo_type;
+ trafo_type.set_relative();
+ translate(offset, trafo_type);
+#else
translate(offset);
+#endif // ENABLE_WORLD_COORDINATE
wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot
wxGetApp().obj_manipul()->set_dirty();
@@ -1045,7 +1247,7 @@ void Selection::mirror(Axis axis)
#if !DISABLE_INSTANCES_SYNCH
if (m_mode == Instance)
- synchronize_unselected_instances(SYNC_ROTATION_NONE);
+ synchronize_unselected_instances(SyncRotationType::NONE);
else if (m_mode == Volume)
synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH
@@ -1053,6 +1255,137 @@ void Selection::mirror(Axis axis)
set_bounding_boxes_dirty();
}
+#if ENABLE_WORLD_COORDINATE
+void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type)
+{
+ if (!m_valid)
+ return;
+
+ Vec3d relative_scale = scale;
+
+ for (unsigned int i : m_list) {
+ GLVolume& v = *(*m_volumes)[i];
+ const VolumeCache& volume_data = m_cache.volumes_data[i];
+ const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform();
+
+ if (transformation_type.absolute()) {
+ // convert from absolute scaling to relative scaling
+ BoundingBoxf3 original_box;
+ if (m_mode == Instance) {
+ assert(is_from_fully_selected_instance(i));
+ if (transformation_type.world())
+ original_box = get_full_unscaled_instance_bounding_box();
+ else
+ original_box = get_full_unscaled_instance_local_bounding_box();
+ }
+ else {
+ if (transformation_type.world())
+ original_box = v.transformed_convex_hull_bounding_box((volume_data.get_instance_transform() *
+ volume_data.get_volume_transform()).get_matrix_no_scaling_factor());
+ else if (transformation_type.instance())
+ original_box = v.transformed_convex_hull_bounding_box(volume_data.get_volume_transform().get_matrix_no_scaling_factor());
+ else
+ original_box = v.bounding_box();
+ }
+
+ relative_scale = original_box.size().cwiseProduct(scale).cwiseQuotient(m_box.get_bounding_box().size());
+ }
+
+ if (m_mode == Instance) {
+ assert(is_from_fully_selected_instance(i));
+ if (transformation_type.world()) {
+ const Transform3d scale_matrix = Geometry::scale_transform(relative_scale);
+ const Transform3d offset_matrix = (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) ?
+ // non-constrained scaling - add offset to scale around selection center
+ Geometry::translation_transform(m_cache.dragging_center + scale_matrix * (inst_trafo.get_offset() - m_cache.dragging_center)) :
+ // constrained scaling - add offset to keep constraint
+ Geometry::translation_transform(translation) * inst_trafo.get_offset_matrix();
+ v.set_instance_transformation(offset_matrix * scale_matrix * inst_trafo.get_matrix_no_offset());
+ }
+ else if (transformation_type.local()) {
+ const Transform3d scale_matrix = Geometry::scale_transform(relative_scale);
+ Vec3d offset;
+ if (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) {
+ // non-constrained scaling - add offset to scale around selection center
+ offset = inst_trafo.get_matrix_no_offset().inverse() * (inst_trafo.get_offset() - m_cache.dragging_center);
+ offset = inst_trafo.get_matrix_no_offset() * (scale_matrix * offset - offset);
+ }
+ else
+ // constrained scaling - add offset to keep constraint
+ offset = translation;
+
+ v.set_instance_transformation(Geometry::translation_transform(offset) * inst_trafo.get_matrix() * scale_matrix);
+ }
+ else
+ assert(false);
+ }
+ else
+ transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(relative_scale));
+ }
+
+#if !DISABLE_INSTANCES_SYNCH
+ if (m_mode == Instance)
+ synchronize_unselected_instances(SyncRotationType::NONE);
+ else if (m_mode == Volume)
+ synchronize_unselected_volumes();
+#endif // !DISABLE_INSTANCES_SYNCH
+
+ ensure_on_bed();
+ set_bounding_boxes_dirty();
+ wxGetApp().plater()->canvas3D()->requires_check_outside_state();
+}
+
+void Selection::reset_skew()
+{
+ if (!m_valid)
+ return;
+
+ for (unsigned int i : m_list) {
+ GLVolume& v = *(*m_volumes)[i];
+ const VolumeCache& volume_data = m_cache.volumes_data[i];
+ Geometry::Transformation inst_trafo = volume_data.get_instance_transform();
+ Geometry::Transformation vol_trafo = volume_data.get_volume_transform();
+ Geometry::Transformation world_trafo = inst_trafo * vol_trafo;
+ if (world_trafo.has_skew()) {
+ if (!inst_trafo.has_skew() && !vol_trafo.has_skew()) {
+ // <W> = [I][V]
+ world_trafo.reset_offset();
+ world_trafo.reset_skew();
+ v.set_volume_transformation(vol_trafo.get_offset_matrix() * inst_trafo.get_matrix_no_offset().inverse() * world_trafo.get_matrix());
+ }
+ else {
+ // <W> = <I><V>
+ // <W> = <I>[V]
+ // <W> = [I]<V>
+ if (inst_trafo.has_skew()) {
+ inst_trafo.reset_skew();
+ v.set_instance_transformation(inst_trafo);
+ }
+ if (vol_trafo.has_skew()) {
+ vol_trafo.reset_skew();
+ v.set_volume_transformation(vol_trafo);
+ }
+ }
+ }
+ else {
+ // [W] = [I][V]
+ // [W] = <I><V>
+ if (inst_trafo.has_skew()) {
+ inst_trafo.reset_skew();
+ v.set_instance_transformation(inst_trafo);
+ }
+ if (vol_trafo.has_skew()) {
+ vol_trafo.reset_skew();
+ v.set_volume_transformation(vol_trafo);
+ }
+ }
+ }
+
+ ensure_on_bed();
+ set_bounding_boxes_dirty();
+ wxGetApp().plater()->canvas3D()->requires_check_outside_state();
+}
+#else
void Selection::translate(unsigned int object_idx, const Vec3d& displacement)
{
if (!m_valid)
@@ -1101,6 +1434,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement)
this->set_bounding_boxes_dirty();
}
+#endif // ENABLE_WORLD_COORDINATE
void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement)
{
@@ -1110,7 +1444,11 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co
for (unsigned int i : m_list) {
GLVolume& v = *(*m_volumes)[i];
if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx)
+#if ENABLE_WORLD_COORDINATE
+ v.set_instance_transformation(Geometry::translation_transform(displacement) * v.get_instance_transformation().get_matrix());
+#else
v.set_instance_offset(v.get_instance_offset() + displacement);
+#endif // ENABLE_WORLD_COORDINATE
}
std::set<unsigned int> done; // prevent processing volumes twice
@@ -1143,7 +1481,11 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co
if (v.object_idx() != object_idx || v.instance_idx() != (int)instance_idx)
continue;
+#if ENABLE_WORLD_COORDINATE
+ v.set_instance_transformation(Geometry::translation_transform(displacement) * v.get_instance_transformation().get_matrix());
+#else
v.set_instance_offset(v.get_instance_offset() + displacement);
+#endif // ENABLE_WORLD_COORDINATE
done.insert(j);
}
}
@@ -1151,6 +1493,59 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co
this->set_bounding_boxes_dirty();
}
+#if ENABLE_WORLD_COORDINATE
+int Selection::bake_transform_if_needed() const
+{
+ if ((is_single_full_instance() && wxGetApp().obj_manipul()->is_world_coordinates()) ||
+ (is_single_volume_or_modifier() && !wxGetApp().obj_manipul()->is_local_coordinates())) {
+ // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible.
+ // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
+ const GLVolume& volume = *get_first_volume();
+ bool needs_baking = false;
+ if (is_single_full_instance()) {
+ // Is the instance angle close to a multiple of 90 degrees?
+ needs_baking |= !Geometry::is_rotation_ninety_degrees(volume.get_instance_rotation());
+ // Are all volumes angles close to a multiple of 90 degrees?
+ for (unsigned int id : get_volume_idxs()) {
+ if (needs_baking)
+ break;
+ needs_baking |= !Geometry::is_rotation_ninety_degrees(get_volume(id)->get_volume_rotation());
+ }
+ }
+ else if (is_single_volume_or_modifier()) {
+ // is the volume angle close to a multiple of 90 degrees?
+ needs_baking |= !Geometry::is_rotation_ninety_degrees(volume.get_volume_rotation());
+ if (wxGetApp().obj_manipul()->is_world_coordinates())
+ // Is the instance angle close to a multiple of 90 degrees?
+ needs_baking |= !Geometry::is_rotation_ninety_degrees(volume.get_instance_rotation());
+ }
+
+ if (needs_baking) {
+ MessageDialog dlg((wxWindow*)wxGetApp().mainframe,
+ _L("The currently manipulated object is tilted or contains tilted parts (rotation angles are not multiples of 90°). "
+ "Non-uniform scaling of tilted objects is only possible in non-local coordinate systems, "
+ "once the rotation is embedded into the object coordinates.") + "\n" +
+ _L("This operation is irreversible.") + "\n" +
+ _L("Do you want to proceed?"),
+ SLIC3R_APP_NAME,
+ wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
+ if (dlg.ShowModal() != wxID_YES)
+ return -1;
+
+ wxGetApp().plater()->take_snapshot(_("Bake transform"));
+
+ // Bake the rotation into the meshes of the object.
+ wxGetApp().model().objects[volume.composite_id.object_id]->bake_xy_rotation_into_meshes(volume.composite_id.instance_id);
+ // Update the 3D scene, selections etc.
+ wxGetApp().plater()->update();
+ return 0;
+ }
+ }
+
+ return 1;
+}
+#endif // ENABLE_WORLD_COORDINATE
+
void Selection::erase()
{
if (!m_valid)
@@ -1264,7 +1659,34 @@ void Selection::render(float scale_factor)
m_scale_factor = scale_factor;
// render cumulative bounding box of selected volumes
#if ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_WORLD_COORDINATE
+ BoundingBoxf3 box;
+ Transform3d trafo;
+ const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
+ if (coordinates_type == ECoordinatesType::World) {
+ box = get_bounding_box();
+ trafo = Transform3d::Identity();
+ }
+ else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) {
+ const GLVolume& v = *get_first_volume();
+ box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_scaling_factor_matrix());
+ trafo = v.get_instance_transformation().get_matrix() * v.get_volume_transformation().get_matrix_no_scaling_factor();
+ }
+ else {
+ const Selection::IndicesList& ids = get_volume_idxs();
+ for (unsigned int id : ids) {
+ const GLVolume& v = *get_volume(id);
+ box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
+ }
+ const Geometry::Transformation inst_trafo = get_first_volume()->get_instance_transformation();
+ box = box.transformed(inst_trafo.get_scaling_factor_matrix());
+ trafo = inst_trafo.get_matrix_no_scaling_factor();
+ }
+
+ render_bounding_box(box, trafo, ColorRGB::WHITE());
+#else
render_bounding_box(get_bounding_box(), ColorRGB::WHITE());
+#endif // ENABLE_WORLD_COORDINATE
#else
render_selected_volumes();
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
@@ -1350,16 +1772,30 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
glsafe(::glPushMatrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#if ENABLE_WORLD_COORDINATE
+ const Vec3d center = get_bounding_box().center();
+ Vec3d axes_center = center;
+#endif // ENABLE_WORLD_COORDINATE
+
if (!boost::starts_with(sidebar_field, "layer")) {
#if ENABLE_GL_SHADERS_ATTRIBUTES
shader->set_uniform("emission_factor", 0.05f);
-#else
- const Vec3d& center = get_bounding_box().center();
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE
+ const Vec3d& center = get_bounding_box().center();
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE
+#if ENABLE_WORLD_COORDINATE
+ if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) {
+#else
if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) {
-#if !ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_WORLD_COORDINATE
+#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE
glsafe(::glTranslated(center.x(), center.y(), center.z()));
-#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE
+#if ENABLE_WORLD_COORDINATE
+ orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix();
+ axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset();
+#else
if (!boost::starts_with(sidebar_field, "position")) {
#if !ENABLE_GL_SHADERS_ATTRIBUTES
Transform3d orient_matrix = Transform3d::Identity();
@@ -1380,33 +1816,57 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
#if !ENABLE_GL_SHADERS_ATTRIBUTES
glsafe(::glMultMatrixd(orient_matrix.data()));
#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
- }
+ }
+#endif // ENABLE_WORLD_COORDINATE
}
+#if ENABLE_WORLD_COORDINATE
+ else if (is_single_volume_or_modifier()) {
+#else
else if (is_single_volume() || is_single_modifier()) {
+#endif // ENABLE_WORLD_COORDINATE
+#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE
+ glsafe(::glTranslated(center.x(), center.y(), center.z()));
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE
+#if ENABLE_WORLD_COORDINATE
+ if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
+ if (wxGetApp().obj_manipul()->is_local_coordinates()) {
+ const GLVolume* v = (*m_volumes)[*m_list.begin()];
+ orient_matrix = v->get_instance_transformation().get_rotation_matrix() * v->get_volume_transformation().get_rotation_matrix();
+ axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation();
+ }
+ else {
+ orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix();
+ axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset();
+ }
+ }
+#else
#if ENABLE_GL_SHADERS_ATTRIBUTES
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
#else
- glsafe(::glTranslated(center.x(), center.y(), center.z()));
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
if (!boost::starts_with(sidebar_field, "position"))
orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true);
-
#if !ENABLE_GL_SHADERS_ATTRIBUTES
glsafe(::glMultMatrixd(orient_matrix.data()));
#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_WORLD_COORDINATE
}
else {
-#if ENABLE_GL_SHADERS_ATTRIBUTES
+#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE
if (requires_local_axes())
+#if ENABLE_WORLD_COORDINATE
+ orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix();
+#else
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
+#endif // ENABLE_WORLD_COORDINATE
#else
glsafe(::glTranslated(center.x(), center.y(), center.z()));
if (requires_local_axes()) {
const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE
}
}
@@ -1415,6 +1875,17 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_WORLD_COORDINATE
+ if (!boost::starts_with(sidebar_field, "layer")) {
+ shader->set_uniform("emission_factor", 0.1f);
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPushMatrix());
+ glsafe(::glTranslated(center.x(), center.y(), center.z()));
+ glsafe(::glMultMatrixd(orient_matrix.data()));
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+ }
+#endif // ENABLE_WORLD_COORDINATE
+
#if ENABLE_GL_SHADERS_ATTRIBUTES
if (boost::starts_with(sidebar_field, "position"))
render_sidebar_position_hints(sidebar_field, *shader, base_matrix * orient_matrix);
@@ -1424,6 +1895,13 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix);
else if (boost::starts_with(sidebar_field, "layer"))
render_sidebar_layers_hints(sidebar_field, *shader);
+
+#if ENABLE_WORLD_COORDINATE
+ if (!boost::starts_with(sidebar_field, "layer")) {
+ if (!wxGetApp().obj_manipul()->is_world_coordinates())
+ m_axes.render(Geometry::assemble_transform(axes_center) * orient_matrix, 0.25f);
+ }
+#endif // ENABLE_WORLD_COORDINATE
#else
if (boost::starts_with(sidebar_field, "position"))
render_sidebar_position_hints(sidebar_field);
@@ -1434,9 +1912,25 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
else if (boost::starts_with(sidebar_field, "layer"))
render_sidebar_layers_hints(sidebar_field);
- glsafe(::glPopMatrix());
+#if ENABLE_WORLD_COORDINATE
+ if (!boost::starts_with(sidebar_field, "layer")) {
+ glsafe(::glPopMatrix());
+ glsafe(::glPushMatrix());
+ glsafe(::glTranslated(axes_center.x(), axes_center.y(), axes_center.z()));
+ glsafe(::glMultMatrixd(orient_matrix.data()));
+ if (!wxGetApp().obj_manipul()->is_world_coordinates())
+ m_axes.render(0.25f);
+ glsafe(::glPopMatrix());
+ }
+#endif // ENABLE_WORLD_COORDINATE
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
+#if ENABLE_WORLD_COORDINATE
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_WORLD_COORDINATE
+
#if !ENABLE_LEGACY_OPENGL_REMOVAL
if (!boost::starts_with(sidebar_field, "layer"))
#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
@@ -1519,8 +2013,7 @@ std::vector<unsigned int> Selection::get_volume_idxs_from_object(unsigned int ob
{
std::vector<unsigned int> idxs;
- for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
- {
+ for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
if ((*m_volumes)[i]->object_idx() == (int)object_idx)
idxs.push_back(i);
}
@@ -1532,10 +2025,9 @@ std::vector<unsigned int> Selection::get_volume_idxs_from_instance(unsigned int
{
std::vector<unsigned int> idxs;
- for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
- {
+ for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
const GLVolume* v = (*m_volumes)[i];
- if ((v->object_idx() == (int)object_idx) && (v->instance_idx() == (int)instance_idx))
+ if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx)
idxs.push_back(i);
}
@@ -1549,9 +2041,8 @@ std::vector<unsigned int> Selection::get_volume_idxs_from_volume(unsigned int ob
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
{
const GLVolume* v = (*m_volumes)[i];
- if ((v->object_idx() == (int)object_idx) && (v->volume_idx() == (int)volume_idx))
- {
- if (((int)instance_idx != -1) && (v->instance_idx() == (int)instance_idx))
+ if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) {
+ if ((int)instance_idx != -1 && v->instance_idx() == (int)instance_idx)
idxs.push_back(i);
}
}
@@ -1563,8 +2054,7 @@ std::vector<unsigned int> Selection::get_missing_volume_idxs_from(const std::vec
{
std::vector<unsigned int> idxs;
- for (unsigned int i : m_list)
- {
+ for (unsigned int i : m_list) {
std::vector<unsigned int>::const_iterator it = std::find(volume_idxs.begin(), volume_idxs.end(), i);
if (it == volume_idxs.end())
idxs.push_back(i);
@@ -1577,8 +2067,7 @@ std::vector<unsigned int> Selection::get_unselected_volume_idxs_from(const std::
{
std::vector<unsigned int> idxs;
- for (unsigned int i : volume_idxs)
- {
+ for (unsigned int i : volume_idxs) {
if (m_list.find(i) == m_list.end())
idxs.push_back(i);
}
@@ -1596,8 +2085,7 @@ void Selection::update_type()
m_cache.content.clear();
m_type = Mixed;
- for (unsigned int i : m_list)
- {
+ for (unsigned int i : m_list) {
const GLVolume* volume = (*m_volumes)[i];
int obj_idx = volume->object_idx();
int inst_idx = volume->instance_idx();
@@ -1616,23 +2104,19 @@ void Selection::update_type()
{
if (m_list.empty())
m_type = Empty;
- else if (m_list.size() == 1)
- {
+ else if (m_list.size() == 1) {
const GLVolume* first = (*m_volumes)[*m_list.begin()];
if (first->is_wipe_tower)
m_type = WipeTower;
- else if (first->is_modifier)
- {
+ else if (first->is_modifier) {
m_type = SingleModifier;
requires_disable = true;
}
- else
- {
+ else {
const ModelObject* model_object = m_model->objects[first->object_idx()];
unsigned int volumes_count = (unsigned int)model_object->volumes.size();
unsigned int instances_count = (unsigned int)model_object->instances.size();
- if (volumes_count * instances_count == 1)
- {
+ if (volumes_count * instances_count == 1) {
m_type = SingleFullObject;
// ensures the correct mode is selected
m_mode = Instance;
@@ -1643,15 +2127,13 @@ void Selection::update_type()
// ensures the correct mode is selected
m_mode = Instance;
}
- else
- {
+ else {
m_type = SingleVolume;
requires_disable = true;
}
}
}
- else
- {
+ else {
unsigned int sla_volumes_count = 0;
// Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is
for (unsigned int i : m_list) {
@@ -1666,25 +2148,20 @@ void Selection::update_type()
unsigned int instances_count = (unsigned int)model_object->instances.size();
unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size();
- if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size())
- {
+ if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) {
m_type = SingleFullObject;
// ensures the correct mode is selected
m_mode = Instance;
}
- else if (selected_instances_count == 1)
- {
- if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())
- {
+ else if (selected_instances_count == 1) {
+ if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) {
m_type = SingleFullInstance;
// ensures the correct mode is selected
m_mode = Instance;
}
- else
- {
+ else {
unsigned int modifiers_count = 0;
- for (unsigned int i : m_list)
- {
+ for (unsigned int i : m_list) {
if ((*m_volumes)[i]->is_modifier)
++modifiers_count;
}
@@ -1697,25 +2174,21 @@ void Selection::update_type()
requires_disable = true;
}
}
- else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()))
- {
+ else if (selected_instances_count > 1 && selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) {
m_type = MultipleFullInstance;
// ensures the correct mode is selected
m_mode = Instance;
}
}
- else
- {
+ else {
unsigned int sels_cntr = 0;
- for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it)
- {
+ for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) {
const ModelObject* model_object = m_model->objects[it->first];
unsigned int volumes_count = (unsigned int)model_object->volumes.size();
unsigned int instances_count = (unsigned int)model_object->instances.size();
sels_cntr += volumes_count * instances_count;
}
- if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size())
- {
+ if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) {
m_type = MultipleFullObject;
// ensures the correct mode is selected
m_mode = Instance;
@@ -1726,8 +2199,7 @@ void Selection::update_type()
int object_idx = get_object_idx();
int instance_idx = get_instance_idx();
- for (GLVolume* v : *m_volumes)
- {
+ for (GLVolume* v : *m_volumes) {
v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false;
}
@@ -1893,6 +2365,12 @@ void Selection::render_synchronized_volumes()
float color[3] = { 1.0f, 1.0f, 0.0f };
#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_WORLD_COORDINATE
+ const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
+ BoundingBoxf3 box;
+ Transform3d trafo;
+#endif // ENABLE_WORLD_COORDINATE
+
for (unsigned int i : m_list) {
const GLVolume& volume = *(*m_volumes)[i];
int object_idx = volume.object_idx();
@@ -1906,7 +2384,23 @@ void Selection::render_synchronized_volumes()
continue;
#if ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_WORLD_COORDINATE
+ if (coordinates_type == ECoordinatesType::World) {
+ box = v.transformed_convex_hull_bounding_box();
+ trafo = Transform3d::Identity();
+ }
+ else if (coordinates_type == ECoordinatesType::Local) {
+ box = v.bounding_box();
+ trafo = v.world_matrix();
+ }
+ else {
+ box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix());
+ trafo = v.get_instance_transformation().get_matrix();
+ }
+ render_bounding_box(box, trafo, ColorRGB::YELLOW());
+#else
render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW());
+#endif // ENABLE_WORLD_COORDINATE
#else
render_bounding_box(v.transformed_convex_hull_bounding_box(), color);
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
@@ -1915,7 +2409,11 @@ void Selection::render_synchronized_volumes()
}
#if ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_WORLD_COORDINATE
+void Selection::render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color)
+#else
void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color)
+#endif // ENABLE_WORLD_COORDINATE
{
#else
void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const
@@ -1934,6 +2432,7 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con
#if ENABLE_LEGACY_OPENGL_REMOVAL
const BoundingBoxf3& curr_box = m_box.get_bounding_box();
+
if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max)) {
m_box.reset();
@@ -2019,15 +2518,32 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con
if (shader == nullptr)
return;
+#if ENABLE_WORLD_COORDINATE
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPushMatrix());
+ glsafe(::glMultMatrixd(trafo.data()));
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_WORLD_COORDINATE
+
shader->start_using();
#if ENABLE_GL_SHADERS_ATTRIBUTES
const Camera& camera = wxGetApp().plater()->get_camera();
+#if ENABLE_WORLD_COORDINATE
+ shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo);
+#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
+#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
m_box.set_color(to_rgba(color));
m_box.render();
shader->stop_using();
+
+#if ENABLE_WORLD_COORDINATE
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
+ glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
+#endif // ENABLE_WORLD_COORDINATE
#else
::glBegin(GL_LINES);
@@ -2223,7 +2739,11 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLS
void Selection::render_sidebar_scale_hints(const std::string& sidebar_field)
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
{
+#if ENABLE_WORLD_COORDINATE
+ const bool uniform_scale = wxGetApp().obj_manipul()->get_uniform_scaling();
+#else
const bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling();
+#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_GL_SHADERS_ATTRIBUTES
auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& matrix) {
@@ -2479,22 +2999,30 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
if (done.size() == m_volumes->size())
break;
- const GLVolume* volume = (*m_volumes)[i];
+ const GLVolume* volume_i = (*m_volumes)[i];
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
- if (volume->is_wipe_tower)
+ if (volume_i->is_wipe_tower)
continue;
- const int object_idx = volume->object_idx();
+ const int object_idx = volume_i->object_idx();
#else
- const int object_idx = volume->object_idx();
+ const int object_idx = volume_i->object_idx();
if (object_idx >= 1000)
continue;
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
- const int instance_idx = volume->instance_idx();
- const Vec3d& rotation = volume->get_instance_rotation();
- const Vec3d& scaling_factor = volume->get_instance_scaling_factor();
- const Vec3d& mirror = volume->get_instance_mirror();
+ const int instance_idx = volume_i->instance_idx();
+#if ENABLE_WORLD_COORDINATE
+ const Geometry::Transformation& curr_inst_trafo_i = volume_i->get_instance_transformation();
+ const Vec3d curr_inst_rotation_i = curr_inst_trafo_i.get_rotation();
+ const Vec3d& curr_inst_scaling_factor_i = curr_inst_trafo_i.get_scaling_factor();
+ const Vec3d& curr_inst_mirror_i = curr_inst_trafo_i.get_mirror();
+ const Vec3d old_inst_rotation_i = m_cache.volumes_data[i].get_instance_transform().get_rotation();
+#else
+ const Vec3d& rotation = volume_i->get_instance_rotation();
+ const Vec3d& scaling_factor = volume_i->get_instance_scaling_factor();
+ const Vec3d& mirror = volume_i->get_instance_mirror();
+#endif // ENABLE_WORLD_COORDINATE
// Process unselected instances.
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) {
@@ -2504,29 +3032,68 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
if (done.find(j) != done.end())
continue;
- GLVolume* v = (*m_volumes)[j];
- if (v->object_idx() != object_idx || v->instance_idx() == instance_idx)
+ GLVolume* volume_j = (*m_volumes)[j];
+ if (volume_j->object_idx() != object_idx || volume_j->instance_idx() == instance_idx)
continue;
+#if ENABLE_WORLD_COORDINATE
+ const Vec3d old_inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation();
+ assert(is_rotation_xy_synchronized(old_inst_rotation_i, old_inst_rotation_j));
+ const Geometry::Transformation& curr_inst_trafo_j = volume_j->get_instance_transformation();
+ const Vec3d curr_inst_rotation_j = curr_inst_trafo_j.get_rotation();
+ Vec3d new_inst_offset_j = curr_inst_trafo_j.get_offset();
+ Vec3d new_inst_rotation_j = curr_inst_rotation_j;
+#else
assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()));
+#endif // ENABLE_WORLD_COORDINATE
+
switch (sync_rotation_type) {
- case SYNC_ROTATION_NONE: {
+ case SyncRotationType::NONE: {
// z only rotation -> synch instance z
// The X,Y rotations should be synchronized from start to end of the rotation.
- assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation()));
+#if ENABLE_WORLD_COORDINATE
+ assert(is_rotation_xy_synchronized(curr_inst_rotation_i, curr_inst_rotation_j));
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
- v->set_instance_offset(Z, volume->get_instance_offset().z());
+ new_inst_offset_j.z() = curr_inst_trafo_i.get_offset().z();
+#else
+ assert(is_rotation_xy_synchronized(rotation, volume_j->get_instance_rotation()));
+ if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
+ volume_j->set_instance_offset(Z, volume_i->get_instance_offset().z());
+#endif // ENABLE_WORLD_COORDINATE
break;
}
- case SYNC_ROTATION_GENERAL:
+ case SyncRotationType::GENERAL: {
// generic rotation -> update instance z with the delta of the rotation.
+#if ENABLE_WORLD_COORDINATE
+ const double z_diff = Geometry::rotation_diff_z(old_inst_rotation_i, old_inst_rotation_j);
+ new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ();
+#else
const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation());
- v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff });
+ volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff });
+#endif // ENABLE_WORLD_COORDINATE
break;
}
+#if ENABLE_WORLD_COORDINATE
+ case SyncRotationType::FULL: {
+ // generic rotation -> update instance z with the delta of the rotation.
+ const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(curr_inst_rotation_i, old_inst_rotation_j));
+ const Vec3d& axis = angle_axis.axis();
+ const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ?
+ angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(curr_inst_rotation_i, old_inst_rotation_j);
+
+ new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ();
+ break;
+ }
+#endif // ENABLE_WORLD_COORDINATE
+ }
- v->set_instance_scaling_factor(scaling_factor);
- v->set_instance_mirror(mirror);
+#if ENABLE_WORLD_COORDINATE
+ volume_j->set_instance_transformation(Geometry::assemble_transform(new_inst_offset_j, new_inst_rotation_j,
+ curr_inst_scaling_factor_i, curr_inst_mirror_i));
+#else
+ volume_j->set_instance_scaling_factor(scaling_factor);
+ volume_j->set_instance_mirror(mirror);
+#endif // ENABLE_WORLD_COORDINATE
done.insert(j);
}
@@ -2553,10 +3120,14 @@ void Selection::synchronize_unselected_volumes()
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
const int volume_idx = volume->volume_idx();
+#if ENABLE_WORLD_COORDINATE
+ const Geometry::Transformation& trafo = volume->get_volume_transformation();
+#else
const Vec3d& offset = volume->get_volume_offset();
const Vec3d& rotation = volume->get_volume_rotation();
const Vec3d& scaling_factor = volume->get_volume_scaling_factor();
const Vec3d& mirror = volume->get_volume_mirror();
+#endif // ENABLE_WORLD_COORDINATE
// Process unselected volumes.
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) {
@@ -2567,10 +3138,14 @@ void Selection::synchronize_unselected_volumes()
if (v->object_idx() != object_idx || v->volume_idx() != volume_idx)
continue;
+#if ENABLE_WORLD_COORDINATE
+ v->set_volume_transformation(trafo);
+#else
v->set_volume_offset(offset);
v->set_volume_rotation(rotation);
v->set_volume_scaling_factor(scaling_factor);
v->set_volume_mirror(mirror);
+#endif // ENABLE_WORLD_COORDINATE
}
}
}
@@ -2685,8 +3260,13 @@ void Selection::paste_volumes_from_clipboard()
{
ModelInstance* dst_instance = dst_object->instances[dst_inst_idx];
BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx);
+#if ENABLE_WORLD_COORDINATE
+ Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix_no_offset();
+ Transform3d dst_matrix = dst_instance->get_transformation().get_matrix_no_offset();
+#else
Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true);
Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true);
+#endif // ENABLE_WORLD_COORDINATE
bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix);
// used to keep relative position of multivolume selections when pasting from another object
@@ -2764,5 +3344,27 @@ void Selection::paste_objects_from_clipboard()
#endif /* _DEBUG */
}
+#if ENABLE_WORLD_COORDINATE
+void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type,
+ const Transform3d& transform)
+{
+ const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform();
+ const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform();
+ if (transformation_type.world()) {
+ const Transform3d inst_matrix_no_offset = inst_trafo.get_matrix_no_offset();
+ const Transform3d new_volume_matrix = inst_matrix_no_offset.inverse() * transform * inst_matrix_no_offset;
+ volume.set_volume_transformation(volume_trafo.get_offset_matrix() * new_volume_matrix * volume_trafo.get_matrix_no_offset());
+ }
+ else if (transformation_type.instance())
+ volume.set_volume_transformation(volume_trafo.get_offset_matrix() * transform * volume_trafo.get_matrix_no_offset());
+ else if (transformation_type.local()) {
+ const Geometry::Transformation trafo(transform);
+ volume.set_volume_transformation(trafo.get_offset_matrix() * volume_trafo.get_matrix() * trafo.get_matrix_no_offset());
+ }
+ else
+ assert(false);
+}
+#endif // ENABLE_WORLD_COORDINATE
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp
index c9e55cf82..0e6922c63 100644
--- a/src/slic3r/GUI/Selection.hpp
+++ b/src/slic3r/GUI/Selection.hpp
@@ -2,7 +2,12 @@
#define slic3r_GUI_Selection_hpp_
#include "libslic3r/Geometry.hpp"
+#if ENABLE_WORLD_COORDINATE
+#include "GUI_Geometry.hpp"
+#include "CoordAxes.hpp"
+#else
#include "GLModel.hpp"
+#endif // ENABLE_WORLD_COORDINATE
#include <set>
#include <optional>
@@ -24,6 +29,7 @@ using ModelObjectPtrs = std::vector<ModelObject*>;
namespace GUI {
+#if !ENABLE_WORLD_COORDINATE
class TransformationType
{
public:
@@ -76,6 +82,7 @@ private:
Enum m_value;
};
+#endif // !ENABLE_WORLD_COORDINATE
class Selection
{
@@ -110,16 +117,19 @@ private:
private:
struct TransformCache
{
- Vec3d position;
- Vec3d rotation;
- Vec3d scaling_factor;
- Vec3d mirror;
- Transform3d rotation_matrix;
- Transform3d scale_matrix;
- Transform3d mirror_matrix;
- Transform3d full_matrix;
-
- TransformCache();
+ Vec3d position{ Vec3d::Zero() };
+ Vec3d rotation{ Vec3d::Zero() };
+ Vec3d scaling_factor{ Vec3d::Ones() };
+ Vec3d mirror{ Vec3d::Ones() };
+ Transform3d rotation_matrix{ Transform3d::Identity() };
+ Transform3d scale_matrix{ Transform3d::Identity() };
+ Transform3d mirror_matrix{ Transform3d::Identity() };
+ Transform3d full_matrix{ Transform3d::Identity() };
+#if ENABLE_WORLD_COORDINATE
+ Geometry::Transformation transform;
+#endif // ENABLE_WORLD_COORDINATE
+
+ TransformCache() = default;
explicit TransformCache(const Geometry::Transformation& transform);
};
@@ -131,13 +141,18 @@ private:
VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform);
const Vec3d& get_volume_position() const { return m_volume.position; }
+#if !ENABLE_WORLD_COORDINATE
const Vec3d& get_volume_rotation() const { return m_volume.rotation; }
const Vec3d& get_volume_scaling_factor() const { return m_volume.scaling_factor; }
const Vec3d& get_volume_mirror() const { return m_volume.mirror; }
+#endif // !ENABLE_WORLD_COORDINATE
const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; }
const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; }
const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; }
const Transform3d& get_volume_full_matrix() const { return m_volume.full_matrix; }
+#if ENABLE_WORLD_COORDINATE
+ const Geometry::Transformation& get_volume_transform() const { return m_volume.transform; }
+#endif // ENABLE_WORLD_COORDINATE
const Vec3d& get_instance_position() const { return m_instance.position; }
const Vec3d& get_instance_rotation() const { return m_instance.rotation; }
@@ -147,6 +162,9 @@ private:
const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; }
const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; }
const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; }
+#if ENABLE_WORLD_COORDINATE
+ const Geometry::Transformation& get_instance_transform() const { return m_instance.transform; }
+#endif // ENABLE_WORLD_COORDINATE
};
public:
@@ -207,15 +225,32 @@ private:
Cache m_cache;
Clipboard m_clipboard;
std::optional<BoundingBoxf3> m_bounding_box;
- // Bounding box of a selection, with no instance scaling applied. This bounding box
- // is useful for absolute scaling of tilted objects in world coordinate space.
+ // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied.
+ // This bounding box is useful for absolute scaling of tilted objects in world coordinate space.
+ // Modifiers are NOT taken in account
std::optional<BoundingBoxf3> m_unscaled_instance_bounding_box;
+ // Bounding box of a single full instance selection, in world coordinates.
+ // Modifiers are NOT taken in account
std::optional<BoundingBoxf3> m_scaled_instance_bounding_box;
+#if ENABLE_WORLD_COORDINATE
+ // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied.
+ // Modifiers are taken in account
+ std::optional<BoundingBoxf3> m_full_unscaled_instance_bounding_box;
+ // Bounding box of a single full instance selection, in world coordinates.
+ // Modifiers are taken in account
+ std::optional<BoundingBoxf3> m_full_scaled_instance_bounding_box;
+ // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied.
+ // Modifiers are taken in account
+ std::optional<BoundingBoxf3> m_full_unscaled_instance_local_bounding_box;
+#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_RENDER_SELECTION_CENTER
GLModel m_vbo_sphere;
#endif // ENABLE_RENDER_SELECTION_CENTER
+#if ENABLE_WORLD_COORDINATE
+ CoordAxes m_axes;
+#endif // ENABLE_WORLD_COORDINATE
GLModel m_arrow;
GLModel m_curved_arrow;
#if ENABLE_LEGACY_OPENGL_REMOVAL
@@ -290,6 +325,9 @@ public:
bool is_from_single_object() const;
bool is_sla_compliant() const;
bool is_instance_mode() const { return m_mode == Instance; }
+#if ENABLE_WORLD_COORDINATE
+ bool is_single_volume_or_modifier() const { return is_single_volume() || is_single_modifier(); }
+#endif // ENABLE_WORLD_COORDINATE
bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); }
// returns true if the selection contains all the given indices
@@ -299,7 +337,18 @@ public:
// returns true if the selection contains all and only the given indices
bool matches(const std::vector<unsigned int>& volume_idxs) const;
+#if ENABLE_WORLD_COORDINATE
+ enum class EUniformScaleRequiredReason : unsigned char
+ {
+ NotRequired,
+ InstanceNotAxisAligned_World,
+ VolumeNotAxisAligned_World,
+ VolumeNotAxisAligned_Instance,
+ MultipleSelection,
+ };
+#else
bool requires_uniform_scale() const;
+#endif // ENABLE_WORLD_COORDINATE
// Returns the the object id if the selection is from a single object, otherwise is -1
int get_object_idx() const;
@@ -311,28 +360,60 @@ public:
const IndicesList& get_volume_idxs() const { return m_list; }
const GLVolume* get_volume(unsigned int volume_idx) const;
+ const GLVolume* get_first_volume() const { return get_volume(*m_list.begin()); }
const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; }
unsigned int volumes_count() const { return (unsigned int)m_list.size(); }
const BoundingBoxf3& get_bounding_box() const;
- // Bounding box of a selection, with no instance scaling applied. This bounding box
- // is useful for absolute scaling of tilted objects in world coordinate space.
+ // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied.
+ // This bounding box is useful for absolute scaling of tilted objects in world coordinate space.
+ // Modifiers are NOT taken in account
const BoundingBoxf3& get_unscaled_instance_bounding_box() const;
+ // Bounding box of a single full instance selection, in world coordinates.
+ // Modifiers are NOT taken in account
const BoundingBoxf3& get_scaled_instance_bounding_box() const;
+#if ENABLE_WORLD_COORDINATE
+ // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied.
+ // Modifiers are taken in account
+ const BoundingBoxf3& get_full_unscaled_instance_bounding_box() const;
+ // Bounding box of a single full instance selection, in world coordinates.
+ // Modifiers are taken in account
+ const BoundingBoxf3& get_full_scaled_instance_bounding_box() const;
+
+ // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied.
+ // Modifiers are taken in account
+ const BoundingBoxf3& get_full_unscaled_instance_local_bounding_box() const;
+#endif // ENABLE_WORLD_COORDINATE
void setup_cache();
+#if ENABLE_WORLD_COORDINATE
+ void translate(const Vec3d& displacement, TransformationType transformation_type);
+#else
void translate(const Vec3d& displacement, bool local = false);
+#endif // ENABLE_WORLD_COORDINATE
void rotate(const Vec3d& rotation, TransformationType transformation_type);
void flattening_rotate(const Vec3d& normal);
void scale(const Vec3d& scale, TransformationType transformation_type);
void scale_to_fit_print_volume(const BuildVolume& volume);
void mirror(Axis axis);
-
+#if ENABLE_WORLD_COORDINATE
+ void scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type);
+ void reset_skew();
+#else
void translate(unsigned int object_idx, const Vec3d& displacement);
+#endif // ENABLE_WORLD_COORDINATE
void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement);
+#if ENABLE_WORLD_COORDINATE
+ // returns:
+ // -1 if the user refused to proceed with baking when asked
+ // 0 if the baking was performed
+ // 1 if no baking was needed
+ int bake_transform_if_needed() const;
+#endif // ENABLE_WORLD_COORDINATE
+
void erase();
void render(float scale_factor = 1.0);
@@ -368,10 +449,23 @@ private:
void do_remove_volume(unsigned int volume_idx);
void do_remove_instance(unsigned int object_idx, unsigned int instance_idx);
void do_remove_object(unsigned int object_idx);
+#if ENABLE_WORLD_COORDINATE
+ void set_bounding_boxes_dirty() {
+ m_bounding_box.reset();
+ m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset();
+ m_full_unscaled_instance_bounding_box.reset(); m_full_scaled_instance_bounding_box.reset();
+ m_full_unscaled_instance_local_bounding_box.reset();;
+ }
+#else
void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); }
+#endif // ENABLE_WORLD_COORDINATE
void render_synchronized_volumes();
#if ENABLE_LEGACY_OPENGL_REMOVAL
+#if ENABLE_WORLD_COORDINATE
+ void render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color);
+#else
void render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color);
+#endif // ENABLE_WORLD_COORDINATE
#else
void render_selected_volumes() const;
void render_bounding_box(const BoundingBoxf3& box, float* color) const;
@@ -389,11 +483,15 @@ private:
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
public:
- enum SyncRotationType {
+ enum class SyncRotationType {
// Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis.
- SYNC_ROTATION_NONE = 0,
+ NONE = 0,
// Synchronize after rotation by an axis not parallel with Z.
- SYNC_ROTATION_GENERAL = 1,
+ GENERAL = 1,
+#if ENABLE_WORLD_COORDINATE
+ // Fully synchronize rotation.
+ FULL = 2,
+#endif // ENABLE_WORLD_COORDINATE
};
void synchronize_unselected_instances(SyncRotationType sync_rotation_type);
void synchronize_unselected_volumes();
@@ -405,6 +503,11 @@ private:
void paste_volumes_from_clipboard();
void paste_objects_from_clipboard();
+
+#if ENABLE_WORLD_COORDINATE
+ void transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type,
+ const Transform3d& transform);
+#endif // ENABLE_WORLD_COORDINATE
};
} // namespace GUI
diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp
index dc177ade3..876398510 100644
--- a/src/slic3r/GUI/wxExtensions.cpp
+++ b/src/slic3r/GUI/wxExtensions.cpp
@@ -581,8 +581,12 @@ void LockButton::OnButton(wxCommandEvent& event)
if (m_disabled)
return;
+#if ENABLE_WORLD_COORDINATE
+ SetLock(!m_is_pushed);
+#else
m_is_pushed = !m_is_pushed;
update_button_bitmaps();
+#endif // ENABLE_WORLD_COORDINATE
event.Skip();
}