diff --git a/resources/icons/seam.svg b/resources/icons/seam.svg
new file mode 100644
index 000000000..119fb6afc
--- /dev/null
+++ b/resources/icons/seam.svg
@@ -0,0 +1,42 @@
+
+
+
diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp
index 3d7e58112..0adb2019e 100644
--- a/src/libslic3r/ExtrusionEntity.hpp
+++ b/src/libslic3r/ExtrusionEntity.hpp
@@ -122,8 +122,8 @@ public:
// Height of the extrusion, used for visualization purposes.
float height;
- ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {};
- ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {};
+ ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {}
+ ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {}
ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
index 52a3335ee..66dd0049c 100644
--- a/src/libslic3r/Format/3mf.cpp
+++ b/src/libslic3r/Format/3mf.cpp
@@ -87,6 +87,7 @@ const char* TRANSFORM_ATTR = "transform";
const char* PRINTABLE_ATTR = "printable";
const char* INSTANCESCOUNT_ATTR = "instances_count";
const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
+const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
const char* KEY_ATTR = "key";
const char* VALUE_ATTR = "value";
@@ -285,6 +286,7 @@ namespace Slic3r {
std::vector vertices;
std::vector triangles;
std::vector custom_supports;
+ std::vector custom_seam;
bool empty()
{
@@ -296,6 +298,7 @@ namespace Slic3r {
vertices.clear();
triangles.clear();
custom_supports.clear();
+ custom_seam.clear();
}
};
@@ -1553,6 +1556,7 @@ namespace Slic3r {
m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR));
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
+ m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
return true;
}
@@ -1886,14 +1890,18 @@ namespace Slic3r {
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
volume->calculate_convex_hull();
- // recreate custom supports from previously loaded attribute
+ // recreate custom supports and seam from previously loaded attribute
for (unsigned i=0; im_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
+ if (! geometry.custom_seam[index].empty())
+ volume->m_seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
}
+
// apply the remaining volume's metadata
for (const Metadata& metadata : volume_data.metadata)
{
@@ -2410,6 +2418,10 @@ namespace Slic3r {
if (! custom_supports_data_string.empty())
stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" ";
+ std::string custom_seam_data_string = volume->m_seam_facets.get_triangle_as_string(i);
+ if (! custom_seam_data_string.empty())
+ stream << CUSTOM_SEAM_ATTR << "=\"" << custom_seam_data_string << "\" ";
+
stream << "/>\n";
}
}
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 9e1600cb4..135389eb3 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -2590,7 +2590,7 @@ plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400)
}
}
-static Points::iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps)
+static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps)
{
assert(polygon.points.size() >= 2);
if (polygon.points.size() <= 1)
@@ -2778,7 +2778,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// Insert a projection of last_pos into the polygon.
size_t last_pos_proj_idx;
{
- Points::iterator it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r);
+ auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r);
last_pos_proj_idx = it - polygon.points.begin();
}
@@ -2798,11 +2798,9 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
if (was_clockwise)
ccwAngle = - ccwAngle;
float penalty = 0;
-// if (ccwAngle <- float(PI/3.))
if (ccwAngle <- float(0.6 * PI))
// Sharp reflex vertex. We love that, it hides the seam perfectly.
penalty = 0.f;
-// else if (ccwAngle > float(PI/3.))
else if (ccwAngle > float(0.6 * PI))
// Seams on sharp convex vertices are more visible than on reflex vertices.
penalty = penaltyConvexVertex;
@@ -2815,7 +2813,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
}
// Give a negative penalty for points close to the last point or the prefered seam location.
- //float dist_to_last_pos_proj = last_pos_proj.distance_to(polygon.points[i]);
float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?
std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :
std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]);
@@ -2835,14 +2832,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// Signed distance is positive outside the object, negative inside the object.
// The point is considered at an overhang, if it is more than nozzle radius
// outside of the lower layer contour.
- #ifdef NDEBUG // to suppress unused variable warning in release mode
- (*lower_layer_edge_grid)->signed_distance(p, search_r, dist);
- #else
- bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist);
- #endif
+ [[maybe_unused]] bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist);
// If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
// then the signed distnace shall always be known.
- assert(found);
+ assert(found);
penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist));
}
}
@@ -2850,7 +2843,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// Find a point with a minimum penalty.
size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
- // if (seam_position == spAligned)
// For all (aligned, nearest, rear) seams:
{
// Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index ad48bd6bd..67fe63871 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -1009,6 +1009,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial
for (ModelVolume* volume : volumes)
{
volume->m_supported_facets.clear();
+ volume->m_seam_facets.clear();
if (!volume->mesh().empty()) {
TriangleMesh mesh(volume->mesh());
mesh.require_shared_vertices();
@@ -1114,6 +1115,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
const auto volume_matrix = volume->get_matrix();
volume->m_supported_facets.clear();
+ volume->m_seam_facets.clear();
if (! volume->is_model_part()) {
// Modifiers are not cut, but we still need to add the instance transformation
@@ -1833,7 +1835,7 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
}
-indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSupportType type) const
+indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const
{
TriangleSelector selector(mv.mesh());
selector.deserialize(m_data);
@@ -1995,6 +1997,16 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject
return false;
}
+bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new) {
+ assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
+ assert(mo.volumes.size() == mo_new.volumes.size());
+ for (size_t i=0; im_seam_facets.is_same_as(mo.volumes[i]->m_seam_facets))
+ return true;
+ }
+ return false;
+}
+
extern bool model_has_multi_part_objects(const Model &model)
{
for (const ModelObject *model_object : model.objects)
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 92dc84d17..a623f5cca 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -394,7 +394,7 @@ enum class ModelVolumeType : int {
SUPPORT_BLOCKER,
};
-enum class FacetSupportType : int8_t {
+enum class EnforcerBlockerType : int8_t {
// Maximum is 3. The value is serialized in TriangleSelector into 2 bits!
NONE = 0,
ENFORCER = 1,
@@ -407,7 +407,7 @@ public:
const std::map>& get_data() const { return m_data; }
bool set(const TriangleSelector& selector);
- indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const;
+ indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
void clear();
std::string get_triangle_as_string(int i) const;
void set_triangle_from_string(int triangle_id, const std::string& str);
@@ -464,6 +464,9 @@ public:
// List of mesh facets to be supported/unsupported.
FacetsAnnotation m_supported_facets;
+ // List of seam enforcers/blockers.
+ FacetsAnnotation m_seam_facets;
+
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; }
ModelVolumeType type() const { return m_type; }
@@ -593,7 +596,7 @@ private:
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),
- m_supported_facets(other.m_supported_facets)
+ m_supported_facets(other.m_supported_facets), m_seam_facets(other.m_seam_facets)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == other.id() && this->config.id() == other.config.id());
@@ -612,6 +615,7 @@ private:
assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id());
m_supported_facets.clear();
+ m_seam_facets.clear();
}
ModelVolume& operator=(ModelVolume &rhs) = delete;
@@ -625,7 +629,7 @@ private:
template 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, m_supported_facets);
+ m_is_splittable, has_convex_hull, m_supported_facets, m_seam_facets);
cereal::load_by_value(ar, config);
assert(m_mesh);
if (has_convex_hull) {
@@ -639,7 +643,7 @@ private:
template 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, m_supported_facets);
+ m_is_splittable, has_convex_hull, m_supported_facets, m_seam_facets);
cereal::save_by_value(ar, config);
if (has_convex_hull)
cereal::save_optional(ar, m_convex_hull);
@@ -904,6 +908,10 @@ extern bool model_volume_list_changed(const ModelObject &model_object_old, const
// The function assumes that volumes list is synchronized.
extern 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.
+extern bool model_custom_seam_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.
extern bool model_has_multi_part_objects(const Model &model);
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 7924f1890..c405c764e 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -404,6 +404,7 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
mv_dst.name = mv_src.name;
static_cast(mv_dst.config) = static_cast(mv_src.config);
mv_dst.m_supported_facets = mv_src.m_supported_facets;
+ mv_dst.m_seam_facets = mv_src.m_seam_facets;
//FIXME what to do with the materials?
// mv_dst.m_material_id = mv_src.m_material_id;
++ i_src;
@@ -867,6 +868,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
model_volume_list_update_supports(model_object, model_object_new);
}
}
+ if (model_custom_seam_data_changed(model_object, model_object_new)) {
+ update_apply_status(this->invalidate_step(psGCodeExport));
+ }
if (! model_parts_differ && ! modifiers_differ) {
// Synchronize Object's config.
bool object_config_changed = model_object.config != model_object_new.config;
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index da98bba5f..d436f90bf 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -191,10 +191,8 @@ public:
std::vector slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); }
std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); }
- // Helpers to project custom supports on slices
- void project_and_append_custom_supports(FacetSupportType type, std::vector& expolys) const;
- void project_and_append_custom_enforcers(std::vector& enforcers) const { project_and_append_custom_supports(FacetSupportType::ENFORCER, enforcers); }
- void project_and_append_custom_blockers(std::vector& blockers) const { project_and_append_custom_supports(FacetSupportType::BLOCKER, blockers); }
+ // Helpers to project custom facets on slices
+ void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const;
private:
// to be called from Print only.
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
index c90a05ef3..aecf90771 100644
--- a/src/libslic3r/PrintObject.cpp
+++ b/src/libslic3r/PrintObject.cpp
@@ -2669,12 +2669,14 @@ void PrintObject::_generate_support_material()
}
-void PrintObject::project_and_append_custom_supports(
- FacetSupportType type, std::vector& expolys) const
+void PrintObject::project_and_append_custom_facets(
+ bool seam, EnforcerBlockerType type, std::vector& expolys) const
{
for (const ModelVolume* mv : this->model_object()->volumes) {
- const indexed_triangle_set custom_facets = mv->m_supported_facets.get_facets(*mv, type);
- if (custom_facets.indices.empty())
+ const indexed_triangle_set custom_facets = seam
+ ? mv->m_seam_facets.get_facets(*mv, type)
+ : mv->m_supported_facets.get_facets(*mv, type);
+ if (! mv->is_model_part() || custom_facets.indices.empty())
continue;
const Transform3f& tr1 = mv->get_matrix().cast();
@@ -2721,7 +2723,7 @@ void PrintObject::project_and_append_custom_supports(
// Ignore triangles with upward-pointing normal. Don't forget about mirroring.
float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z();
- if (tr_det_sign * z_comp > 0.)
+ if (! seam && tr_det_sign * z_comp > 0.)
continue;
// Sort the three vertices according to z-coordinate.
diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp
index 95b4c334b..1669f60d2 100644
--- a/src/libslic3r/SupportMaterial.cpp
+++ b/src/libslic3r/SupportMaterial.cpp
@@ -972,8 +972,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
std::vector blockers = object.slice_support_blockers();
// Append custom supports.
- object.project_and_append_custom_enforcers(enforcers);
- object.project_and_append_custom_blockers(blockers);
+ object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers);
+ object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers);
// Output layers, sorted by top Z.
MyLayersPtr contact_out;
diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp
index 9555c42a6..9f04374fd 100644
--- a/src/libslic3r/TriangleSelector.cpp
+++ b/src/libslic3r/TriangleSelector.cpp
@@ -35,7 +35,7 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
const Vec3f& source, const Vec3f& dir,
- float radius, FacetSupportType new_state)
+ float radius, EnforcerBlockerType new_state)
{
assert(facet_start < m_orig_size_indices);
assert(is_approx(dir.norm(), 1.f));
@@ -77,7 +77,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
// the triangle recursively, selecting just subtriangles truly inside the circle.
// This is done by an actual recursive call. Returns false if the triangle is
// outside the cursor.
-bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call)
+bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool recursive_call)
{
assert(facet_idx < int(m_triangles.size()));
@@ -140,7 +140,7 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo
-void TriangleSelector::set_facet(int facet_idx, FacetSupportType state)
+void TriangleSelector::set_facet(int facet_idx, EnforcerBlockerType state)
{
assert(facet_idx < m_orig_size_indices);
undivide_triangle(facet_idx);
@@ -157,7 +157,7 @@ void TriangleSelector::split_triangle(int facet_idx)
Triangle* tr = &m_triangles[facet_idx];
- FacetSupportType old_type = tr->get_state();
+ EnforcerBlockerType old_type = tr->get_state();
if (tr->was_split_before() != 0) {
// This triangle is not split at the moment, but was at one point
@@ -323,7 +323,7 @@ void TriangleSelector::remove_useless_children(int facet_idx)
// Return if a child is not leaf or two children differ in type.
- FacetSupportType first_child_type = FacetSupportType::NONE;
+ EnforcerBlockerType first_child_type = EnforcerBlockerType::NONE;
for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) {
if (m_triangles[tr.children[child_idx]].is_split())
return;
@@ -456,7 +456,7 @@ void TriangleSelector::push_triangle(int a, int b, int c)
}
-void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state)
+void TriangleSelector::perform_split(int facet_idx, EnforcerBlockerType old_state)
{
Triangle* tr = &m_triangles[facet_idx];
@@ -520,7 +520,7 @@ void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state)
-indexed_triangle_set TriangleSelector::get_facets(FacetSupportType state) const
+indexed_triangle_set TriangleSelector::get_facets(EnforcerBlockerType state) const
{
indexed_triangle_set out;
for (const Triangle& tr : m_triangles) {
@@ -542,7 +542,7 @@ std::map> TriangleSelector::serialize() const
{
// Each original triangle of the mesh is assigned a number encoding its state
// or how it is split. Each triangle is encoded by 4 bits (xxyy):
- // leaf triangle: xx = FacetSupportType, yy = 0
+ // leaf triangle: xx = EnforcerBlockerType, yy = 0
// non-leaf: xx = special side, yy = number of split sides
// These are bitwise appended and formed into one 64-bit integer.
@@ -553,7 +553,7 @@ std::map> TriangleSelector::serialize() const
for (int i=0; i data; // complete encoding of this mesh triangle
@@ -627,7 +627,7 @@ void TriangleSelector::deserialize(const std::map> data)
int num_of_split_sides = (next_code & 0b11);
int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0;
bool is_split = num_of_children != 0;
- FacetSupportType state = FacetSupportType(next_code >> 2);
+ EnforcerBlockerType state = EnforcerBlockerType(next_code >> 2);
int special_side = (next_code >> 2);
// Take care of the first iteration separately, so handling of the others is simpler.
@@ -641,7 +641,7 @@ void TriangleSelector::deserialize(const std::map> data)
// then go to the next.
parents.push_back({triangle_id, 0, num_of_children});
m_triangles[triangle_id].set_division(num_of_children-1, special_side);
- perform_split(triangle_id, FacetSupportType::NONE);
+ perform_split(triangle_id, EnforcerBlockerType::NONE);
continue;
}
}
@@ -655,7 +655,7 @@ void TriangleSelector::deserialize(const std::map> data)
const ProcessingInfo& last = parents.back();
int this_idx = m_triangles[last.facet_id].children[last.processed_children];
m_triangles[this_idx].set_division(num_of_children-1, special_side);
- perform_split(this_idx, FacetSupportType::NONE);
+ perform_split(this_idx, EnforcerBlockerType::NONE);
parents.push_back({this_idx, 0, num_of_children});
} else {
// this triangle belongs to last split one
diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp
index fb90cff76..be1b20ed4 100644
--- a/src/libslic3r/TriangleSelector.hpp
+++ b/src/libslic3r/TriangleSelector.hpp
@@ -9,7 +9,7 @@
namespace Slic3r {
-enum class FacetSupportType : int8_t;
+enum class EnforcerBlockerType : int8_t;
@@ -29,13 +29,13 @@ public:
const Vec3f& source, // camera position (mesh coords)
const Vec3f& dir, // direction of the ray (mesh coords)
float radius, // radius of the cursor
- FacetSupportType new_state); // enforcer or blocker?
+ EnforcerBlockerType new_state); // enforcer or blocker?
// Get facets currently in the given state.
- indexed_triangle_set get_facets(FacetSupportType state) const;
+ indexed_triangle_set get_facets(EnforcerBlockerType state) const;
// Set facet of the mesh to a given state. Only works for original triangles.
- void set_facet(int facet_idx, FacetSupportType state);
+ void set_facet(int facet_idx, EnforcerBlockerType state);
// Clear everything and make the tree empty.
void reset();
@@ -59,7 +59,7 @@ protected:
// It increments/decrements reference counter on vertices.
Triangle(int a, int b, int c)
: verts_idxs{a, b, c},
- state{FacetSupportType(0)},
+ state{EnforcerBlockerType(0)},
number_of_splits{0},
special_side_idx{0},
old_number_of_splits{0}
@@ -77,8 +77,8 @@ protected:
void set_division(int sides_to_split, int special_side_idx = -1);
// Get/set current state.
- void set_state(FacetSupportType type) { assert(! is_split()); state = type; }
- FacetSupportType get_state() const { assert(! is_split()); return state; }
+ void set_state(EnforcerBlockerType type) { assert(! is_split()); state = type; }
+ EnforcerBlockerType get_state() const { assert(! is_split()); return state; }
// Get info on how it's split.
bool is_split() const { return number_of_split_sides() != 0; }
@@ -90,7 +90,7 @@ protected:
private:
int number_of_splits;
int special_side_idx;
- FacetSupportType state;
+ EnforcerBlockerType state;
// How many children were spawned during last split?
// Is not reset on remerging the triangle.
@@ -133,7 +133,7 @@ protected:
float m_old_cursor_radius;
// Private functions:
- bool select_triangle(int facet_idx, FacetSupportType type,
+ bool select_triangle(int facet_idx, EnforcerBlockerType type,
bool recursive_call = false);
bool is_point_inside_cursor(const Vec3f& point) const;
int vertices_inside(int facet_idx) const;
@@ -144,7 +144,7 @@ protected:
bool is_pointer_in_triangle(int facet_idx) const;
bool is_edge_inside_cursor(int facet_idx) const;
void push_triangle(int a, int b, int c);
- void perform_split(int facet_idx, FacetSupportType old_state);
+ void perform_split(int facet_idx, EnforcerBlockerType old_state);
};
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 4cc66e36f..b452c8ffb 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -53,6 +53,10 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoCut.hpp
GUI/Gizmos/GLGizmoHollow.cpp
GUI/Gizmos/GLGizmoHollow.hpp
+ GUI/Gizmos/GLGizmoPainterBase.cpp
+ GUI/Gizmos/GLGizmoPainterBase.hpp
+ GUI/Gizmos/GLGizmoSeam.cpp
+ GUI/Gizmos/GLGizmoSeam.hpp
GUI/GLSelectionRectangle.cpp
GUI/GLSelectionRectangle.hpp
GUI/GLModel.hpp
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 81f63cb4d..e7f0f094d 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -3674,7 +3674,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled)
{
if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports
- && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports)
+ && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
+ && m_gizmos.get_current_type() != GLGizmosManager::Seam)
{
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
m_dirty = true;
@@ -3749,14 +3750,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (!m_mouse.drag.move_requires_threshold)
{
m_mouse.dragging = true;
-
- // Translation of objects is forbidden when SLA supports/hollowing/fdm
- // supports gizmo is active.
- if (m_gizmos.get_current_type() == GLGizmosManager::SlaSupports
- || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports
- || m_gizmos.get_current_type() == GLGizmosManager::Hollow)
- return;
-
Vec3d cur_pos = m_mouse.drag.start_position_3D;
// we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag
if (m_selection.contains_volume(get_first_hover_volume_idx()))
@@ -5474,7 +5467,8 @@ void GLCanvas3D::_render_bed(bool bottom, bool show_axes) const
bool show_texture = ! bottom ||
(m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
- && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports);
+ && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports
+ && m_gizmos.get_current_type() != GLGizmosManager::Seam);
wxGetApp().plater()->get_bed().render(const_cast(*this), bottom, scale_factor, show_axes, show_texture);
}
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 18c5fd4f5..ff6b72a52 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -41,6 +41,7 @@
#include "3DScene.hpp"
#include "MainFrame.hpp"
#include "Plater.hpp"
+#include "GLCanvas3D.hpp"
#include "../Utils/PresetUpdater.hpp"
#include "../Utils/PrintHost.hpp"
@@ -1026,6 +1027,7 @@ void GUI_App::update_mode()
tab->update_mode();
plater()->update_object_menu();
+ plater()->canvas3D()->update_gizmos_on_off_state();
}
void GUI_App::add_config_menu(wxMenuBar *menu)
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
index 3ab58c258..44f0a6972 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
@@ -124,7 +124,6 @@ public:
void set_state(EState state) { m_state = state; on_set_state(); }
int get_shortcut_key() const { return m_shortcut_key; }
- void set_shortcut_key(int key) { m_shortcut_key = key; }
const std::string& get_icon_filename() const { return m_icon_filename; }
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
index bd52e9ec6..af1517637 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
@@ -1,40 +1,40 @@
-// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoFdmSupports.hpp"
+
+#include "libslic3r/Model.hpp"
+
+//#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
-#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/ImGuiWrapper.hpp"
+#include "slic3r/GUI/Plater.hpp"
+
#include
-#include "slic3r/GUI/GUI_App.hpp"
-#include "slic3r/GUI/Camera.hpp"
-#include "slic3r/GUI/Plater.hpp"
-#include "libslic3r/PresetBundle.hpp"
-#include "libslic3r/Model.hpp"
-
-
namespace Slic3r {
+
namespace GUI {
-GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
- : GLGizmoBase(parent, icon_filename, sprite_id)
- , m_quadric(nullptr)
+
+void GLGizmoFdmSupports::on_shutdown()
{
- m_clipping_plane.reset(new ClippingPlane());
- m_quadric = ::gluNewQuadric();
- if (m_quadric != nullptr)
- // using GLU_FILL does not work when the instance's transformation
- // contains mirroring (normals are reverted)
- ::gluQuadricDrawStyle(m_quadric, GLU_FILL);
+ if (m_setting_angle) {
+ m_setting_angle = false;
+ m_parent.use_slope(false);
+ }
}
-GLGizmoFdmSupports::~GLGizmoFdmSupports()
+
+
+std::string GLGizmoFdmSupports::on_get_name() const
{
- if (m_quadric != nullptr)
- ::gluDeleteQuadric(m_quadric);
+ return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data();
}
+
+
bool GLGizmoFdmSupports::on_init()
{
m_shortcut_key = WXK_CONTROL_L;
@@ -54,38 +54,6 @@ bool GLGizmoFdmSupports::on_init()
}
-void GLGizmoFdmSupports::activate_internal_undo_redo_stack(bool activate)
-{
- if (activate && ! m_internal_stack_active) {
- Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on"));
- wxGetApp().plater()->enter_gizmos_stack();
- m_internal_stack_active = true;
- }
- if (! activate && m_internal_stack_active) {
- wxGetApp().plater()->leave_gizmos_stack();
- Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off"));
- m_internal_stack_active = false;
- }
-}
-
-void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const Selection& selection)
-{
- if (m_state != On)
- return;
-
- const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
-
- 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;
- }
-}
-
-
void GLGizmoFdmSupports::on_render() const
{
@@ -94,7 +62,8 @@ void GLGizmoFdmSupports::on_render() const
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
- render_triangles(selection);
+ if (! m_setting_angle)
+ render_triangles(selection);
m_c->object_clipper()->render_cut();
render_cursor_circle();
@@ -102,405 +71,6 @@ void GLGizmoFdmSupports::on_render() const
glsafe(::glDisable(GL_BLEND));
}
-void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
-{
- if (m_setting_angle)
- return;
-
- const ModelObject* mo = m_c->selection_info()->model_object();
-
- glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
- ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); } );
- glsafe(::glPolygonOffset(-1.0, 1.0));
-
- // 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)
- bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.;
- if (clipping_plane_active) {
- const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
- double clp_data[4];
- memcpy(clp_data, clp->get_data(), 4 * sizeof(double));
- for (int i=0; i<3; ++i)
- clp_data[i] = -1. * clp_data[i];
-
- glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)clp_data));
- glsafe(::glEnable(GL_CLIP_PLANE0));
- }
-
- 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));
-
- glsafe(::glPushMatrix());
- glsafe(::glMultMatrixd(trafo_matrix.data()));
-
- if (! m_setting_angle)
- m_triangle_selectors[mesh_id]->render(m_imgui);
-
- glsafe(::glPopMatrix());
- if (is_left_handed)
- glsafe(::glFrontFace(GL_CCW));
- }
- if (clipping_plane_active)
- glsafe(::glDisable(GL_CLIP_PLANE0));
-}
-
-
-void GLGizmoFdmSupports::render_cursor_circle() const
-{
- const Camera& camera = wxGetApp().plater()->get_camera();
- float zoom = (float)camera.get_zoom();
- float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
-
- Size cnv_size = m_parent.get_canvas_size();
- float cnv_half_width = 0.5f * (float)cnv_size.get_width();
- float cnv_half_height = 0.5f * (float)cnv_size.get_height();
- if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f))
- return;
- Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1));
- Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1));
- center = center * inv_zoom;
-
- glsafe(::glLineWidth(1.5f));
- float color[3];
- color[0] = 0.f;
- color[1] = 1.f;
- color[2] = 0.3f;
- glsafe(::glColor3fv(color));
- glsafe(::glDisable(GL_DEPTH_TEST));
-
- 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
- double gui_scale = camera.get_gui_scale();
- glsafe(::glScaled(gui_scale, gui_scale, 1.0));
-
- glsafe(::glPushAttrib(GL_ENABLE_BIT));
- glsafe(::glLineStipple(4, 0xAAAA));
- glsafe(::glEnable(GL_LINE_STIPPLE));
-
- ::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());
-
- glsafe(::glPopAttrib());
- glsafe(::glPopMatrix());
-}
-
-
-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->m_supported_facets.set(*m_triangle_selectors[idx].get());
- }
-
- if (updated)
- 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(*mesh));
- m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data());
- }
-}
-
-
-
-bool GLGizmoFdmSupports::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();
-
- 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);
-}
-
-
-// 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 GLGizmoFdmSupports::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) {
- m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown
- ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
- : std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
- m_parent.set_as_dirty();
- return true;
- }
- }
-
- 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;
-
- FacetSupportType new_state = FacetSupportType::NONE;
- if (! shift_down) {
- if (action == SLAGizmoEventType::Dragging)
- new_state = m_button_down == Button::Left
- ? FacetSupportType::ENFORCER
- : FacetSupportType::BLOCKER;
- else
- new_state = action == SLAGizmoEventType::LeftDown
- ? FacetSupportType::ENFORCER
- : FacetSupportType::BLOCKER;
- }
-
- 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();
-
- // List of mouse positions that will be used as seeds for painting.
- std::vector mouse_positions{mouse_position};
-
- // 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 (m_last_mouse_position == Vec2d::Zero())
- m_last_mouse_position = mouse_position;
- // resolution describes minimal distance limit using circle radius
- // as a unit (e.g., 2 would mean the patches will be touching).
- double resolution = 0.7;
- double diameter_px = resolution * m_cursor_radius * camera.get_zoom();
- int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px);
- if (patches_in_between > 0) {
- Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1);
- for (int i=1; i<=patches_in_between; ++i)
- mouse_positions.emplace_back(m_last_mouse_position + i*diff);
- }
- }
- m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved
-
- // Now "click" into all the prepared points and spill paint around them.
- for (const Vec2d& mp : mouse_positions) {
- std::vector>> hit_positions_and_facet_ids;
- bool clipped_mesh_was_hit = false;
-
- 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::max();
- size_t closest_facet = 0;
- int closest_hit_mesh_id = -1;
-
- // Transformations of individual meshes
- std::vector trafo_matrices;
-
- int mesh_id = -1;
- // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
- for (const ModelVolume* mv : mo->volumes) {
- if (! mv->is_model_part())
- continue;
-
- ++mesh_id;
-
- trafo_matrices.push_back(instance_trafo * mv->get_matrix());
- hit_positions_and_facet_ids.push_back(std::vector>());
-
- if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
- mp,
- trafo_matrices[mesh_id],
- camera,
- hit,
- normal,
- m_clipping_plane.get(),
- &facet))
- {
- // In case this hit is clipped, skip it.
- if (is_mesh_point_clipped(hit.cast())) {
- clipped_mesh_was_hit = true;
- continue;
- }
-
- // Is this hit the closest to the camera so far?
- double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).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;
- }
- }
- }
-
- 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
- // or when the user clicks the clipping plane. Missing the object entirely
- // shall not capture the mouse.
- if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) {
- if (m_button_down == Button::None)
- m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
- }
-
- if (closest_hit_mesh_id == -1) {
- // In case we have no valid hit, we can return. The event will
- // be stopped in following two cases:
- // 1. clicking the clipping plane
- // 2. dragging while painting (to prevent scene rotations and moving the object)
- return clipped_mesh_was_hit
- || dragging_while_painting;
- }
-
- // Find respective mesh id.
- mesh_id = -1;
- for (const ModelVolume* mv : mo->volumes) {
- if (! mv->is_model_part())
- continue;
- ++mesh_id;
- if (mesh_id == closest_hit_mesh_id)
- break;
- }
-
- const Transform3d& trafo_matrix = trafo_matrices[mesh_id];
-
- // Calculate how far can a point be from the line (in mesh coords).
- // FIXME: The scaling of the mesh can be non-uniform.
- const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor();
- const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.;
- const float limit = m_cursor_radius/avg_scaling;
-
- // Calculate direction from camera to the hit (in mesh coords):
- Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast();
- Vec3f dir = (closest_hit - camera_pos).normalized();
-
- assert(mesh_id < int(m_triangle_selectors.size()));
- m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos,
- dir, limit, new_state);
- m_last_mouse_position = mouse_position;
- }
-
- return true;
- }
-
- if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
- && m_button_down != Button::None) {
- // Take snapshot and update ModelVolume data.
- wxString action_name = shift_down
- ? _L("Remove selection")
- : (m_button_down == Button::Left
- ? _L("Add supports")
- : _L("Block supports"));
- activate_internal_undo_redo_stack(true);
- Plater::TakeSnapshot(wxGetApp().plater(), action_name);
- update_model_object();
-
- m_button_down = Button::None;
- m_last_mouse_position = Vec2d::Zero();
- return true;
- }
-
- return false;
-}
-
-
-
-void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
-{
- float threshold = (M_PI/180.)*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().normalized();
- Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized();
-
- float dot_limit = limit.dot(down);
-
- // Now calculate dot product of vert_direction and facets' normals.
- int idx = -1;
- for (const stl_facet& facet : mv->mesh().stl.facet_start) {
- ++idx;
- if (facet.normal.dot(down) > dot_limit)
- m_triangle_selectors[mesh_id]->set_facet(idx,
- block
- ? FacetSupportType::BLOCKER
- : FacetSupportType::ENFORCER);
- }
- }
-
- activate_internal_undo_redo_stack(true);
-
- Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
- : _L("Add supports by angle"));
- update_model_object();
- m_parent.set_as_dirty();
- m_setting_angle = false;
-}
void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit)
@@ -561,6 +131,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
m_triangle_selectors[idx]->reset();
}
}
+
update_model_object();
m_parent.set_as_dirty();
}
@@ -604,12 +175,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
}
m_imgui->end();
- if (m_setting_angle) {
- m_parent.show_slope(false);
- m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg});
- m_parent.use_slope(true);
- m_parent.set_as_dirty();
- }
}
else {
std::string name = "Autoset custom supports";
@@ -634,248 +199,91 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
}
}
-bool GLGizmoFdmSupports::on_is_activable() const
+
+
+void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
{
+ float threshold = (M_PI/180.)*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()];
- if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF
- || !selection.is_single_full_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)
- return false;
-
- return true;
-}
-
-bool GLGizmoFdmSupports::on_is_selectable() const
-{
- return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF );
-}
-
-std::string GLGizmoFdmSupports::on_get_name() const
-{
- return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data();
-}
-
-
-CommonGizmosDataID GLGizmoFdmSupports::on_get_requirements() const
-{
- return CommonGizmosDataID(
- int(CommonGizmosDataID::SelectionInfo)
- | int(CommonGizmosDataID::InstancesHider)
- | int(CommonGizmosDataID::Raycaster)
- | int(CommonGizmosDataID::ObjectClipper));
-}
-
-
-void GLGizmoFdmSupports::on_set_state()
-{
- if (m_state == m_old_state)
- return;
-
- if (m_state == On && m_old_state != On) { // the gizmo was just turned on
- if (! m_parent.get_gizmos_manager().is_serializing()) {
- wxGetApp().CallAfter([this]() {
- activate_internal_undo_redo_stack(true);
- });
- }
- }
- if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
- // we are actually shutting down
- if (m_setting_angle) {
- m_setting_angle = false;
- m_parent.use_slope(false);
- }
- activate_internal_undo_redo_stack(false);
- m_old_mo_id = -1;
- //m_iva.release_geometry();
- m_triangle_selectors.clear();
- }
- m_old_state = m_state;
-}
-
-
-
-void GLGizmoFdmSupports::on_start_dragging()
-{
-
-}
-
-
-void GLGizmoFdmSupports::on_stop_dragging()
-{
-
-}
-
-
-
-void GLGizmoFdmSupports::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_fdm_support_data, which will be called
- // soon after.
- m_schedule_update = true;
-}
-
-
-
-void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const
-{
-
-}
-
-
-void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
-{
- int enf_cnt = 0;
- int blc_cnt = 0;
-
- m_iva_enforcers.release_geometry();
- m_iva_blockers.release_geometry();
-
- for (const Triangle& tr : m_triangles) {
- if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE)
+ int mesh_id = -1;
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
continue;
- GLIndexedVertexArray& va = tr.get_state() == FacetSupportType::ENFORCER
- ? m_iva_enforcers
- : m_iva_blockers;
- int& cnt = tr.get_state() == FacetSupportType::ENFORCER
- ? enf_cnt
- : blc_cnt;
+ ++mesh_id;
- 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);
- cnt += 3;
+ const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true);
+ Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized();
+ Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized();
+
+ float dot_limit = limit.dot(down);
+
+ // Now calculate dot product of vert_direction and facets' normals.
+ int idx = -1;
+ for (const stl_facet& facet : mv->mesh().stl.facet_start) {
+ ++idx;
+ if (facet.normal.dot(down) > dot_limit)
+ m_triangle_selectors[mesh_id]->set_facet(idx,
+ block
+ ? EnforcerBlockerType::BLOCKER
+ : EnforcerBlockerType::ENFORCER);
+ }
}
- m_iva_enforcers.finalize_geometry(true);
- m_iva_blockers.finalize_geometry(true);
+ activate_internal_undo_redo_stack(true);
- if (m_iva_enforcers.has_VBOs()) {
- ::glColor4f(0.f, 0.f, 1.f, 0.2f);
- m_iva_enforcers.render();
- }
-
-
- if (m_iva_blockers.has_VBOs()) {
- ::glColor4f(1.f, 0.f, 0.f, 0.2f);
- m_iva_blockers.render();
- }
-
-
-#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
- if (imgui)
- render_debug(imgui);
- else
- assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
-#endif
+ Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
+ : _L("Add supports by angle"));
+ update_model_object();
+ m_parent.set_as_dirty();
+ m_setting_angle = false;
}
-#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
-void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
+void GLGizmoFdmSupports::update_model_object() const
{
- 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);
+ 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->m_supported_facets.set(*m_triangle_selectors[idx].get());
}
- imgui->end();
-
- if (! m_show_triangles)
- return;
-
- enum vtype {
- ORIGINAL = 0,
- SPLIT,
- INVALID
- };
-
- for (auto& va : m_varrays)
- va.release_geometry();
-
- std::array cnts;
-
- ::glScalef(1.01f, 1.01f, 1.01f);
-
- for (int tr_id=0; tr_idpush_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);
- *cnt += 3;
- }
-
- ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
- for (vtype i : {ORIGINAL, SPLIT, INVALID}) {
- 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();
- }
- }
- ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
+ if (updated)
+ m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
-#endif
+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(*mesh));
+ m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data());
+ }
+}
+
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
index 350f7c890..913133617 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
@@ -1,127 +1,46 @@
#ifndef slic3r_GLGizmoFdmSupports_hpp_
#define slic3r_GLGizmoFdmSupports_hpp_
-#include "GLGizmoBase.hpp"
-
-#include "slic3r/GUI/3DScene.hpp"
-
-#include "libslic3r/ObjectID.hpp"
-#include "libslic3r/TriangleSelector.hpp"
-
-#include
-
-
-
+#include "GLGizmoPainterBase.hpp"
namespace Slic3r {
-enum class FacetSupportType : int8_t;
-
namespace GUI {
-enum class SLAGizmoEventType : unsigned char;
-class ClippingPlane;
-
-
-
-class TriangleSelectorGUI : public TriangleSelector {
-public:
- explicit TriangleSelectorGUI(const TriangleMesh& mesh)
- : TriangleSelector(mesh) {}
-
- // Render current selection. Transformation matrices are supposed
- // to be already set.
- void render(ImGuiWrapper* imgui = nullptr);
-
-#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
- void render_debug(ImGuiWrapper* imgui);
- bool m_show_triangles{false};
- bool m_show_invalid{false};
-#endif
-
-private:
- GLIndexedVertexArray m_iva_enforcers;
- GLIndexedVertexArray m_iva_blockers;
- std::array m_varrays;
-};
-
-
-
-class GLGizmoFdmSupports : public GLGizmoBase
+class GLGizmoFdmSupports : public GLGizmoPainterBase
{
-private:
- ObjectID m_old_mo_id;
- size_t m_old_volumes_size = 0;
-
- GLUquadricObj* m_quadric;
-
- float m_cursor_radius = 2.f;
- static constexpr float CursorRadiusMin = 0.4f; // cannot be zero
- static constexpr float CursorRadiusMax = 8.f;
- static constexpr float CursorRadiusStep = 0.2f;
-
- // For each model-part volume, store status and division of the triangles.
- std::vector> m_triangle_selectors;
-
public:
- GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
- ~GLGizmoFdmSupports() override;
- void set_fdm_support_data(ModelObject* model_object, const Selection& selection);
- bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
+ GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
+ : GLGizmoPainterBase(parent, icon_filename, sprite_id) {}
+protected:
+ void on_render_input_window(float x, float y, float bottom_limit) override;
+ std::string on_get_name() const override;
private:
bool on_init() override;
void on_render() const override;
void on_render_for_picking() const override {}
- void render_triangles(const Selection& selection) const;
- void render_cursor_circle() const;
+ void update_model_object() const override;
+ void update_from_model_object() override;
- void update_model_object() const;
- void update_from_model_object();
- void activate_internal_undo_redo_stack(bool activate);
+ void on_opening() override {}
+ void on_shutdown() override;
void select_facets_by_angle(float threshold, bool block);
float m_angle_threshold_deg = 45.f;
-
- bool is_mesh_point_clipped(const Vec3d& point) const;
-
- float m_clipping_plane_distance = 0.f;
- std::unique_ptr m_clipping_plane;
bool m_setting_angle = false;
- bool m_internal_stack_active = false;
- bool m_schedule_update = false;
- Vec2d m_last_mouse_position = Vec2d::Zero();
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
std::map m_desc;
-
- enum class Button {
- None,
- Left,
- Right
- };
-
- Button m_button_down = Button::None;
- EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
-
-protected:
- void on_set_state() override;
- void on_start_dragging() override;
- void on_stop_dragging() override;
- void on_render_input_window(float x, float y, float bottom_limit) override;
- std::string on_get_name() const override;
- bool on_is_activable() const override;
- bool on_is_selectable() const override;
- void on_load(cereal::BinaryInputArchive& ar) override;
- void on_save(cereal::BinaryOutputArchive& ar) const override;
- CommonGizmosDataID on_get_requirements() const override;
};
+
} // namespace GUI
} // namespace Slic3r
+
#endif // slic3r_GLGizmoFdmSupports_hpp_
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
new file mode 100644
index 000000000..1809b417c
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
@@ -0,0 +1,597 @@
+// 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
+
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/Camera.hpp"
+#include "slic3r/GUI/Plater.hpp"
+#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/Model.hpp"
+
+
+
+namespace Slic3r {
+namespace GUI {
+
+
+GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
+ : GLGizmoBase(parent, icon_filename, sprite_id)
+{
+ m_clipping_plane.reset(new ClippingPlane());
+}
+
+
+
+void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate)
+{
+ if (activate && ! m_internal_stack_active) {
+ Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on"));
+ wxGetApp().plater()->enter_gizmos_stack();
+ m_internal_stack_active = true;
+ }
+ if (! activate && m_internal_stack_active) {
+ wxGetApp().plater()->leave_gizmos_stack();
+ Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off"));
+ m_internal_stack_active = false;
+ }
+}
+
+
+
+void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection)
+{
+ if (m_state != On)
+ return;
+
+ const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
+
+ 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;
+ }
+}
+
+
+
+void GLGizmoPainterBase::render_triangles(const Selection& selection) const
+{
+ const ModelObject* mo = m_c->selection_info()->model_object();
+
+ glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
+ ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); } );
+ glsafe(::glPolygonOffset(-1.0, 1.0));
+
+ // 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)
+ bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.;
+ if (clipping_plane_active) {
+ const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
+ double clp_data[4];
+ memcpy(clp_data, clp->get_data(), 4 * sizeof(double));
+ for (int i=0; i<3; ++i)
+ clp_data[i] = -1. * clp_data[i];
+
+ glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)clp_data));
+ glsafe(::glEnable(GL_CLIP_PLANE0));
+ }
+
+ 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));
+
+ glsafe(::glPushMatrix());
+ glsafe(::glMultMatrixd(trafo_matrix.data()));
+
+ m_triangle_selectors[mesh_id]->render(m_imgui);
+
+ glsafe(::glPopMatrix());
+ if (is_left_handed)
+ glsafe(::glFrontFace(GL_CCW));
+ }
+ if (clipping_plane_active)
+ glsafe(::glDisable(GL_CLIP_PLANE0));
+}
+
+
+void GLGizmoPainterBase::render_cursor_circle() const
+{
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ float zoom = (float)camera.get_zoom();
+ float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+
+ Size cnv_size = m_parent.get_canvas_size();
+ float cnv_half_width = 0.5f * (float)cnv_size.get_width();
+ float cnv_half_height = 0.5f * (float)cnv_size.get_height();
+ if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f))
+ return;
+ Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1));
+ Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1));
+ center = center * inv_zoom;
+
+ glsafe(::glLineWidth(1.5f));
+ float color[3];
+ color[0] = 0.f;
+ color[1] = 1.f;
+ color[2] = 0.3f;
+ glsafe(::glColor3fv(color));
+ glsafe(::glDisable(GL_DEPTH_TEST));
+
+ 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
+ double gui_scale = camera.get_gui_scale();
+ glsafe(::glScaled(gui_scale, gui_scale, 1.0));
+
+ glsafe(::glPushAttrib(GL_ENABLE_BIT));
+ glsafe(::glLineStipple(4, 0xAAAA));
+ glsafe(::glEnable(GL_LINE_STIPPLE));
+
+ ::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());
+
+ glsafe(::glPopAttrib());
+ glsafe(::glPopMatrix());
+}
+
+
+
+bool GLGizmoPainterBase::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();
+
+ 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);
+}
+
+
+// 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) {
+ m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown
+ ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
+ : std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
+ m_parent.set_as_dirty();
+ return true;
+ }
+ }
+
+ 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
+ ? EnforcerBlockerType::ENFORCER
+ : EnforcerBlockerType::BLOCKER;
+ else
+ new_state = action == SLAGizmoEventType::LeftDown
+ ? EnforcerBlockerType::ENFORCER
+ : EnforcerBlockerType::BLOCKER;
+ }
+
+ 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();
+
+ // List of mouse positions that will be used as seeds for painting.
+ std::vector mouse_positions{mouse_position};
+
+ // 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 (m_last_mouse_position == Vec2d::Zero())
+ m_last_mouse_position = mouse_position;
+ // resolution describes minimal distance limit using circle radius
+ // as a unit (e.g., 2 would mean the patches will be touching).
+ double resolution = 0.7;
+ double diameter_px = resolution * m_cursor_radius * camera.get_zoom();
+ int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px);
+ if (patches_in_between > 0) {
+ Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1);
+ for (int i=1; i<=patches_in_between; ++i)
+ mouse_positions.emplace_back(m_last_mouse_position + i*diff);
+ }
+ }
+ m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved
+
+ // Now "click" into all the prepared points and spill paint around them.
+ for (const Vec2d& mp : mouse_positions) {
+ std::vector>> hit_positions_and_facet_ids;
+ bool clipped_mesh_was_hit = false;
+
+ 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::max();
+ size_t closest_facet = 0;
+ int closest_hit_mesh_id = -1;
+
+ // Transformations of individual meshes
+ std::vector trafo_matrices;
+
+ int mesh_id = -1;
+ // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+
+ ++mesh_id;
+
+ trafo_matrices.push_back(instance_trafo * mv->get_matrix());
+ hit_positions_and_facet_ids.push_back(std::vector>());
+
+ if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
+ mp,
+ trafo_matrices[mesh_id],
+ camera,
+ hit,
+ normal,
+ m_clipping_plane.get(),
+ &facet))
+ {
+ // In case this hit is clipped, skip it.
+ if (is_mesh_point_clipped(hit.cast())) {
+ clipped_mesh_was_hit = true;
+ continue;
+ }
+
+ // Is this hit the closest to the camera so far?
+ double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).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;
+ }
+ }
+ }
+
+ 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
+ // or when the user clicks the clipping plane. Missing the object entirely
+ // shall not capture the mouse.
+ if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) {
+ if (m_button_down == Button::None)
+ m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
+ }
+
+ if (closest_hit_mesh_id == -1) {
+ // In case we have no valid hit, we can return. The event will
+ // be stopped in following two cases:
+ // 1. clicking the clipping plane
+ // 2. dragging while painting (to prevent scene rotations and moving the object)
+ return clipped_mesh_was_hit
+ || dragging_while_painting;
+ }
+
+ // Find respective mesh id.
+ mesh_id = -1;
+ for (const ModelVolume* mv : mo->volumes) {
+ if (! mv->is_model_part())
+ continue;
+ ++mesh_id;
+ if (mesh_id == closest_hit_mesh_id)
+ break;
+ }
+
+ const Transform3d& trafo_matrix = trafo_matrices[mesh_id];
+
+ // Calculate how far can a point be from the line (in mesh coords).
+ // FIXME: The scaling of the mesh can be non-uniform.
+ const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor();
+ const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.;
+ const float limit = m_cursor_radius/avg_scaling;
+
+ // Calculate direction from camera to the hit (in mesh coords):
+ Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast();
+ Vec3f dir = (closest_hit - camera_pos).normalized();
+
+ assert(mesh_id < int(m_triangle_selectors.size()));
+ m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos,
+ dir, limit, new_state);
+ m_last_mouse_position = mouse_position;
+ }
+
+ return true;
+ }
+
+ if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
+ && m_button_down != Button::None) {
+ // Take snapshot and update ModelVolume data.
+ wxString action_name = shift_down
+ ? _L("Remove selection")
+ : (m_button_down == Button::Left
+ ? _L("Add supports")
+ : _L("Block supports"));
+ activate_internal_undo_redo_stack(true);
+ Plater::TakeSnapshot(wxGetApp().plater(), action_name);
+ update_model_object();
+
+ m_button_down = Button::None;
+ m_last_mouse_position = Vec2d::Zero();
+ return true;
+ }
+
+ return false;
+}
+
+
+
+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())
+ 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)
+ return false;
+
+ return true;
+}
+
+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_parent.get_gizmos_manager().is_serializing()) {
+ wxGetApp().CallAfter([this]() {
+ activate_internal_undo_redo_stack(true);
+ });
+ }
+ }
+ if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
+ // we are actually shutting down
+ on_shutdown();
+ activate_internal_undo_redo_stack(false);
+ 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;
+}
+
+
+
+void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
+{
+ int enf_cnt = 0;
+ int blc_cnt = 0;
+
+ m_iva_enforcers.release_geometry();
+ m_iva_blockers.release_geometry();
+
+ for (const Triangle& tr : m_triangles) {
+ if (! tr.valid || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE)
+ continue;
+
+ GLIndexedVertexArray& va = tr.get_state() == EnforcerBlockerType::ENFORCER
+ ? m_iva_enforcers
+ : m_iva_blockers;
+ int& cnt = tr.get_state() == EnforcerBlockerType::ENFORCER
+ ? enf_cnt
+ : blc_cnt;
+
+ 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);
+ cnt += 3;
+ }
+
+ m_iva_enforcers.finalize_geometry(true);
+ m_iva_blockers.finalize_geometry(true);
+
+ if (m_iva_enforcers.has_VBOs()) {
+ ::glColor4f(0.f, 0.f, 1.f, 0.2f);
+ m_iva_enforcers.render();
+ }
+
+
+ if (m_iva_blockers.has_VBOs()) {
+ ::glColor4f(1.f, 0.f, 0.f, 0.2f);
+ m_iva_blockers.render();
+ }
+
+
+#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
+ if (imgui)
+ render_debug(imgui);
+ else
+ assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
+#endif
+}
+
+
+
+#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
+ };
+
+ for (auto& va : m_varrays)
+ va.release_geometry();
+
+ std::array cnts;
+
+ ::glScalef(1.01f, 1.01f, 1.01f);
+
+ for (int tr_id=0; tr_idpush_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);
+ *cnt += 3;
+ }
+
+ ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
+ for (vtype i : {ORIGINAL, SPLIT, INVALID}) {
+ 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();
+ }
+ }
+ ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
+}
+#endif
+
+
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp
new file mode 100644
index 000000000..da9b37895
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp
@@ -0,0 +1,118 @@
+#ifndef slic3r_GLGizmoPainterBase_hpp_
+#define slic3r_GLGizmoPainterBase_hpp_
+
+#include "GLGizmoBase.hpp"
+
+#include "slic3r/GUI/3DScene.hpp"
+
+#include "libslic3r/ObjectID.hpp"
+#include "libslic3r/TriangleSelector.hpp"
+
+#include
+
+
+
+
+namespace Slic3r {
+
+enum class EnforcerBlockerType : int8_t;
+
+namespace GUI {
+
+enum class SLAGizmoEventType : unsigned char;
+class ClippingPlane;
+
+
+
+class TriangleSelectorGUI : public TriangleSelector {
+public:
+ explicit TriangleSelectorGUI(const TriangleMesh& mesh)
+ : TriangleSelector(mesh) {}
+
+ // Render current selection. Transformation matrices are supposed
+ // to be already set.
+ void render(ImGuiWrapper* imgui = nullptr);
+
+#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
+ void render_debug(ImGuiWrapper* imgui);
+ bool m_show_triangles{false};
+ bool m_show_invalid{false};
+#endif
+
+private:
+ GLIndexedVertexArray m_iva_enforcers;
+ GLIndexedVertexArray m_iva_blockers;
+ std::array m_varrays;
+};
+
+
+// Following class is a base class for a gizmo with ability to paint on mesh
+// using circular blush (such as FDM supports gizmo and seam painting gizmo).
+// The purpose is not to duplicate code related to mesh painting.
+class GLGizmoPainterBase : public GLGizmoBase
+{
+private:
+ ObjectID m_old_mo_id;
+ size_t m_old_volumes_size = 0;
+
+public:
+ GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
+ ~GLGizmoPainterBase() override {}
+ void set_painter_gizmo_data(const Selection& selection);
+ bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
+
+protected:
+ void render_triangles(const Selection& selection) const;
+ void render_cursor_circle() const;
+ virtual void update_model_object() const = 0;
+ virtual void update_from_model_object() = 0;
+ void activate_internal_undo_redo_stack(bool activate);
+
+ float m_cursor_radius = 2.f;
+ static constexpr float CursorRadiusMin = 0.4f; // cannot be zero
+ static constexpr float CursorRadiusMax = 8.f;
+ static constexpr float CursorRadiusStep = 0.2f;
+
+ // For each model-part volume, store status and division of the triangles.
+ std::vector> m_triangle_selectors;
+
+
+private:
+ bool is_mesh_point_clipped(const Vec3d& point) const;
+
+ float m_clipping_plane_distance = 0.f;
+ std::unique_ptr m_clipping_plane;
+
+ bool m_internal_stack_active = false;
+ bool m_schedule_update = false;
+ Vec2d m_last_mouse_position = Vec2d::Zero();
+
+ enum class Button {
+ None,
+ Left,
+ Right
+ };
+
+ Button m_button_down = Button::None;
+ EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
+
+protected:
+ void on_set_state() override;
+ void on_start_dragging() override {}
+ void on_stop_dragging() override {}
+
+ virtual void on_opening() = 0;
+ virtual void on_shutdown() = 0;
+
+ bool on_is_activable() const override;
+ bool on_is_selectable() const override;
+ void on_load(cereal::BinaryInputArchive& ar) override;
+ void on_save(cereal::BinaryOutputArchive& ar) const override {}
+ CommonGizmosDataID on_get_requirements() const override;
+};
+
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLGizmoPainterBase_hpp_
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
new file mode 100644
index 000000000..3c7d180a7
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
@@ -0,0 +1,208 @@
+#include "GLGizmoSeam.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
+
+
+namespace Slic3r {
+
+namespace GUI {
+
+
+
+bool GLGizmoSeam::on_init()
+{
+ m_shortcut_key = WXK_CONTROL_P;
+
+ m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
+ m_desc["reset_direction"] = _L("Reset direction");
+ m_desc["cursor_size"] = _L("Cursor size") + ": ";
+ m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
+ m_desc["enforce"] = _L("Enforce seam");
+ m_desc["block_caption"] = _L("Right mouse button") + " ";
+ m_desc["block"] = _L("Block seam");
+ m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
+ m_desc["remove"] = _L("Remove selection");
+ m_desc["remove_all"] = _L("Remove all selection");
+
+ return true;
+}
+
+
+
+std::string GLGizmoSeam::on_get_name() const
+{
+ return (_(L("Seam Editing")) + " [P]").ToUTF8().data();
+}
+
+
+
+void GLGizmoSeam::on_render() const
+{
+ 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();
+ render_cursor_circle();
+
+ glsafe(::glDisable(GL_BLEND));
+}
+
+
+
+void GLGizmoSeam::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(18.0f);
+ y = std::min(y, bottom_limit - approx_height);
+ m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
+
+
+ m_imgui->begin(on_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 button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
+ const float minimal_slider_width = m_imgui->scaled(4.f);
+
+ float caption_max = 0.f;
+ float total_text_max = 0.;
+ for (const std::string& t : {"enforce", "block", "remove"}) {
+ caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t+"_caption")).x);
+ total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x);
+ }
+ caption_max += m_imgui->scaled(1.f);
+ total_text_max += m_imgui->scaled(1.f);
+
+ float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left);
+ window_width = std::max(window_width, total_text_max);
+ window_width = std::max(window_width, button_width);
+
+ auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
+ static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
+ m_imgui->text_colored(ORANGE, caption);
+ ImGui::SameLine(caption_max);
+ m_imgui->text(text);
+ };
+
+ for (const std::string& t : {"enforce", "block", "remove"})
+ draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
+
+ m_imgui->text("");
+
+ if (m_imgui->button(m_desc.at("remove_all"))) {
+ Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
+ 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();
+ }
+ }
+
+ update_model_object();
+ m_parent.set_as_dirty();
+ }
+
+ const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
+
+ m_imgui->text(m_desc.at("cursor_size"));
+ ImGui::SameLine(clipping_slider_left);
+ ImGui::PushItemWidth(window_width - clipping_slider_left);
+ ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
+ if (ImGui::IsItemHovered()) {
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(max_tooltip_width);
+ ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+ ImGui::Separator();
+ if (m_c->object_clipper()->get_position() == 0.f)
+ 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 (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f"))
+ m_c->object_clipper()->set_position(clp_dist, true);
+ if (ImGui::IsItemHovered()) {
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(max_tooltip_width);
+ ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+ m_imgui->end();
+}
+
+
+
+void GLGizmoSeam::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->m_seam_facets.set(*m_triangle_selectors[idx].get());
+ }
+
+ if (updated)
+ m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
+}
+
+
+
+void GLGizmoSeam::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(*mesh));
+ m_triangle_selectors.back()->deserialize(mv->m_seam_facets.get_data());
+ }
+}
+
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
new file mode 100644
index 000000000..469ec9180
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
@@ -0,0 +1,42 @@
+#ifndef slic3r_GLGizmoSeam_hpp_
+#define slic3r_GLGizmoSeam_hpp_
+
+#include "GLGizmoPainterBase.hpp"
+
+namespace Slic3r {
+
+namespace GUI {
+
+class GLGizmoSeam : public GLGizmoPainterBase
+{
+public:
+ GLGizmoSeam(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
+ : GLGizmoPainterBase(parent, icon_filename, sprite_id) {}
+
+protected:
+ void on_render_input_window(float x, float y, float bottom_limit) override;
+ std::string on_get_name() const override;
+
+private:
+ bool on_init() override;
+ void on_render() const override;
+ void on_render_for_picking() const override {}
+
+ void update_model_object() const override;
+ void update_from_model_object() override;
+
+ void on_opening() override {}
+ void on_shutdown() override {}
+
+ // This map holds all translated description texts, so they can be easily referenced during layout calculations
+ // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
+ std::map m_desc;
+};
+
+
+
+} // namespace GUI
+} // namespace Slic3r
+
+
+#endif // slic3r_GLGizmoSeam_hpp_
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index c33ba2850..1087c64d5 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -16,6 +16,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
@@ -104,6 +105,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5));
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6));
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "sla_supports.svg", 7));
+ m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8));
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
@@ -144,8 +146,11 @@ void GLGizmosManager::refresh_on_off_state()
if (m_serializing || m_current == Undefined || m_gizmos.empty())
return;
- if (m_current != Undefined && ! m_gizmos[m_current]->is_activable())
+ if (m_current != Undefined
+ && (! m_gizmos[m_current]->is_activable() || ! m_gizmos[m_current]->is_selectable())) {
activate_gizmo(Undefined);
+ update_data();
+ }
}
void GLGizmosManager::reset_all_states()
@@ -204,9 +209,10 @@ void GLGizmosManager::update_data()
enable_grabber(Scale, i, enable_scale_xyz);
}
- m_common_gizmos_data->update(get_current()
- ? get_current()->get_requirements()
- : CommonGizmosDataID(0));
+ if (m_common_gizmos_data)
+ m_common_gizmos_data->update(get_current()
+ ? get_current()->get_requirements()
+ : CommonGizmosDataID(0));
if (selection.is_single_full_instance())
{
@@ -217,7 +223,7 @@ void GLGizmosManager::update_data()
ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()];
set_flattening_data(model_object);
set_sla_support_data(model_object);
- set_fdm_support_data(model_object);
+ set_painter_gizmo_data();
}
else if (selection.is_single_volume() || selection.is_single_modifier())
{
@@ -226,7 +232,7 @@ void GLGizmosManager::update_data()
set_rotation(Vec3d::Zero());
set_flattening_data(nullptr);
set_sla_support_data(nullptr);
- set_fdm_support_data(nullptr);
+ set_painter_gizmo_data();
}
else if (is_wipe_tower)
{
@@ -235,7 +241,7 @@ void GLGizmosManager::update_data()
set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(config.option("wipe_tower_rotation_angle"))->value));
set_flattening_data(nullptr);
set_sla_support_data(nullptr);
- set_fdm_support_data(nullptr);
+ set_painter_gizmo_data();
}
else
{
@@ -243,7 +249,7 @@ void GLGizmosManager::update_data()
set_rotation(Vec3d::Zero());
set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
- set_fdm_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
+ set_painter_gizmo_data();
}
}
@@ -378,12 +384,13 @@ void GLGizmosManager::set_sla_support_data(ModelObject* model_object)
gizmo_supports->set_sla_support_data(model_object, m_parent.get_selection());
}
-void GLGizmosManager::set_fdm_support_data(ModelObject* model_object)
+void GLGizmosManager::set_painter_gizmo_data()
{
if (!m_enabled || m_gizmos.empty())
return;
- dynamic_cast(m_gizmos[FdmSupports].get())->set_fdm_support_data(model_object, m_parent.get_selection());
+ dynamic_cast(m_gizmos[FdmSupports].get())->set_painter_gizmo_data(m_parent.get_selection());
+ dynamic_cast(m_gizmos[Seam].get())->set_painter_gizmo_data(m_parent.get_selection());
}
// Returns true if the gizmo used the event to do something, false otherwise.
@@ -398,6 +405,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
return dynamic_cast(m_gizmos[Hollow].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == FdmSupports)
return dynamic_cast(m_gizmos[FdmSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
+ else if (m_current == Seam)
+ return dynamic_cast(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else
return false;
}
@@ -461,7 +470,7 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
{
bool processed = false;
- if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) {
+ if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) {
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
processed = true;
@@ -603,7 +612,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
if (evt.LeftDown())
{
- if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports)
+ if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports ||m_current == Seam)
&& gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
// the gizmo got the event and took some action, there is no need to do anything more
processed = true;
@@ -630,23 +639,24 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
// event was taken care of by the SlaSupports gizmo
processed = true;
}
- else if (evt.RightDown() && (selected_object_idx != -1) && m_current == FdmSupports
+ else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == FdmSupports || m_current == Seam)
&& gizmo_event(SLAGizmoEventType::RightDown, mouse_pos))
{
- // event was taken care of by the FdmSupports gizmo
+ // event was taken care of by the FdmSupports / Seam gizmo
processed = true;
}
- else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow))
+ else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1)
+ && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam))
// don't allow dragging objects with the Sla gizmo on
processed = true;
- else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports )
+ else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam )
&& gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
{
// the gizmo got the event and took some action, no need to do anything more here
m_parent.set_as_dirty();
processed = true;
}
- else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && !m_parent.is_mouse_dragging())
+ else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) && !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
@@ -658,7 +668,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
// to avoid to loose the selection when user clicks an the white faces of a different object while the Flatten gizmo is active
processed = true;
}
- else if (evt.RightUp() && m_current == FdmSupports && !m_parent.is_mouse_dragging())
+ else if (evt.RightUp() && (m_current == FdmSupports || m_current == Seam) && !m_parent.is_mouse_dragging())
{
gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown());
processed = true;
@@ -748,7 +758,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case 'r' :
case 'R' :
{
- if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
+ if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
processed = true;
break;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
index 4ad46a2a9..6b965525d 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
@@ -67,6 +67,7 @@ public:
Hollow,
SlaSupports,
FdmSupports,
+ Seam,
Undefined
};
@@ -203,7 +204,7 @@ public:
void set_sla_support_data(ModelObject* model_object);
- void set_fdm_support_data(ModelObject* model_object);
+ void set_painter_gizmo_data();
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false);
ClippingPlane get_clipping_plane() const;