diff --git a/resources/icons/snap.svg b/resources/icons/snap.svg
new file mode 100644
index 0000000000..0242c8e3bc
--- /dev/null
+++ b/resources/icons/snap.svg
@@ -0,0 +1,13 @@
+
+
+
diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp
index e600f343ca..480f42811e 100644
--- a/src/PrusaSlicer.cpp
+++ b/src/PrusaSlicer.cpp
@@ -40,6 +40,7 @@
#include "libslic3r/Geometry.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/Model.hpp"
+#include "libslic3r/CutUtils.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "libslic3r/Platform.hpp"
#include "libslic3r/Print.hpp"
@@ -437,8 +438,11 @@ int CLI::run(int argc, char **argv)
}
#else
// model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower);
- model.objects.front()->cut(0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()),
+ Cut cut(model.objects.front(), 0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()),
ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper);
+ auto cut_objects = cut.perform_with_plane();
+ for (ModelObject* obj : cut_objects)
+ model.add_object(*obj);
#endif
model.delete_object(size_t(0));
}
diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h
index 5aed978426..99d933ea87 100644
--- a/src/imgui/imconfig.h
+++ b/src/imgui/imconfig.h
@@ -152,6 +152,7 @@ namespace ImGui
// const wchar_t MmuSegmentationMarker = 0x1F;
const wchar_t PlugMarker = 0x1C;
const wchar_t DowelMarker = 0x1D;
+ const wchar_t SnapMarker = 0x1E;
// Do not forget use following letters only in wstring
const wchar_t DocumentationButton = 0x2600;
const wchar_t DocumentationHoverButton = 0x2601;
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 0de0b4e517..a31e4cc7c8 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -201,6 +201,8 @@ set(SLIC3R_SOURCES
BlacklistedLibraryCheck.hpp
LocalesUtils.cpp
LocalesUtils.hpp
+ CutUtils.cpp
+ CutUtils.hpp
Model.cpp
Model.hpp
ModelArrange.hpp
diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp
new file mode 100644
index 0000000000..1622af87e1
--- /dev/null
+++ b/src/libslic3r/CutUtils.cpp
@@ -0,0 +1,645 @@
+
+#include "CutUtils.hpp"
+#include "Geometry.hpp"
+#include "libslic3r.h"
+#include "Model.hpp"
+#include "TriangleMeshSlicer.hpp"
+#include "TriangleSelector.hpp"
+#include "ObjectID.hpp"
+
+
+namespace Slic3r {
+
+using namespace Geometry;
+
+static void apply_tolerance(ModelVolume* vol)
+{
+ ModelVolume::CutInfo& cut_info = vol->cut_info;
+
+ assert(cut_info.is_connector);
+ if (!cut_info.is_processed)
+ return;
+
+ Vec3d sf = vol->get_scaling_factor();
+
+ // make a "hole" wider
+ sf[X] += double(cut_info.radius_tolerance);
+ sf[Y] += double(cut_info.radius_tolerance);
+
+ // make a "hole" dipper
+ sf[Z] += double(cut_info.height_tolerance);
+
+ vol->set_scaling_factor(sf);
+
+ // correct offset in respect to the new depth
+ Vec3d rot_norm = rotation_transform(vol->get_rotation()) * Vec3d::UnitZ();
+ if (rot_norm.norm() != 0.0)
+ rot_norm.normalize();
+
+ double z_offset = 0.5 * static_cast(cut_info.height_tolerance);
+ if (cut_info.connector_type == CutConnectorType::Plug ||
+ cut_info.connector_type == CutConnectorType::Snap)
+ z_offset -= 0.05; // add small Z offset to better preview
+
+ vol->set_offset(vol->get_offset() + rot_norm * z_offset);
+}
+
+static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
+{
+ if (mesh.empty())
+ return;
+
+ mesh.transform(cut_matrix);
+ ModelVolume* vol = object->add_volume(mesh);
+ vol->set_type(type);
+
+ vol->name = src_volume->name + suffix;
+ // Don't copy the config's ID.
+ vol->config.assign_config(src_volume->config);
+ assert(vol->config.id().valid());
+ assert(vol->config.id() != src_volume->config.id());
+ vol->set_material(src_volume->material_id(), *src_volume->material());
+ vol->cut_info = src_volume->cut_info;
+}
+
+static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
+ ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
+{
+ const auto volume_matrix = volume->get_matrix();
+
+ const Transformation cut_transformation = Transformation(cut_matrix);
+ const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset());
+
+ // Transform the mesh by the combined transformation matrix.
+ // Flip the triangles in case the composite transformation is left handed.
+ TriangleMesh mesh(volume->mesh());
+ mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
+
+ indexed_triangle_set upper_its, lower_its;
+ cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper))
+ upper_mesh = TriangleMesh(upper_its);
+ if (attributes.has(ModelObjectCutAttribute::KeepLower))
+ lower_mesh = TriangleMesh(lower_its);
+}
+
+static void process_connector_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
+ ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
+ std::vector& dowels)
+{
+ assert(volume->cut_info.is_connector);
+ volume->cut_info.set_processed();
+
+ const auto volume_matrix = volume->get_matrix();
+
+ // ! Don't apply instance transformation for the conntectors.
+ // This transformation is already there
+ if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
+ ModelVolume* vol = nullptr;
+ if (volume->cut_info.connector_type == CutConnectorType::Snap) {
+ TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.));
+
+ vol = upper->add_volume(std::move(mesh));
+ vol->set_transformation(volume->get_transformation());
+ vol->set_type(ModelVolumeType::NEGATIVE_VOLUME);
+
+ vol->cut_info = volume->cut_info;
+ vol->name = volume->name;
+ }
+ else
+ vol = upper->add_volume(*volume);
+
+ vol->set_transformation(volume_matrix);
+ apply_tolerance(vol);
+ }
+ if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
+ ModelVolume* vol = lower->add_volume(*volume);
+ vol->set_transformation(volume_matrix);
+ // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
+ vol->set_type(ModelVolumeType::MODEL_PART);
+ }
+ }
+ else {
+ if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
+ ModelObject* dowel{ nullptr };
+ // Clone the object to duplicate instances, materials etc.
+ volume->get_object()->clone_for_cut(&dowel);
+
+ // add one more solid part same as connector if this connector is a dowel
+ ModelVolume* vol = dowel->add_volume(*volume);
+ vol->set_type(ModelVolumeType::MODEL_PART);
+
+ // But discard rotation and Z-offset for this volume
+ vol->set_rotation(Vec3d::Zero());
+ vol->set_offset(Z, 0.0);
+
+ dowels.push_back(dowel);
+ }
+
+ // Cut the dowel
+ apply_tolerance(volume);
+
+ // Perform cut
+ TriangleMesh upper_mesh, lower_mesh;
+ process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
+
+ // add small Z offset to better preview
+ upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast());
+ lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast());
+
+ // Add cut parts to the related objects
+ add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
+ add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
+ }
+}
+
+static void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
+ ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
+{
+ const auto volume_matrix = instance_matrix * volume->get_matrix();
+
+ // Modifiers are not cut, but we still need to add the instance transformation
+ // to the modifier volume transformation to preserve their shape properly.
+ volume->set_transformation(Transformation(volume_matrix));
+
+ if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
+ upper->add_volume(*volume);
+ return;
+ }
+
+ // Some logic for the negative volumes/connectors. Add only needed modifiers
+ auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
+ bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
+ upper->add_volume(*volume);
+ if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
+ lower->add_volume(*volume);
+}
+
+static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
+ ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
+{
+ // Perform cut
+ TriangleMesh upper_mesh, lower_mesh;
+ process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
+
+ // Add required cut parts to the objects
+
+ if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
+ add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
+ if (!lower_mesh.empty()) {
+ add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
+ upper->volumes.back()->cut_info.is_from_upper = false;
+ }
+ return;
+ }
+
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper))
+ add_cut_volume(upper_mesh, upper, volume, cut_matrix);
+
+ if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
+ add_cut_volume(lower_mesh, lower, volume, cut_matrix);
+}
+
+static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx,
+ const Transform3d& cut_matrix = Transform3d::Identity(),
+ bool place_on_cut = false, bool flip = false)
+{
+ // Reset instance transformation except offset and Z-rotation
+
+ for (size_t i = 0; i < object->instances.size(); ++i) {
+ auto& obj_instance = object->instances[i];
+ const double rot_z = obj_instance->get_rotation().z();
+
+ Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor());
+ // add respect to mirroring
+ if (obj_instance->is_left_handed())
+ inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1)));
+
+ obj_instance->set_transformation(inst_trafo);
+
+ Vec3d rotation = Vec3d::Zero();
+ if (!flip && !place_on_cut) {
+ if ( i != src_instance_idx)
+ rotation[Z] = rot_z;
+ }
+ else {
+ Transform3d rotation_matrix = Transform3d::Identity();
+ if (flip)
+ rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
+
+ if (place_on_cut)
+ rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
+
+ if (i != src_instance_idx)
+ rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
+
+ rotation = Transformation(rotation_matrix).get_rotation();
+ }
+
+ obj_instance->set_rotation(rotation);
+ }
+}
+
+
+Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
+ ModelObjectCutAttributes attributes/*= ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts*/)
+ : m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes)
+{
+ m_model = Model();
+ if (object)
+ m_model.add_object(*object);
+}
+
+void Cut::post_process(ModelObject* object, ModelObjectPtrs& cut_object_ptrs, bool keep, bool place_on_cut, bool flip)
+{
+ if (!object) return;
+
+ if (keep && !object->volumes.empty()) {
+ reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip);
+ cut_object_ptrs.push_back(object);
+ }
+ else
+ m_model.objects.push_back(object); // will be deleted in m_model.clear_objects();
+}
+
+void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& cut_object_ptrs)
+{
+ post_process(upper, cut_object_ptrs,
+ m_attributes.has(ModelObjectCutAttribute::KeepUpper),
+ m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
+ m_attributes.has(ModelObjectCutAttribute::FlipUpper));
+
+ post_process(lower, cut_object_ptrs,
+ m_attributes.has(ModelObjectCutAttribute::KeepLower),
+ m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
+ m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower));
+}
+
+
+void Cut::finalize(const ModelObjectPtrs& objects)
+{
+ //clear model from temporarry objects
+ m_model.clear_objects();
+
+ // add to model result objects
+ m_model.objects = objects;
+}
+
+
+const ModelObjectPtrs& Cut::perform_with_plane()
+{
+ if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) {
+ m_model.clear_objects();
+ return m_model.objects;
+ }
+
+ ModelObject* mo = m_model.objects.front();
+
+ BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
+
+ // Clone the object to duplicate instances, materials etc.
+ ModelObject* upper{ nullptr };
+ if (m_attributes.has(ModelObjectCutAttribute::KeepUpper))
+ mo->clone_for_cut(&upper);
+
+ ModelObject* lower{ nullptr };
+ if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !m_attributes.has(ModelObjectCutAttribute::KeepAsParts))
+ mo->clone_for_cut(&lower);
+
+ std::vector dowels;
+
+ // Because transformations are going to be applied to meshes directly,
+ // we reset transformation of all instances and volumes,
+ // except for translation and Z-rotation on instances, which are preserved
+ // in the transformation matrix and not applied to the mesh transform.
+
+ const auto instance_matrix = mo->instances[m_instance]->get_transformation().get_matrix_no_offset();
+ const Transformation cut_transformation = Transformation(m_cut_matrix);
+ const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
+
+ for (ModelVolume* volume : mo->volumes) {
+ volume->reset_extra_facets();
+
+ if (!volume->is_model_part()) {
+ if (volume->cut_info.is_processed)
+ process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower);
+ else
+ process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels);
+ }
+ else if (!volume->mesh().empty())
+ process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower);
+ }
+
+ // Post-process cut parts
+
+ if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty()) {
+ m_model = Model();
+ m_model.objects.push_back(upper);
+ return m_model.objects;
+ }
+
+ ModelObjectPtrs cut_object_ptrs;
+
+ if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) {
+ reset_instance_transformation(upper, m_instance, m_cut_matrix);
+ cut_object_ptrs.push_back(upper);
+ }
+ else {
+ // Delete all modifiers which are not intersecting with solid parts bounding box
+ auto delete_extra_modifiers = [this](ModelObject* mo) {
+ if (!mo) return;
+ const BoundingBoxf3 obj_bb = mo->instance_bounding_box(m_instance);
+ const Transform3d inst_matrix = mo->instances[m_instance]->get_transformation().get_matrix();
+
+ for (int i = int(mo->volumes.size()) - 1; i >= 0; --i)
+ if (const ModelVolume* vol = mo->volumes[i];
+ !vol->is_model_part() && !vol->is_cut_connector()) {
+ auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
+ if (!obj_bb.intersects(bb))
+ mo->delete_volume(i);
+ }
+ };
+
+ post_process(upper, lower, cut_object_ptrs);
+ delete_extra_modifiers(upper);
+ delete_extra_modifiers(lower);
+
+ if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
+ for (auto dowel : dowels) {
+ reset_instance_transformation(dowel, m_instance);
+ dowel->name += "-Dowel-" + dowel->volumes[0]->name;
+ cut_object_ptrs.push_back(dowel);
+ }
+ }
+ }
+
+ BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
+
+ finalize(cut_object_ptrs);
+
+ return m_model.objects;
+}
+
+static void distribute_modifiers_from_object(ModelObject* from_obj, const int instance_idx, ModelObject* to_obj1, ModelObject* to_obj2)
+{
+ auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3();
+ auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3();
+ const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix();
+
+ for (ModelVolume* vol : from_obj->volumes)
+ if (!vol->is_model_part()) {
+ auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
+ // Don't add modifiers which are not intersecting with solid parts
+ if (obj1_bb.intersects(bb))
+ to_obj1->add_volume(*vol);
+ if (obj2_bb.intersects(bb))
+ to_obj2->add_volume(*vol);
+ }
+}
+
+static void merge_solid_parts_inside_object(ModelObjectPtrs& objects)
+{
+ for (ModelObject* mo : objects) {
+ TriangleMesh mesh;
+ // Merge all SolidPart but not Connectors
+ for (const ModelVolume* mv : mo->volumes) {
+ if (mv->is_model_part() && !mv->is_cut_connector()) {
+ TriangleMesh m = mv->mesh();
+ m.transform(mv->get_matrix());
+ mesh.merge(m);
+ }
+ }
+ if (!mesh.empty()) {
+ ModelVolume* new_volume = mo->add_volume(mesh);
+ new_volume->name = mo->name;
+ // Delete all merged SolidPart but not Connectors
+ for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) {
+ const ModelVolume* mv = mo->volumes[i];
+ if (mv->is_model_part() && !mv->is_cut_connector())
+ mo->delete_volume(i);
+ }
+ }
+ }
+}
+
+
+const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowels_count)
+{
+ ModelObject* cut_mo = m_model.objects.front();
+
+ // Clone the object to duplicate instances, materials etc.
+ ModelObject* upper{ nullptr };
+ if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_mo->clone_for_cut(&upper);
+ ModelObject* lower{ nullptr };
+ if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) cut_mo->clone_for_cut(&lower);
+
+ const size_t cut_parts_cnt = parts.size();
+ bool has_modifiers = false;
+
+ // Distribute SolidParts to the Upper/Lower object
+ for (size_t id = 0; id < cut_parts_cnt; ++id) {
+ if (parts[id].is_modifier)
+ has_modifiers = true; // modifiers will be added later to the related parts
+ else if (ModelObject* obj = (parts[id].selected ? upper : lower))
+ obj->add_volume(*(cut_mo->volumes[id]));
+ }
+
+ if (has_modifiers) {
+ // Distribute Modifiers to the Upper/Lower object
+ distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
+ }
+
+ ModelObjectPtrs cut_object_ptrs;
+
+ ModelVolumePtrs& volumes = cut_mo->volumes;
+ if (volumes.size() == cut_parts_cnt) {
+ // Means that object is cut without connectors
+
+ // Just add Upper and Lower objects to cut_object_ptrs
+ post_process(upper, lower, cut_object_ptrs);
+ }
+ else if (volumes.size() > cut_parts_cnt) {
+ // Means that object is cut with connectors
+
+ // All volumes are distributed to Upper / Lower object,
+ // So we don’t need them anymore
+ for (size_t id = 0; id < cut_parts_cnt; id++)
+ delete* (volumes.begin() + id);
+ volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
+
+ // Perform cut just to get connectors
+ Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes);
+ const ModelObjectPtrs& cut_connectors_obj = cut.perform_with_plane();
+ assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
+
+ // Connectors from upper object
+ for (const ModelVolume* volume : cut_connectors_obj[0]->volumes)
+ upper->add_volume(*volume, volume->type());
+
+ // Connectors from lower object
+ for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
+ lower->add_volume(*volume, volume->type());
+
+ // Add Upper and Lower objects to cut_object_ptrs
+ post_process(upper, lower, cut_object_ptrs);
+
+ // Add Dowel-connectors as separate objects to cut_object_ptrs
+ if (cut_connectors_obj.size() >= 3)
+ for (size_t id = 2; id < cut_connectors_obj.size(); id++)
+ cut_object_ptrs.push_back(cut_connectors_obj[id]);
+ }
+
+ // Now merge all model parts together:
+ merge_solid_parts_inside_object(cut_object_ptrs);
+
+ finalize(cut_object_ptrs);
+
+ return m_model.objects;
+}
+
+
+const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/)
+{
+ ModelObject* cut_mo = m_model.objects.front();
+
+ // Clone the object to duplicate instances, materials etc.
+ ModelObject* upper{ nullptr };
+ cut_mo->clone_for_cut(&upper);
+ ModelObject* lower{ nullptr };
+ cut_mo->clone_for_cut(&lower);
+
+ const double groove_half_depth = 0.5 * double(groove.depth);
+
+ Model tmp_model_for_cut = Model();
+
+ Model tmp_model = Model();
+ tmp_model.add_object(*cut_mo);
+ ModelObject* tmp_object = tmp_model.objects.front();
+
+ auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) {
+ const auto& volumes = tmp_model_for_cut.objects.front()->volumes;
+ for (const ModelVolume* volume : volumes)
+ if (volume->is_model_part()) {
+ if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) ||
+ (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) {
+ ModelVolume* new_vol = object->add_volume(*volume);
+ new_vol->reset_from_upper();
+ }
+ }
+ };
+
+ auto cut = [this, add_volumes_from_cut]
+ (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) {
+ Cut cut(object, m_instance, cut_matrix);
+
+ tmp_model_for_cut = Model();
+ tmp_model_for_cut.add_object(*cut.perform_with_plane().front());
+ assert(!tmp_model_for_cut.objects.empty());
+
+ object->clear_volumes();
+ add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut);
+ reset_instance_transformation(object, m_instance);
+ };
+
+ // cut by upper plane
+
+ const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
+ {
+ cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+ add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+ }
+
+ // cut by lower plane
+
+ const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
+ {
+ cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+ add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+ }
+
+ // cut middle part with 2 angles and add parts to related upper/lower objects
+
+ const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle));
+
+ // cut by angle1 plane
+ {
+ const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
+
+ cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+ add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+ }
+
+ // cut by angle2 plane
+ {
+ const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
+
+ cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+ add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+ }
+
+ // apply tolerance to the middle part
+ {
+ const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance;
+
+ const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix;
+ cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+
+ const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance);
+
+ const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
+ cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+
+ const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
+ cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
+ }
+
+ // this part can be added to the upper object now
+ add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
+
+ ModelObjectPtrs cut_object_ptrs;
+
+ if (keep_as_parts) {
+ // add volumes from lower object to the upper, but mark them as a lower
+ const auto& volumes = lower->volumes;
+ for (const ModelVolume* volume : volumes) {
+ ModelVolume* new_vol = upper->add_volume(*volume);
+ new_vol->cut_info.is_from_upper = false;
+ }
+
+ // add modifiers
+ for (const ModelVolume* volume : cut_mo->volumes)
+ if (!volume->is_model_part())
+ upper->add_volume(*volume);
+
+ cut_object_ptrs.push_back(upper);
+
+ // add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks
+ cut_object_ptrs.push_back(lower);
+ }
+ else {
+ // add modifiers if object has any
+ for (const ModelVolume* volume : cut_mo->volumes)
+ if (!volume->is_model_part()) {
+ distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
+ break;
+ }
+
+ assert(!upper->volumes.empty() && !lower->volumes.empty());
+
+ // Add Upper and Lower parts to cut_object_ptrs
+
+ post_process(upper, lower, cut_object_ptrs);
+
+ // Now merge all model parts together:
+ merge_solid_parts_inside_object(cut_object_ptrs);
+ }
+
+ finalize(cut_object_ptrs);
+
+ return m_model.objects;
+}
+
+} // namespace Slic3r
+
diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp
new file mode 100644
index 0000000000..2c477a3e2b
--- /dev/null
+++ b/src/libslic3r/CutUtils.hpp
@@ -0,0 +1,66 @@
+#ifndef slic3r_CutUtils_hpp_
+#define slic3r_CutUtils_hpp_
+
+#include "enum_bitmask.hpp"
+#include "Point.hpp"
+#include "Model.hpp"
+
+#include
+
+namespace Slic3r {
+
+using ModelObjectPtrs = std::vector;
+
+enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
+using ModelObjectCutAttributes = enum_bitmask;
+ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
+
+
+class Cut {
+
+ Model m_model;
+ int m_instance;
+ const Transform3d m_cut_matrix;
+ ModelObjectCutAttributes m_attributes;
+
+ void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip);
+ void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects);
+ void finalize(const ModelObjectPtrs& objects);
+
+public:
+
+ Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
+ ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper |
+ ModelObjectCutAttribute::KeepLower |
+ ModelObjectCutAttribute::KeepAsParts );
+ ~Cut() { m_model.clear_objects(); }
+
+ struct Groove
+ {
+ float depth{ 0.f };
+ float width{ 0.f };
+ float flaps_angle{ 0.f };
+ float angle{ 0.f };
+ float depth_init{ 0.f };
+ float width_init{ 0.f };
+ float flaps_angle_init{ 0.f };
+ float angle_init{ 0.f };
+ float depth_tolerance{ 0.1f };
+ float width_tolerance{ 0.1f };
+ };
+
+ struct Part
+ {
+ bool selected;
+ bool is_modifier;
+ };
+
+ const ModelObjectPtrs& perform_with_plane();
+ const ModelObjectPtrs& perform_by_contour(std::vector parts, int dowels_count);
+ const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false);
+
+}; // namespace Cut
+
+} // namespace Slic3r
+
+#endif /* slic3r_CutUtils_hpp_ */
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 39f6ef98eb..2a8c78729b 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -1280,64 +1280,6 @@ bool ModelObject::has_connectors() const
return false;
}
-indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes)
-{
- indexed_triangle_set connector_mesh;
-
- int sectorCount {1};
- switch (CutConnectorShape(connector_attributes.shape)) {
- case CutConnectorShape::Triangle:
- sectorCount = 3;
- break;
- case CutConnectorShape::Square:
- sectorCount = 4;
- break;
- case CutConnectorShape::Circle:
- sectorCount = 360;
- break;
- case CutConnectorShape::Hexagon:
- sectorCount = 6;
- break;
- default:
- break;
- }
-
- if (connector_attributes.style == CutConnectorStyle::Prism)
- connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
- else if (connector_attributes.type == CutConnectorType::Plug)
- connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount));
- else
- connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
-
- return connector_mesh;
-}
-
-void ModelObject::apply_cut_connectors(const std::string& new_name)
-{
- if (cut_connectors.empty())
- return;
-
- using namespace Geometry;
-
- size_t connector_id = cut_id.connectors_cnt();
- for (const CutConnector& connector : cut_connectors) {
- TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs));
- // Mesh will be centered when loading.
- ModelVolume* new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME);
-
- // Transform the new modifier to be aligned inside the instance
- new_volume->set_transformation(translation_transform(connector.pos) * connector.rotation_m *
- scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast()));
-
- new_volume->cut_info = { connector.attribs.type, connector.radius_tolerance, connector.height_tolerance };
- new_volume->name = new_name + "-" + std::to_string(++connector_id);
- }
- cut_id.increase_connectors_cnt(cut_connectors.size());
-
- // delete all connectors
- cut_connectors.clear();
-}
-
void ModelObject::invalidate_cut()
{
this->cut_id.invalidate();
@@ -1390,297 +1332,6 @@ void ModelVolume::reset_extra_facets()
this->mmu_segmentation_facets.reset();
}
-void ModelVolume::apply_tolerance()
-{
- assert(cut_info.is_connector);
- if (!cut_info.is_processed)
- return;
-
- Vec3d sf = get_scaling_factor();
-
- // make a "hole" wider
- sf[X] += double(cut_info.radius_tolerance);
- sf[Y] += double(cut_info.radius_tolerance);
-
- // make a "hole" dipper
- sf[Z] += double(cut_info.height_tolerance);
-
- set_scaling_factor(sf);
-
- // correct offset in respect to the new depth
- Vec3d rot_norm = Geometry::rotation_transform(get_rotation()) * Vec3d::UnitZ();
- if (rot_norm.norm() != 0.0)
- rot_norm.normalize();
-
- double z_offset = 0.5 * static_cast(cut_info.height_tolerance);
- if (cut_info.connector_type == CutConnectorType::Plug)
- z_offset -= 0.05; // add small Z offset to better preview
-
- set_offset(get_offset() + rot_norm * z_offset);
-}
-
-static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
-{
- if (mesh.empty())
- return;
-
- mesh.transform(cut_matrix);
- ModelVolume* vol = object->add_volume(mesh);
- vol->set_type(type);
-
- vol->name = src_volume->name + suffix;
- // Don't copy the config's ID.
- vol->config.assign_config(src_volume->config);
- assert(vol->config.id().valid());
- assert(vol->config.id() != src_volume->config.id());
- vol->set_material(src_volume->material_id(), *src_volume->material());
- vol->cut_info = src_volume->cut_info;
-}
-
-void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
- ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
- std::vector& dowels)
-{
- assert(volume->cut_info.is_connector);
- volume->cut_info.set_processed();
-
- const auto volume_matrix = volume->get_matrix();
-
- // ! Don't apply instance transformation for the conntectors.
- // This transformation is already there
- if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
- if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
- ModelVolume* vol = upper->add_volume(*volume);
- vol->set_transformation(volume_matrix);
- vol->apply_tolerance();
- }
- if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
- ModelVolume* vol = lower->add_volume(*volume);
- vol->set_transformation(volume_matrix);
- // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
- vol->set_type(ModelVolumeType::MODEL_PART);
- }
- }
- else {
- if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
- ModelObject* dowel{ nullptr };
- // Clone the object to duplicate instances, materials etc.
- clone_for_cut(&dowel);
-
- // add one more solid part same as connector if this connector is a dowel
- ModelVolume* vol = dowel->add_volume(*volume);
- vol->set_type(ModelVolumeType::MODEL_PART);
-
- // But discard rotation and Z-offset for this volume
- vol->set_rotation(Vec3d::Zero());
- vol->set_offset(Z, 0.0);
-
- dowels.push_back(dowel);
- }
-
- // Cut the dowel
- volume->apply_tolerance();
-
- // Perform cut
- TriangleMesh upper_mesh, lower_mesh;
- process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
-
- // add small Z offset to better preview
- upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast());
- lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast());
-
- // Add cut parts to the related objects
- add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
- add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
- }
-}
-
-void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
- ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
-{
- const auto volume_matrix = instance_matrix * volume->get_matrix();
-
- // Modifiers are not cut, but we still need to add the instance transformation
- // to the modifier volume transformation to preserve their shape properly.
- volume->set_transformation(Geometry::Transformation(volume_matrix));
-
- if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
- upper->add_volume(*volume);
- return;
- }
-
- // Some logic for the negative volumes/connectors. Add only needed modifiers
- auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
- bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
- if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
- upper->add_volume(*volume);
- if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
- lower->add_volume(*volume);
-}
-
-void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
- ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
-{
- const auto volume_matrix = volume->get_matrix();
-
- using namespace Geometry;
-
- const Transformation cut_transformation = Transformation(cut_matrix);
- const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset());
-
- // Transform the mesh by the combined transformation matrix.
- // Flip the triangles in case the composite transformation is left handed.
- TriangleMesh mesh(volume->mesh());
- mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
-
- indexed_triangle_set upper_its, lower_its;
- cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
- if (attributes.has(ModelObjectCutAttribute::KeepUpper))
- upper_mesh = TriangleMesh(upper_its);
- if (attributes.has(ModelObjectCutAttribute::KeepLower))
- lower_mesh = TriangleMesh(lower_its);
-}
-void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
- ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
-{
- // Perform cut
- TriangleMesh upper_mesh, lower_mesh;
- process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
-
- // Add required cut parts to the objects
-
- if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
- add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
- add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
- return;
- }
-
- if (attributes.has(ModelObjectCutAttribute::KeepUpper))
- add_cut_volume(upper_mesh, upper, volume, cut_matrix);
-
- if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
- add_cut_volume(lower_mesh, lower, volume, cut_matrix);
-}
-
-void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
- bool place_on_cut/* = false*/, bool flip/* = false*/)
-{
- using namespace Geometry;
-
- // Reset instance transformation except offset and Z-rotation
-
- for (size_t i = 0; i < object->instances.size(); ++i) {
- auto& obj_instance = object->instances[i];
- const double rot_z = obj_instance->get_rotation().z();
-
- Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor());
- if (obj_instance->is_left_handed())
- inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1)));
-
- obj_instance->set_transformation(inst_trafo);
-
- Vec3d rotation = Vec3d::Zero();
- if (!flip && !place_on_cut) {
- if ( i != src_instance_idx)
- rotation[Z] = rot_z;
- }
- else {
- Transform3d rotation_matrix = Transform3d::Identity();
- if (flip)
- rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
-
- if (place_on_cut)
- rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
-
- if (i != src_instance_idx)
- rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
-
- rotation = Transformation(rotation_matrix).get_rotation();
- }
-
- obj_instance->set_rotation(rotation);
- }
-}
-
-ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
-{
- if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower))
- return {};
-
- BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
-
- // Clone the object to duplicate instances, materials etc.
- ModelObject* upper{ nullptr };
- if (attributes.has(ModelObjectCutAttribute::KeepUpper))
- clone_for_cut(&upper);
-
- ModelObject* lower{ nullptr };
- if (attributes.has(ModelObjectCutAttribute::KeepLower) && !attributes.has(ModelObjectCutAttribute::KeepAsParts))
- clone_for_cut(&lower);
-
- std::vector dowels;
-
- using namespace Geometry;
-
- // Because transformations are going to be applied to meshes directly,
- // we reset transformation of all instances and volumes,
- // except for translation and Z-rotation on instances, which are preserved
- // in the transformation matrix and not applied to the mesh transform.
-
- // const auto instance_matrix = instances[instance]->get_matrix(true);
- const auto instance_matrix = instances[instance]->get_transformation().get_matrix_no_offset();
- const Transformation cut_transformation = Transformation(cut_matrix);
- const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
-
- for (ModelVolume* volume : volumes) {
- volume->reset_extra_facets();
-
- if (!volume->is_model_part()) {
- if (volume->cut_info.is_processed)
- process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
- else
- process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels);
- }
- else if (!volume->mesh().empty())
- process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower);
- }
-
- // Post-process cut parts
-
- ModelObjectPtrs res;
-
- if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) {
- reset_instance_transformation(upper, instance, cut_matrix);
- res.push_back(upper);
- }
- else {
- if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
- reset_instance_transformation(upper, instance, cut_matrix,
- attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
- attributes.has(ModelObjectCutAttribute::FlipUpper));
- res.push_back(upper);
- }
-
- if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
- reset_instance_transformation(lower, instance, cut_matrix,
- attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
- attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower));
- res.push_back(lower);
- }
-
- if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
- for (auto dowel : dowels) {
- reset_instance_transformation(dowel, instance, Transform3d::Identity());
- dowel->name += "-Dowel-" + dowel->volumes[0]->name;
- res.push_back(dowel);
- }
- }
- }
-
- BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
-
- return res;
-}
///
/// Compare TriangleMeshes by Bounding boxes (mainly for sort)
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index bedd26436f..221033c521 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -224,6 +224,7 @@ private:
enum class CutConnectorType : int {
Plug
, Dowel
+ , Snap
, Undef
};
@@ -316,10 +317,6 @@ enum class ModelVolumeType : int {
SUPPORT_ENFORCER,
};
-enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
-using ModelObjectCutAttributes = enum_bitmask;
-ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
-
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
@@ -461,29 +458,12 @@ public:
size_t materials_count() const;
size_t facets_count() const;
size_t parts_count() const;
- static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
- void apply_cut_connectors(const std::string& name);
// invalidate cut state for this object and its connectors/volumes
void invalidate_cut();
// delete volumes which are marked as connector for this object
void delete_connectors();
void clone_for_cut(ModelObject **obj);
-private:
- void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
- ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
- std::vector& dowels);
- void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
- ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
- void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
- ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh);
- void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
- ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
-public:
- static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
- bool place_on_cut = false, bool flip = false);
-
- ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
void split(ModelObjectPtrs*new_objects);
void merge();
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
@@ -777,6 +757,7 @@ public:
// It contains information about connetors
struct CutInfo
{
+ bool is_from_upper{ true };
bool is_connector{ false };
bool is_processed{ true };
CutConnectorType connector_type{ CutConnectorType::Plug };
@@ -794,6 +775,7 @@ public:
void set_processed() { is_processed = true; }
void invalidate() { is_connector = false; }
+ void reset_from_upper() { is_from_upper = true; }
template inline void serialize(Archive& ar) {
ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance);
@@ -801,6 +783,9 @@ public:
};
CutInfo cut_info;
+ bool is_from_upper() const { return cut_info.is_from_upper; }
+ void reset_from_upper() { cut_info.reset_from_upper(); }
+
bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; }
void invalidate_cut_info() { cut_info.invalidate(); }
@@ -846,7 +831,6 @@ public:
bool is_the_only_one_part() const; // behave like an object
t_model_material_id material_id() const { return m_material_id; }
void reset_extra_facets();
- void apply_tolerance();
void set_material_id(t_model_material_id material_id);
ModelMaterial* material() const;
void set_material(t_model_material_id material_id, const ModelMaterial &material);
diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp
index 4bf9fd486a..236fa22c51 100644
--- a/src/libslic3r/TriangleMesh.cpp
+++ b/src/libslic3r/TriangleMesh.cpp
@@ -1262,6 +1262,127 @@ indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorC
return mesh;
}
+indexed_triangle_set its_make_snap(double r, double h, float space_proportion, float bulge_proportion)
+{
+ const float radius = (float)r;
+ const float height = (float)h;
+ const size_t sectors_cnt = 10; //(float)fa;
+ const float halfPI = 0.5f * (float)PI;
+
+ const float space_len = space_proportion * radius;
+
+ const float b_len = radius;
+ const float m_len = (1 + bulge_proportion) * radius;
+ const float t_len = 0.5f * radius;
+
+ const float b_height = 0.f;
+ const float m_height = 0.5f * height;
+ const float t_height = height;
+
+ const float b_angle = acos(space_len/b_len);
+ const float t_angle = acos(space_len/t_len);
+
+ const float b_angle_step = b_angle / (float)sectors_cnt;
+ const float t_angle_step = t_angle / (float)sectors_cnt;
+
+ const Vec2f b_vec = Eigen::Vector2f(0, b_len);
+ const Vec2f t_vec = Eigen::Vector2f(0, t_len);
+
+
+ auto add_side_vertices = [b_vec, t_vec, b_height, m_height, t_height](std::vector& vertices, float b_angle, float t_angle, const Vec2f& m_vec) {
+ Vec2f b_pt = Eigen::Rotation2Df(b_angle) * b_vec;
+ Vec2f m_pt = Eigen::Rotation2Df(b_angle) * m_vec;
+ Vec2f t_pt = Eigen::Rotation2Df(t_angle) * t_vec;
+
+ vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height));
+ vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height));
+ vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height));
+ };
+
+ auto add_side_facets = [](std::vector& facets, int vertices_cnt, int frst_id, int scnd_id) {
+ int id = vertices_cnt - 1;
+
+ facets.emplace_back(frst_id, id - 2, id - 5);
+
+ facets.emplace_back(id - 2, id - 1, id - 5);
+ facets.emplace_back(id - 1, id - 4, id - 5);
+ facets.emplace_back(id - 4, id - 1, id);
+ facets.emplace_back(id, id - 3, id - 4);
+
+ facets.emplace_back(id, scnd_id, id - 3);
+ };
+
+ const float f = (b_len - m_len) / m_len; // Flattening
+
+ auto get_m_len = [b_len, f](float angle) {
+ const float rad_sqr = b_len * b_len;
+ const float sin_sqr = sin(angle) * sin(angle);
+ const float f_sqr = (1-f)*(1-f);
+ return sqrtf(rad_sqr / (1 + (1 / f_sqr - 1) * sin_sqr));
+ };
+
+ auto add_sub_mesh = [add_side_vertices, add_side_facets, get_m_len,
+ b_height, t_height, b_angle, t_angle, b_angle_step, t_angle_step]
+ (indexed_triangle_set& mesh, float center_x, float angle_rotation, int frst_vertex_id) {
+ auto& vertices = mesh.vertices;
+ auto& facets = mesh.indices;
+
+ // 2 special vertices, top and bottom center, rest are relative to this
+ vertices.emplace_back(Vec3f(center_x, 0.f, b_height));
+ vertices.emplace_back(Vec3f(center_x, 0.f, t_height));
+
+ float b_angle_start = angle_rotation - b_angle;
+ float t_angle_start = angle_rotation - t_angle;
+ const float b_angle_stop = angle_rotation + b_angle;
+
+ const int frst_id = frst_vertex_id;
+ const int scnd_id = frst_id + 1;
+
+ // add first side vertices and internal facets
+ {
+ const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start));
+ add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec);
+
+ int id = (int)vertices.size() - 1;
+
+ facets.emplace_back(frst_id, id - 2, id - 1);
+ facets.emplace_back(frst_id, id - 1, id);
+ facets.emplace_back(frst_id, id, scnd_id);
+ }
+
+ // add d side vertices and facets
+ while (!is_approx(b_angle_start, b_angle_stop)) {
+ b_angle_start += b_angle_step;
+ t_angle_start += t_angle_step;
+
+ const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start));
+ add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec);
+
+ add_side_facets(facets, (int)vertices.size(), frst_id, scnd_id);
+ }
+
+ // add last internal facets to close the mesh
+ {
+ int id = (int)vertices.size() - 1;
+
+ facets.emplace_back(frst_id, scnd_id, id);
+ facets.emplace_back(frst_id, id, id - 1);
+ facets.emplace_back(frst_id, id - 1, id - 2);
+ }
+ };
+
+
+ indexed_triangle_set mesh;
+
+ mesh.vertices.reserve(2 * (3 * (2 * sectors_cnt + 1) + 2));
+ mesh.indices.reserve(2 * (6 * 2 * sectors_cnt + 6));
+
+ add_sub_mesh(mesh, -space_len, halfPI , 0);
+ add_sub_mesh(mesh, space_len, 3 * halfPI, (int)mesh.vertices.size());
+
+ return mesh;
+}
+
indexed_triangle_set its_convex_hull(const std::vector &pts)
{
std::vector dst_vertices;
diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp
index 0f43f9d58d..4b524402c0 100644
--- a/src/libslic3r/TriangleMesh.hpp
+++ b/src/libslic3r/TriangleMesh.hpp
@@ -321,6 +321,7 @@ indexed_triangle_set its_make_frustum(double r, double h, double fa=(2*PI/360
indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCount);
indexed_triangle_set its_make_pyramid(float base, float height);
indexed_triangle_set its_make_sphere(double radius, double fa);
+indexed_triangle_set its_make_snap(double r, double h, float space_proportion = 0.25f, float bulge_proportion = 0.125f);
indexed_triangle_set its_convex_hull(const std::vector &pts);
inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); }
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index c2a4e752f6..9f09619c88 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -2685,6 +2685,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
if (curr_gizmo != nullptr)
curr_gizmo->unregister_raycasters_for_picking();
m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo);
+ if (curr_gizmo != nullptr && !m_selection.is_empty())
+ curr_gizmo->register_raycasters_for_picking();
+ m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::FallbackGizmo);
if (curr_gizmo != nullptr && !m_selection.is_empty())
curr_gizmo->register_raycasters_for_picking();
@@ -5740,6 +5743,7 @@ void GLCanvas3D::_picking_pass()
break;
}
case SceneRaycaster::EType::Gizmo:
+ case SceneRaycaster::EType::FallbackGizmo:
{
const Size& cnv_size = get_canvas_size();
const bool inside = 0 <= m_mouse.position.x() && m_mouse.position.x() < cnv_size.get_width() &&
@@ -5772,6 +5776,7 @@ void GLCanvas3D::_picking_pass()
{
case SceneRaycaster::EType::Bed: { object_type = "Bed"; break; }
case SceneRaycaster::EType::Gizmo: { object_type = "Gizmo element"; break; }
+ case SceneRaycaster::EType::FallbackGizmo: { object_type = "Gizmo2 element"; break; }
case SceneRaycaster::EType::Volume:
{
if (m_volumes.volumes[hit.raycaster_id]->is_wipe_tower)
@@ -5826,6 +5831,8 @@ void GLCanvas3D::_picking_pass()
add_strings_row_to_table("Volumes", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text));
sprintf(buf, "%d (%d)", (int)m_scene_raycaster.gizmos_count(), (int)m_scene_raycaster.active_gizmos_count());
add_strings_row_to_table("Gizmo elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text));
+ sprintf(buf, "%d (%d)", (int)m_scene_raycaster.fallback_gizmos_count(), (int)m_scene_raycaster.active_fallback_gizmos_count());
+ add_strings_row_to_table("Gizmo2 elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text));
ImGui::EndTable();
}
@@ -5843,6 +5850,20 @@ void GLCanvas3D::_picking_pass()
}
}
+ std::vector>* gizmo2_raycasters = m_scene_raycaster.get_raycasters(SceneRaycaster::EType::FallbackGizmo);
+ if (gizmo2_raycasters != nullptr && !gizmo2_raycasters->empty()) {
+ ImGui::Separator();
+ imgui.text("Gizmo2 raycasters IDs:");
+ if (ImGui::BeginTable("Gizmo2Raycasters", 3)) {
+ for (size_t i = 0; i < gizmo2_raycasters->size(); ++i) {
+ add_strings_row_to_table(std::to_string(i), ImGuiWrapper::COL_ORANGE_LIGHT,
+ std::to_string(SceneRaycaster::decode_id(SceneRaycaster::EType::FallbackGizmo, (*gizmo2_raycasters)[i]->get_id())), ImGui::GetStyleColorVec4(ImGuiCol_Text),
+ to_string(Geometry::Transformation((*gizmo2_raycasters)[i]->get_transform()).get_offset()), ImGui::GetStyleColorVec4(ImGuiCol_Text));
+ }
+ ImGui::EndTable();
+ }
+ }
+
imgui.end();
#endif // ENABLE_RAYCAST_PICKING_DEBUG
}
diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp
index a08673e235..5ac51d54f7 100644
--- a/src/slic3r/GUI/GalleryDialog.cpp
+++ b/src/slic3r/GUI/GalleryDialog.cpp
@@ -134,7 +134,12 @@ GalleryDialog::GalleryDialog(wxWindow* parent) :
}
GalleryDialog::~GalleryDialog()
-{
+{
+ // From wxWidgets docs:
+ // The method void wxListCtrl::SetImageList(wxImageList* imageList, int which)
+ // does not take ownership of the image list, you have to delete it yourself.
+ if (m_image_list)
+ delete m_image_list;
}
int GalleryDialog::show(bool show_from_menu)
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
index 36576e9494..9d23a51808 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
@@ -35,6 +35,9 @@ static const ColorRGBA CONNECTOR_DEF_COLOR = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f);
static const ColorRGBA CONNECTOR_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f);
static const ColorRGBA HOVERED_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 1.0f);
+static const ColorRGBA CUT_PLANE_DEF_COLOR = ColorRGBA(0.9f, 0.9f, 0.9f, 0.5f);
+static const ColorRGBA CUT_PLANE_ERR_COLOR = ColorRGBA(1.0f, 0.8f, 0.8f, 0.5f);
+
const unsigned int AngleResolution = 64;
const unsigned int ScaleStepsCount = 72;
const float ScaleStepRad = 2.0f * float(PI) / ScaleStepsCount;
@@ -183,15 +186,16 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename,
, m_connector_style (int(CutConnectorStyle::Prism))
, m_connector_shape_id (int(CutConnectorShape::Circle))
{
-// m_modes = { _u8L("Planar"), _u8L("Grid")
+ m_modes = { _u8L("Planar"), _u8L("Tongue and Groove")//, _u8L("Grid")
// , _u8L("Radial"), _u8L("Modular")
-// };
+ };
m_connector_modes = { _u8L("Auto"), _u8L("Manual") };
std::map connetor_types = {
{ImGui::PlugMarker , _u8L("Plug") },
- {ImGui::DowelMarker, _u8L("Dowel") },
+ {ImGui::DowelMarker, _u8L("Dowel") },
+ {ImGui::SnapMarker, _u8L("Snap") },
};
for (auto connector : connetor_types) {
std::string type_label = " " + connector.second + " ";
@@ -222,9 +226,13 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename,
{"Shape" , _u8L("Shape")},
{"Depth" , _u8L("Depth")},
{"Size" , _u8L("Size")},
+ {"Groove" , _u8L("Groove")},
+ {"Width" , _u8L("Width")},
+ {"Flaps Angle" , _u8L("Flaps Angle")},
+ {"Groove Angle" , _u8L("Groove Angle")},
};
- update_connector_shape();
+// update_connector_shape();
}
std::string GLGizmoCut3D::get_tooltip() const
@@ -249,13 +257,17 @@ std::string GLGizmoCut3D::get_tooltip() const
return tooltip;
}
- if (!m_dragging && m_hover_id == CutPlane)
+ if (!m_dragging && m_hover_id == CutPlane) {
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove)
+ return _u8L("Click to flip the cut plane\n"
+ "Drag to move the cut plane");
return _u8L("Click to flip the cut plane\n"
"Drag to move the cut plane\n"
"Right-click a part to assign it to the other side");
+ }
- if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) {
- std::string axis = m_hover_id == X ? "X" : "Y";
+ if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation)) {
+ std::string axis = m_hover_id == X ? "X" : m_hover_id == Y ? "Y" : "Z";
return axis + ": " + format(float(rad2deg(m_angle)), 1) + _u8L("°");
}
@@ -283,6 +295,14 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event)
else if (mouse_event.Moving())
return false;
+ if (m_hover_id >= CutPlane && mouse_event.LeftDown() && !m_connectors_editing) {
+ // before processing of a use_grabbers(), detect start move position as a projection of mouse position to the cut plane
+ Vec3d pos;
+ Vec3d pos_world;
+ if (unproject_on_cut_plane(mouse_pos, pos, pos_world, false))
+ m_cut_plane_start_move_pos = pos_world;
+ }
+
if (use_grabbers(mouse_event)) {
if (m_hover_id >= m_connectors_group_id) {
if (mouse_event.LeftDown() && !mouse_event.CmdDown() && !mouse_event.AltDown())
@@ -297,7 +317,7 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event)
// disable / enable current contour
Vec3d pos;
Vec3d pos_world;
- m_was_contour_selected = unproject_on_cut_plane(mouse_pos.cast(), pos, pos_world, false);
+ m_was_contour_selected = unproject_on_cut_plane(mouse_pos.cast(), pos, pos_world);
if (m_was_contour_selected) {
// Following would inform the clipper about the mouse click, so it can
// toggle the respective contour as disabled.
@@ -311,8 +331,15 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event)
flip_cut_plane();
}
- if (m_part_selection.valid())
- m_parent.toggle_model_objects_visibility(false);
+ if (m_hover_id >= CutPlane && mouse_event.Dragging() && !m_connectors_editing) {
+ // if we continue to dragging a cut plane, than update a start move position as a projection of mouse position to the cut plane after processing of a use_grabbers()
+ Vec3d pos;
+ Vec3d pos_world;
+ if (unproject_on_cut_plane(mouse_pos, pos, pos_world, false))
+ m_cut_plane_start_move_pos = pos_world;
+ }
+
+ toggle_model_objects_visibility();
return true;
}
@@ -353,7 +380,8 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event)
return true;
}
else if (mouse_event.RightDown()) {
- if (! m_connectors_editing && mouse_event.GetModifiers() == wxMOD_NONE) {
+ if (! m_connectors_editing && mouse_event.GetModifiers() == wxMOD_NONE &&
+ CutMode(m_mode) == CutMode::cutPlanar) {
// Check the internal part raycasters.
if (! m_part_selection.valid())
process_contours();
@@ -466,13 +494,43 @@ void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/)
update_clipper();
}
+void GLGizmoCut3D::switch_to_mode(size_t new_mode)
+{
+ m_mode = new_mode;
+ update_raycasters_for_picking();
+
+ apply_color_clip_plane_colors();
+ if (auto oc = m_c->object_clipper()) {
+ m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f;
+ oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width));
+ }
+
+ update_plane_model();
+ reset_cut_by_contours();
+}
+
+bool GLGizmoCut3D::render_cut_mode_combo()
+{
+ ImGui::AlignTextToFramePadding();
+ int selection_idx = int(m_mode);
+ const bool is_changed = m_imgui->combo(_u8L("Mode"), m_modes, selection_idx, 0, m_label_width, m_control_width);
+
+ if (is_changed) {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change cut mode"), UndoRedo::SnapshotType::GizmoAction);
+ switch_to_mode(size_t(selection_idx));
+ check_and_update_connectors_state();
+ }
+
+ return is_changed;
+}
+
bool GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, int& selection_idx)
{
ImGui::AlignTextToFramePadding();
const bool is_changed = m_imgui->combo(label, lines, selection_idx, 0, m_label_width, m_control_width);
- if (is_changed)
- update_connector_shape();
+ //if (is_changed)
+ // update_connector_shape();
return is_changed;
}
@@ -497,7 +555,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i
return !is_approx(old_val, value);
}
-bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in)
+bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/)
{
constexpr float UndefMinVal = -0.1f;
const float f_mm_to_in = static_cast(ObjectManipulation::mm_to_in);
@@ -516,23 +574,29 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v
m_imgui->slider_float(label.c_str(), &value, min_val, max_val, format.c_str(), 1.f, true, tooltip);
val = value * (m_imperial_units ? static_cast(ObjectManipulation::in_to_mm) : 1.f);
+ m_is_slider_editing_done |= m_imgui->get_last_slider_status().deactivated_after_edit;
+
return !is_approx(old_val, value);
};
const BoundingBoxf3 bbox = m_bounding_box;
const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0) * (m_imperial_units ? f_mm_to_in : 1.f);
+ const float min_v = min_val > 0.f ? /*std::min(max_val, mean_size)*/min_val : 1.f;
ImGuiWrapper::text(label);
ImGui::SameLine(m_label_width);
ImGui::PushItemWidth(m_control_width * 0.7f);
- const bool is_value_changed = render_slider("##" + label, value_in, 1.f, mean_size, _L("Value"));
+// const bool is_value_changed = render_slider("##" + label, value_in, 1.f, mean_size, _L("Value"));
+ const bool is_value_changed = render_slider("##" + label, value_in, min_v, mean_size, _L("Value"));
ImGui::SameLine();
ImGui::PushItemWidth(m_control_width * 0.45f);
- const bool is_tolerance_changed = render_slider("##tolerance_" + label, tolerance_in, 0.f, 0.5f * mean_size, _L("Tolerance"));
+// const bool is_tolerance_changed = render_slider("##tolerance_" + label, tolerance_in, 0.f, 0.5f * mean_size, _L("Tolerance"));
+ const float max_tolerance_v = max_tolerance > 0.f ? std::min(max_tolerance, 0.5f * mean_size) : 0.5f * mean_size;
+ const bool is_tolerance_changed = render_slider("##tolerance_" + label, tolerance_in, 0.f, max_tolerance_v, _L("Tolerance"));
return is_value_changed || is_tolerance_changed;
}
@@ -568,7 +632,7 @@ bool GLGizmoCut3D::render_connect_type_radio_button(CutConnectorType type)
ImGui::PushItemWidth(m_control_width);
if (ImGui::RadioButton(m_connector_types[size_t(type)].c_str(), m_connector_type == type)) {
m_connector_type = type;
- update_connector_shape();
+// update_connector_shape();
return true;
}
return false;
@@ -606,6 +670,232 @@ bool GLGizmoCut3D::render_reset_button(const std::string& label_id, const std::s
return revert;
}
+static double get_grabber_mean_size(const BoundingBoxf3& bb)
+{
+ return (bb.size().x() + bb.size().y() + bb.size().z()) / 30.;
+}
+
+indexed_triangle_set GLGizmoCut3D::its_make_groove_plane()
+{
+ // values for calculation
+
+ const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle));
+ const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle);
+
+ const float groove_half_width_upper = 0.5f * (m_groove.width);
+ const float groove_half_width_lower = 0.5f * (m_groove.width + flaps_width);
+
+ const float cut_plane_radius = 1.5f * float(m_radius);
+ const float cut_plane_length = 1.5f * cut_plane_radius;
+
+ const float groove_half_depth = 0.5f * m_groove.depth;
+
+ const float x = 0.5f * cut_plane_radius;
+ const float y = 0.5f * cut_plane_length;
+ float z_upper = groove_half_depth;
+ float z_lower = -groove_half_depth;
+
+ const float proj = y * tan(m_groove.angle);
+
+ float ext_upper_x = groove_half_width_upper + proj; // upper_x extension
+ float ext_lower_x = groove_half_width_lower + proj; // lower_x extension
+
+ float nar_upper_x = groove_half_width_upper - proj; // upper_x narrowing
+ float nar_lower_x = groove_half_width_lower - proj; // lower_x narrowing
+
+ const float cut_plane_thiknes = 0.02f;// 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes
+
+ // Vertices of the groove used to detection if groove is valid
+ // They are written as:
+ // {left_ext_lower, left_nar_lower, left_ext_upper, left_nar_upper,
+ // right_ext_lower, right_nar_lower, right_ext_upper, right_nar_upper }
+ {
+ m_groove_vertices.clear();
+ m_groove_vertices.reserve(8);
+
+ m_groove_vertices.emplace_back(Vec3f(-ext_lower_x, -y, z_lower).cast());
+ m_groove_vertices.emplace_back(Vec3f(-nar_lower_x, y, z_lower).cast());
+ m_groove_vertices.emplace_back(Vec3f(-ext_upper_x, -y, z_upper).cast());
+ m_groove_vertices.emplace_back(Vec3f(-nar_upper_x, y, z_upper).cast());
+ m_groove_vertices.emplace_back(Vec3f( ext_lower_x, -y, z_lower).cast());
+ m_groove_vertices.emplace_back(Vec3f( nar_lower_x, y, z_lower).cast());
+ m_groove_vertices.emplace_back(Vec3f( ext_upper_x, -y, z_upper).cast());
+ m_groove_vertices.emplace_back(Vec3f( nar_upper_x, y, z_upper).cast());
+ }
+
+ // Different cases of groove plane:
+
+ // groove is open
+
+ if (groove_half_width_upper > proj && groove_half_width_lower > proj) {
+ indexed_triangle_set mesh;
+
+ auto get_vertices = [x, y](float z_upper, float z_lower, float nar_upper_x, float nar_lower_x, float ext_upper_x, float ext_lower_x) {
+ return std::vector({
+ // upper left part vertices
+ {-x, -y, z_upper}, {-x, y, z_upper}, {-nar_upper_x, y, z_upper}, {-ext_upper_x, -y, z_upper},
+ // lower part vertices
+ {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower},
+ // upper right part vertices
+ {ext_upper_x, -y, z_upper}, {nar_upper_x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}
+ });
+ };
+
+ mesh.vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x);
+ mesh.vertices.reserve(2 * mesh.vertices.size());
+
+ z_upper -= cut_plane_thiknes;
+ z_lower -= cut_plane_thiknes;
+
+ const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle);
+
+ nar_upper_x += under_x_shift;
+ nar_lower_x += under_x_shift;
+ ext_upper_x += under_x_shift;
+ ext_lower_x += under_x_shift;
+
+ std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x);
+ mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
+
+ mesh.indices = {
+ // above view
+ {5,4,7}, {5,7,6}, // lower part
+ {3,4,5}, {3,5,2}, // left side
+ {9,6,8}, {8,6,7}, // right side
+ {1,0,2}, {2,0,3}, // upper left part
+ {9,8,10}, {10,8,11}, // upper right part
+ // under view
+ {20,21,22}, {20,22,23}, // upper right part
+ {12,13,14}, {12,14,15}, // upper left part
+ {18,21,20}, {18,20,19}, // right side
+ {16,15,14}, {16,14,17}, // left side
+ {16,17,18}, {16,18,19}, // lower part
+ // left edge
+ {1,13,0}, {0,13,12},
+ // front edge
+ {0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23},
+ // right edge
+ {11,23,10}, {10,23,22},
+ // back edge
+ {1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22}
+ };
+ return mesh;
+ }
+
+ float cross_pt_upper_y = groove_half_width_upper / tan(m_groove.angle);
+
+ // groove is closed
+
+ if (groove_half_width_upper < proj && groove_half_width_lower < proj) {
+ float cross_pt_lower_y = groove_half_width_lower / tan(m_groove.angle);
+
+ indexed_triangle_set mesh;
+
+ auto get_vertices = [x, y](float z_upper, float z_lower, float cross_pt_upper_y, float cross_pt_lower_y, float ext_upper_x, float ext_lower_x) {
+ return std::vector({
+ // upper part vertices
+ {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper},
+ {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper},
+ // lower part vertices
+ {-ext_lower_x, -y, z_lower}, {0.f, cross_pt_lower_y, z_lower}, {ext_lower_x, -y, z_lower}
+ });
+ };
+
+ mesh.vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x);
+ mesh.vertices.reserve(2 * mesh.vertices.size());
+
+ z_upper -= cut_plane_thiknes;
+ z_lower -= cut_plane_thiknes;
+
+ const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle);
+
+ cross_pt_upper_y += cut_plane_thiknes;
+ cross_pt_lower_y += cut_plane_thiknes;
+ ext_upper_x += under_x_shift;
+ ext_lower_x += under_x_shift;
+
+ std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x);
+ mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
+
+ mesh.indices = {
+ // above view
+ {8,7,9}, // lower part
+ {5,8,6}, {6,8,7}, // left side
+ {4,9,8}, {4,8,5}, // right side
+ {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part
+ // under view
+ {10,11,16}, {16,11,15}, {15,11,12}, {15,12,14}, {14,12,13}, // upper part
+ {18,15,14}, {14,18,19}, // right side
+ {17,16,15}, {17,15,18}, // left side
+ {17,18,19}, // lower part
+ // left edge
+ {1,11,0}, {0,11,10},
+ // front edge
+ {0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3},
+ // right edge
+ {3,13,12}, {3,12,2},
+ // back edge
+ {2,12,11}, {2,11,1}
+ };
+
+ return mesh;
+ }
+
+ // groove is closed from the roof
+
+ indexed_triangle_set mesh;
+ mesh.vertices = {
+ // upper part vertices
+ {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper},
+ {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper},
+ // lower part vertices
+ {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower}
+ };
+
+ mesh.vertices.reserve(2 * mesh.vertices.size() + 1);
+
+ z_upper -= cut_plane_thiknes;
+ z_lower -= cut_plane_thiknes;
+
+ const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle);
+
+ nar_lower_x += under_x_shift;
+ ext_upper_x += under_x_shift;
+ ext_lower_x += under_x_shift;
+
+ std::vector vertices = {
+ // upper part vertices
+ {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper},
+ {ext_upper_x, -y, z_upper}, {under_x_shift, cross_pt_upper_y, z_upper}, {-under_x_shift, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper},
+ // lower part vertices
+ {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower}
+ };
+ mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end());
+
+ mesh.indices = {
+ // above view
+ {8,7,10}, {8,10,9}, // lower part
+ {5,8,7}, {5,7,6}, // left side
+ {4,10,9}, {4,9,5}, // right side
+ {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part
+ // under view
+ {11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part
+ {21,16,15}, {21,15,22}, // right side
+ {19,18,17}, {19,17,20}, // left side
+ {19,20,21}, {19,21,22}, // lower part
+ // left edge
+ {1,12,11}, {1,11,0},
+ // front edge
+ {0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3},
+ // right edge
+ {3,14,13}, {3,14,2},
+ // back edge
+ {2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8}
+ };
+
+ return mesh;
+}
+
void GLGizmoCut3D::render_cut_plane()
{
if (cut_line_processing())
@@ -623,19 +913,16 @@ void GLGizmoCut3D::render_cut_plane()
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
- const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m;
- shader->set_uniform("view_model_matrix", view_model_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
- if (can_perform_cut() && has_valid_contour()) {
- if (m_hover_id == CutPlane)
- m_plane.model.set_color({ 0.9f, 0.9f, 0.9f, 0.5f });
- else
- m_plane.model.set_color({ 0.8f, 0.8f, 0.8f, 0.5f });
- }
- else
- m_plane.model.set_color({ 1.0f, 0.8f, 0.8f, 0.5f });
+ ColorRGBA cp_clr = can_perform_cut() && has_valid_groove() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR;
+ if (m_mode == size_t(CutMode::cutTongueAndGroove))
+ cp_clr.a(cp_clr.a() - 0.1f);
+ m_plane.model.set_color(cp_clr);
+
+ const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m;
+ shader->set_uniform("view_model_matrix", view_model_matrix);
m_plane.model.render();
glsafe(::glEnable(GL_CULL_FACE));
@@ -644,11 +931,6 @@ void GLGizmoCut3D::render_cut_plane()
shader->stop_using();
}
-static double get_grabber_mean_size(const BoundingBoxf3& bb)
-{
- return (bb.size().x() + bb.size().y() + bb.size().z()) / 30.;
-}
-
static double get_half_size(double size)
{
return std::max(size * 0.35, 0.05);
@@ -703,8 +985,10 @@ void GLGizmoCut3D::render_rotation_snapping(GrabberID axis, const ColorRGBA& col
if (axis == X)
view_model_matrix = view_model_matrix * rotation_transform(0.5 * PI * Vec3d::UnitY()) * rotation_transform(-PI * Vec3d::UnitZ());
- else
+ else if (axis == Y)
view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * rotation_transform(-0.5 * PI * Vec3d::UnitY());
+ else
+ view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ());
line_shader->start_using();
line_shader->set_uniform("projection_matrix", camera.get_projection_matrix());
@@ -724,9 +1008,9 @@ void GLGizmoCut3D::render_rotation_snapping(GrabberID axis, const ColorRGBA& col
line_shader->stop_using();
}
-void GLGizmoCut3D::render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix)
+void GLGizmoCut3D::render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef/* = 1.0*/)
{
- const Transform3d line_view_matrix = view_matrix * scale_transform(Vec3d(1.0, 1.0, m_grabber_connection_len));
+ const Transform3d line_view_matrix = view_matrix * scale_transform(Vec3d(1.0, 1.0, line_len_koef * m_grabber_connection_len));
render_line(m_grabber_connection, color, line_view_matrix, 0.2f);
};
@@ -742,9 +1026,9 @@ void GLGizmoCut3D::render_cut_plane_grabbers()
const double mean_size = get_grabber_mean_size(m_bounding_box);
double size;
- const bool dragging_by_cut_plane = m_dragging && m_hover_id == CutPlane;
+ const bool no_xy_dragging = m_dragging && m_hover_id == CutPlane;
- if (!dragging_by_cut_plane) {
+ if (!no_xy_dragging && m_hover_id != CutPlaneZRotation && m_hover_id != CutPlaneXMove && m_hover_id != CutPlaneYMove) {
render_grabber_connection(GRABBER_COLOR, view_matrix);
// render sphere grabber
@@ -755,11 +1039,11 @@ void GLGizmoCut3D::render_cut_plane_grabbers()
render_model(m_sphere.model, color, view_matrix * translation_transform(m_grabber_connection_len * Vec3d::UnitZ()) * scale_transform(size));
}
- const bool no_one_grabber_hovered = !m_dragging && (m_hover_id < 0 || m_hover_id == CutPlane);
+ const bool no_xy_grabber_hovered = !m_dragging && (m_hover_id < 0 || m_hover_id == CutPlane);
// render X grabber
- if (no_one_grabber_hovered || m_hover_id == X)
+ if (no_xy_grabber_hovered || m_hover_id == X)
{
size = m_dragging && m_hover_id == X ? get_dragging_half_size(mean_size) : get_half_size(mean_size);
const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size);
@@ -778,7 +1062,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers()
// render Y grabber
- if (no_one_grabber_hovered || m_hover_id == Y)
+ if (no_xy_grabber_hovered || m_hover_id == Y)
{
size = m_dragging && m_hover_id == Y ? get_dragging_half_size(mean_size) : get_half_size(mean_size);
const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size);
@@ -792,7 +1076,73 @@ void GLGizmoCut3D::render_cut_plane_grabbers()
Vec3d offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len);
render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale));
offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len);
- render_model(m_cone.model, color, view_matrix * translation_transform(offset)* rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale));
+ render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale));
+ }
+
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove) {
+
+ // render CutPlaneZRotation grabber
+
+ if (no_xy_grabber_hovered || m_hover_id == CutPlaneZRotation)
+ {
+ size = 0.75 * (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size));
+ color = ColorRGBA::BLUE();
+ const ColorRGBA cp_color = m_hover_id == CutPlaneZRotation ? color : m_plane.model.get_color();
+
+ const double grabber_shift = -1.75 * m_grabber_connection_len;
+
+ render_model(m_sphere.model, cp_color, view_matrix * translation_transform(grabber_shift * Vec3d::UnitY()) * scale_transform(size));
+
+ if (m_hover_id == CutPlaneZRotation) {
+ const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size);
+
+ render_rotation_snapping(CutPlaneZRotation, color);
+ render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(0.5 * PI * Vec3d::UnitX()), 1.75);
+
+ Vec3d offset = Vec3d(1.25 * size, grabber_shift, 0.0);
+ render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale));
+ offset = Vec3d(-1.25 * size, grabber_shift, 0.0);
+ render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale));
+ }
+ }
+
+ const double xy_connection_len = 0.75 * m_grabber_connection_len;
+
+ // render CutPlaneXMove grabber
+
+ if (no_xy_grabber_hovered || m_hover_id == CutPlaneXMove)
+ {
+ size = (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size));
+ color = m_hover_id == CutPlaneXMove ? ColorRGBA::RED() : m_plane.model.get_color();
+
+ render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(0.5 * PI * Vec3d::UnitY()), 0.75);
+
+ Vec3d offset = xy_connection_len * Vec3d::UnitX() - 0.5 * size * Vec3d::Ones();
+ render_model(m_cube.model, color, view_matrix * translation_transform(offset) * scale_transform(size));
+
+ const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size);
+
+ offset = (size + xy_connection_len) * Vec3d::UnitX();
+ render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale));
+ }
+
+ // render CutPlaneYMove grabber
+
+ if (m_groove.angle > 0.0f && (no_xy_grabber_hovered || m_hover_id == CutPlaneYMove))
+ {
+ size = (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size));
+ color = m_hover_id == CutPlaneYMove ? ColorRGBA::GREEN() : m_plane.model.get_color();
+
+ render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitX()), 0.75);
+
+ Vec3d offset = xy_connection_len * Vec3d::UnitY() - 0.5 * size * Vec3d::Ones();
+ render_model(m_cube.model, color, view_matrix * translation_transform(offset) * scale_transform(size));
+
+ const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size);
+
+ offset = (size + xy_connection_len) * Vec3d::UnitY();
+ render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale));
+ }
}
}
@@ -832,21 +1182,52 @@ bool GLGizmoCut3D::on_init()
void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar)
{
- ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,
- m_ar_plane_center, m_rotation_m);
+ size_t mode;
+ float groove_depth;
+ float groove_width;
+ float groove_flaps_angle;
+ float groove_angle;
+ float groove_depth_tolerance;
+ float groove_width_tolerance;
+
+ ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, mode, m_connectors_editing,
+ m_ar_plane_center, m_rotation_m,
+ groove_depth, groove_width, groove_flaps_angle, groove_angle, groove_depth_tolerance, groove_width_tolerance);
m_start_dragging_m = m_rotation_m;
m_transformed_bounding_box = transformed_bounding_box(m_ar_plane_center, m_rotation_m);
set_center_pos(m_ar_plane_center);
+ if (m_mode != mode)
+ switch_to_mode(mode);
+ else if (CutMode(m_mode) == CutMode::cutTongueAndGroove) {
+ if (!is_approx(m_groove.depth , groove_depth) ||
+ !is_approx(m_groove.width , groove_width) ||
+ !is_approx(m_groove.flaps_angle , groove_flaps_angle) ||
+ !is_approx(m_groove.angle , groove_angle) ||
+ !is_approx(m_groove.depth_tolerance, groove_depth_tolerance) ||
+ !is_approx(m_groove.width_tolerance, groove_width_tolerance) )
+ {
+ m_groove.depth = groove_depth;
+ m_groove.width = groove_width;
+ m_groove.flaps_angle = groove_flaps_angle;
+ m_groove.angle = groove_angle;
+ m_groove.depth_tolerance= groove_depth_tolerance;
+ m_groove.width_tolerance= groove_width_tolerance;
+ update_plane_model();
+ }
+ reset_cut_by_contours();
+ }
+
m_parent.request_extra_frame();
}
void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const
{
ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,
- m_ar_plane_center, m_start_dragging_m);
+ m_ar_plane_center, m_start_dragging_m,
+ m_groove.depth, m_groove.width, m_groove.flaps_angle, m_groove.angle, m_groove.depth_tolerance, m_groove.width_tolerance);
}
std::string GLGizmoCut3D::on_get_name() const
@@ -854,11 +1235,18 @@ std::string GLGizmoCut3D::on_get_name() const
return _u8L("Cut");
}
+void GLGizmoCut3D::apply_color_clip_plane_colors()
+{
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove)
+ m_parent.set_color_clip_plane_colors({ CUT_PLANE_DEF_COLOR , CUT_PLANE_DEF_COLOR });
+ else
+ m_parent.set_color_clip_plane_colors({ UPPER_PART_COLOR , LOWER_PART_COLOR });
+}
+
void GLGizmoCut3D::on_set_state()
{
if (m_state == On) {
m_parent.set_use_color_clip_plane(true);
- m_parent.set_color_clip_plane_colors({ UPPER_PART_COLOR , LOWER_PART_COLOR });
update_bb();
m_connectors_editing = !m_selected.empty();
@@ -891,7 +1279,9 @@ void GLGizmoCut3D::on_set_state()
void GLGizmoCut3D::on_register_raycasters_for_picking()
{
- assert(m_raycasters.empty());
+ // assert(m_raycasters.empty());
+ if (!m_raycasters.empty())
+ on_unregister_raycasters_for_picking();
// the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account
m_parent.set_raycaster_gizmos_on_top(true);
@@ -913,7 +1303,19 @@ void GLGizmoCut3D::on_register_raycasters_for_picking()
m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Z, *m_sphere.mesh_raycaster, Transform3d::Identity()));
- m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlane, *m_plane.mesh_raycaster, Transform3d::Identity()));
+ m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::FallbackGizmo, CutPlane, *m_plane.mesh_raycaster, Transform3d::Identity()));
+
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove) {
+ m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_sphere.mesh_raycaster, Transform3d::Identity()));
+ m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity()));
+ m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity()));
+
+ m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cube.mesh_raycaster, Transform3d::Identity()));
+ m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cone.mesh_raycaster, Transform3d::Identity()));
+
+ m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cube.mesh_raycaster, Transform3d::Identity()));
+ m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cone.mesh_raycaster, Transform3d::Identity()));
+ }
}
update_raycasters_for_picking_transform();
@@ -922,6 +1324,7 @@ void GLGizmoCut3D::on_register_raycasters_for_picking()
void GLGizmoCut3D::on_unregister_raycasters_for_picking()
{
m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo);
+ m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::FallbackGizmo);
m_raycasters.clear();
// the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account
m_parent.set_raycaster_gizmos_on_top(false);
@@ -1005,13 +1408,52 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform()
offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len);
m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale));
- offset = 1.25 * size * Vec3d::UnitZ();
m_raycasters[id++]->set_transform(trafo * translation_transform(m_grabber_connection_len * Vec3d::UnitZ()) * scale_transform(size));
m_raycasters[id++]->set_transform(trafo);
+
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove) {
+
+ double grabber_y_shift = -1.75 * m_grabber_connection_len;
+
+ m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_y_shift * Vec3d::UnitY()) * scale_transform(size));
+
+ offset = Vec3d(1.25 * size, grabber_y_shift, 0.0);
+ m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale));
+ offset = Vec3d(-1.25 * size, grabber_y_shift, 0.0);
+ m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale));
+
+ const double xy_connection_len = 0.75 * m_grabber_connection_len;
+ const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size);
+
+ offset = xy_connection_len * Vec3d::UnitX() - 0.5 * size * Vec3d::Ones();
+ m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * scale_transform(size));
+ offset = (size + xy_connection_len) * Vec3d::UnitX();
+ m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale));
+
+ if (m_groove.angle > 0.0f) {
+ offset = xy_connection_len * Vec3d::UnitY() - 0.5 * size * Vec3d::Ones();
+ m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * scale_transform(size));
+ offset = (size + xy_connection_len) * Vec3d::UnitY();
+ m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale));
+ }
+ else {
+ // discard transformation for CutPlaneYMove grabbers
+ m_raycasters[id++]->set_transform(Transform3d::Identity());
+ m_raycasters[id++]->set_transform(Transform3d::Identity());
+ }
+ }
}
}
+void GLGizmoCut3D::update_plane_model()
+{
+ m_plane.reset();
+ on_unregister_raycasters_for_picking();
+
+ init_picking_models();
+}
+
void GLGizmoCut3D::on_set_hover_id()
{
}
@@ -1060,8 +1502,8 @@ Vec3d GLGizmoCut3D::mouse_position_in_local_plane(GrabberID axis, const Linef3&
m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ()));
break;
}
- default:
case Z:
+ default:
{
// no rotation applied
break;
@@ -1074,17 +1516,21 @@ Vec3d GLGizmoCut3D::mouse_position_in_local_plane(GrabberID axis, const Linef3&
return transform(mouse_ray, m).intersect_plane(0.0);
}
-void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data)
+void GLGizmoCut3D::dragging_grabber_move(const GLGizmoBase::UpdateData &data)
{
- const Vec3d grabber_init_pos = (m_hover_id == CutPlane ? 0. : m_grabber_connection_len) * Vec3d::UnitZ();
- const Vec3d starting_drag_position = translation_transform(m_plane_center) * m_rotation_m * grabber_init_pos;
- double projection = 0.0;
+ Vec3d starting_drag_position;
+ if (m_hover_id == Z)
+ starting_drag_position = translation_transform(m_plane_center) * m_rotation_m * (m_grabber_connection_len * Vec3d::UnitZ());
+ else
+ starting_drag_position = m_cut_plane_start_move_pos;
- Vec3d starting_vec = m_rotation_m * Vec3d::UnitZ();
+ double projection = 0.0;
+
+ Vec3d starting_vec = m_rotation_m * (m_hover_id == CutPlaneXMove ? Vec3d::UnitX() : m_hover_id == CutPlaneYMove ? Vec3d::UnitY() : Vec3d::UnitZ());
if (starting_vec.norm() != 0.0) {
const Vec3d mouse_dir = data.mouse_ray.unit_vector();
- // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
- // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
+ // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing through the starting position
+ // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebraic form
// in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
const Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) * mouse_dir;
@@ -1108,7 +1554,7 @@ void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data)
m_was_cut_plane_dragged = true;
}
-void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data)
+void GLGizmoCut3D::dragging_grabber_rotation(const GLGizmoBase::UpdateData &data)
{
const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane((GrabberID)m_hover_id, data.mouse_ray));
@@ -1135,14 +1581,14 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data)
if (is_approx(theta, two_pi))
theta = 0.0;
- if (m_hover_id == X)
+ if (m_hover_id != Y)
theta += 0.5 * PI;
if (!is_approx(theta, 0.0))
reset_cut_by_contours();
Vec3d rotation = Vec3d::Zero();
- rotation[m_hover_id] = theta;
+ rotation[m_hover_id == CutPlaneZRotation ? Z : m_hover_id] = theta;
const Transform3d rotation_tmp = m_start_dragging_m * rotation_transform(rotation);
const bool update_tbb = !m_rotation_m.rotation().isApprox(rotation_tmp.rotation());
@@ -1175,13 +1621,16 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data)
{
if (m_hover_id < 0)
return;
- if (m_hover_id == Z || m_hover_id == CutPlane)
- dragging_grabber_z(data);
- else if (m_hover_id == X || m_hover_id == Y)
- dragging_grabber_xy(data);
+ if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove || m_hover_id == CutPlaneYMove)
+ dragging_grabber_move(data);
+ else if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation)
+ dragging_grabber_rotation(data);
else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual)
dragging_connector(data);
check_and_update_connectors_state();
+
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove)
+ reset_cut_by_contours();
}
void GLGizmoCut3D::on_start_dragging()
@@ -1190,23 +1639,26 @@ void GLGizmoCut3D::on_start_dragging()
if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual)
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move connector"), UndoRedo::SnapshotType::GizmoAction);
- if (m_hover_id == X || m_hover_id == Y)
+ if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation)
m_start_dragging_m = m_rotation_m;
}
void GLGizmoCut3D::on_stop_dragging()
{
- if (m_hover_id == X || m_hover_id == Y) {
+ if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) {
m_angle_arc.reset();
m_angle = 0.0;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction);
m_start_dragging_m = m_rotation_m;
}
- else if (m_hover_id == Z || m_hover_id == CutPlane) {
+ else if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove|| m_hover_id == CutPlaneYMove) {
if (m_was_cut_plane_dragged)
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction);
m_ar_plane_center = m_plane_center;
}
+
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove)
+ reset_cut_by_contours();
//check_and_update_connectors_state();
}
@@ -1220,7 +1672,8 @@ void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=fa
bool can_set_center_pos = false;
{
- if (tbb.max.z() > -.5 && tbb.min.z() < .5)
+ double limit_val = /*CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.5 * double(m_groove.depth) : */0.5;
+ if (tbb.max.z() > -limit_val && tbb.min.z() < limit_val)
can_set_center_pos = true;
else {
const double old_dist = (m_bb_center - m_plane_center).norm();
@@ -1286,8 +1739,14 @@ void GLGizmoCut3D::update_bb()
m_bounding_box = box;
+ // check, if mode is set to Planar, when object has a connectors
+ if (const int object_idx = m_parent.get_selection().get_object_idx();
+ object_idx >= 0 && !wxGetApp().plater()->model().objects[object_idx]->cut_connectors.empty())
+ m_mode = size_t(CutMode::cutPlanar);
+
invalidate_cut_plane();
reset_cut_by_contours();
+ apply_color_clip_plane_colors();
m_max_pos = box.max;
m_min_pos = box.min;
@@ -1298,6 +1757,8 @@ void GLGizmoCut3D::update_bb()
else
set_center_pos(m_bb_center);
+ m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f;
+
m_radius = box.radius();
m_grabber_connection_len = 0.5 * m_radius;// std::min(0.75 * m_radius, 35.0);
m_grabber_radius = m_grabber_connection_len * 0.85;
@@ -1307,9 +1768,15 @@ void GLGizmoCut3D::update_bb()
m_snap_fine_in_radius = m_grabber_connection_len * 0.85;
m_snap_fine_out_radius = m_grabber_connection_len * 1.15;
+ // input params for cut with tongue and groove
+ m_groove.depth = m_groove.depth_init = std::max(1.f , 0.5f * float(get_grabber_mean_size(m_bounding_box)));
+ m_groove.width = m_groove.width_init = 4.0f * m_groove.depth;
+ m_groove.flaps_angle = m_groove.flaps_angle_init = float(PI) / 3.f;
+ m_groove.angle = m_groove.angle_init = 0.f;
m_plane.reset();
m_cone.reset();
m_sphere.reset();
+ m_cube.reset();
m_grabber_connection.reset();
m_circle.reset();
m_scale.reset();
@@ -1337,10 +1804,17 @@ void GLGizmoCut3D::init_picking_models()
m_sphere.model.init_from(its);
m_sphere.mesh_raycaster = std::make_unique(std::make_shared(std::move(its)));
}
+ if (!m_cube.model.is_initialized()) {
+ indexed_triangle_set its = its_make_cube(1., 1., 1.);
+ m_cube.model.init_from(its);
+ m_cube.mesh_raycaster = std::make_unique(std::make_shared(std::move(its)));
+ }
if (!m_plane.model.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) {
const double cp_width = 0.02 * get_grabber_mean_size(m_bounding_box);
- indexed_triangle_set its = its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4);
+ indexed_triangle_set its = m_mode == size_t(CutMode::cutTongueAndGroove) ? its_make_groove_plane() :
+ its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4);
+
m_plane.model.init_from(its);
m_plane.mesh_raycaster = std::make_unique(std::make_shared(std::move(its)));
}
@@ -1383,20 +1857,25 @@ void GLGizmoCut3D::render_clipper_cut()
::glEnable(GL_DEPTH_TEST);
}
-
-GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc)
+void GLGizmoCut3D::PartSelection::add_object(const ModelObject* object)
{
m_model = Model();
- m_model.add_object(*mo);
- ModelObjectPtrs cut_part_ptrs = m_model.objects.front()->cut(instance_idx_in, cut_matrix,
- ModelObjectCutAttribute::KeepUpper |
- ModelObjectCutAttribute::KeepLower |
- ModelObjectCutAttribute::KeepAsParts);
- assert(cut_part_ptrs.size() == 1);
- m_model = Model();
- m_model.add_object(*cut_part_ptrs.front());
+ m_model.add_object(*object);
- m_instance_idx = instance_idx_in;
+ const double sla_shift_z = wxGetApp().plater()->canvas3D()->get_selection().get_first_volume()->get_sla_shift_z();
+ if (!is_approx(sla_shift_z, 0.)) {
+ Vec3d inst_offset = model_object()->instances[m_instance_idx]->get_offset();
+ inst_offset[Z] += sla_shift_z;
+ model_object()->instances[m_instance_idx]->set_offset(inst_offset);
+ }
+}
+
+
+GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc)
+ : m_instance_idx(instance_idx_in)
+{
+ Cut cut(mo, instance_idx_in, cut_matrix);
+ add_object(cut.perform_with_plane().front());
const ModelVolumePtrs& volumes = model_object()->volumes;
@@ -1467,6 +1946,26 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor
m_valid = true;
}
+// In CutMode::cutTongueAndGroove we use PartSelection just for rendering
+GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* object, int instance_idx_in)
+ : m_instance_idx (instance_idx_in)
+{
+ add_object(object);
+
+ m_parts.clear();
+
+ for (const ModelVolume* volume : object->volumes) {
+ assert(volume != nullptr);
+ m_parts.emplace_back(Part{ GLModel(), MeshRaycaster(volume->mesh()), true, !volume->is_model_part() });
+ m_parts.back().glmodel.init_from(volume->mesh());
+
+ // Now check whether this part is below or above the plane.
+ m_parts.back().selected = volume->is_from_upper();
+ }
+
+ m_valid = true;
+}
+
void GLGizmoCut3D::PartSelection::render(const Vec3d* normal, GLModel& sphere_model)
{
if (! valid())
@@ -1563,6 +2062,16 @@ bool GLGizmoCut3D::PartSelection::is_one_object() const
});
}
+std::vector GLGizmoCut3D::PartSelection::get_cut_parts()
+{
+ std::vector parts;
+
+ for (const auto& part : m_parts)
+ parts.push_back({part.selected, part.is_modifier});
+
+ return parts;
+}
+
void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos)
{
@@ -1617,6 +2126,8 @@ void GLGizmoCut3D::on_render()
m_c->selection_info()->set_use_shift(true);
}
+ // check objects visibility
+ toggle_model_objects_visibility();
update_clipper();
@@ -1648,9 +2159,7 @@ void GLGizmoCut3D::render_debug_input_window(float x)
return;
m_imgui->begin(wxString("DEBUG"));
- ImVec2 pos = ImGui::GetWindowPos();
- pos.x = x;
- ImGui::SetWindowPos(pos, ImGuiCond_Always);
+ m_imgui->end();
/*
static bool hide_clipped = false;
static bool fill_cut = false;
@@ -1775,22 +2284,30 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors)
m_imgui->text(m_labels_map["Type"]);
bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug);
type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel);
+ type_changed |= render_connect_type_radio_button(CutConnectorType::Snap);
if (type_changed)
apply_selected_connectors([this, &connectors] (size_t idx) { connectors[idx].attribs.type = CutConnectorType(m_connector_type); });
- m_imgui->disabled_begin(m_connector_type == CutConnectorType::Dowel);
- if (type_changed && m_connector_type == CutConnectorType::Dowel) {
- m_connector_style = int(CutConnectorStyle::Prism);
- apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); });
- }
- if (render_combo(m_labels_map["Style"], m_connector_styles, m_connector_style))
- apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); });
+ m_imgui->disabled_begin(m_connector_type != CutConnectorType::Plug);
+ if (type_changed && m_connector_type == CutConnectorType::Dowel) {
+ m_connector_style = int(CutConnectorStyle::Prism);
+ apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); });
+ }
+ if (render_combo(m_labels_map["Style"], m_connector_styles, m_connector_style))
+ apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); });
m_imgui->disabled_end();
- if (render_combo(m_labels_map["Shape"], m_connector_shapes, m_connector_shape_id))
- apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); });
+ m_imgui->disabled_begin(m_connector_type == CutConnectorType::Snap);
+ if (type_changed && m_connector_type == CutConnectorType::Snap) {
+ m_connector_shape_id = int(CutConnectorShape::Circle);
+ apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); });
+ }
+ if (render_combo(m_labels_map["Shape"], m_connector_shapes, m_connector_shape_id))
+ apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); });
+ m_imgui->disabled_end();
- if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance))
+ const float depth_min_value = m_connector_type == CutConnectorType::Snap ? m_connector_size : -0.1f;
+ if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value))
apply_selected_connectors([this, &connectors](size_t idx) {
if (m_connector_depth_ratio > 0)
connectors[idx].height = m_connector_depth_ratio;
@@ -1806,6 +2323,45 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors)
connectors[idx].radius_tolerance = 0.5f * m_connector_size_tolerance;
});
+ if (m_connector_type == CutConnectorType::Snap) {
+
+ const std::string format = "%.0f %%";
+
+ bool is_changed = false;
+ {
+ const std::string label = _u8L("Bulge");
+ ImGuiWrapper::text(label);
+
+ ImGui::SameLine(m_label_width);
+ ImGui::PushItemWidth(m_control_width * 0.7f);
+
+ float val = m_snap_bulge_proportion *100.f;
+ if (m_imgui->slider_float(("##snap_" + label).c_str(), &val, 5.f, 100.f * m_snap_space_proportion, format.c_str(), 1.f, true, _u8L("Bulge proportion related to radius"))) {
+ m_snap_bulge_proportion = val * 0.01f;
+ is_changed = true;
+ }
+ }
+
+ {
+ const std::string label = _u8L("Space");
+ ImGuiWrapper::text(label);
+
+ ImGui::SameLine(m_label_width);
+ ImGui::PushItemWidth(m_control_width * 0.7f);
+
+ float val = m_snap_space_proportion *100.f;
+ if (m_imgui->slider_float(("##snap_" + label).c_str(), &val, 10.f, 50.f, format.c_str(), 1.f, true, _u8L("Space proportion related to radius"))) {
+ m_snap_space_proportion = val * 0.01f;
+ is_changed = true;
+ }
+ }
+
+ if (is_changed) {
+ update_connector_shape();
+ update_raycasters_for_picking();
+ }
+ }
+
ImGui::Separator();
if (m_imgui->button(_L("Confirm connectors"))) {
@@ -1882,30 +2438,50 @@ void GLGizmoCut3D::flip_cut_plane()
update_clipper();
m_part_selection.turn_over_selection();
+
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove)
+ reset_cut_by_contours();
}
void GLGizmoCut3D::reset_cut_by_contours()
{
m_part_selection = PartSelection();
- const Selection& selection = m_parent.get_selection();
- const ModelObjectPtrs& model_objects = selection.get_model()->objects;
- m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx());
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove) {
+ if (m_dragging || m_groove_editing || !has_valid_groove())
+ return;
+ process_contours();
+ }
+ else
+ toggle_model_objects_visibility();
}
void GLGizmoCut3D::process_contours()
{
- reset_cut_by_contours();
-
const Selection& selection = m_parent.get_selection();
const ModelObjectPtrs& model_objects = selection.get_model()->objects;
- wxBusyCursor wait;
const int instance_idx = selection.get_instance_idx();
+ if (instance_idx < 0)
+ return;
const int object_idx = selection.get_object_idx();
- m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper());
- m_parent.toggle_model_objects_visibility(false);
+ wxBusyCursor wait;
+
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove) {
+ if (has_valid_groove()) {
+ Cut cut(model_objects[object_idx], instance_idx, get_cut_matrix(selection));
+ const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, true);
+ if (!new_objects.empty())
+ m_part_selection = PartSelection(new_objects.front(), instance_idx);
+ }
+ }
+ else {
+ reset_cut_by_contours();
+ m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper());
+ }
+
+ toggle_model_objects_visibility();
}
void GLGizmoCut3D::render_flip_plane_button(bool disable_pred /*=false*/)
@@ -1950,12 +2526,100 @@ void GLGizmoCut3D::render_color_marker(float size, const ImU32& color)
ImGuiWrapper::text(" ");
}
+void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance)
+{
+ bool is_changed{false};
+
+ float val = in_val;
+ float tolerance = in_tolerance;
+ if (render_slider_double_input(label, val, tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) {
+ if (m_imgui->get_last_slider_status().can_take_snapshot) {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction);
+ m_imgui->get_last_slider_status().invalidate_snapshot();
+ m_groove_editing = true;
+ }
+ in_val = val;
+ in_tolerance = tolerance;
+ is_changed = true;
+ }
+
+ ImGui::SameLine();
+
+ m_imgui->disabled_begin(is_approx(in_val, init_val) && is_approx(in_tolerance, 0.1f));
+ const std::string act_name = _u8L("Reset");
+ if (render_reset_button(("##groove_" + label + act_name).c_str(), act_name)) {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction);
+ in_val = init_val;
+ in_tolerance = 0.1f;
+ is_changed = true;
+ }
+ m_imgui->disabled_end();
+
+ if (is_changed) {
+ update_plane_model();
+ reset_cut_by_contours();
+ }
+
+ if (m_is_slider_editing_done) {
+ m_groove_editing = false;
+ reset_cut_by_contours();
+ }
+}
+
+void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val)
+{
+ bool is_changed{ false };
+
+ ImGuiWrapper::text(label);
+
+ ImGui::SameLine(m_label_width);
+ ImGui::PushItemWidth(m_control_width * 0.7f);
+
+ float val = rad2deg(in_val);
+ const float old_val = val;
+
+ const std::string format = "%.0f " + _u8L("°");
+ m_imgui->slider_float(("##groove_" + label).c_str(), &val, min_val, max_val, format.c_str(), 1.f, true, from_u8(label));
+
+ m_is_slider_editing_done |= m_imgui->get_last_slider_status().deactivated_after_edit;
+ if (!is_approx(old_val, val)) {
+ if (m_imgui->get_last_slider_status().can_take_snapshot) {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction);
+ m_imgui->get_last_slider_status().invalidate_snapshot();
+ m_groove_editing = true;
+ }
+ in_val = deg2rad(val);
+ is_changed = true;
+ }
+
+ ImGui::SameLine();
+
+ m_imgui->disabled_begin(is_approx(in_val, init_val));
+ const std::string act_name = _u8L("Reset");
+ if (render_reset_button(("##groove_" + label + act_name).c_str(), act_name)) {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction);
+ in_val = init_val;
+ is_changed = true;
+ }
+ m_imgui->disabled_end();
+
+ if (is_changed) {
+ update_plane_model();
+ reset_cut_by_contours();
+ }
+
+ if (m_is_slider_editing_done) {
+ m_groove_editing = false;
+ reset_cut_by_contours();
+ }
+}
+
+
void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
{
- // WIP : cut plane mode
- // render_combo(_u8L("Mode"), m_modes, m_mode);
-
- if (m_mode == size_t(CutMode::cutPlanar)) {
+// if (m_mode == size_t(CutMode::cutPlanar)) {
+ CutMode mode = CutMode(m_mode);
+ if (mode == CutMode::cutPlanar || mode == CutMode::cutTongueAndGroove) {
ImGui::AlignTextToFramePadding();
ImGuiWrapper::text(wxString(ImGui::InfoMarkerSmall));
ImGui::SameLine();
@@ -1963,6 +2627,13 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
get_wraped_wxString(_L("Hold SHIFT key to draw a cut line"), 40));
ImGui::Separator();
+ const bool has_connectors = !connectors.empty();
+
+ m_imgui->disabled_begin(has_connectors);
+ if (render_cut_mode_combo())
+ mode = CutMode(m_mode);
+ m_imgui->disabled_end();
+
render_build_size();
ImGui::AlignTextToFramePadding();
@@ -1971,8 +2642,6 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
render_move_center_input(Z);
ImGui::SameLine();
- const bool has_connectors = !connectors.empty();
-
const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center);
m_imgui->disabled_begin(is_cut_plane_init);
wxString act_name = _L("Reset cutting plane");
@@ -1984,23 +2653,34 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
// render_flip_plane_button();
- add_vertical_scaled_interval(0.75f);
+ if (mode == CutMode::cutPlanar) {
+ add_vertical_scaled_interval(0.75f);
- m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object()));
- if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors")))
- set_connectors_editing(true);
- m_imgui->disabled_end();
+ m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object()));
+ if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors")))
+ set_connectors_editing(true);
+ m_imgui->disabled_end();
- ImGui::SameLine(1.5f * m_control_width);
+ ImGui::SameLine(1.5f * m_control_width);
- m_imgui->disabled_begin(is_cut_plane_init && !has_connectors);
- act_name = _L("Reset cut");
- if (m_imgui->button(act_name, _L("Reset cutting plane and remove connectors"))) {
- Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction);
- reset_cut_plane();
- reset_connectors();
- }
- m_imgui->disabled_end();
+ m_imgui->disabled_begin(is_cut_plane_init && !has_connectors);
+ act_name = _L("Reset cut");
+ if (m_imgui->button(act_name, _L("Reset cutting plane and remove connectors"))) {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction);
+ reset_cut_plane();
+ reset_connectors();
+ }
+ m_imgui->disabled_end();
+ }
+ else if (mode == CutMode::cutTongueAndGroove) {
+ m_is_slider_editing_done = false;
+ ImGui::Separator();
+ ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": ");
+ render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance);
+ render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance);
+ render_groove_angle_input(m_labels_map["Flaps Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f);
+ render_groove_angle_input(m_labels_map["Groove Angle"], m_groove.angle, m_groove.angle_init, 0.f, 15.f);
+ }
ImGui::Separator();
@@ -2069,7 +2749,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
add_vertical_scaled_interval(0.75f);
- m_imgui->disabled_begin(has_connectors || m_part_selection.valid());
+ m_imgui->disabled_begin(has_connectors || m_part_selection.valid() || mode == CutMode::cutTongueAndGroove);
ImGuiWrapper::text(_L("Cut into") + ":");
if (m_part_selection.valid())
@@ -2094,7 +2774,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
ImGui::Separator();
- m_imgui->disabled_begin(!m_is_contour_changed && !can_perform_cut());
+ m_imgui->disabled_begin(!can_perform_cut());
if(m_imgui->button(_L("Perform cut")))
perform_cut(m_parent.get_selection());
m_imgui->disabled_end();
@@ -2190,8 +2870,6 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors)
void GLGizmoCut3D::render_input_window_warning() const
{
- if (m_is_contour_changed)
- return;
if (! m_invalid_connectors_idxs.empty()) {
wxString out = wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected") + ":";
if (m_info_stats.outside_cut_contour > size_t(0))
@@ -2208,6 +2886,8 @@ void GLGizmoCut3D::render_input_window_warning() const
m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Select at least one object to keep after cutting."));
if (!has_valid_contour())
m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Cut plane is placed out of object"));
+ else if (!has_valid_groove())
+ m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Cut plane with groove is invalid"));
}
void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit)
@@ -2317,6 +2997,8 @@ void GLGizmoCut3D::check_and_update_connectors_state()
{
m_info_stats.invalidate();
m_invalid_connectors_idxs.clear();
+ if (CutMode(m_mode) != CutMode::cutPlanar)
+ return;
const ModelObject* mo = m_c->selection_info()->model_object();
auto inst_id = m_c->selection_info()->get_active_instance();
if (inst_id < 0)
@@ -2334,11 +3016,31 @@ void GLGizmoCut3D::check_and_update_connectors_state()
}
}
+void GLGizmoCut3D::toggle_model_objects_visibility()
+{
+ bool has_active_volume = false;
+ std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume);
+ for (const auto raycaster : *raycasters)
+ if (raycaster->is_active()) {
+ has_active_volume = true;
+ break;
+ }
+
+ if (m_part_selection.valid() && has_active_volume)
+ m_parent.toggle_model_objects_visibility(false);
+ else if (!m_part_selection.valid() && !has_active_volume) {
+ const Selection& selection = m_parent.get_selection();
+ const ModelObjectPtrs& model_objects = selection.get_model()->objects;
+ m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx());
+ }
+}
+
void GLGizmoCut3D::render_connectors()
{
::glEnable(GL_DEPTH_TEST);
- if (m_is_contour_changed || cut_line_processing() ||
+ if (cut_line_processing() ||
+ CutMode(m_mode) != CutMode::cutPlanar ||
m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info())
return;
@@ -2414,10 +3116,51 @@ bool GLGizmoCut3D::can_perform_cut() const
{
if (! m_invalid_connectors_idxs.empty() || (!m_keep_upper && !m_keep_lower) || m_connectors_editing)
return false;
+
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove)
+ return has_valid_groove();
+
if (m_part_selection.valid())
return ! m_part_selection.is_one_object();
- return true;// has_valid_contour();
+ return true;
+}
+
+bool GLGizmoCut3D::has_valid_groove() const
+{
+ if (CutMode(m_mode) != CutMode::cutTongueAndGroove)
+ return true;
+
+ const float flaps_width = -2.f * m_groove.depth / tan(m_groove.flaps_angle);
+ if (flaps_width > m_groove.width)
+ return false;
+
+ const Selection& selection = m_parent.get_selection();
+ const auto&list = selection.get_volume_idxs();
+ // is more volumes selected?
+ if (list.empty())
+ return false;
+
+ const Transform3d cp_matrix = translation_transform(m_plane_center) * m_rotation_m;
+
+ for (size_t id = 0; id < m_groove_vertices.size(); id += 2) {
+ const Vec3d beg = cp_matrix * m_groove_vertices[id];
+ const Vec3d end = cp_matrix * m_groove_vertices[id + 1];
+
+ bool intersection = false;
+ for (const unsigned int volume_idx : list) {
+ const GLVolume* glvol = selection.get_volume(volume_idx);
+ if (!glvol->is_modifier &&
+ glvol->mesh_raycaster->intersects_line(beg, end - beg, glvol->world_matrix())) {
+ intersection = true;
+ break;
+ }
+ }
+ if (!intersection)
+ return false;
+ }
+
+ return true;
}
bool GLGizmoCut3D::has_valid_contour() const
@@ -2428,6 +3171,8 @@ bool GLGizmoCut3D::has_valid_contour() const
void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, int &dowels_count)
{
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove)
+ return;
if (m_connector_mode == CutConnectorMode::Manual) {
clear_selection();
@@ -2444,7 +3189,7 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, int &dowels_count)
connector.pos += m_cut_normal * 0.5 * double(connector.height);
}
}
- mo->apply_cut_connectors(_u8L("Connector"));
+ apply_cut_connectors(mo, _u8L("Connector"));
}
}
@@ -2518,7 +3263,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
// This shall delete the part selection class and deallocate the memory.
ScopeGuard part_selection_killer([this]() { m_part_selection = PartSelection(); });
- const bool cut_by_contour = m_part_selection.valid();
+ const bool cut_with_groove = CutMode(m_mode) == CutMode::cutTongueAndGroove;
+ const bool cut_by_contour = !cut_with_groove && m_part_selection.valid();
+
ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object() : nullptr;
if (cut_mo)
cut_mo->cut_connectors = mo->cut_connectors;
@@ -2532,8 +3279,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
wxBusyCursor wait;
- const Transform3d cut_matrix = get_cut_matrix(selection);
-
ModelObjectCutAttributes attributes = only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) |
only_if(has_connectors ? false : m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) |
@@ -2542,129 +3287,20 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) |
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) |
only_if(dowels_count > 0, ModelObjectCutAttribute::CreateDowels) |
- only_if(!has_connectors && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo);
+ only_if(!has_connectors && !cut_with_groove && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo);
// update cut_id for the cut object in respect to the attributes
update_object_cut_id(cut_mo->cut_id, attributes, dowels_count);
- ModelObjectPtrs cut_object_ptrs;
- if (cut_by_contour) {
- // Clone the object to duplicate instances, materials etc.
- ModelObject* upper{ nullptr };
- if (m_keep_upper) cut_mo->clone_for_cut(&upper);
- ModelObject* lower{ nullptr };
- if (m_keep_lower) cut_mo->clone_for_cut(&lower);
-
- auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower) {
- if (upper && !upper->volumes.empty()) {
- ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper);
- cut_objects.push_back(upper);
- }
- if (lower && !lower->volumes.empty()) {
- ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower);
- cut_objects.push_back(lower);
- }
- };
-
- const size_t cut_parts_cnt = m_part_selection.parts().size();
- bool has_modifiers = false;
-
- // Distribute SolidParts to the Upper/Lower object
- for (size_t id = 0; id < cut_parts_cnt; ++id) {
- if (m_part_selection.parts()[id].is_modifier)
- has_modifiers = true; // modifiers will be added later to the related parts
- else if (ModelObject* obj = (m_part_selection.parts()[id].selected ? upper : lower))
- obj->add_volume(*(cut_mo->volumes[id]));
- }
-
- if (has_modifiers) {
- // Distribute Modifiers to the Upper/Lower object
- auto upper_bb = upper ? upper->instance_bounding_box(instance_idx) : BoundingBoxf3();
- auto lower_bb = lower ? lower->instance_bounding_box(instance_idx) : BoundingBoxf3();
- const Transform3d inst_matrix = cut_mo->instances[instance_idx]->get_transformation().get_matrix();
-
- for (size_t id = 0; id < cut_parts_cnt; ++id)
- if (m_part_selection.parts()[id].is_modifier) {
- ModelVolume* vol = cut_mo->volumes[id];
- auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
- // Don't add modifiers which are not intersecting with solid parts
- if (upper_bb.intersects(bb))
- upper->add_volume(*vol);
- if (lower_bb.intersects(bb))
- lower->add_volume(*vol);
- }
- }
-
- ModelVolumePtrs& volumes = cut_mo->volumes;
- if (volumes.size() == cut_parts_cnt) {
- // Means that object is cut without connectors
-
- // Just add Upper and Lower objects to cut_object_ptrs
- add_cut_objects(cut_object_ptrs, upper, lower);
- }
- else if (volumes.size() > cut_parts_cnt) {
- // Means that object is cut with connectors
-
- // All volumes are distributed to Upper / Lower object,
- // So we don’t need them anymore
- for (size_t id = 0; id < cut_parts_cnt; id++)
- delete *(volumes.begin() + id);
- volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
-
- // Perform cut just to get connectors
- const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes);
- assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
-
- // Connectors from upper object
- for (const ModelVolume* volume : cut_connectors_obj[0]->volumes)
- upper->add_volume(*volume, volume->type());
-
- // Connectors from lower object
- for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
- lower->add_volume(*volume, volume->type());
-
- // Add Upper and Lower objects to cut_object_ptrs
- add_cut_objects(cut_object_ptrs, upper, lower);
-
- // Add Dowel-connectors as separate objects to cut_object_ptrs
- if (cut_connectors_obj.size() >= 3)
- for (size_t id = 2; id < cut_connectors_obj.size(); id++)
- cut_object_ptrs.push_back(cut_connectors_obj[id]);
- }
-
- // Now merge all model parts together:
- {
- for (ModelObject* mo : cut_object_ptrs) {
- TriangleMesh mesh;
- // Merge all SolidPart but not Connectors
- for (const ModelVolume* mv : mo->volumes) {
- if (mv->is_model_part() && !mv->is_cut_connector()) {
- TriangleMesh m = mv->mesh();
- m.transform(mv->get_matrix());
- mesh.merge(m);
- }
- }
- if (! mesh.empty()) {
- ModelVolume* new_volume = mo->add_volume(mesh);
- new_volume->name = mo->name;
- // Delete all merged SolidPart but not Connectors
- for (int i=int(mo->volumes.size())-2; i>=0; --i) {
- const ModelVolume* mv = mo->volumes[i];
- if (mv->is_model_part() && !mv->is_cut_connector())
- mo->delete_volume(i);
- }
- }
- }
- }
- }
- else
- cut_object_ptrs = cut_mo->cut(instance_idx, cut_matrix, attributes);
-
+ Cut cut(cut_mo, instance_idx, get_cut_matrix(selection), attributes);
+ const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(m_part_selection.get_cut_parts(), dowels_count):
+ cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m) :
+ cut.perform_with_plane();
// save cut_id to post update synchronization
const CutObjectBase cut_id = cut_mo->cut_id;
// update cut results on plater and in the model
- plater->cut(object_idx, cut_object_ptrs);
+ plater->apply_cut_object_to_model(object_idx, new_objects);
synchronize_model_after_cut(plater->model(), cut_id);
}
@@ -2672,7 +3308,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
// Return false if no intersection was found, true otherwise.
-bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour/* = true*/)
+bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world, bool respect_contours/* = true*/)
{
const float sla_shift = m_c->selection_info()->get_sla_shift();
@@ -2709,6 +3345,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& po
}
}*/
+ if (respect_contours)
{
// Do not react to clicks outside a contour (or inside a contour that is ignored)
int cont_id = m_c->object_clipper()->is_projection_inside_cut(hit);
@@ -2750,13 +3387,15 @@ void GLGizmoCut3D::reset_connectors()
void GLGizmoCut3D::init_connector_shapes()
{
- for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug})
+ for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug, CutConnectorType::Snap})
for (const CutConnectorStyle& style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prism}) {
if (type == CutConnectorType::Dowel && style == CutConnectorStyle::Frustum)
continue;
for (const CutConnectorShape& shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) {
+ if (type == CutConnectorType::Snap && shape != CutConnectorShape::Circle)
+ continue;
const CutConnectorAttributes attribs = { type, style, shape };
- const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs);
+ indexed_triangle_set its = get_connector_mesh(attribs);
m_shapes[attribs].model.init_from(its);
m_shapes[attribs].mesh_raycaster = std::make_unique(std::make_shared(std::move(its)));
}
@@ -2767,9 +3406,18 @@ void GLGizmoCut3D::update_connector_shape()
{
CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) };
- const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs);
- m_connector_mesh.clear();
- m_connector_mesh = TriangleMesh(its);
+ if (m_connector_type == CutConnectorType::Snap) {
+ indexed_triangle_set its = get_connector_mesh(attribs);
+ m_shapes[attribs].reset();
+ m_shapes[attribs].model.init_from(its);
+ m_shapes[attribs].mesh_raycaster = std::make_unique(std::make_shared(std::move(its)));
+
+ //const indexed_triangle_set its = get_connector_mesh(attribs);
+ //m_connector_mesh.clear();
+ //m_connector_mesh = TriangleMesh(its);
+ }
+
+
}
bool GLGizmoCut3D::cut_line_processing() const
@@ -2800,6 +3448,8 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse
}
if (cut_line_processing()) {
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove)
+ m_groove_editing = true;
reset_cut_by_contours();
m_line_end = pt;
@@ -2831,6 +3481,11 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse
m_angle_arc.reset();
discard_cut_line_processing();
+
+ if (CutMode(m_mode) == CutMode::cutTongueAndGroove) {
+ m_groove_editing = false;
+ reset_cut_by_contours();
+ }
}
else if (action == SLAGizmoEventType::Moving)
this->set_dirty();
@@ -3026,5 +3681,69 @@ void GLGizmoCut3D::data_changed(bool is_serializing)
}
+
+
+indexed_triangle_set GLGizmoCut3D::get_connector_mesh(CutConnectorAttributes connector_attributes)
+{
+ indexed_triangle_set connector_mesh;
+
+ int sectorCount{ 1 };
+ switch (CutConnectorShape(connector_attributes.shape)) {
+ case CutConnectorShape::Triangle:
+ sectorCount = 3;
+ break;
+ case CutConnectorShape::Square:
+ sectorCount = 4;
+ break;
+ case CutConnectorShape::Circle:
+ sectorCount = 360;
+ break;
+ case CutConnectorShape::Hexagon:
+ sectorCount = 6;
+ break;
+ default:
+ break;
+ }
+
+ if (connector_attributes.type == CutConnectorType::Snap)
+ connector_mesh = its_make_snap(1.0, 1.0, m_snap_space_proportion, m_snap_bulge_proportion);
+ else if (connector_attributes.style == CutConnectorStyle::Prism)
+ connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
+ else if (connector_attributes.type == CutConnectorType::Plug)
+ connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount));
+ else
+ connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
+
+ return connector_mesh;
+}
+
+void GLGizmoCut3D::apply_cut_connectors(ModelObject* mo, const std::string& connector_name)
+{
+ if (mo->cut_connectors.empty())
+ return;
+
+ using namespace Geometry;
+
+ size_t connector_id = mo->cut_id.connectors_cnt();
+ for (const CutConnector& connector : mo->cut_connectors) {
+ TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs));
+ // Mesh will be centered when loading.
+ ModelVolume* new_volume = mo->add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME);
+
+ // Transform the new modifier to be aligned inside the instance
+ new_volume->set_transformation(translation_transform(connector.pos) * connector.rotation_m *
+ scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast()));
+
+ new_volume->cut_info = { connector.attribs.type, connector.radius_tolerance, connector.height_tolerance };
+ new_volume->name = connector_name + "-" + std::to_string(++connector_id);
+ }
+ mo->cut_id.increase_connectors_cnt(mo->cut_connectors.size());
+
+ // delete all connectors
+ mo->cut_connectors.clear();
+}
+
+
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
index 51174a51af..aa8ed04dd9 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
@@ -7,12 +7,14 @@
#include "slic3r/GUI/I18N.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Model.hpp"
+#include "libslic3r/CutUtils.hpp"
#include "imgui/imgui.h"
namespace Slic3r {
enum class CutConnectorType : int;
class ModelVolume;
+class GLShaderProgram;
struct CutConnectorAttributes;
namespace GUI {
@@ -29,6 +31,9 @@ class GLGizmoCut3D : public GLGizmoBase
Y,
Z,
CutPlane,
+ CutPlaneZRotation,
+ CutPlaneXMove,
+ CutPlaneYMove,
Count,
};
@@ -54,6 +59,7 @@ class GLGizmoCut3D : public GLGizmoBase
double m_radius{ 0.0 };
double m_grabber_radius{ 0.0 };
double m_grabber_connection_len{ 0.0 };
+ Vec3d m_cut_plane_start_move_pos {Vec3d::Zero()};
double m_snap_coarse_in_radius{ 0.0 };
double m_snap_coarse_out_radius{ 0.0 };
@@ -78,6 +84,7 @@ class GLGizmoCut3D : public GLGizmoBase
PickingModel m_plane;
PickingModel m_sphere;
PickingModel m_cone;
+ PickingModel m_cube;
std::map m_shapes;
std::vector> m_raycasters;
@@ -111,6 +118,16 @@ class GLGizmoCut3D : public GLGizmoBase
bool m_rotate_upper{ false };
bool m_rotate_lower{ false };
+ // Input params for cut with tongue and groove
+ Cut::Groove m_groove;
+ bool m_groove_editing { false };
+
+ bool m_is_slider_editing_done { false };
+
+ // Input params for cut with snaps
+ float m_snap_bulge_proportion{ 0.15f };
+ float m_snap_space_proportion{ 0.3f };
+
bool m_hide_cut_plane{ false };
bool m_connectors_editing{ false };
bool m_cut_plane_as_circle{ false };
@@ -127,7 +144,6 @@ class GLGizmoCut3D : public GLGizmoBase
float m_contour_width{ 0.4f };
float m_cut_plane_radius_koef{ 1.5f };
- bool m_is_contour_changed{ false };
float m_shortcut_label_width{ -1.f };
mutable std::vector m_selected; // which pins are currently selected
@@ -139,10 +155,14 @@ class GLGizmoCut3D : public GLGizmoBase
bool m_was_cut_plane_dragged { false };
bool m_was_contour_selected { false };
+ // Vertices of the groove used to detection if groove is valid
+ std::vector m_groove_vertices;
+
class PartSelection {
public:
PartSelection() = default;
PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc);
+ PartSelection(const ModelObject* mo, int instance_idx_in);
~PartSelection() { m_model.clear_objects(); }
struct Part {
@@ -161,6 +181,8 @@ class GLGizmoCut3D : public GLGizmoBase
const std::vector& parts() const { return m_parts; }
const std::vector* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); }
+ std::vector get_cut_parts();
+
private:
Model m_model;
int m_instance_idx;
@@ -171,6 +193,8 @@ class GLGizmoCut3D : public GLGizmoBase
std::vector m_contour_points; // Debugging
std::vector> m_debug_pts; // Debugging
+
+ void add_object(const ModelObject* object);
};
PartSelection m_part_selection;
@@ -180,7 +204,8 @@ class GLGizmoCut3D : public GLGizmoBase
enum class CutMode {
cutPlanar
- , cutGrig
+ , cutTongueAndGroove
+ //, cutGrig
//,cutRadial
//,cutModular
};
@@ -190,7 +215,7 @@ class GLGizmoCut3D : public GLGizmoBase
, Manual
};
-// std::vector m_modes;
+ std::vector m_modes;
size_t m_mode{ size_t(CutMode::cutPlanar) };
std::vector m_connector_modes;
@@ -215,7 +240,7 @@ public:
GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
std::string get_tooltip() const override;
- bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour = true);
+ bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_contours = true);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
bool is_in_editing_mode() const override { return m_connectors_editing; }
@@ -249,8 +274,8 @@ protected:
bool on_is_activable() const override;
bool on_is_selectable() const override;
Vec3d mouse_position_in_local_plane(GrabberID axis, const Linef3&mouse_ray) const;
- void dragging_grabber_z(const GLGizmoBase::UpdateData &data);
- void dragging_grabber_xy(const GLGizmoBase::UpdateData &data);
+ void dragging_grabber_move(const GLGizmoBase::UpdateData &data);
+ void dragging_grabber_rotation(const GLGizmoBase::UpdateData &data);
void dragging_connector(const GLGizmoBase::UpdateData &data);
void on_dragging(const UpdateData&data) override;
void on_start_dragging() override;
@@ -275,6 +300,8 @@ protected:
void add_horizontal_scaled_interval(float interval);
void add_horizontal_shift(float shift);
void render_color_marker(float size, const ImU32& color);
+ void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance);
+ void render_groove_angle_input(const std::string &label, float &in_val, const float &init_val, float min_val, float max_val);
void render_cut_plane_input_window(CutConnectors &connectors);
void init_input_window_data(CutConnectors &connectors);
void render_input_window_warning() const;
@@ -290,6 +317,8 @@ protected:
void set_volumes_picking_state(bool state);
void update_raycasters_for_picking_transform();
+ void update_plane_model();
+
void on_render_input_window(float x, float y, float bottom_limit) override;
bool wants_enter_leave_snapshots() const override { return true; }
@@ -301,10 +330,12 @@ protected:
Transform3d get_cut_matrix(const Selection& selection);
private:
- void set_center(const Vec3d& center, bool update_tbb = false);
- bool render_combo(const std::string& label, const std::vector& lines, int& selection_idx);
+ void set_center(const Vec3d¢er, bool update_tbb = false);
+ void switch_to_mode(size_t new_mode);
+ bool render_cut_mode_combo();
+ bool render_combo(const std::string&label, const std::vector&lines, int&selection_idx);
bool render_double_input(const std::string& label, double& value_in);
- bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in);
+ bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f);
void render_move_center_input(int axis);
void render_connect_mode_radio_button(CutConnectorMode mode);
bool render_reset_button(const std::string& label_id, const std::string& tooltip) const;
@@ -314,16 +345,18 @@ private:
void render_connectors();
bool can_perform_cut() const;
+ bool has_valid_groove() const;
bool has_valid_contour() const;
void apply_connectors_in_model(ModelObject* mo, int &dowels_count);
bool cut_line_processing() const;
void discard_cut_line_processing();
+ void apply_color_clip_plane_colors();
void render_cut_plane();
static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix);
void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width);
void render_rotation_snapping(GrabberID axis, const ColorRGBA& color);
- void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix);
+ void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef = 1.0);
void render_cut_plane_grabbers();
void render_cut_line();
void perform_cut(const Selection&selection);
@@ -339,6 +372,13 @@ private:
void validate_connector_settings();
bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position);
void check_and_update_connectors_state();
+
+ void toggle_model_objects_visibility();
+
+ indexed_triangle_set its_make_groove_plane();
+
+ indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
+ void apply_cut_connectors(ModelObject* mo, const std::string& connector_name);
};
} // namespace GUI
diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp
index 1a800eee42..0023a4f63b 100644
--- a/src/slic3r/GUI/ImGuiWrapper.cpp
+++ b/src/slic3r/GUI/ImGuiWrapper.cpp
@@ -67,6 +67,7 @@ static const std::map font_icons = {
{ImGui::InfoMarkerSmall , "notification_info" },
{ImGui::PlugMarker , "plug" },
{ImGui::DowelMarker , "dowel" },
+ {ImGui::SnapMarker , "snap" },
};
static const std::map font_icons_large = {
@@ -639,6 +640,8 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float
m_last_slider_status.edited = ImGui::IsItemEdited();
m_last_slider_status.clicked = ImGui::IsItemClicked();
m_last_slider_status.deactivated_after_edit = ImGui::IsItemDeactivatedAfterEdit();
+ if (!m_last_slider_status.can_take_snapshot)
+ m_last_slider_status.can_take_snapshot = ImGui::IsItemClicked();
if (!tooltip.empty() && ImGui::IsItemHovered())
this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width);
diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp
index 26e58d4ad1..92e761a434 100644
--- a/src/slic3r/GUI/ImGuiWrapper.hpp
+++ b/src/slic3r/GUI/ImGuiWrapper.hpp
@@ -49,6 +49,11 @@ public:
bool edited { false };
bool clicked { false };
bool deactivated_after_edit { false };
+ // flag to indicate possibility to take snapshot from the slider value
+ // It's used from Gizmos to take snapshots just from the very beginning of the editing
+ bool can_take_snapshot { false };
+ // When Undo/Redo snapshot is taken, then call this function
+ void invalidate_snapshot() { can_take_snapshot = false; }
};
ImGuiWrapper();
@@ -80,6 +85,7 @@ public:
ImVec2 get_item_spacing() const;
float get_slider_float_height() const;
const LastSliderStatus& get_last_slider_status() const { return m_last_slider_status; }
+ LastSliderStatus& get_last_slider_status() { return m_last_slider_status; }
void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
void set_next_window_bg_alpha(float alpha);
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 9c8297b29c..36f284f3a8 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -6317,20 +6317,7 @@ void Plater::toggle_layers_editing(bool enable)
canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting"));
}
-void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
-{
- wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
- auto* object = p->model.objects[obj_idx];
-
- wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds");
-
- wxBusyCursor wait;
-
- const auto new_objects = object->cut(instance_idx, cut_matrix, attributes);
- cut(obj_idx, new_objects);
-}
-
-void Plater::cut(size_t obj_idx, const ModelObjectPtrs& new_objects)
+void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& new_objects)
{
model().delete_object(obj_idx);
sidebar().obj_list()->delete_object_from_list(obj_idx);
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 8eeabae428..bed65d7d13 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -264,8 +264,7 @@ public:
void convert_unit(ConversionType conv_type);
void toggle_layers_editing(bool enable);
- void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes);
- void cut(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
+ void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
void export_gcode(bool prefer_removable);
void export_stl_obj(bool extended = false, bool selection_only = false);
diff --git a/src/slic3r/GUI/SceneRaycaster.cpp b/src/slic3r/GUI/SceneRaycaster.cpp
index 64493d86b4..08c3407632 100644
--- a/src/slic3r/GUI/SceneRaycaster.cpp
+++ b/src/slic3r/GUI/SceneRaycaster.cpp
@@ -40,6 +40,7 @@ std::shared_ptr SceneRaycaster::add_raycaster(EType type, in
case EType::Bed: { return m_bed.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); }
case EType::Volume: { return m_volumes.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); }
case EType::Gizmo: { return m_gizmos.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); }
+ case EType::FallbackGizmo: { return m_fallback_gizmos.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); }
default: { assert(false); return nullptr; }
};
}
@@ -62,6 +63,7 @@ void SceneRaycaster::remove_raycasters(EType type)
case EType::Bed: { m_bed.clear(); break; }
case EType::Volume: { m_volumes.clear(); break; }
case EType::Gizmo: { m_gizmos.clear(); break; }
+ case EType::FallbackGizmo: { m_fallback_gizmos.clear(); break; }
default: { break; }
};
}
@@ -86,6 +88,12 @@ void SceneRaycaster::remove_raycaster(std::shared_ptr item)
return;
}
}
+ for (auto it = m_fallback_gizmos.begin(); it != m_fallback_gizmos.end(); ++it) {
+ if (*it == item) {
+ m_fallback_gizmos.erase(it);
+ return;
+ }
+ }
}
SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const
@@ -174,6 +182,9 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
if (!m_gizmos.empty())
test_raycasters(EType::Gizmo, mouse_pos, camera, ret);
+ if (!m_fallback_gizmos.empty() && !ret.is_valid())
+ test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret);
+
if (!m_gizmos_on_top || !ret.is_valid()) {
if (camera.is_looking_downward() && !m_bed.empty())
test_raycasters(EType::Bed, mouse_pos, camera, ret);
@@ -241,6 +252,14 @@ size_t SceneRaycaster::active_gizmos_count() const {
}
return count;
}
+size_t SceneRaycaster::active_fallback_gizmos_count() const {
+ size_t count = 0;
+ for (const auto& g : m_fallback_gizmos) {
+ if (g->is_active())
+ ++count;
+ }
+ return count;
+}
#endif // ENABLE_RAYCAST_PICKING_DEBUG
std::vector>* SceneRaycaster::get_raycasters(EType type)
@@ -251,6 +270,7 @@ std::vector>* SceneRaycaster::get_raycasters
case EType::Bed: { ret = &m_bed; break; }
case EType::Volume: { ret = &m_volumes; break; }
case EType::Gizmo: { ret = &m_gizmos; break; }
+ case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; }
default: { break; }
}
assert(ret != nullptr);
@@ -265,6 +285,7 @@ const std::vector>* SceneRaycaster::get_rayc
case EType::Bed: { ret = &m_bed; break; }
case EType::Volume: { ret = &m_volumes; break; }
case EType::Gizmo: { ret = &m_gizmos; break; }
+ case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; }
default: { break; }
}
assert(ret != nullptr);
@@ -278,6 +299,7 @@ int SceneRaycaster::base_id(EType type)
case EType::Bed: { return int(EIdBase::Bed); }
case EType::Volume: { return int(EIdBase::Volume); }
case EType::Gizmo: { return int(EIdBase::Gizmo); }
+ case EType::FallbackGizmo: { return int(EIdBase::FallbackGizmo); }
default: { break; }
};
diff --git a/src/slic3r/GUI/SceneRaycaster.hpp b/src/slic3r/GUI/SceneRaycaster.hpp
index df44b1701c..8102e20de4 100644
--- a/src/slic3r/GUI/SceneRaycaster.hpp
+++ b/src/slic3r/GUI/SceneRaycaster.hpp
@@ -42,14 +42,16 @@ public:
None,
Bed,
Volume,
- Gizmo
+ Gizmo,
+ FallbackGizmo // Is used for gizmo grabbers which will be hit after all grabbers of Gizmo type
};
enum class EIdBase
{
Bed = 0,
Volume = 1000,
- Gizmo = 1000000
+ Gizmo = 1000000,
+ FallbackGizmo = 2000000
};
struct HitResult
@@ -66,6 +68,7 @@ private:
std::vector> m_bed;
std::vector> m_volumes;
std::vector> m_gizmos;
+ std::vector> m_fallback_gizmos;
// When set to true, if checking gizmos returns a valid hit,
// the search is not performed on other types
@@ -99,9 +102,11 @@ public:
size_t beds_count() const { return m_bed.size(); }
size_t volumes_count() const { return m_volumes.size(); }
size_t gizmos_count() const { return m_gizmos.size(); }
+ size_t fallback_gizmos_count() const { return m_fallback_gizmos.size(); }
size_t active_beds_count() const;
size_t active_volumes_count() const;
size_t active_gizmos_count() const;
+ size_t active_fallback_gizmos_count() const;
#endif // ENABLE_RAYCAST_PICKING_DEBUG
static int decode_id(EType type, int id);