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-04-29 14:51:58 +0300
committerenricoturri1966 <enricoturri@seznam.cz>2022-06-03 14:58:36 +0300
commit7e729632936836cf38d59910e5e11c1244cd6ee5 (patch)
tree98625c81e38264b6e1d611ed73495ec5b65050e1
parenta4c0d99616579058d70904022cd6585904d71e8d (diff)
Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - 1st installment. Geometry::Transformation modified to store data in a single matrix, without store the matrix components
Fixed conflicts during rebase with master
-rw-r--r--src/libslic3r/Geometry.cpp182
-rw-r--r--src/libslic3r/Geometry.hpp118
-rw-r--r--src/libslic3r/Model.cpp69
-rw-r--r--src/libslic3r/Model.hpp2466
-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/GUI/3DScene.hpp1594
-rw-r--r--src/slic3r/GUI/GUI_ObjectList.cpp8
-rw-r--r--src/slic3r/GUI/GUI_ObjectManipulation.cpp8
-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.cpp132
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoMove.hpp4
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp2790
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp30
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoScale.cpp63
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoScale.hpp4
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp2744
-rw-r--r--src/slic3r/GUI/MeshUtils.cpp748
-rw-r--r--src/slic3r/GUI/Plater.cpp10
-rw-r--r--src/slic3r/GUI/Selection.cpp48
23 files changed, 9768 insertions, 9095 deletions
diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp
index 58c90d9bc..fba1b2378 100644
--- a/src/libslic3r/Geometry.cpp
+++ b/src/libslic3r/Geometry.cpp
@@ -313,6 +313,34 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation,
return transform;
}
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+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;
+}
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+
Vec3d extract_euler_angles(const Eigen::Matrix<double, 3, 3, Eigen::DontAlign>& rotation_matrix)
{
// reference: http://eecs.qmul.ac.uk/~gslabaugh/publications/euler.pdf
@@ -363,6 +391,46 @@ Vec3d extract_euler_angles(const Transform3d& transform)
return extract_euler_angles(m);
}
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+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) };
+}
+
+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 +468,19 @@ void Transformation::set_offset(Axis axis, double offset)
m_dirty = true;
}
}
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
void Transformation::set_rotation(const Vec3d& rotation)
{
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
}
void Transformation::set_rotation(Axis axis, double rotation)
@@ -414,32 +489,106 @@ void Transformation::set_rotation(Axis axis, double rotation)
if (is_approx(std::abs(rotation), 2.0 * double(PI)))
rotation = 0.0;
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+}
+
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+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_TRANSFORMATIONS_BY_MATRICES
+
void Transformation::set_scaling_factor(const Vec3d& scaling_factor)
{
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
}
void Transformation::set_scaling_factor(Axis axis, double scaling_factor)
{
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
}
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+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_TRANSFORMATIONS_BY_MATRICES
+
void Transformation::set_mirror(const Vec3d& mirror)
{
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
}
void Transformation::set_mirror(Axis axis, double mirror)
@@ -450,12 +599,19 @@ void Transformation::set_mirror(Axis axis, double mirror)
else if (abs_mirror != 1.0)
mirror /= abs_mirror;
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
}
+#if !ENABLE_TRANSFORMATIONS_BY_MATRICES
void Transformation::set_from_transform(const Transform3d& transform)
{
// offset
@@ -493,17 +649,38 @@ 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_TRANSFORMATIONS_BY_MATRICES
void Transformation::reset()
{
+#if !ENABLE_TRANSFORMATIONS_BY_MATRICES
m_offset = Vec3d::Zero();
m_rotation = Vec3d::Zero();
m_scaling_factor = Vec3d::Ones();
m_mirror = Vec3d::Ones();
+#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES
m_matrix = Transform3d::Identity();
+#if !ENABLE_TRANSFORMATIONS_BY_MATRICES
m_dirty = false;
+#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES
+}
+
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+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,6 +697,7 @@ const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rot
return m_matrix;
}
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
Transformation Transformation::operator * (const Transformation& other) const
{
@@ -533,7 +711,11 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
if (instance_transformation.is_scaling_uniform()) {
// No need to run the non-linear least squares fitting for uniform scaling.
// Just set the inverse.
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ out = instance_transformation.get_matrix_no_offset().inverse();
+#else
out.set_from_transform(instance_transformation.get_matrix(true).inverse());
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) {
// Anisotropic scaling, rotation by multiples of ninety degrees.
diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp
index 2ca4ef884..ffe96cd3a 100644
--- a/src/libslic3r/Geometry.hpp
+++ b/src/libslic3r/Geometry.hpp
@@ -334,6 +334,26 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d
// 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());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+// 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);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+
// Returns the euler angles extracted from the given rotation matrix
// Warning -> The matrix should not contain any scale or shear !!!
Vec3d extract_euler_angles(const Eigen::Matrix<double, 3, 3, Eigen::DontAlign>& rotation_matrix);
@@ -344,6 +364,9 @@ Vec3d extract_euler_angles(const Transform3d& transform);
class Transformation
{
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ Transform3d m_matrix{ Transform3d::Identity() };
+#else
struct Flags
{
bool dont_translate{ true };
@@ -363,42 +386,104 @@ class Transformation
mutable Transform3d m_matrix{ Transform3d::Identity() };
mutable Flags m_flags;
mutable bool m_dirty{ false };
+#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES
public:
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ Transformation() = default;
+ explicit Transformation(const Transform3d & transform) : m_matrix(transform) {}
+#else
Transformation();
- explicit Transformation(const Transform3d& transform);
+ explicit Transformation(const Transform3d & transform);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ Transformation& operator = (const Transform3d& transform) { m_matrix = transform; return *this; }
+
+ 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; }
+#else
const Vec3d& get_offset() const { return m_offset; }
double get_offset(Axis axis) const { return m_offset(axis); }
void set_offset(const Vec3d& offset);
void set_offset(Axis axis, double offset);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ Vec3d get_rotation() const;
+ double get_rotation(Axis axis) const { return get_rotation()[axis]; }
+ Transform3d get_rotation_matrix() const;
+#else
const Vec3d& get_rotation() const { return m_rotation; }
double get_rotation(Axis axis) const { return m_rotation(axis); }
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
void set_rotation(const Vec3d& rotation);
void set_rotation(Axis axis, double rotation);
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
void set_scaling_factor(const Vec3d& scaling_factor);
void set_scaling_factor(Axis axis, double scaling_factor);
+
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
void set_mirror(const Vec3d& mirror);
void set_mirror(Axis axis, double mirror);
+#if !ENABLE_TRANSFORMATIONS_BY_MATRICES
void set_from_transform(const Transform3d& transform);
+#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES
void reset();
-
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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()); }
+
+ const Transform3d& get_matrix() const { return m_matrix; }
+ Transform3d get_matrix_no_offset() const;
+ Transform3d get_matrix_no_scaling_factor() const;
+#else
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const;
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
Transformation operator * (const Transformation& other) const;
@@ -408,15 +493,26 @@ public:
static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox);
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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
};
// For parsing a transformation matrix from 3MF / AMF.
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 0f48f2bb2..7fb7ed9f1 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
model_instance->set_offset(model_instance->get_offset() + shift);
}
@@ -1434,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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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();
@@ -1444,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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
// 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.
@@ -1491,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_TRANSFORMATIONS_BY_MATRICES
+ const Transform3d& mi = inst->get_matrix_no_offset();
+#else
const Transform3d& mi = inst->get_matrix(true);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
for (const ModelVolume* v : volumes) {
if (!v->is_model_part())
@@ -1512,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_TRANSFORMATIONS_BY_MATRICES
+ const Transform3d& mi = inst->get_matrix_no_offset();
+#else
const Transform3d& mi = inst->get_matrix(true);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
for (const ModelVolume* v : volumes) {
if (!v->is_model_part())
@@ -1938,14 +1979,22 @@ void ModelVolume::convert_from_meters()
void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const
{
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ mesh->transform(dont_translate ? get_matrix_no_offset() : get_matrix());
+#else
mesh->transform(get_matrix(dont_translate));
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const
{
// Rotate around mesh origin.
TriangleMesh copy(mesh);
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ copy.transform(get_transformation().get_rotation_matrix());
+#else
copy.transform(get_matrix(true, false, true, true));
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
BoundingBoxf3 bbox = copy.bounding_box();
if (!empty(bbox)) {
@@ -1970,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_TRANSFORMATIONS_BY_MATRICES
+ return bbox.transformed(dont_translate ? get_matrix_no_offset() : get_matrix());
+#else
return bbox.transformed(get_matrix(dont_translate));
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const
{
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ return dont_translate ? get_matrix_no_offset() * v : get_matrix() * v;
+#else
return get_matrix(dont_translate) * v;
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
void ModelInstance::transform_polygon(Polygon* polygon) const
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 0c95d98c0..f2844abcb 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -1,1212 +1,1254 @@
-#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_TRANSFORMATIONS_BY_MATRICES
+ void set_transformation(const Transform3d& trafo) { m_transformation = trafo; }
+#else
+ void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); }
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ Vec3d get_offset() const { return m_transformation.get_offset(); }
+#else
+ const Vec3d& get_offset() const { return m_transformation.get_offset(); }
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ Vec3d get_rotation() const { return m_transformation.get_rotation(); }
+#else
+ const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ Vec3d get_mirror() const { return m_transformation.get_mirror(); }
+#else
+ const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+
+ 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_TRANSFORMATIONS_BY_MATRICES
+ Vec3d get_offset() const { return m_transformation.get_offset(); }
+#else
+ const Vec3d& get_offset() const { return m_transformation.get_offset(); }
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ Vec3d get_rotation() const { return m_transformation.get_rotation(); }
+#else
+ const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ Vec3d get_mirror() const { return m_transformation.get_mirror(); }
+#else
+ const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+
+ 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..4fc76800c 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+}
+
+// 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..b88ae9e50 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ }
+ } 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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 6a73aaf1e..c44b914b4 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -79,6 +79,8 @@
#define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE)
// Enable alternate implementation of manipulating scale for instances and volumes
#define ENABLE_WORLD_COORDINATE_SCALE_REVISITED (1 && ENABLE_WORLD_COORDINATE)
+// Enable implementation of Geometry::Transformation using matrices only
+#define ENABLE_TRANSFORMATIONS_BY_MATRICES (1 && ENABLE_WORLD_COORDINATE)
// 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/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp
index a85b8092a..263d8af08 100644
--- a/src/slic3r/GUI/3DScene.hpp
+++ b/src/slic3r/GUI/3DScene.hpp
@@ -1,783 +1,811 @@
-#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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 353577944..216c2f472 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -1532,7 +1532,11 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo
// 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 Geometry::Transformation inst_transform = v->get_instance_transformation();
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
const Vec3d instance_offset = v->get_instance_offset();
for (size_t i = 0; i < input_files.size(); ++i) {
@@ -1660,7 +1664,11 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
const wxString name = _L("Generic") + "-" + _(type_name);
new_volume->name = into_u8(name);
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
index 96429c924..c73f5eb4a 100644
--- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
@@ -354,7 +354,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
const double min_z = get_volume_min_z(*volume);
if (!is_world_coordinates()) {
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ());
+#else
const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ());
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(0, diff.x());
@@ -381,7 +385,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
const double min_z = selection.get_scaled_instance_bounding_box().min.z();
if (!is_world_coordinates()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ());
+#else
const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ());
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(0, diff.x());
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
index 66b6dcf60..a52c85d67 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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..b882570fc 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_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();
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ const Transform3d inst_matrix = mo->instances.front()->get_matrix_no_offset();
+#else
+ const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+
+ // 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..a60da3591 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_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
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
index 14c41ce54..52dd207e9 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
@@ -90,32 +90,32 @@ bool GLGizmoMove3D::on_is_activable() const
void GLGizmoMove3D::on_start_dragging()
{
- if (m_hover_id != -1) {
- m_displacement = Vec3d::Zero();
+ 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_volume(*selection.get_volume_idxs().begin());
- m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center;
- }
- else {
- const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
- m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * 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();
+ 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_volume(*selection.get_volume_idxs().begin());
+ m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center;
+ }
+ else {
+ const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
+ m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * 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.z() = box.min.z();
+ 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.z() = box.min.z();
#endif // ENABLE_WORLD_COORDINATE
- }
}
void GLGizmoMove3D::on_stop_dragging()
@@ -134,7 +134,11 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data)
m_displacement.z() = calc_projection(data);
Selection &selection = m_parent.get_selection();
+#if ENABLE_WORLD_COORDINATE
+ selection.translate(m_displacement, wxGetApp().obj_manipul()->get_coordinates_type());
+#else
selection.translate(m_displacement);
+#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoMove3D::on_render()
@@ -148,9 +152,18 @@ void GLGizmoMove3D::on_render()
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();
@@ -236,7 +249,7 @@ void GLGizmoMove3D::on_render()
#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("view_model_matrix", camera.get_view_matrix() * base_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
@@ -270,8 +283,12 @@ void GLGizmoMove3D::on_render()
#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);
@@ -292,7 +309,7 @@ void GLGizmoMove3D::on_render()
#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("view_model_matrix", camera.get_view_matrix()* base_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
@@ -329,7 +346,11 @@ void GLGizmoMove3D::on_render()
}
#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
@@ -337,7 +358,9 @@ void GLGizmoMove3D::on_render()
}
#if ENABLE_WORLD_COORDINATE
+#if !ENABLE_GL_SHADERS_ATTRIBUTES
glsafe(::glPopMatrix());
+#endif // !ENABLE_GL_SHADERS_ATTRIBUTES
#endif // ENABLE_WORLD_COORDINATE
}
@@ -346,15 +369,30 @@ 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);
@@ -393,9 +431,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
@@ -420,7 +463,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)
@@ -454,6 +497,28 @@ 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_volume(*selection.get_volume_idxs().begin());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix();
+#else
+ Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+ if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix();
+#else
+ orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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()));
@@ -466,6 +531,7 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const
glsafe(::glMultMatrixd(orient_matrix.data()));
}
}
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
void GLGizmoMove3D::calc_selection_box_and_center()
{
@@ -477,7 +543,12 @@ void GLGizmoMove3D::calc_selection_box_and_center()
}
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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());
+#else
m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true));
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
m_center = v.world_matrix() * m_bounding_box.center();
}
else {
@@ -487,8 +558,13 @@ void GLGizmoMove3D::calc_selection_box_and_center()
const GLVolume& v = *selection.get_volume(id);
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
}
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix());
+ m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor() * m_bounding_box.center();
+#else
m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true));
m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center();
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
}
#endif // ENABLE_WORLD_COORDINATE
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
index c1368cf62..85f3c4785 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
@@ -71,7 +71,11 @@ 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
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
index 0cba59c6d..c7731f1ff 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+
+ // 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ }
+
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+
+ // 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ }
+
+ // 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 c9947ee18..6579a1431 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
@@ -190,11 +190,7 @@ void GLGizmoRotate::on_render()
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
#if ENABLE_LEGACY_OPENGL_REMOVAL
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- GLShaderProgram* shader = wxGetApp().get_shader("flat_attr");
-#else
GLShaderProgram* shader = wxGetApp().get_shader("flat");
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
if (shader != nullptr) {
shader->start_using();
@@ -298,7 +294,12 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection)
}
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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());
+#else
m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true));
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
m_center = v.world_matrix() * m_bounding_box.center();
}
else {
@@ -308,8 +309,13 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection)
const GLVolume& v = *selection.get_volume(id);
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
}
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix());
+ m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor() * m_bounding_box.center();
+#else
m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true));
m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center();
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
m_radius = Offset + m_bounding_box.radius();
@@ -322,11 +328,19 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection)
m_orient_matrix = Transform3d::Identity();
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix();
+#else
m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
else {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ m_orient_matrix = v.get_instance_transformation().get_rotation_matrix();
+#else
m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
}
#endif // ENABLE_WORLD_COORDINATE
@@ -656,11 +670,7 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick
const double size = m_dragging ? double(m_grabbers.front().get_dragging_half_size(mean_size)) : double(m_grabbers.front().get_half_size(mean_size));
#if ENABLE_LEGACY_OPENGL_REMOVAL
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat_attr" : "gouraud_light_attr");
-#else
GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light");
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
if (shader == nullptr)
return;
@@ -739,12 +749,12 @@ 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));
+ ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ());
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));
+ ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY());
break;
}
default:
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
index 36e5b0217..05df5f193 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
@@ -219,14 +219,24 @@ void GLGizmoScale3D::on_render()
}
#if ENABLE_WORLD_COORDINATE
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_scaling_factor_matrix());
+#else
m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true));
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
#endif // ENABLE_WORLD_COORDINATE
// gets transform from first selected volume
const GLVolume& v = *selection.get_volume(*idxs.begin());
#if ENABLE_WORLD_COORDINATE
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ const Transform3d inst_trafo = v.get_instance_transformation().get_matrix_no_scaling_factor();
+ m_grabbers_transform = inst_trafo * Geometry::assemble_transform(m_bounding_box.center());
+ m_center = inst_trafo * m_bounding_box.center();
+#else
m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center());
m_center = selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center();
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
m_instance_center = v.get_instance_offset();
}
else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) {
@@ -243,8 +253,14 @@ void GLGizmoScale3D::on_render()
#endif // ENABLE_WORLD_COORDINATE
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
#if ENABLE_WORLD_COORDINATE
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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());
+#else
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, false, false, true)));
Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true));
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
trafo.set_offset(v.world_matrix().translation());
m_grabbers_transform = trafo.get_matrix();
m_center = v.world_matrix() * m_bounding_box.center();
@@ -252,8 +268,14 @@ void GLGizmoScale3D::on_render()
}
else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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());
+#else
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)));
Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true));
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
trafo.set_offset(v.world_matrix().translation());
m_grabbers_transform = trafo.get_matrix();
m_center = v.world_matrix() * m_bounding_box.center();
@@ -352,8 +374,15 @@ void GLGizmoScale3D::on_render()
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
float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0);
#else
@@ -557,14 +586,23 @@ 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
void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color)
@@ -773,6 +811,28 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const
}
#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_volume(*selection.get_volume_idxs().begin());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix();
+#else
+ Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+ if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix();
+#else
+ orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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()));
@@ -784,6 +844,7 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const
glsafe(::glMultMatrixd(orient_matrix.data()));
}
}
+#endif // ENABLE_GL_SHADERS_ATTRIBUTES
#endif // ENABLE_WORLD_COORDINATE
} // namespace GUI
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
index e279e53e2..b8001d7a9 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
@@ -107,7 +107,11 @@ private:
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..f72ce3206 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_volume(*selection.get_volume_idxs().begin());
+ Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+#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
diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp
index bbdcdeb34..777a4d7ba 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ // 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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
+ 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 cd9f221b4..0c1985733 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -3492,7 +3492,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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
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();
@@ -3847,10 +3851,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_TRANSFORMATIONS_BY_MATRICES
+ new_volume->set_transformation(Geometry::assemble_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_TRANSFORMATIONS_BY_MATRICES
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);
diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp
index 3bb2db1ac..45d80a145 100644
--- a/src/slic3r/GUI/Selection.cpp
+++ b/src/slic3r/GUI/Selection.cpp
@@ -723,7 +723,11 @@ const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const
const GLVolume& volume = *(*m_volumes)[i];
if (volume.is_modifier)
continue;
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
trafo.translation().z() += volume.get_sla_shift_z();
(*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo));
}
@@ -1486,8 +1490,14 @@ void Selection::render(float scale_factor)
}
else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) {
const GLVolume& v = *get_volume(*get_volume_idxs().begin());
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ box = v.transformed_convex_hull_bounding_box(
+ v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix());
+ trafo = v.get_instance_transformation().get_matrix_no_scaling_factor() * v.get_volume_transformation().get_matrix_no_scaling_factor();
+#else
box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true));
trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
else {
const Selection::IndicesList& ids = get_volume_idxs();
@@ -1495,8 +1505,13 @@ void Selection::render(float scale_factor)
const GLVolume& v = *get_volume(id);
box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
}
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix());
+ trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor();
+#else
box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true));
trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
render_bounding_box(box, trafo, ColorRGB::WHITE());
@@ -1516,11 +1531,7 @@ void Selection::render_center(bool gizmo_is_dragging)
return;
#if ENABLE_LEGACY_OPENGL_REMOVAL
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- GLShaderProgram* shader = wxGetApp().get_shader("flat_attr");
-#else
GLShaderProgram* shader = wxGetApp().get_shader("flat");
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
if (shader == nullptr)
return;
@@ -1565,11 +1576,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
return;
#if ENABLE_LEGACY_OPENGL_REMOVAL
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat_attr" : "gouraud_light_attr");
-#else
GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat" : "gouraud_light");
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
if (shader == nullptr)
return;
@@ -1622,7 +1629,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES
Transform3d orient_matrix = Transform3d::Identity();
#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
#if ENABLE_WORLD_COORDINATE_SHOW_AXES
axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset();
#else
@@ -1667,13 +1678,21 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES
if (wxGetApp().obj_manipul()->is_local_coordinates()) {
const GLVolume* v = (*m_volumes)[*m_list.begin()];
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ orient_matrix = v->get_instance_transformation().get_rotation_matrix() * v->get_volume_transformation().get_rotation_matrix();
+#else
orient_matrix = v->get_instance_transformation().get_matrix(true, false, true, true) * v->get_volume_transformation().get_matrix(true, false, true, true);
+#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
#if ENABLE_WORLD_COORDINATE_SHOW_AXES
axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation();
#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES
}
else {
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
#if ENABLE_WORLD_COORDINATE_SHOW_AXES
axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset();
}
@@ -1700,7 +1719,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
else {
#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES
if (requires_local_axes())
+#if ENABLE_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
#else
glsafe(::glTranslated(center.x(), center.y(), center.z()));
if (requires_local_axes()) {
@@ -2357,11 +2380,7 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con
glsafe(::glLineWidth(2.0f * m_scale_factor));
-#if ENABLE_GL_SHADERS_ATTRIBUTES
- GLShaderProgram* shader = wxGetApp().get_shader("flat_attr");
-#else
GLShaderProgram* shader = wxGetApp().get_shader("flat");
-#endif // ENABLE_GL_SHADERS_ATTRIBUTES
if (shader == nullptr)
return;
@@ -3061,8 +3080,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_TRANSFORMATIONS_BY_MATRICES
+ 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_TRANSFORMATIONS_BY_MATRICES
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